yolov3 C コードの説明 - takgto/utokyo-chipathon2023 GitHub Wiki
ここで説明するC++コードは、Xilinx(現AMD)のVART(Vitis-AI Runtime)ライブラリを使用します。yolov3のC++コードは以下にあります。
kv260: ~/Vitis-AI/examples/VART/yolov3/
WSL2:上 ~/Vitis-AI/examples/VART/yolov3/
これらコードにおいて、コンパイルして実行するにはxmodelが必要です。
yolov3_video_series.cpp
画像の読み出し --> 前処理 --> 検出(yolov3) --> 後処理 --> 表示を順番に実行していきます。並列処理は行っていません。
main関数
以下の行から始まる関数です。まずこの関数が動作して他の関数を呼び出します。
まず、ビデオファイルをオープンします。
whileループで、画像の読み出し --> 前処理 --> 検出(yolov3) --> 後処理 --> 表示 を繰り返し行います。前処理・検出・後処理はrunYOLO()が行います。
if (is_shown){}で検出した画像を表示するかしないかのコントロールができます。SHOW_EVERY(上の方で#define)は何枚に1回表示するかを表し、1なら毎回表示、2ならwhileループの2回に1回表示します。
表示は201行目でimshowが行います。runYOLO()の中でimgに結果(Bbox:Bounding box)が上書きされます。
ループの始めの時刻:loop_startとループ終わりの時刻:206行目steady_clock::now()の差からFPSを算出します。
関数runYOLO
以下の行から始まる関数です。この関数内でYOLOV3を処理しています。
runYOLOの中で元画像をYOLOv3の既定入力に合うようにresizeします。resizeのためsetInputImageForYOLO()とsetInputPointer()の2種類を用意しており、定数Lbox_on(上の方で#defineされている)で切り替えられます。両関数で得られる変換後の画像の例を以下に示します。setInputImageForYOLO()ではアスペクト比を固定したまま、416×416の画像に変換しています。また両関数とも後半で8ビット量子化を行っています。
次はDPUを起動するステップです。118-127行目までは起動に必要な変数とメモリ領域の確保です。ここは通常変更の必要はありません。
DPUはKV260の場合、1つしかないので他のタスクが使っているとrunner->wait()で終わるまで待って実行されます。
runYOLO()の最後の2行で、検出したBboxの情報(results)を元に入力画像上にBboxをClassに基づいた色で描きます。
yolov3_video_series_prof.cpp
yolov3_video_series.cppにプロファイリング用のコードを付け足したコードです。プロファイリング用コードの詳細はcpp-labの演習4-2で詳しく学べます。他にmAP計測用のコード(#define SAVE_LOG 1でON)も含まれていますが、ここではプロファイリンクについて説明します。
class Profilerはプロファイリングのためのclassです。Profilerの定義の後にグローバルとしてインスタンスを作っています。
main()に追加されたプロファイリングでコードです。
runYOLO()に追加されたプロファイリング用コードです。
以下はKV260での実行結果の例です。使うモデルや条件によって結果は異なります。なお、動画再生中に中断してもそれまで再生したフレーム数による統計処理結果が表示されます。
yolov3_video_study.cpp
本コードでは高速化のため、並列処理を行うスレッドを使っています。スレッドとその使用方法については演習形式でcpp-labに詳しい解説があります。
コードの最初のコメント部分をよく読んでください。
コードには、プロファイリングのコードとmAP測定用のコードが埋め込まれており、#define PROFILE 1、#define SAVE_LOG 1、でそれぞれ有効になります。そのため、変更して良い部分と変更してはいけない部分([DO NOT MODIFY] で囲まれた部分)があるので注意してください。またプロファイリングは、yolov3_video_series_profとは違ってもっと簡略化されたものです。
まずmain()関数です。
yolov3_video_seriesと違って、スレッドを4本立てており、内2本はrunYOLO(#define NINFER 2)、他はreadFreme()、displayFrame()です。スレッド間のやりとりは、ConcurrentQueue q1:Read→Infer、q2:Infer→Showを使っています。ConcurrentQueueではQueueの読み書きでConflictが避けられる仕組みを備えています(cpp-lab 演習4-2参照)。SAVE_LOGのフラグを立てるとプロファイリング結果の表示とmAP測定用データのログ(detection_log.txt)を実行ディレクトリと同じディレクトリに保存します。関数save_log()とreport()はなるべく変更しないでください。特にsave_log()の出力であるdetection_log.txtはmAP測定用に使いますので注意が必要です。
PROFILE=1, SAVE_LOG=1での実行結果の一例を示します。SAVE_LOG=1にした場合は動画再生を途中で止めず、最後まで再生してください。
結果の解釈の仕方はcpp-lab 演習4-4が参考になります。
yolov3_video_series_tiny, yolov3_video_study_tiny
ファイル名の最後が*_tiny.cppとなっているコードは、ネットワークモデルがyolov3-tinyであることを示しています。yolov3とyolov3-tinyのネットワーク構造については、cnn事前問題集の5章に詳しく記述しています。コードに関連する大きな違いは出力解像度です。yolov3では13×13・26×26・52×52 の3つの解像度で出力するのに対し、yolov3-tinyでは13×13・26×26の2つのみです。なので小さい物体の検出精度はyolov3-tinyにすると低下します。
次はコードの比較です。出力配列の個数、ポインタの数が異なります。