obnizとスマホで自動運転カー

白線を追従する自動運転カーを作りました。
車体はラジコンカーを改造し、obnizからモーターを制御しています。
スマホのカメラで道路を撮影し、OpenCV.jsを使って白線認識を行っています。

#ラジコンカーの選定
obnizの出力は5Vなので、5Vで動くモーターを搭載したラジコンカーを選ぶ必要があります。
本体に使用する電池が4本以下のラジコンであれば使用できます。
商品説明に「単3電池4本」という感じの記載があればOKです。

本格的なラジコンによくありますが、リポバッテリーを使うタイプのラジコンは避けてください。
電圧・電流不足により、モーターをobnizから回すことができない可能性が高いです。

今回はこのラジコンカーを使いました。
[ジョーゼン ダートマックス 1/24スケール ラジコン ジープラングラー ルビコン JRVT062-BL](http://amzn.asia/d/e0RBhMh)

#ラジコンカーの改造
初期不良が無いことを確認したら、さっそく分解します！
![DSC_0301.JPG](https://qiita-image-store.s3.amazonaws.com/0/291462/ed8ae3d4-c16e-ba70-0681-35b7ff8a5d96.jpeg)

裏面のネジを外し、カバー部分とシャシーを分離します。
![DSC_0303-edit.png](https://qiita-image-store.s3.amazonaws.com/0/291462/343004bc-ce75-ccad-2e66-12728e623013.png)
配線がいくつかありますが、写真の赤丸の線を使います。
左の赤丸はステアリングのモーターへつながる配線、右赤丸はリアモーターへつながっている配線です。
ラジコンのステアリング部分は、モーターの回転方向が左右方向に対応する仕組みでした。
正転:右,逆転:左,停止:中央、といった感じです。
角度の制御はできないようです。
（サーボモーターとは仕組みが違いますね）

一本の黒い導線に見えますが、中には2本の導線が入っています。
この2本を取り出し、ジャンパワイヤを取り付けます。
![DSC_0304.JPG](https://qiita-image-store.s3.amazonaws.com/0/291462/d8284078-7da4-7686-0d10-798a0f0f79fd.jpeg)
ステアリングとリアモーターでそれぞれ2本、合計4本のジャンパワイヤができるはずです。
ジャンパワイヤとの接続部分は、ショートしないようにビニールテープで保護しました。
![DSC_0423.JPG](https://qiita-image-store.s3.amazonaws.com/0/291462/8f8e300f-92ca-0b5b-117e-2879aa2c97ce.jpeg)
カバーに穴をあけて、obnizと接続します。
リアモーターをobnizの0,1番へ、ステアリングモーターを2,3番へ接続します。
スマホを乗せる部分を確保するために、運転席の上の枠をカットしました。

#obnizのプログラム
###車線認識と走行のしくみ
最も単純な車線認識は、二値化して道路と白線の境目を探す方法です。
![2018-08-17-034920_1280x768_scrot.jpg](https://qiita-image-store.s3.amazonaws.com/0/291462/e10dcb00-4b65-77e0-3d27-d84020f8d0a3.jpeg)
画像を二値化して、横一列を抜き取ります。
抜き取った配列は、白線がある場合は以下のような配列になるはずです。
[黒黒黒黒黒白白黒黒黒白白黒黒黒黒黒]
この2番目と3番目の白の座標の真ん中の座標が車線の中心になるはずです。

また、カメラを車両の中心に取り付けたとすれば、車両の中心は画像のX座標の中心になります。
境目を青丸、道路の中心を赤丸、車両(画像)の中心を白丸で表した処理結果が以下の画像です。
![lanedetection.jpg](https://qiita-image-store.s3.amazonaws.com/0/291462/edf1b56f-7e86-985d-7ff4-d711612890f3.jpeg)
道路(赤)に対して車両(白)が左に寄っているので、この場合は右にハンドルを切ればよいことがわかります。
###実際のプログラム
二値化後、白黒の境目探しはfor文で1ピクセルずつ探します。

```javascript
var boundary = [];
// 画像の幅だけループを実行
for (let i = 0; i < pixelArray.length; i++) {
  if (pixelArray[i] != pixelArray[i+1]){
    boundary.push(i);
  }
}

// 道路の中心座標を求める
let centerX = 0;
if (boundary.length >= 4){
  centerX = ((boundary[2] - boundary[1])/2) + boundary[1];
} else{
  centerX = 0;
}
```
pixelArrayが二値化して1行だけ抜き出した配列です。
隣のピクセルと違う値だった場合は、そのX座標を配列boundaryに格納しています。
境目が4点以上あった場合(=白線が2本以上ある場合)は、その中心X座標をcenterXに代入します。

ハンドルを切るプログラムは次のようになっています。

```javascript
// 左寄りを走行
if ((video.width/2-centerX) < -10){
  // ステアリングを右へ 
  motorSteer.reverse();
// 右寄りを走行
} else if ((video.width/2-centerX) > 10){
  // ステアリングを左へ
  motorSteer.forward();
// 車線中央から+/-10ピクセル以内
} else {
  // ステアリングを中央へ
  motorSteer.stop();
}
```
単純に、右寄りならステアリングを左に、左寄りならステアリングを右にしています。
中央から少しでもずれるとハンドルを切ってしまい、操舵が不安定になるので、10ピクセルの不感帯を設けてあります。

全体のプログラムは[こちら](https://obniz.io/explore/43)です。

#走らせてみた
アスファルト上に白色のビニールテープを貼って走らせてみました。
![走行様子](https://qiita-image-store.s3.amazonaws.com/0/291462/9099478e-d905-93ba-78dc-4f8ba509e02a.gif)
きちんと白線に沿って走りました！
単純なアルゴリズムですが、思っていたよりも白線を逸脱する回数は少なかったです。
[動画 (Youtube)](https://www.youtube.com/watch?v=0rPX6Ha2Ukk)

#まとめ
obnizはIOの出力が最大1Aなので、モータードライバを用意せずに済むことはもちろんなのですが、モータードライバの省略によって配線も簡略化できるという恩恵はとても大きいと感じました。

OpenCVを使っているので、もう少し手を加えて信号機の認識もできそうですね。