Ar-Rayのてく日記

技術系を中心にした日記です。ただしジャンルは様々

HUSKYLENSを試してみた。Arduinoで簡単プログラミング!

 HUSKYLENSを借りました。

f:id:Ray_ar:20210122005204j:plain
HUSKYLENS

 HUSKYLENSは、画像認識を行うAIチップを搭載した高性能センサと言われています。プログラム自体はHUSKYLENSに書き込むのではなく、通信専用APIが提供されており、ArduinoRaspberry Piなどに受信プログラムを書き込み、結果を送信するだけとなっています。本当にセンサー…

ただし、その認識精度は馬鹿にならないレベルで優秀です。AIってここまで身近になったんだなー。

 少し遊んでみました。

 ソースコードと配線先についてはGithubに上げています。サーボモータはPCA9685搭載基板を用いています。

github.com

 本当にプログラムをしていないので、感動とともに敗北感を感じました。ROS1・ROS2と組み合わせたいなーと思っているので、近いうちに実装すると思います!

f:id:Ray_ar:20210122004832g:plain
デモ

Darknet用のtextファイルを使ってトリミング

 トリミングにDarknet用のテキストデータを用いるというくだらないプログラムを作成しました。 github.com

 ただし、このプログラムにはしっかりとした目的があります。私は、定点撮影をした動画から連番画像を生成して特定位置の画像を取得したいと思っていましたが、2000枚を超える画像から特定箇所(2か所)をわざわざGIMPなどのツールを使ってトリミングしたくないと思いました。しかし、convertコマンドもなかなか指定が面倒…

 そこで、convertコマンドの引数を簡単に指定できるようにラベリングした複数個所を自動で切り抜くプログラムを作成しました。

使用方法

使用方法は簡単で、対象のフォルダにある1枚の画像に対してアノテーション作業を行い、

python3 crop_img.py --input_txt <target picture's text path(for darknet)>

のコマンドを打ち込むだけです。OpenCVで切り抜きをするわけではないですが、OpenCVが必要です。Pillowとかを使えばよかったかな…

 darknetに使用されるテキストファイルは、ラベル番号→xの中心位置→yの中心位置→幅→高さの順に記載されており、全て比率で書かれています。(つまり、0~1の範囲)

 このフォーマット意外と応用利くかも!

import subprocess
import sys
import argparse
import warnings
import cv2
import os

warnings.simplefilter('ignore')

def img_save(txt, sample_img_path, output_path, save_count):

    ## get abs path
    abs_path = os.path.dirname(os.path.abspath(sample_img_path))
    output_path = abs_path+"/"+output_path

    sample_img = cv2.imread(sample_img_path)
    img_width  = sample_img.shape[1]
    img_height = sample_img.shape[0]

    width    = int(float(txt[3])*img_width)
    height   = int(float(txt[4])*img_height)
    
    cut_x = int((img_width*float(txt[1])) - width /2)
    cut_y = int((img_height*float(txt[2])) - height/2)

    os.makedirs(output_path+str(save_count), exist_ok=True)

    exec_str="for i in "+abs_path+"/*.jpg;do convert -crop "+str(width)+"x"+str(height)+"+"+str(cut_x)+"+"+str(cut_y)+" ${i} "+output_path+str(save_count)+"/$(basename ${i%.jpg})_"+str(save_count)+".jpg;done"
    os.system(exec_str)


## arg------------------------------------------
parser = argparse.ArgumentParser(description="image generator")
parser.add_argument('--input_txt',default=None)
parser.add_argument('--output_folder',default='data')
parser.add_argument('--format',default='jpg')

args = parser.parse_args()

## jpg name
sample_img_txt = os.path.splitext(args.input_txt)[0]+"."+args.format

read_txt_file = open(args.input_txt,'r')

lines = read_txt_file.readlines()

count = 0
for line in lines:
    txt_str = line.split()
    img_save(txt_str, sample_img_txt, args.output_folder, count)
    count = count + 1
    txt_str.clear()

read_txt_file.close()

YOLOv3-tinyでお手軽物体検出のやり方!(備忘録)

 今日は、YOLOv3-tinyを使ったボルト・ナット検出を通じてYOLOv3-tinyの学習のやり方の備忘録を書きます。

 YOLOv3-tinyは他のYOLOと比べてとても速いです。今回は講習用にボルト・ナットの検出器を作成してみました。

環境

 さまざま記事でYOLOv3の環境構築について言及されているので、このページは特に示しませんが、YOLOv3の本家実装を使っています。YOLOv4でも同様に動作すると思います。

