Node-REDでKeiganMotorロボットアームをAI制御し捺印ロボットを作る

#概要
　最近、自動ハンコロボットが巷で流行ってますね。実は私流行る前にやってたぞ、ということで11月末にNode-RED LTで発壇した際の自動捺印ロボットアーム、通称「上司要らず」について紹介します。
　働き方改革やワークライフバランス改善が叫ばれる中、時流に逆行するがごとく執拗に蔓延るハンコ文化。
それに乗じ、増加の一途をたどる「ハンコ押すだけ上司」。なんとかこの状況打開できないか？そこで自動でハンコを押す装置製作を思い立ったのです。

参考リンク：
https://japanese.engadget.com/2019/12/17/1-2-2019/

#構成
##1.装置構成
　実際の位置決め精度を考えればXY軸移動のほうがよさそうですが、水平多関節の方が動きがかっこいいですよね。障害物があっても回り込んで回避してアームを動作させられるなどメリットもあります。


<img width="700" alt="キャプチャ.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542127/ca6c5207-3b93-020e-bbda-031cb87b3dc3.png">


###1-1アーム
　アーム部にはNode-REDからJSONコマンドを送信することで回転速度や位置、角度指定といった動作を可能とするKeiganMotorを使用しています。なんとBLEでの通信もできるため、スタンプ部をESP32のBluetooth通信としたことでPCとロボットアームは無線通信となっています。
　*概要ではハンコですが、一般ウケも考えてスタンプにしました。

###1-2捺印部
　ESP32とサーボSG90を組みあわせて動作させています。ESPとPC(Lattepanda)間の通信をBluetoothシリアル通信し、Node-RED(node-red-contrib-bluetooth-serial-port)で行っています。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ユニバーサル基板にしてかなり回路コンパクトになった♪<br>これならこのまま載せれる。 <a href="https://t.co/NOBlDcE7uj">pic.twitter.com/NOBlDcE7uj</a></p>&mdash; サクヤtypeR Noodl勉強 (@typeR_Anonymous) <a href="https://twitter.com/typeR_Anonymous/status/1190092481980846080?ref_src=twsrc%5Etfw">November 1, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
##2.制御
　Node-REDでアームの動作とスタンプ部の制御を行い、YOLOv3による物体検知や位置認識、その他Node-REDで表現しきれない処理(私の理解度の問題…)はpythonで書き、execノードを使ってNode-RED側に読み込んでいます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542127/cf680d59-5e6e-7e23-359b-daabb5cf06ef.png)

###*Node-REDでKeiganMotorを使用する際の注意点
今回、node-red-contrib-keiganmotorノードを使用しました。公式にRaspberryPi3では動作確認されているのですが、YOLOの推論とアーム制御を同時に行うと、メモリ的にギリギリなため、Lattepandaにubuntu18.04を入れて使用しています。
この際、Node-REDのパレットの管理からnode-red-contrib-keiganmotorノードをinstallすると、nobleもinstallされるのですが、nobleの相性の影響でUSBシリアルがNode-RED上で使用できなくなります。

[(参考)公式ドキュメント]
https://docs.keigan-motor.com/software_dev/node-red/node-red-node

2020年2月現在では、販売されているRaspberryPi4を使用すれば上記注意は気にせずすみます。また、node-red-contrib-keigan-motor-sequencerノードも新たに発表されましたのでこちらを使用する方がよいかと思います。
［公式Node-REDモジュール］
https://docs.keigan-motor.com/archives/4224

###2-1 AI判定
　今回の位置検出だけでは、AIを使わなくてもopencvなどで検出することは可能だと思います。しかし後々リアルタイム物体検知などやりたかったので、全く無知だったところから勉強しながらKeras-Yolov3を使って学習モデルを作成し認識させました。

###2-2 座標変換
　yolov3で認識したスタンプ欄(写真(2-3下)ではstamp)の位置と、スタンプ部原点(写真ではpush)と原点(写真ではroot)の実寸をあらかじめ測定しておき、pixelの座標から割り出した間隔を実寸に置き換えてます。
　これをsympyを使い、push,root,stampの位置を角度に変換する連立方程式を立式し求解しています。
![キャプチャ.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542127/0bb3cd52-d1b8-9ada-da75-b63e48aceac1.jpeg)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542127/79b07955-633f-5aa0-b7dc-6a5bd30dea7d.png)

###2-3 画像認識
　スタンプ欄の書かれた紙が傾いている場合、傾きに応じてスタンプ部がひねりを加えてくれないとまっすぐにスタンプを押してくれません。そこで認識したスタンプ欄(stamp)の角度を検出させるため、YOLOv3で認識した部分をトリミングし、角度検出するプログラムをopencvにて作成しました。
(トリミングしたほうが狙った画像であるスタンプ欄を確実に認識できます)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542127/2a1fb21d-5c9e-4987-fefe-98a94128069a.png)

#動作
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">今日のデモ動画です。<a href="https://twitter.com/hashtag/noderedjp?src=hash&amp;ref_src=twsrc%5Etfw">#noderedjp</a> <a href="https://twitter.com/hashtag/nodered?src=hash&amp;ref_src=twsrc%5Etfw">#nodered</a> <a href="https://t.co/lLnPaFlvbI">pic.twitter.com/lLnPaFlvbI</a></p>&mdash; サクヤtypeR Noodl勉強 (@typeR_Anonymous) <a href="https://twitter.com/typeR_Anonymous/status/1200401464788602881?ref_src=twsrc%5Etfw">November 29, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

実際のハンコ紙に押すの忘れて動画撮影してしまいました…

#おわりに
　今回、AIに関してほぼ０知識の状態から始めましたが、ネットで調べながら1か月ほどで完成させることができました。
それだけAIに関する文献や記事がオープンになり、浸透してきたということなのだと思います。まだAI画像認識にチャレンジしていない方は意外と簡単なので、ぜひ挑戦してみては！
ご指摘や知りたい情報等あればお気軽にコメント下さい。

##参考
keras-yolov3の学習モデル構築 (一部設定に抜けがあるので注意必要ですが、分かりやすいです)
https://sleepless-se.net/2019/06/21/how-to-train-keras%E2%88%92yolo3/

###*付録
![キャプチャ.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542127/3d23ebea-8da1-0f41-7fcd-d83fdaca01bb.jpeg)