こんばんは。
コンピュータビジョン(『ロボットの眼』開発)が専門の”はやぶさ”@Cpp_Learningです。
最近は、Chainerで遊ぶのにハマっています!特にChainerファミリのひとつで画像処理に特化したChainerCVが楽しい!!
ChainerCV サンプルソースが豊富で嬉しいけど、カメラ・動画には対応してないのよね…
リアルタイム物体検出のYolo使うなら、カメラ・動画に対応させたい…
無いなら作るか!工学部出身だし!!
以降から ChainerCV と Yolo で『カメラ・動画対応!リアルタイム物体検出ソフト』の作り方を説明します。ソースコードだけ見せて!という人は目次から『Yolo_Chainer_Video.py開発』の項目に飛んでください。
Contents
ChainerCVのYoloサンプルソースを確認
ChainerでYoloを動かすサンプルソースは、ChainerCV公式Githubから取得できます。前回、このソースコードをGoogle Colaboratoryで動かしました。
ChainerCVのサンプルソースはOpenCV使わずに画像処理できる。これはこれで、すごく良いけど、本稿ではカメラ・動画を扱うためにOpenCVを使います。
環境構築
Chainerの環境構築については、下記の記事で紹介していますので、まだChainer環境を構築していない人は参考にして下さいな。
上記の記事を参考に構築した仮想環境”Chainer”にChainerCVとOpenCVをインストールしてYoloが動く環境を構築します。
仮想環境”ChainerCV”を構築(必須ではない)
仮想環境”Chainer”をアクティブに切り替えて、必要なソフトをインストールします。また、以下のコマンドで仮想環境”Chainer”をベースに仮想環境”ChainerCV”を新規作成しても良いと思います。
conda create -n ChainerCV –clone Chainer
仮想環境よく分からない!という人は下記の記事を参考にしてみて下さい。
ChaineCVインストール
Chainer環境構築済みの仮想環境をアクティブに切り替えたら、以下のコマンドでChainerCVをインストールします。
pip install chainercv
以上でChainerCVのサンプルソースを動かす環境が構築できました。(カメラ・動画未対応のYoloも動かせます)
インストール時にバージョン指定もできますが、上記のコマンドで最新版をインストールするのが良いと思います。基本的にソフトはバージョンが新しいほど高性能なので。。
OpenCVインストール
今回はカメラ・動画を扱いたいので、以下のコマンドでOpenCVもインストールします。
pip install opencv-python
最新バージョンのリリース時期次第ですが、以降で説明するソースコードは以下のバージョンで動作確認しています。
- Chainer 4.3.0
- ChainerCV 0.10.0
- OpenCV 3.4.1
開発手順の確認
下記の記事で簡単にですが『ソフトウェア開発手順』を説明しています。
この記事の中で以下のポイントを説明しています。
複雑な機能や仕様も分解して、一つ一つの単機能の集合と考え、単機能ごとに「調査」→「コード作成」→「テスト」を行う
今回、開発対象はChainerCV と Yolo を使った『カメラ・動画対応!リアルタイム物体検出ソフト』です。このソフトを”ざっくり”分析すると、以下の二つの機能で実現できます。/span>
【機能】
- ChainerCV で Yolo が動く
- OpenCVでカメラ・動画を扱う
各機能をどうやって実現するか検討します。
【実現方法の検討】
- ChainerCVのYoloサンプルソースを使う
- OpenCVでカメラ・動画を扱う雛形ソースコードを使う
ChainerCVのYoloサンプルソースはColaboratoryで動作確認済み。
OpenCVでカメラ・動画を扱う雛形ソースコードは、『ソフトウェア開発手順』の記事で作成・動作確認済み。
この二つのソースコードを合体させれば、対象のソフトが開発できそうです!
Yolo_Chainer_Video.py開発
以上を踏まえて開発したソースコード”Yolo_Chainer_Video.py”は以下の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
import argparse import matplotlib.pyplot as plt import cv2 import numpy as np from timeit import default_timer as timer import chainer from chainercv.datasets import voc_bbox_label_names, voc_semantic_segmentation_label_colors from chainercv.links import YOLOv2 from chainercv.links import YOLOv3 from chainercv import utils from chainercv.visualizations import vis_bbox def main(): parser = argparse.ArgumentParser() parser.add_argument( '--model', choices=('yolo_v2', 'yolo_v3'), default='yolo_v3') parser.add_argument('--gpu', type=int, default=-1) parser.add_argument('--pretrained-model', default='voc0712') parser.add_argument('video') args = parser.parse_args() if args.model == 'yolo_v2': model = YOLOv2( n_fg_class=len(voc_bbox_label_names), pretrained_model=args.pretrained_model) elif args.model == 'yolo_v3': model = YOLOv3( n_fg_class=len(voc_bbox_label_names), pretrained_model=args.pretrained_model) if args.gpu >= 0: chainer.cuda.get_device_from_id(args.gpu).use() model.to_gpu() if args.video == "0": vid = cv2.VideoCapture(0) else: vid = cv2.VideoCapture(args.video) if not vid.isOpened(): raise ImportError("Couldn't open video file or webcam.") # Compute aspect ratio of video vidw = vid.get(cv2.CAP_PROP_FRAME_WIDTH) vidh = vid.get(cv2.CAP_PROP_FRAME_HEIGHT) # vidar = vidw / vidh print(vidw) print(vidh) accum_time = 0 curr_fps = 0 fps = "FPS: ??" prev_time = timer() frame_count = 1 while True: ret, frame = vid.read() if ret == False: print("Done!") return # BGR -> RGB rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # Result image result = frame.copy() # (H, W, C) -> (C, H, W) img = np.asarray(rgb, dtype = np.float32).transpose((2, 0, 1)) # Object Detection bboxes, labels, scores = model.predict([img]) bbox, label, score = bboxes[0], labels[0], scores[0] if len(bbox) != 0: for i, bb in enumerate(bbox): # print(i) lb = label[i] conf = score[i].tolist() ymin = int(bb[0]) xmin = int(bb[1]) ymax = int(bb[2]) xmax = int(bb[3]) class_num = int(lb) # Draw box 1 cv2.rectangle(result, (xmin, ymin), (xmax, ymax), voc_semantic_segmentation_label_colors[class_num], 2) # Draw box 2 # cv2.rectangle(result, (xmin, ymin), (xmax, ymax), (0,255,0), 2) text = voc_bbox_label_names[class_num] + " " + ('%.2f' % conf) print(text) text_top = (xmin, ymin - 10) text_bot = (xmin + 80, ymin + 5) text_pos = (xmin + 5, ymin) # Draw label 1 cv2.rectangle(result, text_top, text_bot, voc_semantic_segmentation_label_colors[class_num], -1) cv2.putText(result, text, text_pos, cv2.FONT_HERSHEY_SIMPLEX, 0.35, (0, 0, 0), 1) # Draw label 2 # cv2.rectangle(result, text_top, text_bot, (255,255,255), -1) # cv2.putText(result, text, text_pos, # cv2.FONT_HERSHEY_SIMPLEX, 0.35, (0, 0, 0), 1) # Calculate FPS curr_time = timer() exec_time = curr_time - prev_time prev_time = curr_time accum_time = accum_time + exec_time curr_fps = curr_fps + 1 if accum_time > 1: accum_time = accum_time - 1 fps = "FPS:" + str(curr_fps) curr_fps = 0 # Draw FPS in top right corner cv2.rectangle(result, (590, 0), (640, 17), (0, 0, 0), -1) cv2.putText(result, fps, (595, 10), cv2.FONT_HERSHEY_SIMPLEX, 0.35, (255, 255, 255), 1) # Draw Frame Number cv2.rectangle(result, (0, 0), (50, 17), (0, 0, 0), -1) cv2.putText(result, str(frame_count), (0, 10), cv2.FONT_HERSHEY_SIMPLEX, 0.35, (255, 255, 255), 1) # Output Result cv2.imshow("Yolo Result", result) # Stop Processing if cv2.waitKey(1) & 0xFF == ord('q'): break frame_count += 1 if __name__ == '__main__': main() |
Yolo_Chainer_Video.pyのポイント解説
Yolo_Chainer_Video.pyのポイントはOpenCVとChainerCVの画像用変数の扱い方です。以下のフローを見た方が分かりやすいですね。
【Yolo_Chainer_Video.pyの簡易フロー】
- OpenCVでカメラ or 動画をキャプチャ(変数名:frame)
- frameがChainerで扱えるようにBGR ⇒ RGBに変換(変数名:rgb)
- Chainerはnumpy配列のfloat32しか扱えず、かつ画像サイズとチャンネルの順番がOpenCVと異なるので、(H, W, C) ⇒ (C, H, W)に変換(変数名:img)
- 結果出力でOpenCVを使うので、frameをコピーしておく(変数名:result)
- imgに対し物体検出(Yolo)を実施し、boxとlabelの算出結果を得る
- boxとlabelの結果をresultに描画して映像出力
分かるかな?青マーカーがOpenCVの赤マーカーがChainerの変数や処理です。
入出力関係がOpenCVで物体検出(Yolo)がChainerで処理していることが分かると嬉しい。つまり、OpenCVがChainerをサンドイッチしている状態。このサンドイッチのような骨組みは、あらゆるコンピュータビジョン(『ロボットの眼』開発)に応用できます。
また、「上記フローの各項目がソースコードのどこに該当してるの?」と思いますよね。ソースコードに丁寧なコメントを残しておいたので、ソースコードとフローを見比べてみて下さい。
OpenCVと別ライブラリ(今回はChainerCV)を組み合わせるときは、画像の入出力をOpenCVで、中身のメインフローを別ライブラリで処理するサンドイッチの骨組みが便利!
Yolo_Chainer_Video.pyの使い方
パーサーで以下の項目を設定してから、実行します。
設定 | 記号 | 選択候補 |
Yoloのバージョン指定 | –model | YoloV3 or YoloV2 |
プロセッサ設定 | –gpu | CPUなら負数(”-1”など) GPUなら”0”以上の数字 |
学習モデル指定 | –pretrained-model | voc0712など |
カメラ・動画モード選択 | 末尾に設定 | カメラなら”0” 動画ならファイルパス |
※色付き文字はデフォルト設定
使用例
python Yolo_Chainer_Video.py –model yolo_v3 –gpu -1 –pretrained-model voc0712 動画ファイルパス
デフォルト設定で良い場合は、以下のようにカメラ・動画ファイルパスだけ選択します。
python Yolo_Chainer_Video.py 0
初回だけは学習モデルのロードのため、オンライン状態で実行してください。学習モデルのロードに少し時間がかかります。
動作確認
Yolo_Chainer_Video.pyを動作確認した映像が以下です。
※この映像は20fpsで再生しています。
第8世代となるインテルCoreプロセッサ(Core i5-8250U)で動かしましたが、1fps(右上に表示)でした。リアルタイム感はないですね。。GPUマシンで動かしたいな~
【おまけ】Yolo_Chainer_Video.pyのカスタム例
Yolo_Chainer_Video.pyのカスタム例を少し紹介したいと思います。
box/labelの設定変更
だそうです。言われてみれば。。
「綺麗だね」・「綺麗だな~」を繰り返しながら、じーーーーと見つめてくる”くるる”ちゃん。可愛い。
ソースコードの88~90行目がbox設定なので、この部分を少し いじって、92~93行目に単色モードを追加しました。
- Draw box 1:デフォルト(検出対象ごとに色が変化)
- Draw box 2:単色(緑)
好きな方を選んでコメントアウトしてください。
また、ソースコードの102~106行目がlabel設定なので、この部分も少し いじって、108~111行目に単色モードを追加しました。
- Draw label 1:デフォルト(検出対象ごとに色が変化)
- Draw label 2:単色(黒文字)
box/labelともに単色モードで動かしたときの映像が以下の通りです。
ときどきlabelが「イヌ」とか「猫」って表示される…別の学習モデルでも遊んでみたいな。。
出力結果の録画
くるるがyoutubeにアップした動画ファイルをじーーーーと見つめながら質問してくれた。
”くるる”賢いなぁ~
Yolo_Chainer_Video.pyに動画保存機能を付けたい場合は、下記の記事を参考にカスタムしてみて下さい。
最後に
本記事はChainerCV と Yolo で『カメラ・動画対応!リアルタイム物体検出ソフト』の作り方を丁寧に説明しました。
この記事書くの結構大変でした。。でも、コンピュータビジョン(『ロボットの眼』開発)という自分の専門分野だったので、妥協せずに書きました!
本記事で紹介したソフト『Yolo_Chainer_Video.py』をロボットや電子工作に組み込みました!って人が現れたらエンジニアとしては最高に嬉しい!!
『リアルタイム物体検出ソフト:Yolo_Chainer_Video.py』で遊んでみて下さいな!