Skip to content

[experiment] 2ライン判定による出庫判定検証 #88

Description

@ucn-yushin

概要

駐車場の出入口付近に2本のラインを設け,車両のトラッキング結果からライン通過を判定することで,入庫・出庫を検出する.

本issueでは,出入口側の Line1 を入出庫の主判定ラインとして扱い,奥側の Line2 を補助判定ラインとして扱う.
最終的な入庫・出庫カウントは Line1 の通過方向によって行い,Line2Line1 判定の信頼度を補助的に評価するために用いる.

これは,Line1Line2 の間に駐車スペースが存在する場合,Line2 が駐車動作や場内移動によって跨がれる可能性があるためである.
そのため,Line2 単体では IN / OUT 判定を行わず,ライン通過の有無と通過順序のみを記録する.

開発目的

駐車場出入口において,車両の入庫・出庫を安定して検出できるかを検証する.

1本のラインのみを用いた判定では,以下のようなケースで誤検出・未検出が発生する可能性がある.

  • 車両がライン付近で停止する
  • bboxの揺れにより,ラインを複数回跨いだように見える
  • トラッキングIDが途中で切れる
  • 車両が斜め方向に進入・退出する
  • 一部フレームで検出が欠落する

そこで,Line1 による主判定に加えて,奥側の Line2 の通過有無・通過順序を補助情報として利用することで,入出庫判定の信頼度を高める.

考えられる開発内容

1. 車両検出・トラッキング

YOLOなどの物体検出モデルで車両を検出し,ByteTrack / BoT-SORTなどで各車両に track_id を付与する.

各フレームにおいて,車両ごとに以下を保持する.

  • track_id
  • bbox座標
  • 車両代表点
  • 過去フレームの代表点
  • Line1 の通過方向
  • Line2 の通過有無
  • ライン通過順序
  • カウント済みかどうか

2. 車両代表点の設定

bboxの中心点ではなく,bbox下辺中央を車両代表点として扱う.

vehicle_point = ((x1 + x2) / 2, y2)

bbox下辺中央を使う理由は,車両の地面上の位置に近く,斜め画角において移動方向を比較的安定して表しやすいためである.

3. ラインの定義

2本のラインをそれぞれ2点で定義する.

line1 = ((x1, y1), (x2, y2))
line2 = ((x3, y3), (x4, y4))

例として,出入口側に Line1,奥側に Line2 を配置する.

駐車場内
  ↑
  |
 Line2
  |
 駐車スペース
  |
 Line1
  |
  ↓
道路側

この場合,基本的には以下のように扱う.

  • Line1 を道路側 → 駐車場側に通過: 入庫
  • Line1 を駐車場側 → 道路側に通過: 出庫
  • Line2 のみを通過: 入出庫としてはカウントしない
  • Line1 → Line2: 入庫判定の信頼度を高く扱う
  • Line2 → Line1: 出庫判定の信頼度を高く扱う

4. ライン判定の数学的ロジック

ライン通過判定では,ラインを2点 A, B で定義し,車両の代表点 P がそのラインのどちら側に存在するかを外積の符号によって判定する.

ラインの始点・終点,車両代表点を以下のように定義する.

A = (x1, y1)
B = (x2, y2)
P = (x3, y3)

ここで,ライン方向のベクトルを a,ライン始点から車両代表点へのベクトルを b とする.

a = B - A = (x2 - x1, y2 - y1)
b = P - A = (x3 - x1, y3 - y1)

2次元ベクトルの外積は以下で求められる.

a × b = ax * by - ay * bx

したがって,点 P がライン AB のどちら側にあるかは以下の値で判定できる.

def side_of_line(p, a, b):
    return (b[0] - a[0]) * (p[1] - a[1]) - (b[1] - a[1]) * (p[0] - a[0])

この計算結果の符号により,点 P がラインのどちら側に存在するかを表すことができる.

side > 0 : ラインの片側
side < 0 : ラインの反対側
side = 0 : ライン上

ただし,side > 0 が駐車場側,道路側のどちらを表すかは,ラインの向き A → B に依存する.
そのため,実装時には,あらかじめ駐車場側の参照点を1点設定し,その符号を駐車場側として扱う.

parking_ref_point = (px, py)
parking_side = side_of_line(parking_ref_point, line_start, line_end)

例えば,あるラインに対して駐車場側の点を与えたとき,parking_side > 0 であれば,そのラインでは side > 0 を駐車場側,side < 0 を道路側として扱う.

ライン通過判定

車両の前フレームの代表点を P_prev,現在フレームの代表点を P_curr とする.

prev_side = side_of_line(prev_point, line_start, line_end)
curr_side = side_of_line(curr_point, line_start, line_end)

前フレームと現在フレームで符号が反転した場合,車両がラインを通過したと判定する.

