ディープラーニングによる映像解析で車両をトラッキングするのに苦労した

![20190217193210.jpg](https://qiita-image-store.s3.amazonaws.com/0/140663/a23690f0-0647-1e65-85af-322b8d1db4fa.jpeg)
# サマリー
・交通量調査をディープラーニングで自動処理するにはトラッキング技術が必要
・サンプルムービーの解説：https://youtu.be/cKdESkOEWPQ
・カルマンフィルタとは
・カルマンフィルタを映像解析に応用するのに苦労した点

要するに大型車両に隠れてしまった車両が再度表示する際、ちゃんとトラッキングして重複カウントしないようにしようね、というお話です。サンプルムービーをぜひご覧ください。自慢したいポイントが3つのほどあるので、これから解説します。

# 交通量調査をディープラーニングで自動処理するにはトラッキング技術が必要

交通量調査というのは、12時間も交差点近くとかに張り付いて人の手でやるのが普通でした。しかしディープラーニングの技術が進化しているので、「YOLO V3とか使えば、映像から車両のカウントくらいすぐに出来るんじゃない？」とか甘く考えて始めてしまったプロジェクトで苦労したシリーズです。

## ・なぜトラッキングが必要なのか？

トラッキングにはざっくり2種類あり、ずっと見えているものをずっと追いかける（同一化判定を行う）技術と、もう一つは一時的に見えなくなったものが再度表示された時、同一判定を行って「これは同じものですね」と判定する技術です。
前者については他の記事でも触れているので、ここでは後者を中心に解説します。

なお海外では「Multiple Object Tracking Benchmark」なんてのがあったりしてトラッキング技術でしのぎを削っているのですが、日本ではあまり話題になっていないような気がします（気のせいかも？）。
https://motchallenge.net/

## ・交通量調査とトラッキング

交通量調査は、車線ごとにどんな車が何台通ったかをカウントするのが目的です。ただし映像でこれをカウントしようとすると、真上からのカメラで撮影でもしない限り、大型車両の影に隠れてしまう普通自動車とかは頻繁に発生します。隠れる前に1台としてカウントして、また出てきてから1台とカウントしてしまえば重複カウントになり、調査の信頼性が損なわれます。

そこで部分的・あるいは一時的に完全に隠れて見えなくなってしまったとしても、トラッキングして重複カウントを回避する必要が出てきました。そこで物体検知に採用したYOLO V3を調べてみると、そんな機能はありません。ええ、これっぽっちも。YOLO V3ができるのは、指定したクラス(この場合だとcarとかbusとか）が静止画内にあるか・ないか、あったらどこの座標か・判定の自信（スコア）…などを教えてくれるだけだったのです。

そこで仕方なくトラッキング機能を用意することにしました。幸い、自動車は直線運動しかしないので何とかなるかな？と甘く見ていた部分もあります。

## ・必要な要件
こと交通量調査（対象が自動車という意味で）に限った場合、トラッキング機能について必要な要件は以下のようになります。

・常に表示されている移動体を映像の前後のフレームで同一化を判定し、トラッキングする。
・移動体が一定期間、部分的あるいは全体が非表示になっても、全体が再表示された時に非表示の移動体と同一判定ができること。
・非表示の期間はカメラアングルにもよるが、画面全体の60%を移動する期間は非表示を可能にする。
・移動体は基本的には直線運動を行うことを前提とし、直進直後にバッグなど複雑な挙動は今回は想定しない。
・逆に静止状態の対象物が部分的あるいは全体が非表示になった後で再表示されても、同一判定ができること。

以上です。最後の要件は信号待ちで止まっている車が、対向車線を通行する大型車で一時的に見えなくなった場合を想定しています。

## ・トラッキングはリアルタイム処理できる？
結論だけ言うとリアルタイム処理は無理でした。私の実装では例えば2秒後に再表示された場合などは、撮影済みの映像を2秒間さかのぼる必要があるため事後処理になりました。リアルタイムでトラッキングできるかは今後の課題です。まあそもそも物体検出に採用しているYOLO V3がそもそもリアルタイムで処理できませんが…（特にフルHD画像だと）。

# サンプルムービーの解説
![カルマンフィルタ解説.jpg](https://qiita-image-store.s3.amazonaws.com/0/140663/1534defc-5e98-adfe-5f0b-84a96cc0c950.jpeg)

もしよろしければ完成ムービーを先にご覧ください。私の説明が一発で分かると思います。
https://youtu.be/cKdESkOEWPQ

## １）完全に隠れた車両をトラッキングする
上の画像でいうとID=12の車はID=4のバスの影に完全に隠れてしまいますが、バスの上を黄色のバウンディングボックス（枠）だけがそのまま移動して、バスの影から再度現れたID=12と一致します。
![20190217192848.jpg](https://qiita-image-store.s3.amazonaws.com/0/140663/6e8e3900-80fe-5076-3889-8cfdfe881c4f.jpeg)

枠にはIDが振られており、バスの影から出てきてもIDは12のままです。もしバスの影に隠れた瞬間に黄色い枠が消えてしまったり、影から出てきた車に別のIDが割り振られているとトラッキングは失敗となります。
![20190217192810.jpg](https://qiita-image-store.s3.amazonaws.com/0/140663/3b61c0e8-f672-65f5-b6f2-0ecaa7e99bea.jpeg)

他にもID=2の車が完全に隠れますが、トラッキングは成功しています。

## ２）部分的に隠れた車をトラッキングする
また上記画像のID=13は完全に隠れることはありませんが（屋根がちょっとだけ見えている）ID=4のバスに隠れても黄色い枠が追従します。IDも13のまま変更がありませんので、トラッキングは成功と言えます。
![20190217192909.jpg](https://qiita-image-store.s3.amazonaws.com/0/140663/2ee0b965-736f-dc52-4a30-df2ad96a4401.jpeg)

他にID=6の車も部分的に隠れていますが、トラッキングは成功しています。
![20190217192831.jpg](https://qiita-image-store.s3.amazonaws.com/0/140663/c7b5faff-6b59-b7b2-061e-4e058c430af6.jpeg)


## ３）car以外のクラスでも検証する

１）と２）はいずれもクラス=carの対象物のトラッキングです。carでは成功しても、他のクラス（truckなど）でトラッキングが失敗しても困るので、他のクラスでも検証します。
ID=17はtruckでこれまでに検証してきたcarとはことなり大型ですが、トラッキングは成功します。
![20190217192639.jpg](https://qiita-image-store.s3.amazonaws.com/0/140663/c96b9dd4-9b96-b256-2284-0b937979c4d6.jpeg)

## ４）静止している対象物が隠れてもトラッキングできる

ここではこれまでと違い、対象物が静止しており移動する大型車両の影に隠れて一時見えなくなるケースを想定します。
ID=1は信号待ちでムービー内では終始停車していますが、反対車線を車が通過して（映像的に）かすったり、あるいは大型のトラックが通りがかって一時的に見えなくなったりします。
![20190217192731.jpg](https://qiita-image-store.s3.amazonaws.com/0/140663/5fd333ff-35e0-797e-5fd1-250654384c38.jpeg)

しかし大型車が通り過ぎた後でも枠ではID=1と表示されており、トラッキングは成功したと言えます。

## その他：サンプル映像について

サンプル映像ではトラッキングの確認に注目してもらいたかったので、画面下側の車線ではあえて物体検出を行っていません。反対車線だけを検出対象外（除外）にするのはマスク機能を使用しています。マスク機能についてはこちらの記事をご参照ください。

またサンプル映像では白線のLine1とLine2を設定し、この間を通過する車両の速度や車間距離を測定しています。二つのラインを通過後に枠上に黄色文字で速度が表示されるので、ご確認ください。
・速度表示の例
![20190217214651.jpg](https://qiita-image-store.s3.amazonaws.com/0/140663/669fbd30-495d-5e44-0776-59669804ed0b.jpeg)
![20190217214632.jpg](https://qiita-image-store.s3.amazonaws.com/0/140663/32f68b1e-f7f1-f85f-07dd-cc4fa2044abd.jpeg)
・車間距離表示の例
![20190217214431.jpg](https://qiita-image-store.s3.amazonaws.com/0/140663/d4b823b0-5348-0d07-ab00-dbf81641889d.jpeg)
※この例ではID=6とID=13の車間距離が27.0mと自動算出されています。

また別記事で記載する予定ですが車両数のカウントだけでなく、正確な速度や車間距離の算出を行うには1/30秒単位でのトラッキングが必須となります。「（この車両は）トラッキングできなかったから正確な速度が算出できませんでした」という言い訳はしたくないですね（自分に言い聞かせ）。

# カルマンフィルタとは？

今回、トラッキングに使用したのはカルマンフィルタという技術（手法）です。トラッキングには使えそうな手法としてはカルマンフィルタとパーティクルフィルタがありそれぞれ用途が異なりますが、車両は基本的には直線運動しかしないので、カルマンフィルタを採用しました。

カルマンフィルタとは簡単にいうと、色んな動きをする値について特定の時間での値を推定する手法です。「値」というのはこの場合は、「車両の位置（画像上のXY座標)」という事になります。簡単にいうと、車両の今までの動きから未来(指定した時間)の位置を予想します。
今回は「見えなかった車が再度表示された時に、隠れる前の車と同一化判定を行う」という課題にフォーカスを当てていますが、見えている車が移動する際に「前のフレームのこの車と、今のフレームのこの車は同じものだよ」という基本的な移動体のトラッキング(同一化判定)にも使用しています。
同じ原理で、一定時間以上離れたフレーム間(見えなくなる前のフレームと再出現したフレーム）での同一化判定を行っています(正確にはカルマンフィルタで算出した予測位置と乖離が少ない場合＋αゴニョゴニョの場合は同一車両と判断する)。

カルマンフィルタについては私より詳しく正確に解説して下さってる記事があるので、そちらをご参照ください。

・位置を求めるカルマンフィルタ
https://qiita.com/xtkd/items/6fe9daf2bc632a2846e6
・機械学習としてのカルマンフィルター
https://qiita.com/komi1230/items/5f48e58f380eaadd3308
・tensorflowでカルマンフィルタ
https://qiita.com/tttamaki/items/3bd1fdab5ef5bbf0d4be

またYOLO V3とカルマンフィルタの組み合わせを簡単にテストしたいならこちらが超お勧めです。ソース公開に感謝！
・deep_sort_yolov3
https://github.com/Qidian213/deep_sort_yolov3

# カルマンフィルタを映像解析に応用するのに苦労した点

## ・YOLO V3とカルマンフィルタの関係
今回は映像解析にYOLO V3を使っています。画面内から解析したい車両の「位置」を教えてくれるのがYOLO V3ですね。YOLO V3(以下YLV3）は「この画像にはこことここに車っぽいのがあるよ！」と教えてくれるだけで、「13フレーム目のこの車と14フレーム目のこの車は一緒だよ！」と同一判定まではしてくれません。してくれたらカルマンフィルタはいらないかもですね。

問題はYLV3は完全でないこと。もっと具体的には、検出洩れを起こして「この画像には車両は映っていないよ！」とか平気で報告してくることです。つまりカルマンフィルタへのインプットが不安定なのですね。
もし100フレーム目で車両AをYLV3が検出したのに、101フレーム目で近くを走るよく似た別の車のみ検出したりするとカルマンフィルタへのインプットが狂うことになり、まったく違う車をトラッキングしたりします(しかし101フレーム目で本当に隠れて見えない時もある)。

そこでYLV3の検出スコアの閾値はあまり高くは設定せず、「車っぽいものでもいいからどんどん教えてよ！」というノリにした方が良い場合があります。要するにカルマンフィルタに渡す「材料」をケチらず全てそろえる、という考え方ですね。
しかしあまりに閾値を下げ過ぎると、偶然できた影が車っぽい感じで認識され(ノイズ）、カルマンフィルタはありもしない車ノイズを一生懸命マッチングさせようと努力して、結果としてトラッキングがグチャグチャになったりすることもありますのでご注意を。

## ・カルマンフィルタが動かない理由

### １）探索範囲がとても大事
車両のトラッキングを目的にカルマンフィルタを実装すると、ある程度のパラメータをいじる(ケースに合わせて自分で設定する)必要が出てきます。そして設定に失敗すると、当然ながらカルマンフィルタは良い仕事をしてくれません。

当初、私は適当な値しか設定せず、「なんだよカルマンフィルタ大したことないな。ぜんぜん動かない」とか思っていました。実装的には処理がループになるのを避けるため、カルマンフィルタには探索する範囲を設定します。今回の交通量計測の場合ですと、「他の車両の影に隠れて、見えなくなる時間」ですね。私はこれを適当に1秒に設定していました。だって長くすると、処理が重たくなるから。

しかし信号前で減速してきた車両が大型バスの影に隠れると、次に再度出現するまで2秒くらいかかるのが普通でした。探索距離を1秒にしていたため、トラッキングが外れてしまうのも当たり前でした。

ただ探索範囲は長ければ設定時に楽できる？という訳でもありません。不要に長く設定すると、画面外にいる（つまりまだカメラの前を通過していない）よく似た車両が出てくるのを待っていて、ヘンテコなマッチングをしてくれるからです。また私の実装では、指定された探索範囲を現フレームから未来に向かって検証を行うので、処理が重たくなりました（1フレーム当たりの処理時間が長くかかる)。

### ２）数値と映像とのギャップ
カルマンフィルタは基本的には「数値」を予想するものです。今回の実装では、YLV3が検出した前フレームでの車両の枠（バウンディングボックス）からの予測値が上がってきます。
ただ同じ車両でもYLV3で検出した枠は、影や接近する他の車の影響を受け、つねにグネグネ動いています（枠の縦横サイズが変わるという意味）。しかもフレームが進むとカメラから遠ざかることにもなり、車両のアングルそのものも変わります。つまり検出前とカルマンフィルタによる予想値と、実際の映像上の「マッチング候補」の枠サイズ（特に縦横サイズや比率）が同じとは限らないのです。

そこでこれらの矩形の同一判定を行うため、「カルマンフィルタが予想した枠とYLV3が検出したマッチング候補の枠との重なり具合が、指定した閾値を越えた時に同一IDとみなす」というルールを設定し、意外とうまく動いたことが確認できました。

ただし例えばバスの影に隠れていく車両はYLV3が検出した枠がどんどん小さくなっていき、逆にバスの影から出てくる際には、YLV3が検出した枠はどんどん大きくなっていきます。
「エビデンスの美しさ」を考えると、バスの影に突っ込む際にも、当初の車両サイズにフィットした枠サイズを記録しておき、大型車に隠れて完全に見えなくなってもその枠サイズを維持できるのが理想です。

また再出現する際には、枠がビヨンと伸び縮みするのではなく、当初の枠サイズのまま移動させたい…このように、カルマンフィルタが出した「結果」からさらに手を動かす必要がありました。

カルマンフィルタがうまく動かない理由はまだ他にもあった気がするので、思い出したら追記するようにします。
最後までお読みいただき、ありがとうございました。

# 関連記事
・[2022年：YOLOXの映像解析で車両の速度をAIで算出して渋滞を判定するのに苦労した～その１：リアルタイム処理編](https://qiita.com/ComputerVision/items/5283a0ebbb59d5a4cb8f)
・[2022年：YOLOXの映像解析で車両の速度をAIで算出して渋滞を判定するのに苦労した～その２：速度算出手法編](https://qiita.com/ComputerVision/items/a86f20673df6b6536345)
・[ディープラーニングで故障車両を発見するのに苦労した](https://qiita.com/ComputerVision/items/4f377bc3eb5e601676ac)
・[ディープラーニングで映像から速度を正確に算出するのに苦労した](https://qiita.com/ComputerVision/items/d5358632209c67dab325)
・[ディープラーニングで交通量調査の映像解析精度を上げるのに苦労した](https://qiita.com/ComputerVision/items/2b5e8ed97610085a20c2)
・[SSD_KerasをWindowsで使って交通量調査できるか試してみた](https://qiita.com/ComputerVision/items/0f87b35f5941b7c3c06b)
・[YOLO V3を使った交通量調査ソフトを作って苦労した](https://qiita.com/ComputerVision/items/bdbf5c237004195a4fbc)