PythonとArduinoで物体選別するマシンを作ってみた::Deep Learningの応用2

2021年6月19日

今日は、PythonとArduinoで物体選別するマシンを作ったのでご紹介します。

3種類のチロルチョコをDeep Learningで学習させ、ヌガー味のものだけを弾くようにしています。

今回紹介するマシンはおもちゃレベルですが、原理的には工場内の外観検査工程などで異物除去や不良品除去に使える技術だと思います。

動画紹介

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

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

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

ヌガー味のチロルチョコ(外観はこげ茶色の包み紙)が検出されると、PythonからArduinoにサーボモータを動かす命令がでて弾き飛ばします。

概要

  • 3種類のチロルチョコをDeep Learningで学習
  • 物体検出にはSSDを使用
  • 転移学習を利用し検出精度を上げたモデルを作成
  • ArduinoはNANOを使用(ブレッドボードに挿せてコンパクトに設計できるため)
  • ベルトコンベアはタミヤの楽しい工作セットを駆使して作成(かなり試行錯誤しました:汗)

使用部品

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

部品数量値段(参考)
Arduino NANO13,050
ブレッドボード1270
ジャンパーワイヤ(単線タイプ)1517
ジャンパーワイヤ(フレキシブルワイヤ)1583
サーボモータ(SG-90)1440
Webカメラ(5-50mm)19,130
LED(青)抵抗内蔵タイプ 5V用125
LED(白)抵抗内蔵タイプ 5V用130
4速ウォームギヤボックス21,698
基板(タミヤ楽しい工作シリーズ)21,018
DC-POWER-JACK基板1439
タミヤ ラダーチェーン21,238
安定化電源17,488
ACアダプタ(5V/2A、外形5.5mm/内径2.1mm)1980
USBケーブル 0.9m (2.0タイプAオス – マイクロBケーブル) ブラック1631
デジタルテスター13,299
合計1930,836

簡易的なベルトコンベアを作るときに注意したいのは次の二点です。

  1. 駆動装置には低速、高トルクが必要なため、4速ウォームギヤボックスが丁度いい
  2. モーターなどの負荷が大きな装置を連続動作させるには電池BOXでは役不足。すぐに電池がなくなり電圧が下がってモーターが動かなくなる。このため、安定化電源で電力を安定供給する必要がある

モーターは3Vで動きます。当初は、単三アルカリ電池×2本で動作実験していました。

しかし、すぐに電池がなくなりベルトコンベアを連続して動かし続けることができませんでした。

試行錯誤の結果、安定化電源で電力供給するのがベストだという考えになりました。

また、電子工作をされる方にはマストアイテムだと思いますが、デジタルテスターがあるととても便利です。

端子間抵抗や電圧をすぐに測定できるので、うまく動作しない原因が配線ミスなのか電圧降下で電圧が足りないのか、部品故障なのか、すぐに判断できるからです。原因調査に無駄な時間を使わずに済みます。

ベルトコンベア外観

以下に外観を示します。

ベルトコンベアはタミヤの楽しい工作セットを使って作りました。

試行錯誤の結果、ベルト駆動には4速ウォームギヤボックスを使用しツインドライブしています。

理由はツインドライブの方が、モーターに掛かる負荷を分散でき、より重い物も搬送できるからです。

また、モーター1個に掛かる負荷が小さいため、動作音も静かです。

ちなみに、ベルトコンベアのベルト部分はホームセンターで売っている発泡ゴム(厚み1mm程度)を適当なサイズに切ってベルト状にしたものです。値段は一枚300×300mmサイズで300円程度です。

LEDは物体検出の有無を発光で分かり易くするために使用しています。抵抗内蔵タイプでΦ5mmの5V動作品です。秋月電子などで1個から購入できます。ただし、500円~600円の送料がかかるので部品をまとめ買いしないと損した気分になります。

カメラはおなじみELP製の焦点距離5-50mmの200万画素品を使用しています。

過去何度も言ってますが、画素数が500万や800万と大きくなるほど、画像をPCで表示する速度(FPS)が下がります。つまり、動きの速い物体を捉えらず結果検出精度が落ち誤判定し易くなります。USB2.0でデータ送信する以上、カメラ側でキャプチャする画像サイズは最大でも1920×1080の30FPSが落とし所だと思います。

