はじめに
業務でYOLOを取り扱っているんですが、
エッジデバイスに組み込むにあたり、
正直YOLOもC言語も素人に近い理解レベルのため、
ちょっと勉強しないとな…と思ったのがきっかけです
(一応AIブログなのにAIに関する話題は久々…笑)
YOLOのおさらい
まずはYOLOのおさらいから
YOLO(v1)の論文はこちら
https://arxiv.org/abs/1506.02640
そして、YOLOといえばおなじみのこの自転車と犬
YOLO自体はあえて言う必要もないくらい、一般的に使われている物体検知手法です
とりあえず物体検知やるならYOLOといってもいいくらい、
速度や精度、情報量など、いろいろ考慮すると最も選択肢に挙げやすい物体検知手法になると思います
YOLOの出力詳細について
YOLOのアーキテクチャ全体を説明するとかなり長くなりますが、
今回はデコードについて考えるため、出力のあたりだけにフォーカスします
また、今回はYOLOv4を想定しています
出力構造
まず出力の構造として、YOLOv4は3つの異なる解像度の特徴マップを持ちます(この後は「レイヤ」と呼ぶことにします)
それによって、小さいオブジェクトから大きいオブジェクトまで検出できるようになっています
このまとめが分かりやすくまとめてくれていたので、
画像も引用させていただきました
このまとめではYOLOv3ですが、YOLOv4のこのあたりの構造は同じです
YOLOv3、YOLOv4ともに、「13x13」「26x26」「52x52」の3つのレイヤがデフォルトになっています
アンカーボックス
各レイヤ、特徴マップセルごとに、「アンカーボックス」が設定されます
「アンカーボックス」についてはたくさん情報があるので詳細は割愛しますが、
予め設定された複数のサイズ・アスペクト比のBBoxであり、
そのアンカーボックスに対して検出対象物体が存在するか判断するためのものです
オリジナルのYOLOv4では各レイヤごとに3つずつアンカーボックスが設定されているため、
すべての検出対象となるBBoxの合計(検出の最大値、この後は「検出対象BBox」と仮に呼ぶ)は以下となります
$$
(13^2+26^2+52^2)*3
$$
アンカーボックスの設計(余談)
本題から外れるので余談にはなりますが、
この設計が非常に重要で、モデルの性能を大きく左右します
そのためには、例えばテストデータの分析によってデータドリブン的に開発することもできます(確かYOLOv2の論文でもそう述べられていた)
しかし、テストデータが十分に揃わず、開発を進める中でデータを集めていくような場合など、
そもそも設計としてどのようなサイズ・アスペクト比の物体を見つけるべきなのか、という観点で決めていく必要もあるかと思います
デコード
さて、ようやくデコードの話になります
オープンソースをベースに使う場合、あまりデコードを意識することはないんですが、
組み込みになるとそのあたりもよく理解する必要があります
特徴マップが持っている情報
特徴マップのセルごと、各アンカーボックスごと(つまり「検出対象BBox」ごと)に、
以下の情報を持っています
- BBox情報:アンカーボックスに対する、オフセット量としての情報
- クラススコア:各クラスのスコア情報
- オブジェクトネススコア:そのアンカーボックスが物体か否(背景)かの情報
BBox情報のデコード
各検出対象BBoxに対するネットワーク出力を$$t_x, t_y, t_w, t_h$$とし、
$$c_x, c_y$$だけ画像左上端からオフセットされていたとすると、
以下の数式でBBox情報へデコードされることになります
このデコード処理を、各検出対象BBoxで実行します
YOLOv3の論文と図はこちら
https://arxiv.org/abs/1804.02767
スコア情報のデコード
スコアに関しては、クラスのスコアとオブジェクト性スコアの2種類がありますが、
各検出対象BBoxに対してオブジェクト性スコアとクラススコアのかけ合わせたスコアが閾値を上回った場合、
その検出対象BBoxは最もスコアが高いクラスのオブジェクトが検出されたことになります
ここで、「オブジェクトが検出された」と判断された検出対象BBoxを、
仮に「候補BBox」と呼ぶことにします
NMS(Non Maximum Supression)
最後に、「候補BBox」に対してクラスごとにNMSを実行し、
重なっているBBoxの中で最もスコアが高いBoxを選定します
詳細な処理については調べればたくさん情報は出てくるので割愛しますが、
ここでNMSが実行され、「候補BBox」の中で残ったBBoxが、
最終的に「物体が検出されたBBox=最終的な出力」となります
デコード処理のまとめ
結論、文章でまとめると以下のような処理になります
- 特徴マップ毎、アンカーボックス毎に、BBoxのデコード処理を実行し、「検出対象BBox」を算出する
- 「検出対象BBox」毎にスコア情報をデコードし、閾値を超える検出対象BBoxを「候補BBox」として抽出する
- 「候補BBox」に対いてNMSを実行し、最終的な出力としての「オブジェクトが検出されたBBox」を抽出する
さいごに
こんな感じでしょうか?100%自信があるかと言われたらそうでもないですが…
思ったよりまとまった情報がなくて、理解が難しかったところもありました
ここでYOLOについて再勉強できたのは良かったなと思います