if prev_side * curr_side < 0:
    # ラインを通過した

これは,車両代表点がラインの片側から反対側へ移動したことを意味する.

5. Line1におけるIN/OUT判定

本ロジックでは,出入口側の Line1 を主判定ラインとして扱う.
そのため,入庫・出庫の最終判定は Line1 の通過方向によって行う.

Line1 に対して,駐車場側の符号を parking_side_sign,道路側の符号を road_side_sign とする.

parking_side_sign = 1 if parking_side > 0 else -1
road_side_sign = -parking_side_sign

前フレームで道路側,現在フレームで駐車場側に移動した場合は入庫とする.
反対に,前フレームで駐車場側,現在フレームで道路側に移動した場合は出庫とする.

prev_sign = 1 if prev_side > 0 else -1
curr_sign = 1 if curr_side > 0 else -1

if prev_sign == road_side_sign and curr_sign == parking_side_sign:
    direction = "IN"
elif prev_sign == parking_side_sign and curr_sign == road_side_sign:
    direction = "OUT"

つまり,Line1 における判定は以下のようになる.

道路側 → 駐車場側 : 入庫
駐車場側 → 道路側 : 出庫

このように,単純に 正 → 負負 → 正IN / OUT を固定するのではなく,駐車場側の参照点を用いて符号の意味を定義することで,ラインの向きやカメラ画角に依存しにくい判定にする.

6. Line2における通過判定

奥側の Line2 についても,同様に外積の符号反転によってライン通過の有無を判定する.

ただし,Line1Line2 の間に駐車スペースが存在する場合,Line2 は駐車動作や場内移動によって跨がれる可能性がある.
そのため,Line2 では IN / OUT の方向判定は行わない.

Line2 では,以下の情報のみを記録する.

Line2を通過したか
Line2を通過したフレーム
Line1との通過順序

したがって,Line2 単体での通過は入庫・出庫イベントとしてカウントしない.
Line2 は,Line1 による入出庫判定の信頼度を補助的に評価するために用いる.

7. Line1主判定とLine2補助判定

本ロジックでは,最終的な入庫・出庫カウントは Line1 を通過した場合のみ行う.
Line2 は,Line1 判定の信頼度を補助的に評価するために用いる.

Line1判定 Line2通過履歴 最終判定
IN Line1 → Line2 入庫確定・高信頼
IN Line2なし 入庫・通常信頼
OUT Line2 → Line1 出庫確定・高信頼
OUT Line2なし 出庫・通常信頼
None Line2のみ カウントしない
None Line2のみ複数回 駐車場内移動として無視

Line2 のみを通過した場合は,駐車場内での移動または駐車動作とみなし,入庫・出庫としてはカウントしない.

8. 通過順序による補助判定

Line1 の判定に加えて,Line2 との通過順序も記録する.

例として,Line1 を出入口側,Line2 を奥側に置いた場合は以下のように判定できる.

Line1 → Line2: 入庫判定の信頼度を高く扱う
Line2 → Line1: 出庫判定の信頼度を高く扱う

これにより,

  • Line1 の通過方向
  • Line2 の通過有無
  • Line1Line2 の通過順序

を用いて,最終的な入出庫判定と信頼度を決定する.

判定例

入庫する車両

道路側 → Line1通過 → 駐車場側

この場合,Line1 において車両代表点が道路側から駐車場側へ移動するため,入庫と判定する.

さらに,その後に Line2 も通過した場合は,

Line1 → Line2

の順序になるため,入庫判定の信頼度を高く扱う.

出庫する車両

駐車場側 → Line1通過 → 道路側

この場合,Line1 において車両代表点が駐車場側から道路側へ移動するため,出庫と判定する.

事前に Line2 を通過していた場合は,

Line2 → Line1

の順序になるため,出庫判定の信頼度を高く扱う.

ライン間の駐車スペースに停める車両

Line2のみ通過
Line1は未通過

この場合,駐車場内での移動または駐車動作とみなし,入庫・出庫としてはカウントしない.

Line2 のみの通過を OUT 判定として扱うと,実際には出庫していない車両を誤って出庫として数える可能性があるため,Line2 では方向判定を行わない.

9. 状態管理

track_id ごとに状態を保持する.

track_states = {
    track_id: {
        "line1_direction": None,
        "line1_frame": None,
        "line2_passed": False,
        "line2_frame": None,
        "passed_order": [],
        "counted": False,
        "confidence": None,
        "last_update_frame": frame_id,
    }
}