全体的な流れ

 下の図は学習コマンド実行までの大まかなフローです。順を追って説明します。

f:id:Ray_ar:20210115225631p:plain:w200

1. 画像の撮影

 最も大事な工程は画像の撮影です。あまり経験がないので関係性などの具体的な根拠は語れませんが、とにかく画像は多く、画質やコントラストなどがいいものを撮ったほうがいいと思います。きれいな画像は加工でぼかしたりノイズを乗せたりできますが、画質の粗い画像は高画質にできないので、この段階が良くないとそのあとの処理で苦労すると思います。(もしかしたら、あえてその手法をとって性能を上げる術があるのかもしれませんが、私はモチベが下がると思います。)

 ここでは、ボルト・ナット・ワッシャー・スプリングワッシャーの4つの分類器を作成するために、画像を収集します。今回は、お手軽画像処理ということで、iPhoneの4K品質で6枚撮影しました。また、検証のために、一回も学習を行わないボルト画像を用意します。

f:id:Ray_ar:20210115230306p:plain
撮影した6枚の画像

f:id:Ray_ar:20210115230559j:plain
学習に用いない画像

2.画像の加工・選定

 この作業は任意ですが、私は、撮影した画像6枚から10枚の画像を作成することで、60枚のデータセットを作成しました。GIMPを用いて切り抜いています。目的によっては何かしらの前処理を行うことがあります。

f:id:Ray_ar:20210115230913p:plain
切り抜いた箇所(枠はイメージ)

3.画像のアノテーション

 とても面倒で退屈な作業です。おそらく、需要の増加によって5~10年経つ頃に新たなギグワークとして若者の注目を集めると(個人的に)思います。

f:id:Ray_ar:20210115231153j:plain
アノテーション

 さまざまなツールがありますが、私はYolo_Labelを使っています。GUIのデザインがいいのでお勧めです。

github.com

4.水増し

 アノテーション工程が終われば、おそらくデータセットフォルダには画像とテキストファイルのセットが画像枚数分だけあると思います。ただし、機械学習は60枚では全く学習になりません。(水増ししても足りませんが…)そこで、水増しを行います。左右反転・ノイズ付加・輝度調整をそれぞれ行い、24倍の水増しをします。自動で行うプログラムをYOLOのtextファイルも一緒に水増しを行うプログラムを作った - Ar-Rayのてく日記で以前作成していたので、これを使って水増しを行います。1枚ずつ増やしていくので、そこそこ時間がかかります…

f:id:Ray_ar:20210109005436p:plain
水増しのアルゴリズム

5.設定ファイルの作成(1):cfgファイルの作成

 yolov3.cfgに代わるファイルを作成します。今回は、YOLOv3-tinyなので、yolov3-tiny.cfgをコピーして、次の箇所を変更します。

  • batchの項目:batch=1をbatch=16に変更
  • [YOLO]のブロック内について(2か所)
    • classesの項目:classes=4に変更
  • [YOLO]のブロックの一つ前のブロックにある項目について(2か所)
    • filtersの項目:filters=3x(classes+5)の計算式より、filters=27に変更

 batchを増やした結果、メモリエラーが発生する場合はsubdivisionを増やすか、batchを減らすかをしましょう。batchを落とすと性能が低下し、subdivisionを増やすと学習時間が延びます。

6.設定ファイルの作成(2):学習・テストデータ分類他

 これは自動化しました。 YOLO v3の学習時に面倒なパス通しをShellで自動化! - Ar-Rayのてく日記プログラムはGithubにあります。

github.com

f:id:Ray_ar:20210113233305p:plain

学習

 6が終わると、設定フォルダにパスの情報があり、アノテーションも全て終わっていることになります。あとは、初期重みとなるdarknet53.conv.74をダウンロードして、 $ <darknet_path>/darknet detector train <darknet_path>/train_cfg.data <your cfg file> <darknet53.conv.74's path>とコマンドを入力します。ファイルのパスは特に指定されていませんが、問題がないなら、全て設定フォルダに入れておくと、分かりやすいでしょう。

 学習は半日~1日かかります。損失のグラフは下の通りです。今回は、3万回学習させました。

f:id:Ray_ar:20210115234105p:plain
損失グラフ