800万画素のELP製カメラも持っていますが、ほとんど使っていません。というか使えないと言った方がいいかもしれません。画像サイズが3264X2448ピクセルと大きいため、PC画面に画像を表示させるときや推論時に余計なリサイズ処理が必要になり、結果的にFPSが下がり検出精度も落ち誤判定し易くなります。

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

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

ベルトコンベアを駆動するためには、モーターに3V/数百mA~数Aの電力供給が必要になります。

この電力をArduinoからの供給することはできないため、DC-POWER-JACK基板を使って外部電源でモータを動かすようにしています。

プログラム

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

Arduino側

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

#include <Servo.h>
Servo mServo;

void setup() {
 mServo.attach(9);
 mServo.write(0);
 delay(500);
 pinMode(11, OUTPUT);
 pinMode(10, OUTPUT);
 Serial.begin(9600);
}

void loop() {
  int val[4] = {0}; // 受信データが複数ある場合はリストを作っておく
  
  if (Serial.available() > 0){
    delay(10);
    for (int i = 0; i < 4; i++){
      val[i] = Serial.parseInt();
    }
    // 受信バッファクリア
    while (Serial.available() > 0){
      char t = Serial.read();
    }
    analogWrite(11, val[0]);
    analogWrite(10, val[1]);
    mServo.write(val[2]);
    delay(200);  // ジャストな時間にしないと動かない
    mServo.write(val[3]);
    delay(200);  // ジャストな時間にしないと動かない
    }
 }

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

#include<Servo.h>の部分です。これはArduino IDEの標準ライブラリで、記載すればすぐに使えます。

今回はサーボが1つだけなので、変数定義もmServoだけでOKです。

次に、Void setup( ) { } の { } の中に以下を指定しておきます。

  • サーボに信号を出力する pin 番号(今回は9番)
  • サーボの初期角度(今回は0度)
  • LEDを光らせるピン番号の指定(11番、10番)
  • 通信速度(9600bps)

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

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

今回はLEDのONで1個、OFFで1個、サーボモータが命令を受けたときの角度で1個、初期位置にもどるときの角度で1個の合計4つの入れ物が必要になります。

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

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

analogWrite( ) の部分でLEDをONするかOFFするか指定します。

mServo.write( ) の部分でサーボモータを指定の角度に動かします。

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

Pyhton側

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

# arduino nano の場合
ser = serial.Serial('COM3', 9600, timeout=0.5)
time.sleep(2)
    
### arduinoパラメータ ###
ad_LED_white = 255
ad_LED_blue = 0
ad_b = 0
ad_b2 = 0

### arduinoに命令 ###
### 物体選別 ###
                       
# 物体が中心付近に来たか計算
pt_dx = int((450-pt[0])-(pt[2]-pt[0])//2)
                        
if abs(pt_dx) <= 80:
     ad_LED_white = 0
     ad_LED_blue = 255
     ad_b = 120
     ad_b2 = 0                          
else:
     ad_LED_white = 255
     ad_LED_blue = 0
     ad_b = 0
     ad_b2 = 0
                            
ser.write((str(ad_LED_white)+'\n').encode('utf-8')) # LEDのON,OFF [0]
ser.write((str(ad_LED_blue)+'\n').encode('utf-8'))  # LEDのON,OFF [1]
ser.write((str(ad_b)+'\n').encode('utf-8'))         # sarvoの角度 [2]
ser.write((str(ad_b2)+'\n').encode('utf-8'))        # sarvoの角度 [3]

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

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

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

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

その座標データを元に物体の中心と画面の中心を求め、指定した物体が画面の中心に来たらサーボを動かして弾き飛ばします。

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

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

ちなみに、今回は物体の中心が80ピクセル以内になったときに、青色LEDがONしサーボモータが120度の角度まで動くように命令しています。

また、そのすぐ後に青色LEDがOFF、白色LEDがONしサーボモータが元の角度0度に戻るよう命令しています。

まとめ

  • ベルトコンベアの駆動装置には低速、高トルクが必要なので、4速ウォームギヤボックスがおすすめ
  • モーターなどの負荷が大きな装置を連続動作させるには安定化電源がおすすめ
  • FPSや物体検出精度のバランスを考えるとカメラの画素数は200万画素(1920×1080px)位がおすすめ