Raspberry Pi を使って、コイン投げ器を作ってみました。
Raspberry Pi でコインを投げ、カメラで撮影後、撮影した画像を別のマシンにアップロードし、 アップロード先でコインの表裏を判別します。
※「コイン投げ器」といいながら、投げてないので、正確には「コイン転がし器」です… (^^;)
※コイン投げ器動作中は、画像アップロード先のマシンは起動したままにしておきます。
※1. LED ライトは撮影時にコイン投げ部を照らすために使用しました。手元にある LED で試したところ、 低電流ではあまり明るくなりません。 ちょうど壊れた屋外用 LED センサーライトがあったので、そのライト部分(LED 12 個が並列に接続されている)を流用しました。
※2. 段ボールはネット通販等の梱包箱の底に敷かれているものを使いました。 強度が必要な部分は、木工用ボンドを使い、複数枚貼り合わせて使用しています。
材料(制御部)をブレッドボードに差して、Raspberry Pi とつなぎます。
LED ライトは定格が不明なため、Raspberry Pi につないで電流を測りながら、 そこそこの明るさで、かつ、電流が流れすぎない程度に抵抗を調整しました。
※下記ブレッドボード図の LED ライト正極側手前の箇所で電流を測ると 20mA 程度になります。
マイクロサーボに綿棒ケースをセロハンテープでくっつけます。
綿棒ケースに百円玉を入れ、ケースのフタが開かないよう、セロハンテープで留めます。
コピー用紙を適当な大きさに切り、綿棒ケースの周りと底に、セロハンテープと両面テープを使い、貼り付けます。
※綿棒ケースをコピー用紙で覆うのは、Raspberry Pi のカメラでコイン投げ部を撮影した画像へのコイン以外の写り込みを減らすためです。
段ボールを木工用ボンドで貼り合わせ、台座部への取り付け用の基礎を作り、カッターで削って穴をあけ、マイクロサーボを埋め込みます。
段ボールを木工用ボンドで貼り合わせ、台座部への取り付け用の基礎(その1)を作り、カッターで削って穴をあけ、カメラを埋め込みます。
段ボールで台座部への取り付け用の基礎(その2)を作ります。基礎(その1)を基礎(その2)に固定するための針金も作ります。
基礎(その1)を基礎(その2)に針金で固定します。
※基礎を 2 つに分けたのは、後でカメラの左右の位置を調整できるようにするためです。
段ボールを木工用ボンドで貼り合わせて作ります。 上部にはひさしを作り、コピー用紙を普通のクリップで留めます。
※ひさしは、Raspberry Pi のカメラでコイン投げ部を撮影した画像への不要な写り込みを減らすためです。
台座部にカメラ部を取り付けます。
大きな洗濯ばさみを使って、台座部にコイン投げ部を取り付けます。
Raspberry Pi と LED ライト&台を台座部に置き、カメラ部を台座部にダブルクリップで留めて完成です。
※組み立ては Raspberry Pi の電源を落として行いました。
以下の環境と設定が必要です。
Python 3 と以下のライブラリ
requests
pi camera の設定
以下の環境と設定が必要です。
Python 3 と以下のライブラリ
マシンの 8080 番ポートが利用できるようファイアウォールを設定
アップロード先マシンに ソース一式 をダウンロードし、適当なディレクトリに展開します。
※以下、アップロード先マシンでの作業は、展開したディレクトリ下のファイルを使用しています。
※プログラム実行などで実行場所を明記していないものは、アップロード先マシンで行っています。
※アップロード先マシンは Windows で、主にコマンドプロンプトを使用しました。
※Raspberry Pi には Windows から Tera Term で ssh 接続しています。
Raspberry Pi に test_tools/raspberry_pi/test_servo.py をダウンロードし、コマンドラインから実行します。
python3 test_servo.py
コイン投げ部が回転します。回転完了後に、コインが見える方が下に(カメラの方を)向いていることを確認します。
確認が済んだら Ctrl + C で test_servo.py を終了します。
※test_servo.py は終了するまで、回転動作を繰り返します。
Raspberry Pi に test_tools/raspberry_pi/test_light.py をダウンロードし、コマンドラインから実行します。
python3 test_light.py
LED ライトが一定時間光ります。 カメラで撮影する部分(コイン投げ部の下の部分)が照らされるよう、LED ライトの位置を調整します。
調整が済んだら Ctrl + C で test_light.py を終了します。
※test_light.py は終了するまで、LED ライトの点滅を繰り返します。
アップロード先マシンで、test_tools/test_uploader.py をコマンドラインから実行します。
python3 test_uploader.py
アドレス・ポートを変更するには、test_uploader.py の host='0.0.0.0' および port=8080 を変更します。
run(host='0.0.0.0', port=8080, debug=True, reloader=False)
Raspberry Pi に test_tools/raspberry_pi/test_camera.py をダウンロードし、test_camera.py の UPLOAD_URL(画像のアップロード先)を、アップロード先マシンに変更してから、コマンドラインから実行します。
UPLOAD_URL = 'http://192.168.1.7:8080/upload'
python3 test_camera.py
test_camera.py がカメラで撮影を行い、画像をアップロード先マシンに送信します。 test_uploader.py が画像を受信し表示します。
表示された画像を見ながら、コイン投げ部が画像の中央に来るよう、コイン投げ部とカメラ部の位置を調整します。
調整が済んだら、両方のプログラムを Ctrl + C で終了します。
※test_camera.py は終了するまで、一定間隔で撮影・送信を繰り返します。
アップロード先マシンで、uploader.py をコマンドラインから実行します。
python3 uploader.py
アドレス・ポートを変更するには、uploader.py の host='0.0.0.0' および port=8080 を変更します。
run(host='0.0.0.0', port=8080, debug=True, reloader=False)
Raspberry Pi に raspberry_pi/toss_and_capture.py をダウンロードし、toss_and_capture.py の UPLOAD_URL(画像のアップロード先)を、アップロード先マシンに変更してから、コマンドラインから実行します。
UPLOAD_URL = 'http://192.168.1.7:8080/upload'
python3 toss_and_capture.py
toss_and_capture.py がコイン投げを行い、画像を撮影・送信し、 uploader.py が受信した画像を upload_files ディレクトリに保存します。
toss_and_capture.py は、TRY_COUNT に設定された回数分、コイン投げ・撮影・送信を行った後、終了します。 さらに、コイン投げを行うには、toss_and_capture.py を再度実行します。
コイン投げを行う回数を変更するには、toss_and_capture.py の TRY_COUNT を変更します。
TRY_COUNT = 100
画像の収集が済んだら、 toss_and_capture.py が実行中であれば Ctrl + C で先に終了し、 uploader.py も Ctrl + C で終了します。
※TRY_COUNT に 1000 を設定し、長時間連続動作させていると、マイクロサーボが異音を発するようになりました。 壊れそうな気がして TRY_COUNT を 100 に変更し、休ませながら間隔を置いて実行しました。
※LED ライトは定格がわからないまま使っており、点けっぱなしにするのが怖いため、カメラ撮影時のみ点灯させています。
upload_files ディレクトリに収集した画像を、orig_imgs ディレクトリに手作業でコピー(または移動)します。
※参考データ: orig_imgs.zip
make_coin_imgs.py をコマンドラインから実行します。
python3 make_coin_imgs.py
make_coin_imgs.py が、orig_imgs ディレクトリの画像からコイン画像を抽出し、coin_imgs ディレクトリに格納します。
make_coin_imgs.py 終了後、coin_imgs ディレクトリにコイン画像が格納されているのを確認します。
※コイン画像がうまく抽出できない場合、common.py で、 CoinImage.get_img 内の cv2.HoughCircles 呼び出しの引数 param1 および param2 を調整します。
coin_imgs ディレクトリのコイン画像を、手作業で manual_classified_coin_imgs ディレクトリ下の各ディレクトリにコピー(または移動)します。
manual_classified_coin_imgs/head ディレクトリ
表面のコイン画像を格納します。
manual_classified_coin_imgs/tail ディレクトリ
裏面のコイン画像を格納します。
manual_classified_coin_imgs/other ディレクトリ
表でも裏でもないコイン画像を格納します。
※この手作業が一番疲れました。。。
manual_classified_coin_imgs_to_data.py をコマンドラインから実行します。
python3 manual_classified_coin_imgs_to_data.py
manual_classified_coin_imgs_to_data.py が、manual_classified_coin_imgs ディレクトリに格納したコイン画像を、 1 割を検証用、残りを訓練用として、data ディレクトリ下にコピーします。
※Keras の ImageDataGenerator.flow_from_directory に対応したディレクトリ構成を data ディレクトリ下に作ります。
manual_classified_coin_imgs_to_data.py 終了後、data ディレクトリ下に以下のディレクトリ構成でコイン画像が格納されているのを確認します。
train/head
train/tail
train/other
validation/head
validation/tail
validation/other
※参考データ: data.zip
train.py をコマンドラインから実行します。
python3 train.py
train.py が、data ディレクトリの内容でモデルを訓練し、 訓練終了時に、訓練済みのモデルの重みを train_weights.h5 ファイルに出力します。
※終了までにはかなりの時間がかかります。私の環境では CPU で 3 時間以上かかりました。
train.py 終了後、train_weights.h5 ファイルが出力されているのを確認します。
train.py は終了時に、訓練の経過を train_history.txt ファイルに出力します。 内容を表示するには plot_history.py を使います。
※参考データ: train_weights.zip
正しく訓練できたかを確認するため、 再度、コイン投げと画像の収集 を行い、新しい画像を upload_files ディレクトリに収集します。
※訓練に使った元画像と新しく収集するテスト用の画像が混ざらないように気をつけます。 訓練に使った元画像が upload_files ディレクトリに残っている場合、新しい画像を収集する前に退避または削除し、 upload_files ディレクトリを空にしてから、新しい画像を収集します。
upload_files ディレクトリに収集した画像を、test_orig_imgs ディレクトリに手作業でコピー(または移動)します。
※参考データ: test_orig_imgs.zip
make_coin_imgs.py を、オプション(-t)付きでコマンドラインから実行します。
python3 make_coin_imgs.py -t
make_coin_imgs.py が、test_orig_imgs ディレクトリの画像からコイン画像を抽出し、test_coin_imgs ディレクトリに格納します。
make_coin_imgs.py 終了後、test_coin_imgs ディレクトリにコイン画像が格納されているのを確認します。
test_classified_coin_imgs.py をコマンドラインから実行します。
python3 test_classified_coin_imgs.py
test_classified_coin_imgs.py が、訓練済みのモデルの重み train_weights.h5 を使って、 test_coin_imgs ディレクトリのコイン画像を分類し、 test_classified_coin_imgs ディレクトリに格納します。
test_classified_coin_imgs.py 終了後、test_classified_coin_imgs ディレクトリ下に 以下のディレクトリ構成でコイン画像が分類・格納されていることを確認します。
head
表面のコイン画像
tail
裏面のコイン画像
other
表でも裏でもないコイン画像
今回は、そこそこに分類できていれば(誤って分類されたコイン画像がいくらかあっても)良しとしました。
アップロード先マシンで、predict_uploader.py をコマンドラインから実行します。
python3 predict_uploader.py
アドレス・ポートを変更するには、predict_uploader.py の host='0.0.0.0' および port=8080 を変更します。
run(host='0.0.0.0', port=8080, debug=True, reloader=False)
Raspberry Pi で raspberry_pi/toss_and_capture.py を、オプション(-m)付きでコマンドラインから実行します。
python3 toss_and_capture.py -m
画像のアップロード先を変更するには、toss_and_capture.py の UPLOAD_URL を変更します。
UPLOAD_URL = 'http://192.168.1.7:8080/upload'
ブレッドボード上のタクトスイッチを押すと、toss_and_capture.py がコイン投げを行い、画像を撮影・送信し、 predict_uploader.py が受信した画像を predict_upload_files ディレクトリに保存、 画像からコイン画像を抽出して predict_coin_imgs ディレクトリに保存し、 コイン画像を判定して、画像とコイン画像および判定結果(head、tail、other)を表示します。
※toss_and_capture.py は、タクトスイッチの状態を一定時間毎にポーリングする実装にしており、 タクトスイッチをちょっと押しただけではコイン投げが始まらないことがあります。 タクトスイッチはコイン投げが始まるまで押しっぱなしにし、始まったら押すのをやめます。
※predict_uploader.py および toss_and_capture.py は Ctrl + C で終了できます。
今回、モデルは Keras の MNIST CNN サンプル をそのまま使わせていただきました。 ただ、私の手抜きのせいで、訓練・検証に使った画像の数が少なく、 また、画像への前処理もしていないため、汎用的な性能を得られていません。
例えば、昼間の明るい環境で収集した画像で訓練したモデルの重みを使い、 夜の暗い環境で収集した画像をテストすると、まったく分類できませんでした。
私のミスで、訓練・検証データを水増しするために使っている ImageDataGenerator.flow_from_directory から得られるコイン画像と、 テスト時に与えるコイン画像の明るさが大きく違っていたことも原因のひとつだったのですが、 それを修正した後でも、 訓練時に与えたことのない明るさの画像をテスト画像として与えると、 やはり、うまく分類できませんでした。
訓練・検証に使う画像の明るさのバリエーションを増やしたり、 画像に前処理を行い、訓練・検証・テスト時に与える画像の明るさにあまりばらつきがないよう、 調整が必要かな、と思いました。
既存の学習済みモデルを流用せずに、ゼロから訓練する場合、実地で使える汎化性能を得るためには、 サンプル数や前処理、モデルなど、全体的に見直す必要がありそうです。
※参考データは、ほぼ同じ明るさで収集しています。
以下の本やサイトのほか、たくさんの情報を参考にさせていただきました。 どうもありがとうございます。