YOLOXの映像解析で車両の速度をAIで算出して渋滞を判定するのに苦労した～その２：速度算出手法編

![速度算出編トップイメージ.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140663/f8239820-f1be-020e-7286-65e5fb77a528.jpeg)

2023/1/23追記：ストリーミング対応の新規記事を追加しました。24時間など長時間ストリーミングを安定受信して映像解析する苦労話を記載しています。最後の「関連記事」からご参照ください。

# サマリー
・速度の算出について
・距離マスクのアイデアとその実装
・距離マスク（複数移動）
・距離マスクと検出点
・速度の異常検出とその対策

前回記事「YOLOXの映像解析で車両の速度をAIで算出して渋滞を判定するのに苦労した～その１：リアルタイム処理編」の続きです。今回は映像からの速度算出の手法について解説します。
渋滞状況を判定するためには、同一画面内で検出した車両をすべてトラッキングし、リアルタイムで速度変化を追い続ける必要があります（何をもって渋滞とするか？の定義については、また別の記事で解説します）。

・そもそもどうやって映像から車両の速度を算出するか。
・正しい速度算出のためには、どんな工夫が必要か。
・工夫しても発生する速度の誤検出の原因と、その除去方法

などを今回は解説します。
なおこの渋滞検知システムは[Traffic Jam Blade](https://youtu.be/OHDSoWFlcAI)としてすでに完成しており、リンクから20分のサンプル動画をご覧いただけます。渋滞発生は動画の最後の方になります。

# 速度の算出について

速度は移動距離÷時間で算出できます。小学校で習いますね。
映像の場合、時間＝フレーム数（正確には、1フレーム分の時間）となります。

問題になるのが距離です。映像では移動したピクセル量しか分かりません。
「1フレームで8ピクセル移動した」と言われても、それが何メートルか分からないとkm/hの速度が算出できません。

そこでここでは道路上に描かれた白線の長さを基準として、まず距離を算出しています。
白線には長さと間隔の[規格](http://www.thr.mlit.go.jp/sendai/furukoku/pdf/kouji/kouji/kukaku-mame.pdf)があり、高速道路では8mの白線と12mの間隔。画像の一般道では6mの白線と9mの間隔となっています（5mの白線と5mの間隔もある）。Google Mapの航空写真で確認すると、長さの凡例が出ているので便利です。

そこで映像上の何ピクセルが1mに該当するか？を設定ファイルに記載することで、移動距離を（大雑把に）算出できます。

なおこの映像では車両は水平方向（x軸方向）に移動するため、前のフレームでのx座標と次フレームのx座標の差分で移動距離（ピクセル）が算出できます。
![vlcsnap-2022-10-29-20h51m52s567.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140663/6adf74b0-3087-ce89-5499-44c316a52766.png)

では以下のような斜め移動の場合はどうなるでしょうか？
![斜め移動説明-1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140663/13ea3067-84ba-f397-7785-fad03b437ff3.jpeg)
これは一見複雑そうですが、実は斜めに移動した場合でも実際の移動距離(メートル換算）はY軸の移動距離に帰結できます（車両の移動は赤い矢印だが、Ｙ軸だけの移動に注目するとそれぞれの緑矢印になる）。
![斜め移動説明-2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140663/7c5a98bd-e18e-9dda-5aa4-9bf0a0e4f752.jpeg)
白線と間隔で黄色のラインを引いて区切りにした場合、車両は赤いラインの通り斜めに移動しますが、黄色の区間内でY軸方向の移動を見た場合、すべて緑の矢印の長さ（＝Y軸方向の移動）と同じになります。
例えば一番下だと、白線の長さが6mなので、この移動距離を6mに設定すればいいわけです。

「ちょっと待って。白線は斜めになっているのに、これを6mで設定していいの？」という疑問があるかもしれません。
これも問題がありません。黄色の区間は実際の距離(メートル）とは無関係で、「一番下の区間の高さ（青矢印）の部分の移動を6mと定義する」ということになるからです。

よって、重要なのは車両の移動方向により、Ｘ軸移動で算出するかＹ軸移動で算出するか？という判断です。
実はどのような映像でも（理論上は）Ｘ軸移動だけで算出できますが、道路の白線の記載や移動距離が大きい方でＸ軸移動・Ｙ軸移動を切り替えて使えばいいでしょう。
※Traffic Jam Bladeでは距離の算出時にX軸・Y軸移動量に加えて、XY移動量も設定できます。

ここまで説明すると、「でも斜め移動の場合、白線が6mで間隔が9mなら、画面の場所によって移動距離指定が異なるのでは？」という疑問です。
これはその通りで、この問題を解決するのが「距離マスク」です。

# 距離マスクのアイデアとその実装

![斜め移動説明-3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140663/0a34d717-a30d-73fc-53e7-a7b6f09a537a.jpeg)
「距離マスク」とは、映像内の場所により移動距離指定が異なる場合にそれを補正する機能です。
実際には、映像サイズと同じ大きさのPNGファイルを用意し、それを別々のRGBで塗り分けたものです。
※上の画像では、分かりやすいよう実画像にオーバーラップさせています。

上から①の高さは6m、②は9m、③は6m、④は9m、⑤は6mとなります。
各色の帯の高さが、移動距離の指定になるわけです。このように指定することで、映像内の場所の違いによる移動距離指定の差異を少なくすることができます。
なおこの距離マスクの例は非常に大雑把なものですが、例えば⑤の部分であればこれをさらにパース状に等分することで、より差異を少なくすることができます。

```SETTING.JSONにおける距離マスク指定の例
    "DISTANCE_RATES": [
        {"color": "#99cc33", "px": 51.0, "km": 0.021, "adj": 1.0},
        {"color": "#00cc00", "px": 24.0, "km": 0.006, "adj": 1.0},
        {"color": "#669900", "px": 42.0, "km": 0.009, "adj": 1.0},
        {"color": "#ffcc00", "px": 43.0, "km": 0.006, "adj": 1.0},
        {"color": "#ff9900", "px": 101.0, "km": 0.009, "adj": 1.0},
        {"color": "#000000", "px": 19.0, "km": 0.001, "adj": 1.0},
        {"color": "#666666", "px": 21.0, "km": 0.001, "adj": 1.0},
        {"color": "#cccccc", "px": 23.0, "km": 0.001, "adj": 1.0},
        {"color": "#999999", "px": 26.0, "km": 0.001, "adj": 1.0},
        {"color": "#333333", "px": 29.0, "km": 0.001, "adj": 1.0},
        {"color": "#ff6600", "px": 33.0, "km": 0.001, "adj": 1.0},
        {"color": "#cc0000", "px": 331.0, "km": 0.006, "adj": 1.0}
    ],
```
なおadj以降は映像サイズなどの変更時に使用する調整用パラメータです。
RGBのカラーコードは同一距離マスク内に置いて、それぞれユニークである必要があります。

# 距離マスク（複数移動）

「距離マスクの概念は分かったけど、1フレームで複数のカラーコードを移動する場合はどうするの？」と思われるかもしれません。例えばこんな感じです。
![斜め移動説明-4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140663/d07aaf1a-9563-b379-4758-f1e9feb85254.jpeg)

例えばこのワンボックスカーがたったの1フレームで黒矢印分を移動したとしましょう。③～⑤まで3つの距離マスクの設定領域を移動しています。この場合は、次のように計算することになります。
![斜め移動説明-5.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140663/8c29a0c3-2230-c321-dfde-2e3873c9c96b.jpeg)

つまり③の部分はY軸方向の移動距離分がA、同様に④の部分はB、⑤の部分はCとなります。
それらを合計すれば、より正確な移動距離（メートル）を算出できます。

# 距離マスクと検出点

当初、速度の検出のためにYOLOXが物体検出したバウンディングボックスの中心点を利用していました。バウンディングボックスはトラッキングに伴い映像のフレームごとに変化しますが、変化の影響が一番少ないと思ったためです。
ただこの方法では水平移動（X軸移動）する車両の場合に問題が起きました。

![距離マスク算出位置説明用_中心用マスク_FIN.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140663/f1f83da8-f5a1-fb5f-7a6b-e531e37dcfb4.jpeg)

距離マスクを正確に車線ごとに塗り分けた場合、大型の車両が越境するのです。
上の図では、③のマスクは車線と同じ幅に塗り分けていますが、大型車両（右下の例）では②に越境しています。
②のマスクは越境を見越して実際の車線幅よりも①側にマスク自体を越境させており、なんとかトラックの中心点が②に収まっています。しかしより大型の観光バスではこのマスクでも越境される可能性があります。
ただでさえ複雑化する距離マスクの描画に越境の要素を入れると混乱の原因です。
そこで以下のように考えました。
![距離マスク算出位置説明用_ピッタリマスク_FIN.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140663/42cbc3bc-c0f2-a081-b9c7-b6aa8cae6ddb.jpeg)

要するに「中心点ではなくタイヤの位置を見れば、越境の心配がない」ということです。
大型の車両は縦に長いのであって、タイヤ位置は小型車とほぼ同じで越境しない（車線をまたがない）という訳ですね。この発見により、映像ごとに「バウンディングボックスのどの位置を距離マスク算出用に変更できる」よう設定ファイルに取り入れて、すっきりした距離マスクを作成できるようになりました。

```SETTING.JSONにおける距離マスク対象点の設定例
    "DISTANCE_MASK_POINT_": "距離用マスクの対象にする点。TOP_LEFT TOP_RIGHT BOTTOM_LEFT BOTTOM_RIGHT CENTER",
    "DISTANCE_MASK_POINT": "BOTTOM_LEFT",
```
※Traffic Jam Bladeでは上記画像のような場合は進行方向を考え、バウンディングボックスの左下（BOTTOM_LEFT）で設定するようにしています。

# 速度の異常検出とその対策

速度算出を映像から行うと、たまに誤検出によりあり得ない速度が算出されています。ここではこれを「ノイズ」と呼んで、排除するために典型的なノイズとその対処方法について説明します。

## １）カルマンフィルタによる速度異常

![カルマンフィルタによる誤検出.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140663/0492a723-7a66-46ad-b885-9d183e3ca63f.png)

時速が1159km/hと表示されてます。もちろん誤算出です。
これはコーディングの単純ミスで、motpyによるトラッキング中に一度ロストして、カルマンフィルタにより再度同じIDで検出された時に起きました。要するに、動画上の前フレームとカルマンフィルタの前フレームが一致していなかったため、「すごい距離を1フレームで移動した」ことになり、このような異常な速度となっています。トラッキング上の前フレームと映像フレームの両方に配慮することで、誤算出を防ぐことができます。

## ２）誤検出による速度異常

![誤検出による速度異常-2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140663/cc3f33e7-e258-4175-657f-efef74d70a82.jpeg)
![誤検出による速度異常-1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140663/88e4ec79-fd5d-8b36-11eb-a3877ec974ed.jpeg)
上の白っぽいワンボックスカーは、直前のフレームでは45km/hですが、次のフレームでは4km/hと急減速しています。動画上は速度は同じなので、これもノイズです。

これの原因は、下の画像で物体検出のバウンディングボックスが大きくずれていることでお分かりの通り、低精度なモデル（ここではyolox_s.pth)を使ったことが原因でした。この例では30fpsで処理した際に速度検出をバウンディングボックスの中央点（の移動距離）で算出しているにもかかわらず、次フレームでバウンディングボックスが大きく膨らんだことにより中央点の位置が大きく後退し、このようなノイズとなっています。

対処方法としては、モデルをより高精度なyolox_m.pthに変更することで低減できました。

## ３）画面の端問題

ベタなネーミングですが、「画面の端にくると、速度検出がおかしくなる気がする」と検出結果の動画を見ていて気づいたのが発端です。実際にはこんな風に発生します。
![画面の端問題_連続.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140663/dc3eebfb-7928-acfa-b4b8-6d5a54da56ba.jpeg)
左端が映像での「画面の端」です。
マイクロバスの先頭が画面の端に到達した時は29km/hと表示されていますが、マイクロバスが徐々に画面外に出るにつれて29km/h⇒22km/h⇒16km/h⇒9km/hと遅くなってきます。もちろん映像上は減速しておりません。

このノイズが発生する理由は車体の一部しか映っていないにもかかわらず、YOLOXが頑張りすぎて物体検出を行っているためです。
車体が短くなるに連れ検出のバウンディングボックスも小さくなり、距離マスク用の検出点の移動（例えば中心点）が見かけ上、小さくなるため速度の低下が画面の端で発生することになります。

これを防ぐには「車体全体が検出されている車両のみで速度を検出する」と定義することですが、現実的ではありません。画面の中央でも大型車両の陰に隠れるような場合があるからです。

そこで別アプローチとして（ベタなネーミングに戻るわけですが）、「画面の端に到達した車両は検出しない」で解決できました。もともと今回は、映像内の不要な車線を物体検出の対象から除外する「除外マスク」という機能を用意していました。
これを利用し、画面の端にも一部除外を適用することで、「画面の端問題」は解決できました。
![画面の端問題-解決.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140663/adaabc83-706c-e06b-2422-cdbaa63a0b0a.jpeg)

具体的には除外マスクを適用するバウンディングボックスの「角」を指定し、それが除外マスクに触れたら物体検出対象画としました。上の図では、色が付いている部分が除外対象です。除外マスク自体は映像サイズと同じPNG画像で、ここでは説明のため実画面とオーバーラップさせています。

```SETTING.JSONにおける除外マスク対象点の設定例
    "MASK_POINT_": "除外マスクの対象にする点。TOP_LEFT TOP_RIGHT BOTTOM_LEFT BOTTOM_RIGHT CENTER",
    "MASK_POINTS": ["BOTTOM_LEFT", "BOTTOM_RIGHT"],
```
なお「画面の端問題」は車両が消える左端だけでなく、車両が部分的に出現する右端でも発生します。
よって、除外マスク対象点はこの映像（水平移動）の場合は、左下(BOTTOM_LEFT)と右下(BOTTOM_RIGHT)の両方を指定しています。

# 関連記事
・[2023年：裏技公開！ AI映像解析で物体検出精度をあげる簡単テクニック](https://qiita.com/ComputerVision/items/1bb9861d0b55b0161818)
・[2023年：24時間以上ストリーミングさせるとトラブルばかりで苦労した](https://qiita.com/ComputerVision/items/26a72a3f83fcf6c5d0d1)
・[2022年：AI映像解析で車両の速度を自動算出すると異常値ばっかりで苦労した](https://qiita.com/ComputerVision/items/fcd39dcb272edf37518c)
・[2022年：YOLOXの映像解析で車両の速度をAIで算出して渋滞を判定するのに苦労した～その１：リアルタイム処理編](https://qiita.com/ComputerVision/items/5283a0ebbb59d5a4cb8f)
・[2022年：YOLOXの映像解析で車両の速度をAIで算出して渋滞を判定するのに苦労した～その２：速度算出手法編](https://qiita.com/ComputerVision/items/a86f20673df6b6536345)