PythonとArduinoで検出物体を追従するカメラを作ってみた::Deep Learningの応用1

2021年6月19日

G.W.は時間があるのでブログが進みます。

今日は、PythonとArduinoを連携させ、検出した物体を追従するカメラを作ったので紹介します。

ポイントはDeep learningによる物体検出(SSD)とArduinoを連携させたことです。

単なる形状認識や特徴認識とは異なります。

動画紹介

まずは次の動画をみてください。

物体検出にはSSDを使用しています。

SSDって何?って思った方は過去記事を参照してください。ちなみに、Solid State Drive ではありません!

物体が検出されると、PythonからArduinoにカメラを動かす命令がでて、その通りにWebカメラが動きます。

追従させたい物体をあらかじめDeep learningで学習させておけば、後はWebカメラが自動で追いかけてくれます。原理的に学習するほど精度が上がるため追従精度も向上します。

概要

  • 追従させたい物体をDeep Learningで学習
  • 物体検出にはSSDを使用
  • 転移学習を利用し検出精度を上げたモデルを作成
  • ArduinoはNANOを使用(ブレッドボードに挿せてコンパクトに設計できるため)
  • Webカメラは組み込み用の軽いものを使用(カメラが重いと動かすの大変)

使用部材

使用部材一覧を下表にまとめます。

部品数量値段(参考)
Arduino NANO13,050
ブレッドボード1270
ジャンパーワイヤ(単線タイプ)1517
ジャンパーワイヤ(フレキシブルワイヤ)1583
Webカメラ(ELP フルHD 2MP)17,325
サーボモータ(SG-90)2880
SG90サーボ用2軸アングル1699
基板(タミヤ楽しい工作シリーズ)1464
DC-POWER-JACK基板1439
ACアダプタ(5V/2A、外形5.5mm/内径2.1mm)1980
USBケーブル 0.9m (2.0タイプAオス – マイクロBケーブル) ブラック1631
合計1215,838

部品単体は安いですが、合計すると結構高いですね。

主に、Arduino NANOとWebカメラの値段です。ここを安くできればコストダウンできます。

Webカメラ外観

以下に外観を示します。

Arduino NANOとDC-POWER-JACK基板はブレッドボードに直接刺すことができます。

Arduino NANOは5Vで動作します。出力電圧は5Vと3.3Vです。

サーボモータが1~2個程度ならArduinoからの電力供給でサーボモータを動かすことができます。

ただし、Arduinoの動作が不安定になったり、最悪壊れることがあるためサーボモータは外部電源で動作させた方がよいと思います。

このため、DC-POWER-JACK基板を使って外部電源でサーボモータを動かすようにしています。

プログラム

以下のコードをみてください。

Arduino側

Arduino側はArduino IDEで作成します。ちなみに、ソースコードのことをスケッチと呼びます。

#include <VarSpeedServo.h>
VarSpeedServo mServo;
VarSpeedServo mServo1;

void setup() {
 mServo.attach(6);         // 6pin使用 回転台用
 mServo.write(90,10,true); // サーボを90度まで速度10で動かし完了までまつ
 
 mServo1.attach(9);         // 9pin使用 カメラ用
 mServo1.write(90,10,true); // サーボを90度まで速度10で動かし完了までまつ
 delay(500);                // 待ち時間 ms
 Serial.begin(9600);        // 通信速度 bps
}

void loop() {
  int val[2] = {0}; // 受信データが複数ある場合はリストを作っておく

  if (Serial.available() > 0){
    delay(10);
    for (int i = 0; i < 2; i++){
      val[i] = Serial.parseInt();
    }
    // 受信バッファクリア
    while (Serial.available() > 0){
      char t = Serial.read();
    }
    mServo.write(val[0],50,true);  // 回転台
    mServo1.write(val[1],50,true); // カメラ
   }
}

はじめに、ライブラリを読み込みます。

#include<VarSpeedServo.h>の部分です。VarSpeedServo がArduino IDEにまだインスト―ルされていないときはGitHubから入手してライブラリをインストールしてください。インスト―ル方法もGitHubに詳しく記載されています。

このライブラリを使用するとサーボの速度調整ができます。Arduinoの標準ライブラリにもServoが入っていますが、こちらは速度調整ができないのでカメラが滑らかに動きません。