処理の流れは以下の通り.

  1. 各フレームで車両を検出・追跡する
  2. 各車両の代表点を計算する
  3. 前フレームの代表点と現在フレームの代表点を比較する
  4. Line1 を横切ったか判定する
  5. Line1 を横切った場合,通過方向から IN / OUT を判定する
  6. Line2 を横切ったか判定する
  7. Line2 を横切った場合,通過フレームと通過順序のみを記録する
  8. Line1 判定をもとに入庫・出庫としてカウントする
  9. Line2 との通過順序に応じて信頼度を付与する
  10. Line2 のみを通過した場合はカウントしない
  11. すでにカウント済みの track_id は再カウントしない

10. 重複カウント対策

ライン付近での停止やbboxの揺れにより,同じ車両が複数回カウントされる可能性がある.
そのため,以下の対策を行う.

  • 同じ track_id は同一イベント内で1回のみカウントする
  • 一度カウントした track_id には counted = True を付与する
  • 一定フレーム以上更新されない track_id は状態管理から削除する
  • ライン近傍での細かい揺れを無視するため,必要に応じてマージンを設定する
  • Line1Line2 の通過時間差が大きすぎる場合は,同一イベントとして扱わない

例:

max_frame_gap = 90  # 30fpsの場合,約3秒

Line1 を通過してから Line2 を通過するまで,または Line2 を通過してから Line1 を通過するまでの時間が長すぎる場合は,別イベントまたは通常信頼のイベントとして扱う.

ライン近傍での揺れ対策

ライン付近では,bboxの揺れによって side の符号が細かく反転する可能性がある.
そのため,必要に応じて,ライン近傍の小さな符号変化を無視するためのマージンを設ける.

margin = 1000

if abs(prev_side) > margin and abs(curr_side) > margin:
    if prev_side * curr_side < 0:
        # ライン通過として扱う

ただし,マージンを大きくしすぎると,低速でラインを通過する車両を見逃す可能性があるため,実映像を用いて調整する.

評価指標

1. 1フレームあたりの処理時間

リアルタイム処理に近い条件で動作可能かを確認するため,1フレームあたりの処理時間を計測する.

1フレームあたりの処理時間 [ms/frame]

処理時間は,動画全体の処理時間を処理フレーム数で割って算出する.

$$ T_{frame} = \frac{T_{total}}{N_{frames}} $$

  • $T_{frame}$: 1フレームあたりの処理時間
  • $T_{total}$: 動画全体の処理時間
  • $N_{frames}$: 処理したフレーム数

ライン判定自体は 車両数 × ライン数 の軽量な処理であるため,主な処理負荷は物体検出とトラッキングに依存すると考えられる.
そのため,1ライン判定,2ライン通過順序判定,Line1 主判定 + Line2 補助判定で1フレームあたりの処理時間がどの程度変化するかを比較する.

2. Count Error

最終的な入庫・出庫台数の誤差を評価する.

実際の入場台数・退場台数と,システムのカウント結果がどれだけずれたかを見る.

$$ \text{Count Error}=|N'_{in}-N_{in}|+|N'_{out}-N_{out}| $$

複数動画で評価する場合は,平均絶対誤差として評価する.

$$ MAE=\frac{1}{M}\sum_{i=1}^{M}|N'_i-N_i| $$

3. F1 score

各車両の入庫・出庫イベントを正しく検出できたかを評価する.

  • TP: 正しく検出したイベント
  • FP: 存在しないのに検出したイベント
  • FN: 存在したのに見逃したイベント

$$ Precision=\frac{TP}{TP+FP} $$

$$ Recall=\frac{TP}{TP+FN} $$

$$ F1=\frac{2 \times Precision \times Recall}{Precision + Recall} $$

4. S1 score

NVIDIA AI City Challengeで用いられていた評価指標を参考に,処理速度とカウント精度の両方を評価する.

参考文献:
https://openaccess.thecvf.com/content_CVPRW_2020/papers/w35/Folenta_Determining_Vehicle_Turn_Counts_at_Multiple_Intersections_by_Separated_Vehicle_CVPRW_2020_paper.pdf

$$ \text{S1} = 0.3 \times \text{S1 Efficiency} + 0.7 \times \text{S1 Effectiveness} $$

  • S1 Efficiency: 処理効率
  • S1 Effectiveness: カウント精度

比較したい内容

本issueでは,以下の方法について検証する。

Line1 を主判定,Line2 を補助判定として扱う方法

この方法では Line2 単体での IN / OUT 判定を行わないため,ライン間の駐車スペースに停める車両や,駐車場内で切り返す車両による誤検出を避けやすいと考えられる.

考えられる開発時間

よしなに

備考

本ロジックでは,ライン判定処理自体は軽量であり,リアルタイム性に大きく影響するのは主に物体検出とトラッキング処理であると考えられる.

そのため,検証時には1フレームあたりの処理時間を計測し,1ライン判定,2ライン通過順序判定,Line1 主判定 + Line2 補助判定で処理負荷にどの程度差があるかを確認する.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions