駐車場の出入り検知精度を向上させる2ライン検知システムです。Line1(入口側)とLine2(駐車場側)の2本のラインを使用し、車両の通過順序と方向から高精度な入出庫カウントを実現します。
- ハイブリッド方式: Line1が主判定、Line2が信頼度を付与
- 外積法による高精度判定: 数学的に正確なライン交差検知
- GUIライン設定ツール: 動画を見ながら対話的にライン座標を設定
- YOLOv8トラッキング: 最新の物体検知・追跡技術
- 詳細なログ出力: JSON/CSV形式で解析しやすい
- リアルタイム可視化: アノテーション付き動画の生成
tracking-parking/
├── .env # 環境設定(ローカル)
├── .env.template # 設定テンプレート
├── pyproject.toml # 依存パッケージ定義(uv)
│
├── scripts/ # CLIエントリポイント
│ ├── run_detection.py # メイン処理
│ ├── setup_lines.py # GUIでライン位置を設定
│ ├── visualize_lines.py # ライン・車両代表点の可視化
│ └── run_multi_video.py # 複数動画の一括処理と集約
│
├── src/tracking_parking/ # 本体パッケージ
│ ├── config.py # 設定管理
│ ├── detection/ # 車両検知・ライン交差判定
│ │ ├── line_crossing.py # ライン交差検知(外積法)
│ │ └── tracker.py # 車両トラッキング・状態管理
│ ├── output/ # 結果出力
│ │ ├── video_writer.py # アノテーション動画生成
│ │ └── event_logger.py # イベントログ出力
│ ├── common/ # 計測・GT・W&Bの共通基盤
│ └── eval/ # 精度評価
│
├── tests/ # テスト
├── models/ # YOLOモデル重み(.pt、Git管理外)
└── data/ # データ(Git管理外)
├── inputs/ # 入力動画(.mp4)
│ ├── configs/ # 正解台数GT(<動画名>_gt.json)
│ ├── videos.example.json # 複数動画処理の対象リスト雛形(Git管理下)
│ └── videos.json # 同・実体
└── outputs/ # 出力結果
├── videos/ # アノテーション済み動画
└── logs/ # イベントログ(JSON/CSV)
プロジェクトルートディレクトリ(tracking-parking/)でuvを使ってインストールします:
cd /path/to/tracking-parking
uv syncこれにより、pyproject.tomlに定義された2ライン検知システムに必要な依存パッケージがすべてインストールされます。
インストールされるパッケージ:
- ultralytics (YOLOv8)
- opencv-python (動画処理)
- python-dotenv (設定管理)
- numpy (数値演算)
- torch, torchvision (深層学習)
- pandas (データ処理)
- wandb (実験管理・offline記録)
GUIツールを使って2本のラインと駐車場基準点を設定します:
python scripts/setup_lines.py --video data/inputs/test.mp4操作方法:
- 動画の最初のフレームが表示されます
- 以下の順番で5点をクリックしてください:
- Line1 始点 (入口側ライン)
- Line1 終点 (入口側ライン)
- Line2 始点 (駐車場側ライン)
- Line2 終点 (駐車場側ライン)
- 駐車場基準点 (駐車場内の任意の点)
- 設定が
.envファイルに自動保存されます
キー操作:
r: やり直しq: 終了
処理したい動画をdata/inputs/に配置します:
cp /path/to/your/video.mp4 data/inputs/python scripts/run_detection.py --input data/inputs/test.mp4処理中の動画を表示しながら実行:
python scripts/run_detection.py --input data/inputs/test.mp4 --displaypython scripts/run_detection.py --camera 0 --displaypython scripts/run_detection.py --input data/inputs/test.mp4 --output /path/to/output速度計測は timing schema v2 に従い、read_ms、inference_tracking_ms、
counting_logic_ms、core_ms、output_ms、end_to_end_ms に分割する。
方式比較では warm-up 除外後の core_ms_p95、実機のリアルタイム判定では
end_to_end_ms と deadline_miss_rate を使用する。
ROI方式と同じ動画・モデル・classes・confidence・IoU・image size・tracker・device・
warm-up・動画保存/表示設定を指定して実行する。
ROI方式の現在の既定値はVEHICLE_CLASSES=2,7、CONFIDENCE_THRESHOLD=0.25、
IOU_THRESHOLD=0.7であり、.env.templateもこの比較条件に揃えている。
WARMUP_FRAMES=30 YOLO_DEVICE=cpu YOLO_IMGSZ=640 \
YOLO_TRACKER=botsort.yaml SAVE_VIDEO=false SHOW_DISPLAY=false \
WANDB_MODE=offline python scripts/run_detection.py --input data/inputs/test.mp4 \
--wandb --device-name raspi5入力・モデルのSHA-256と上記条件から生成したcomparison_keyが同じrunだけを
直接比較する。W&B送信は計測区間外で行うため、online/offlineの通信状態は
速度値に含まれない。offline runは後日次のように同期する。
実験runの識別には、用途を分けた次の値を使用する。
condition_key: Line1・Line2・駐車場基準点、検知パラメータ、入力・モデルhash、 Git・主要ライブラリ版などの型付きcanonical JSONから生成する条件hashexecution_id: 同条件の再実行も区別する実行ごとのUUIDwandb_run_id: W&Bが発行するID(W&B有効時のみ)display_name: W&B UI向けの可読名。一意性の判定には使用しない
旧exp_keyは移行期間中のみcondition_keyのaliasとして残す。
wandb sync <run_dir>正解台数(GT)のJSONを--gtで指定すると、検出結果との差をcount_errorとして
記録する。GTファイルはin/out以外のキーを持っていても無視される。
python scripts/run_detection.py --input data/inputs/test.mp4 \
--gt data/inputs/configs/IMG_2787_gt.json--gtを省略した場合は<動画名>_gt.jsonを入力動画と同じディレクトリから
自動探索する。見つからなければ警告のみでGT比較なしのまま続行する。
--gtで明示的に指定したパスが存在しない場合は起動時にエラーで停止する。
GTのJSON形式:
{
"in": 22,
"out": 0
}- 値が数値(
0を含む)なら「確認済み」として評価する - 値が
nullまたは省略時は「未確認」としてその方向は評価対象から除外する (0とnullは明確に区別する)
記録されるキー:
gt_in/gt_out: GTの値(未確認はNone)count_error_in/count_error_out: 方向ごとの絶対誤差(未評価の方向はNone)count_error: 評価した方向の誤差合計。評価方向数によってスケールが変わるため、 run間で比較してよいのは評価方向が揃っているときだけ。 片方のみの評価では方向別キー(count_error_in等)を使うこと
GT情報(ground_truth_sha256・gt_in・gt_out)はcondition_keyに含まれるため、
GTの有無・内容が変わると同一条件とはみなされなくなる。
W&Bの有効・無効にかかわらず、runごとに
data/outputs/manifests/{execution_id}.jsonを保存する。manifestには上記ID、
完全な実験config、イベントログ・CSV・動画の絶対パスを記録する。
元の動画に以下の情報を重ねて表示:
- Line1 (緑色) - 入口側ライン
- Line2 (黄色) - 駐車場側ライン
- 車両代表点とtrack_ID
- リアルタイムカウント(入庫/出庫/駐車台数)
- 処理時間
{
"video_path": "data/inputs/test.mp4",
"processed_at": "2026-06-24T10:30:00",
"execution_id": "d45174ea-4de7-4bac-9d44-1ab9c86bd07a",
"condition_key": "ck1_...",
"total_frames": 900,
"avg_processing_time_ms": 48.5,
"events": [
{
"track_id": 1,
"event_type": "IN",
"frame_id": 145,
"timestamp_sec": 4.83,
"confidence": "high",
"line2_crossed": true
}
],
"summary": {
"total_in": 5,
"total_out": 3,
"current_parked": 2,
"high_confidence_events": 6,
"normal_confidence_events": 2
},
"accuracy": {
"gt_in": 22,
"gt_out": 0,
"count_error": 3,
"count_error_in": 2,
"count_error_out": 1
}
}accuracyブロックは--gtでGTを指定した場合のみ追加される。GTを指定しない
実行では従来どおりこのキー自体が存在しない。
Excelで開きやすいCSV形式:
track_id,event_type,frame_id,timestamp_sec,confidence,line2_crossed
1,IN,145,4.83,high,true
2,OUT,203,6.77,normal,false.envファイルで以下のパラメータを調整できます:
MODEL_PATH=/path/to/yolov8s.pt
CONFIDENCE_THRESHOLD=0.3 # 検知信頼度閾値(0.0-1.0)setup_lines.pyで自動設定されます。
MARGIN_PX=5.0 # 判定保留帯の半幅(px)。3cスイープ採用値
ENDPOINT_MARGIN_PX=0.0 # 有限線分判定の端点許容量(px)
MAX_FRAME_GAP_SEC=3.0 # Line1とLine2の通過を対応付ける最大の時間差(秒)
CLEANUP_THRESHOLD_SEC=5.0 # 古い追跡をクリーンアップするまでの未更新時間(秒)時間窓は秒で指定する。 動画を開いた時点のfpsからフレーム数へ変換する
(common/time_windows.pyのframes_from_seconds)。フレーム数で直接持つと、
同じ設定値が撮影fpsによって別の長さを意味してしまう。90フレームは30fpsで3秒だが、
10fpsでは9秒になる。検証に使ってきた動画は30fps、実機のRaspberry Piは10fps前後で
動くため、フレーム基準のままでは検証と実運用のあいだに黙って差が入る。
既定値(3.0秒・5.0秒)は旧既定のフレーム数(90・150)を30fpsで換算した値と一致する。
30fpsの動画では挙動が変わらない。runには秒(max_frame_gap_sec)と変換後の
フレーム数(max_frame_gap)の両方を記録し、condition_keyにはフレーム数が入る。
旧MAX_FRAME_GAP/CLEANUP_THRESHOLDが.envに残っている場合は、黙って無視せず
起動時にエラーで移行を促す。
SAVE_VIDEO=true # アノテーション動画を保存
SAVE_LOGS=true # ログを保存
SHOW_DISPLAY=false # リアルタイム表示API_ENABLED=false # カメラ入力かつtrueのときだけ送信する
API_BASE_URL= # 例: http://localhost:8000/api/v1
DEVICE_API_KEY= # デバイス登録時に一度だけ返る平文キー
API_CONNECT_TIMEOUT_SEC=3.0 # 接続確立の待ち時間
API_READ_TIMEOUT_SEC=5.0 # 応答の待ち時間
HEARTBEAT_INTERVAL_SEC=30 # サーバーのオフライン判定(既定120秒)の4分の1
SHUTDOWN_FLUSH_SEC=10 # 終了時にキューが空になるのを待つ上限(秒)
SPOOL_PATH=data/outputs/unsent_events.jsonl # 送れなかったイベントの置き場所(相対パスはHOME_DIR基準)カメラ入力かつ API_ENABLED=true のときだけ、入出庫イベントと集計開始/停止コマンドの
往復(ハートビート)を行う。動画ファイル入力では常に送信しない。--no-api を付けると
.env を書き換えずに送信だけを無効化できる(実機デバッグ用。逆向きの「ファイル入力でも
強制送信する」オプションは安全性の理由から提供しない)。
送信するイベントには request_id(ローカルの event_id、UUID)を必ず含める。サーバー側は
同じ request_id の2回目を既存イベントとして扱い system_count を動かさないため、
接続タイムアウトや応答不明などの失敗はすべて安全に再送できる。POST /events は
イベントの永続化と同時に 202 Accepted を返し、system_count への反映は
サーバー側のバックグラウンド処理に回る(同期的には反映されない)。設計の詳細は
docs/decisions/0002-api-event-delivery.md を参照。
2D平面上のベクトル外積を使用してライン交差を判定:
外積 = (line_end - line_start) × (point - line_start)
- 外積 > 0: ポイントはラインの片側
- 外積 < 0: ポイントはラインの反対側
- 外積の符号が変化 = ライン交差
IF Line1を交差:
IF 方向 == IN:
IF Line2もLine1の後に交差(max_frame_gap以内):
-> 入庫(信頼度: HIGH)
ELSE:
-> 入庫(信頼度: NORMAL)
IF 方向 == OUT:
IF Line2がLine1の前に交差(max_frame_gap以内):
-> 出庫(信頼度: HIGH)
ELSE:
-> 出庫(信頼度: NORMAL)
IF Line2のみ交差(Line1交差なし):
-> カウントしない(駐車スペース内の移動)
エラー: .envファイルが見つかりません
解決方法: まずsetup_lines.pyを実行してライン座標を設定してください。
設定エラー: モデルファイルが見つかりません
解決方法: .envのMODEL_PATHを正しいパスに修正してください。
考えられる原因:
- ライン位置が適切でない →
setup_lines.pyで再設定 MARGIN_PXが大きすぎる/小さすぎる →.envで調整(px単位)MAX_FRAME_GAP_SECが適切でない → 車両の通過速度に合わせて調整
- GitHub Issue #88 - 設計仕様
- Ultralytics YOLOv8 - 物体検知モデル
このプロジェクトのライセンスについては、リポジトリのルートディレクトリを参照してください。