次に、サーボモータを2個動かす必要があるので、変数として mServo と mServo1 の2つを定義しておきます。

Void setup( ) { } の { } の中に信号を出力する pin 番号やサーボの速度、通信速度を指定しておきます。

Void loop( ) { } の中にPythonからの命令を受け取るコードを記載します。

複数の命令を受け取りたいときは、val[ ] = { } のようにリストを作っておきます。

今回はカメラの角度を受け取る命令が1つ、回転台の角度を受け取る命令が1つで合計2つの入れ物が必要になります。

Serial.available( ) > 0 の意味は、信号がある場合は { } の中の処理を行いなさいということです。

つまり、val[0] とval[1] にPythonからの命令、この場合は角度の数値が入ります。

その後、受信バッファをクリアしておきます。

mServo.write( ) および mServo1.write( ) の部分で各サーボを指定の角度に動かします。

PythonからArduinoに命令が送られている間は、この動作を続けます。

Python側

以下のコードをみてください。

# pythonからarduinoに命令を送る

ser = serial.Serial('COM5', 9600, timeout=0.5)
time.sleep(2)

pre_center_x = 0
pre_center_y = 0
pre_angle_x = 90
pre_angle_y = 90

### arduinoに命令 ###
# 物体初期のセンター位置を調べる 
center_x = int(pt[0]+((pt[2]-pt[0])//2))
center_y = int(pt[1]+((pt[3]-pt[1])//2))

# 画面センターと物体センターのズレ量をもとにサーボの回転量を決める
pt_dx_a = int((450-pre_center_x)*0.02)
pt_dy_a = int((-450+pre_center_y)*0.02)

# サーボを回転させる
ser.write((str(pre_angle_x+(pt_dx_a))+'\n').encode('utf-8'))  # 回転台の角度 Val[0]に入る
ser.write((str(pre_angle_y+(pt_dy_a))+'\n').encode('utf-8'))  # カメラの角度 Val[1]に入る

# サーボが回転した後の物体のセンター位置を調べる
center_x = int(pt[0]+((pt[2]-pt[0])//2))
center_y = int(pt[1]+((pt[3]-pt[1])//2))

# サーボ回転後のセンター位置を次のセンター位置とする
pre_center_x = center_x
pre_center_y = center_y

if pre_angle_x > 170:
    pre_angle_x = 170 + pt_dx_a
elif pre_angle_x < 20:
     pre_angle_x = 20 + pt_dx_a
else:
     pre_angle_x = pre_angle_x + pt_dx_a
                        
if pre_angle_y > 120:
     pre_angle_y = 120 + pt_dy_a
elif pre_angle_y < 60:
     pre_angle_y = 60 + pt_dy_a
else:
     pre_angle_y = pre_angle_y + pt_dy_a        

はじめに、serial.Serial( ) で通信ポートの番号、通信速度、timeoutの時間を設定します。

通信ポートはArduino NANOとの通信に使用しているポート番号を入力します。

通信速度は、さきほどArduino IDEのスケッチで設定した通信速度(9600)を記載します。この数値を合わせておかないと通信エラーとなります。

次に、pt[ ] と記載された変数にはSSDで物体検出した際の物体のx座標(x0, x1)とy座標(y0, y1)の数値が入ります。

その座標データを元に物体の中心と画面の中心を求めサーボを動かします。

なお、今回はWebカメラの映像サイズを900x900px としているため、画面中心の座標は(450, 450)です。

もし映像サイズを変える場合は、画面中心の座標も映像サイズに合わせ変える必要があります。

ちなみに、サーボの回転角度は以下のように求めています。

1.検出した物体の最初の中心座標を求めます。

2.次に画面の中心座標と物体の中心座標のズレ量を元にサーボの回転角度を求めます。

3.求めた回転角度でサーボを回転させます。

4.サーボが回転した後の物体の中心座標を求めます。

5.その中心座標を物体の最初の中心座標とします。

上記1~5を繰り返すことでWebカメラに映った物体が常に画面の中心に来るようサーボが動くことになります。

まとめ

  • PythonとArduinoを連携させることで、電子工作の幅が広がる
  • SSDとArduinoの連携により、Deep learning+IoTが簡単に実現できる