学習モデルの検証

 学習が終わったら、backupディレクトリに入っている重みを使って認識できているかチェックしましょう。今回は、数値を正確に出すわけでもないので、ざっくりと調べていきます。

 未使用の画像の対する結果は下の画像の通りです。3000回だと、十分な学習ができておらず、ボルトが正しく認識されていません。10000回の時点ではすべての物体を正しく認識しているようです。しかし、30000回になると、逆にボルトが認識されなくなりました。これは、ある長さのボルトに対して過剰に適応する「過学習」の状態になったと考えられます。水増し後を含めても学習枚数が十分でないことを表しています。

f:id:Ray_ar:20210115234331p:plain
未使用の画像に対する結果

学習に使用したデータでも、誤検出が起こる場合もあります。撮影距離の変更や、そもそも長さに応じてクラス分けをし直すなどの検討をしたほうがいいかもしれませんね。

f:id:Ray_ar:20210115234757p:plain
分割前の画像に適用した結果

 実環境により近い状況ならどうでしょうか?Webカメラで撮影してみました。

f:id:Ray_ar:20210115235235g:plain
Webカメラで取得した画像に対する結果

これまた誤検出や未検出が多発しています…さまざまな背景や処理を施したほうが良いかもしれませんね。

まとめ

 今回はYOLOv3-tinyの学習の流れを簡単に説明しました。特定の物体の検出をする分にはお手軽にできると思うので、ぜひ試してみてください。

Celeron搭載のミニPCの性能を個人的に確認!!(ベンチマーク)

 研究用のRaspberry Piのシステムの置き換えの検討用としてOdyssey Blue Mini PCを購入しました。スイッチサイエンスで購入できます。

www.switch-science.com

f:id:Ray_ar:20210114215651j:plain
Odyssey Blue Mini PCの外観

 IntelのノートPCスペックのCPUが搭載されているミニPCみたいです。青塗装と上面がアクリル板なのがどことなくおもちゃのような見た目です。中は見えなくてもいいのになぁ…

 内容物はこのPC本体と説明書のみです。つまり、ACアダプタは別で購入する必要があります。私は、Jetson AGX Xavierで使っていたものをそのまま流用しました。

 電源を入れると、Windows10 Enterpriseが起動しました。

f:id:Ray_ar:20210114220320j:plain
起動後の画面(タスクマネージャーは自分で開いた)

 Arduinoが最初からインストールされていました。もしかしなくてもこれは小・中学生用の教材の想定で作っているのかもしれません。青塗装なのも納得です。

 Celeron J4105(4コア4スレッド)とメモリ8GBとSSD128GB(SATA)を搭載しています。

ベンチマーク結果

 Windows10が搭載されているPCを開封したらすることは一つ…!ベンチマーク!!!どれくらいの性能が出るのかを(誰かがやってると思うけど)やってみます。

 ちなみに、Raspberry Pi4との比較はcpu-monkeyのGeekbench5で比較されています。性能は倍みたいですね。(価格は3倍だけど…) Raspberry Pi 4 B (Broadcom BCM2711) vs. Intel Celeron J4105 - ベンチマークと技術データ

 CPUのシングルコアもマルチコアも結構微妙…(組み込み向けに使う分にはかなり高性能な部類だと思うけど)ただし、Webブラウザの動作が重いとは感じなかったので、普段使いには問題ないと思います。

 

f:id:Ray_ar:20210114221659j:plain
CINEBENCH R15スコア

 結果を見ると、いわゆる普通のSATA-SSDですね。CPUが足を引っ張っていないことを確認できて安心です。

f:id:Ray_ar:20210114222944j:plain
CrystalDiskMarkの結果

簡単な総評

 普段使いでは微妙(?)、組み込み向けではそこそこ優秀といったところじゃないですかね(?)。外部ベンチマークの結果ではRaspi4の倍の性能があるみたいなので、特に実環境において追加検証することはなさそうです。

 Ubuntu18のインストール方法は普通のノートPCと同じでした。普通にBIOSが立ち上がります。この点はJetsonやRaspiと違って融通が利くところといえるでしょう。

 Ubuntuインストール後はROS2 dashingをインストールしました。本当に普通のノートPCと同じくらいの使用感なので、シミュレーションを動かさない限りは普通に使えます。Raspberry Pi4だってVelodyneがRviz上で動くのだから。

YOLO v3の学習時に面倒なパス通しをShellで自動化!

 初めてYOLOv3の学習を行う人向けにShellで自動化するプログラムを作成しました。コンピュータを固定して学習を行う分にはいいのですが、別のコンピュータで学習させたり、別のデータや学習時の読み込み画像の大きさを変更するなどの対応をするたびに設定ファイルを書き換えなければいけないので面倒だったりします。

 そもそも初心者からすれば手軽に深層学習使えない…(それも経験だと考えればいいのかもしれませんが)

 そこで、アノテーション済のファイルとラベリングリストと初期ファイル(darknet53.conv.74)とfilter項目の編集済yolov3.cfgさえ用意すれば自動的に設定ファイルを構成するShellを作りました。残念ながら、ShellなのでWindows環境のYOLOでは使えません。^^;

 作成するファイルは以下の通りです。なお、ファイルの分類リストの作成にはdemura氏のプログラムを作成しています。感謝申し上げます。自動でgit clone して.divide_filesにフォルダ名が変更されるので、フォルダの見た目はきれいになりますが、隠しファイルが残るのが嫌な人は、Shellの末尾にcd $PROJECT_FOLDER && rm -rf .divide_filesを追記するといいと思います。

github.com

準備が必要なファイルは以下の通りです。 - 対象のデータセットのパス(読み書き権限が与えられているフォルダに属す必要がある。外付けSSDなどの場合は注意)

  • 保存先のフォルダ。(テキストデータとbackupディレクトリの5種類が格納されます)

  • ラベリング用のファイル。(保存先のフォルダにコピーされます。)

  • cfgファイル(コマンドには直接関係しませんが、Training時に必要になります。filtersの編集必須)

  • darknet53.conv.74ファイル(コマンドには直接関係しませんが、Training時に必要になります。backupファイルでもOK)

生成されるファイルは以下の通りです。

  • train.list(for train)
  • test.list (for valid)
  • train_cfg.data(for train command)

f:id:Ray_ar:20210113233305p:plain

生成後は次のコマンドを打ちます。この場合、格納されているフォルダ名は<target_directory>/と表記します。

./darknet detector train <target_directory>/train_cfg.data <target_directory>/yolov3.cfg <target_directory>/darknet53.conv.74

コードはgithubリポジトリに上げています。

github.com

M5 Core2とM5 AtomでTwistをPub/Subしてみる

 今日は、M5 Stackを2つ使ってTwistメッセージをPub/Subしました。気づいた点なども挙げたいと思います。以前、M5 Atom MatrixをROS2で動かしています。M5Atomは最小サイズのROS2マイコンになるのか!? - Ar-Rayのてく日記

f:id:Ray_ar:20210112233955g:plain
動作の様子

 ソースコードGithubリポジトリに上げます。このプログラムの動作に回路の配線は一切ありません。

github.com

 本当は、M5 Stack Core2をJoy stickのノードとして送信 → M5 Stack Atom Core2でJoy Stickノードとして受信というプログラムを書きたかったのですが、Sensor_msgs/Joyではメモリ確保失敗のせいなのか実行エラーが起こり、まともに動きませんでした。妥協でTwistメッセージにしました。(わざわざJoyStickにする必要ないと思いますが…)

 また、M5 Core2とM5 Atomを同時に使うときは、Micro-XRCE-DDS-AGENTのポートは分離する必要があります。例えば、M5Core2が2018ポートだったら、M5 Atomは2019にする…みたいな。

 本当はPS3コントローラをM5Core2で作ろうと思っていましたが、思ったよりしょぼくなってしまいました…。

Qt designer(PyQt)を使ってCUIアプリをGUI化 (yolo_augmentation)

 AnacondaでPyQt5をDesignerを使ってGUIプログラミングをしたい!と思い、自分のPythonプログラムをGUI化してみることにしました。

 使うプログラムは先日 YOLOのtextファイルも一緒に水増しを行うプログラムを作った - Ar-Rayのてく日記 で紹介したものを使います。

設計は、Anaconda3付属designer.exeを使います。

f:id:Ray_ar:20210111231844p:plain
designer.exeの外観

 今回はコマンドラインオプションをGUIで肩代わりするだけなので、シンプルなGUIです。

f:id:Ray_ar:20210111231924p:plain
デザイン中

 ボタンを押下した後に動く関数を作成するためにSignal/Slotの接続します。

f:id:Ray_ar:20210111232031p:plain
Signal/Slotの接続

 追記はyolo_augmentation_gui.pyのみです。そのため、CUIGUIの両方で動きます。 github.com

 designerで作成したuiファイルは、pyuic5でpy化させます。例えばdesigner_file.uiからgenerate_pyfile.pyを作成する場合は、$ pyuic5 -o generate_pyfile.py designer_file.uiと入力する。

f:id:Ray_ar:20210111232516j:plain
動作の様子

 WindowsでもUbuntuでもどちらでも動きました。QtアプリがPythonでも書けるなら、Pythonでもいいかなー。