Skip to content

experiment/ucn/two-lines-detection - #90

Merged
ucn-yushin merged 57 commits into
developfrom
experiment/ucn/two-lines-detection
Sep 9, 2026
Merged

experiment/ucn/two-lines-detection#90
ucn-yushin merged 57 commits into
developfrom
experiment/ucn/two-lines-detection

Conversation

@ucn-yushin

@ucn-yushin ucn-yushin commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

対応Issue

resolve #88

なぜやったか

1本のラインのみを用いた入出庫判定では、以下のようなケースで誤検出・未検出が発生しうる(詳細は #88 )。

  • 車両がライン付近で停止する
  • bboxの揺れにより、ラインを複数回跨いだように見える
  • トラッキングIDが途中で切れる

そこで、出入口側の Line1 を主判定ライン、奥側の Line2 を補助判定ラインとする2ライン方式で入出庫を検出し、その精度と処理速度を検証できる実験基盤を構築した。

何をやったか

2ライン検知システム本体(raspi/line_detection/

  • YOLOv8 + ByteTrackで車両を追跡し、bbox下辺中央を代表点として外積法でライン交差を判定
  • Line1 の通過方向でIN/OUTをカウントし、Line2 の通過有無・通過順序からconfidenceを付与(Line2 通過後に確定)
  • GUIライン座標設定ツール(line_setup/setup_lines.py
  • アノテーション付き動画の生成、JSON/CSV形式のイベントログ出力
  • ライン・車両代表点の可視化ツール(visualize_lines_and_vehicles.py

共通基盤(raspi/common/

  • 処理時間の統一計測(frame_timing / frame_stats。ウォームアップ除外・モデル同期・p95等の統計)
  • W&B実験ロガー(wandb_logger。運用方針は docs/wandb_integration_spec_v2.md に明文化)
  • run識別子・再現用manifestの生成(run_identity。モデルのsha256、条件キーなど)
  • Ground Truth の読み込みと予測イベントとの突合(ground_truth / event_matching
  • 秒基準の時間窓(time_windows。FPSに依存しない設定値)

評価(raspi/eval/build_accuracy_report.py

  • GTとのイベント突合による精度レポート生成
  • SAM3による精度データ更新スクリプト

実験ランナー

  • run_multi_video.py: 複数動画を順に処理し、比較用サマリーへ集約
  • run_gate4_alternating.py: ROI方式と2ライン方式を動画ごとに交互実行し、熱状態の影響を均して処理速度を相対比較

その他

  • 依存管理をuvへ移行(pyproject.toml / uv.lockultralytics==8.4.72 に固定)
  • 検証手順を VERIFICATION.md として整備

どのように実装したか

主要な設計判断は以下のとおり。

  • 交差判定: 外積法に有限線分判定とヒステリシス(margin_px、既定値は実測に基づき設定)を組み合わせ、bboxの揺れによる多重カウントを抑止
  • イベント識別: トラッキングIDの切れ・再割当に影響されないよう、track IDではなくevent IDでイベントを識別
  • 同一フレームで両ラインを交差した場合: 交点位置から記録順を決定し、通過順序の整合性を担保
  • 信頼度: Line1 単独通過はそのままカウントしつつ、Line2 の通過有無・順序でconfidenceを段階付け
  • 再現性: 全runにモデルハッシュ・実行条件・GT情報を含むmanifestを出力し、W&B上で条件キーによる比較を可能にした

ディレクトリ構成は raspi/line_detection/README.md を参照。

画面スクリーンショット等

  • URL
    スクリーンショット

テスト項目

  • uv sync 後、uv run pytest raspi -q が全件パスすること(手元では198件パス)
  • line_setup/setup_lines.py でライン座標をGUI設定でき、保存時に .env のライン座標以外の設定が保持されること
  • main.py で入力動画を処理し、IN/OUTカウント・イベントログ(JSON/CSV)・アノテーション動画が出力されること
  • GTファイルを配置した場合、イベントログJSONへGT比較結果(count_error等)が記録されること
  • run_multi_video.py で複数動画のサマリーが1つに集約されること

詳細な検証手順は raspi/line_detection/VERIFICATION.md を参照。

備考

  • 入力動画・モデル重み・出力ファイル(data/*.pt)はgit管理外。
  • ROI方式( [experiment] ROI (Region Of Interest)のbbox変動分析 #89 / feat/mike/89-bbox-analysis-within-roi)との比較検証の結果、2ライン方式の採用を決定済み。run_gate4_alternating.py の実行にはROI方式ブランチのworktreeを別途用意する必要がある(スクリプト冒頭のdocstring参照)。
  • 方式決定に伴うディレクトリ整理(パッケージ化・レガシー削除など)は本PRに含めず、別PRで実施予定。

ucn-yushin and others added 19 commits June 19, 2026 18:29
- line_setup/: ライン座標設定ツール用ディレクトリ
- detection/: 車両検知・ライン交差判定用ディレクトリ
- result_output/: 結果出力(動画・ログ)用ディレクトリ
- data/: 入出力データ用ディレクトリ
- requirements.txt: 依存パッケージ定義
- detection/config.py: .envから設定を読み込むConfigクラス
- .env.template: 設定ファイルのテンプレート
- Line1/Line2のライン座標、駐車場基準点、検知パラメータを管理
- detection/line_crossing.py: LineCrossingDetectorクラス
- 外積を使用したライン交差判定
- 車両代表点(bbox底面中央)の取得
- IN/OUT方向の判定
- マージン処理による微小な揺れの無視
- detection/tracker.py: VehicleTrackerクラスとVehicleStateデータクラス
- track_id毎の状態管理(Line1/Line2交差情報)
- ハイブリッド方式のイベント判定ロジック
- 信頼度計算(high/normal)
- 古い追跡のクリーンアップ機能
- 統計情報の管理(入庫/出庫/駐車台数)
- line_setup/setup_lines.py: 対話的にライン座標を設定
- OpenCVウィンドウでマウスクリックによる5点設定
- リアルタイムプレビュー表示
- .envファイルへの自動保存
- やり直し機能('r'キー)
- result_output/video_writer.py: VideoAnnotatorクラス
- Line1(緑)とLine2(黄色)の描画
- 車両代表点とtrack_idの表示
- カウント情報のオーバーレイ表示
- 処理時間、入出庫数、駐車台数、信頼度別統計の表示
- result_output/event_logger.py: EventLoggerクラス
- イベント記録(track_id, 種別, フレーム, タイムスタンプ, 信頼度)
- JSON形式での保存(タイムスタンプ付きファイル名)
- CSV形式での保存(Excelで開きやすい)
- サマリー情報の出力(入出庫数、駐車台数、信頼度別統計)
- main.py: 2ライン検知システムのメインエントリーポイント
- YOLOv8トラッキング統合
- フレーム毎のライン交差検知とイベント判定
- ハイブリッド方式による入出庫カウント
- アノテーション動画生成
- JSON/CSVログ出力
- コマンドラインインターフェース(動画ファイル/カメラ対応)
- 進捗表示とエラーハンドリング
- README.md: 2ライン検知システムの完全なドキュメント
- セットアップ手順(依存関係、ライン設定、動画配置)
- 使用方法(基本/表示/カメラ/出力先指定)
- 出力ファイル形式の説明(JSON/CSV)
- 設定パラメータの詳細
- アルゴリズムの解説(外積法、ハイブリッド方式)
- トラブルシューティング
- 入力動画ファイル(mp4/avi)を除外
- 出力動画ファイルを除外
- イベントログ(JSON/CSV)を除外
- pyproject.toml: 2ライン検知システムの依存パッケージを追加
  - ultralytics, opencv-python, python-dotenv, numpy, torch, torchvision, pandas
- README.md: インストール方法をuv syncに変更
ucn-yushin and others added 23 commits August 9, 2026 11:44
docs.local/等の *.local ディレクトリを常時ignore対象にする。
ブランチ切替時の checkout 失敗を防ぐため先にコミットしておく。
これまでROIブランチ側でのuv sync時にインストールされたpytestが
.venvに残留していただけで、pyproject.tomlには宣言されていなかった。
このため単独でuv syncするとpytestごと削除される状態だった。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ROIブランチのtracker初期化処理がUltralyticsの内部APIに依存しており
8.4.72に固定されているため(raspi/roi-counter/README.md参照)、
Phase 3以降の両方式比較で同一バージョンの上で計測できるよう
2ラインブランチ側も同じバージョンへ揃える(D10)。

uv run pytest raspi/ -q: 77 passed (変更前と同数)
実モデルでのスモークチェック(model.track()の実行)も正常終了を確認。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Issue #98対応。以下3つの複合バグを1つの修正としてまとめて解消する
(単位だけ直すと低速通過の取りこぼしが顕在化し現状より悪化するため分割不可):

1. 単位バグ: side_of_line()は外積値(距離×ライン長)を返すが、MARGINと
   直接比較していた。実効的な判定保留帯の幅はLine1で0.0086px、Line2で
   0.037pxとほぼゼロだった。signed_distance()を追加しpx単位で比較する。

2. 無限直線バグ: ラインの延長線を跨いだだけでも交差と判定されていた。
   segment_crossing_param()で線分への射影係数tを求め、endpoint_margin_px
   を加味した範囲内かどうかで有限線分判定を行う。

3. 履歴なしバグ: 前後2フレームしか見ないため、判定保留帯を跨ぐのに
   複数フレームかかる低速通過が一度も検出されなかった。LineTransitionState
   による安定側の記憶(ヒステリシス)を導入し、反対の安定側へ抜けた時点で
   交差を確定する。

側の反転が起きたが有限線分判定が不成立の場合も、stable_side/
last_stable_pointは判定の成否と無関係に更新する(コード内コメント参照)。
更新を止めると、以降のフレームが古い参照点を基準に判定を続けてしまい、
間に本物の交差があっても検出できなくなるため。

信頼度(#97)には触れていない。カウント確定のタイミングはLine1通過のまま。

MARGIN_PX/ENDPOINT_MARGIN_PXへ移行。旧MARGINが残っている場合は起動時
エラーとする(単位が異なるため自動変換しない)。.envの新既定値は0.0
(3cのスイープで正式決定する暫定値)。

新規テスト30件(test_line_crossing.py 22件、test_tracker.py 8件)。
uv run pytest raspi/ -q: 107 passed(既存77+新規30)。
実動画(IMG_2787.MOV, 300フレーム)でのエンドツーエンド動作も確認済み。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Phase 3f完了後の監査(decision-logic-audit.html)で発見したGate 1対象バグ
のうち、2ラインの同一フレーム両線交差を修正。

main.pyの毎フレームループはLine1→Line2の順に固定でupdate_*_crossing()を
呼び、成立した順にpassed_orderへ記録していた。1フレームの移動が両線を
またぐ場合、物理的な通過順(OUTならLine2→Line1)ではなく呼出順が記録される
ため、VehicleState.resolve_confidence()の期待順序と食い違い、confidence
が誤ってnormalに確定していた。

修正: line_crossing.pyにCrossingGeometry(t/u/point)とCrossingResult
(direction/point)を追加し、_update_crossing()の戻り値をOptional[str]から
Optional[CrossingResult]へ変更(交点座標を保持できるように)。main.pyでは
Line1/Line2両方の判定を先に行い、同一フレームで両方成立した場合は現在点
からの交点距離が大きい方(=時間的に先に通過した方)を先に記録する。
last_stable_pointがLine1/Line2で異なっていても交点距離で正しく順序付け
できることをテストで確認済み。

segment_crossing_param()は既存の単体テストを壊さないよう変更していない。
test_tracker.pyのprocess_frame()ヘルパーも同じ順序ロジックへ更新。

回帰テスト4件追加。修正前は
AssertionError: assert ['line1', 'line2'] == ['line2', 'line1']
で失敗することを確認済み。uv run pytest raspi/line_detection -q:
60 passed(既存56+新規4)。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Phase 3f完了後の監査(decision-logic-audit.html)で発見したGate 1対象バグ
のうち、EventLoggerのtrack ID再利用時の衝突を修正。

EventLogger.update_confidence()はtrack_idでself.eventsを検索していたが、
track_idはVehicleTracker.cleanup_stale_tracks()でtrackが削除された後に
再利用されるYOLOのIDであり、一意ではない。確定済みイベント(旧track)と
pendingイベント(同IDを再利用した新track)が共存すると、len(matched)>1で
ValueErrorが送出され実行時に落ちる。

修正: Event/EventLoggerにevent_id(uuid4)を導入し、record_event()が
発行してeventの識別に使う。update_confidence()の検索キーをtrack_idから
event_idへ変更。VehicleState.pending_event_id / ConfidenceUpdate.event_id
を追加してtracker側からevent_idを橋渡しする。events_*.csv/jsonには
event_id列を末尾に追加(condition_keyには含めない)。

main.pyの呼び出し順序も修正: resolve_pending_confidences()をcounting_timer
からoutput_timerへ移し、record_event()でevent_idを確定させた後に呼ぶよう
変更した。IN方向でLine1/Line2を同一フレームで通過した場合、confidenceが
生成された同じフレームでhighに確定することが既存の挙動としてあり
(2ライン同一フレーム両線交差の修正後はOUTも同様)、その場合に旧実装の
呼び出し順ではevent_idが未確定のままupdate_confidence()が呼ばれ
RuntimeErrorで落ちる経路があったため。

回帰テスト: track_id再利用時の衝突(修正前はValueError)、同一フレームで
confidenceが即確定するケース(修正前はRuntimeError)を追加、いずれも
修正前は失敗することを確認済み。uv run pytest raspi/line_detection -q:
63 passed(既存60+新規3)。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Phase 3 Gate3(GT拡張とイベント単位評価)対応。ROIブランチ
(feat/mike/89-bbox-analysis-within-roi)へ移植・拡張した
raspi/common/ground_truth.pyと同一内容をこのブランチへ適用する
(両ブランチでbyte-identicalであることをdiffで確認済み)。

追加内容:
- GtEvent(event_id, direction, t_sec)データクラス
- GroundTruthへevents: tuple[GtEvent, ...] = ()、
  tolerance_sec: float = DEFAULT_TOLERANCE_SEC(10.0)を追加
  (新フィールドはデフォルト値ありのため、既存のGTファイル・呼び出し元は
  無変更で動作する)
- _load_gt_file()でevents配列・tolerance_secキーを検証つきでパース
  (event_id重複・direction不正・t_sec負値/非数値・tolerance_sec非正値を拒否)
- events/tolerance_secがcomparison_keyの対象外であることのテストを追加

uv run pytest raspi/ -q: 137 passed(既存127 + 新規10)。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Phase 3 Gate3(GT拡張とイベント単位評価)対応。

raspi/common/event_matching.pyを新設し、方向一致かつ時刻差が
tolerance_sec以内のGT/予測イベントペアを、時刻差の小さい順に貪欲マッチング
する。1つのGTイベントは1つの予測にしか対応しない(1対1)。方向(IN/OUT)
ごとに独立してマッチングし、全体のtp/fp/fnはその合算。

precision/recall/f1のゼロ除算規約:
- precision: tp+fp==0のときNone(予測が1件も無い場合は未定義)
- recall: tp+fn==0のときNone(GTが1件も無い場合は未定義)
- f1: precision/recallの片方でもNoneならtp+fp/tp+fnの非ゼロ側のみで
  判断できないため0.0、両方Noneなら未定義としてNone、それ以外は調和平均

同時刻差の候補が複数ある場合はGT event_id→予測event_idの順で
決定的にタイブレークする(入力順序に依存しない)。

このモジュールは2ラインブランチ・ROIブランチへ同一内容で配置する
(raspi/commonの共有規約に従う。次のコミットでROI側にも同一内容を配置)。

合成データでの単体テスト11件: 完全一致、tolerance境界、方向不一致、
過剰予測(FP)、予測不足(FN)、最近傍優先の貪欲マッチング、
タイブレークの決定性、空入力の3パターン、方向別の独立集計。

uv run pytest raspi/ -q: 148 passed(既存137 + 新規11)。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Phase 3 Gate3(GT拡張とイベント単位評価)対応。「実行と評価の分離」
(計測スクリプトは精度系summaryキーをNoneで確保するだけにとどめ、GTが
揃った後に別スクリプトでW&B runのsummaryへ後追い書き込みする)という
設計方針は、ROIブランチ限定ではなく両方式に共通して当てはまる。
ROIブランチ(feat/mike/89-bbox-analysis-within-roi)のraspi/eval/を
そのまま移植した(動作変更なし、byte-identical)。

これにより2ライン側でも、G3-2の今後の作業(event_matching.pyの出力を
update_accuracy_from_sam3.pyの入力形式へ変換するスクリプト)で
同じ後追い書き込みの仕組みを使える。

uv run pytest raspi/ -q: 152 passed(既存148 + 新規4)。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Phase 3 Gate3(GT拡張とイベント単位評価)対応。

raspi/eval/build_event_accuracy_rows.py(ブランチ非依存の共通コア、
次のコミットでROI側にも同一内容で配置):
- build_eval_row(): PredictedEventのリストとGroundTruthをevent_matching.
  match_events()で突合し、update_accuracy_from_sam3.py --input が期待する
  行(wandb_run_id/execution_id/condition_key + tp/fp/fn/precision/recall/f1)
  を組み立てる
- write_eval_rows(): 行リストをCSV/JSONへ書き出す(拡張子で判定)

raspi/line_detection/build_accuracy_report.py(2ライン固有アダプタ、新規):
- events_*.json(EventLogger.save_json()の出力)を走査し、event_type/
  timestamp_sec/event_idからPredictedEventを組み立て、video_pathから
  common.ground_truth.load_ground_truth()でGTを読む
- GTにevents配列が無い動画(未アノテーション、現状すべて該当)は
  [WARN]でスキップし、意味のない空GT行を出力に含めない
- 複数のevents_*.jsonをまとめて1つの評価行ファイルへ出力

実データでの検証は、G3-3(GT動画のアノテーション、コード実装の範囲外)
が完了してから可能になる。今回は合成データでのテストのみ。

uv run pytest raspi/ -q: 161 passed(既存152 + 新規9)。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
検出ロジックが期待どおり動くことを実動画1本で端から端まで確認する手順を、
方式ごとに各ディレクトリへ配置する。従来この内容はdocs.local/配下(gitignore
対象のローカル資産)にしか無く、リポジトリを新しく取得した環境や他の作業者が
参照できなかった。

raspi/line_detection/VERIFICATION.md(新規):
- 前提条件(uv sync、gitignore対象のdata/配置、.envの用意)
- ユニットテスト → ライン座標のGUI設定 → 配置の目視確認 → 本実行(GT比較)
  → 出力の確認 → ROI方式との速度比較 → イベント単位の精度評価、の順で記載
- 各手順に実際のコマンドと、期待される標準出力・合格基準を明記した
- setup_lines.pyが.envを丸ごと上書きする点、手元の.envが比較条件
  (CONFIDENCE_THRESHOLD等)と乖離しうるため速度比較時は環境変数で明示的に
  上書きする必要がある点を注意として記載
- build_accuracy_report.pyはGTにevents配列が必要で、現在のIMG_2787_gt.jsonは
  台数のみのため評価行0件で終わる。これが現時点では正常な挙動であることを明記
- 「この手順で確認できないこと」(イベント単位精度、tolerance_sec等の妥当性、
  実機でのリアルタイム性)を独立した章として残した

記載したコマンド・出力文字列は実装(main.py、event_logger.print_summary、
visualize_lines_and_vehicles.pyの位置引数、build_accuracy_report.pyのCLI)と
突き合わせて確認済み。

ROI方式の検証手順はfeat/mike/89-bbox-analysis-within-roiの
raspi/roi-counter/VERIFICATION.mdへ別途追加する。
VERIFICATION.mdに従って検証したときW&Bへの記録が本当に成立するかを点検した
結果の反映。ROI方式側(feat/mike/89-bbox-analysis-within-roi の 079d24b)と
同じ内容を2ライン側にも揃える。

0章に「W&B(実験記録)について」を新設:
- 本手順はWANDB_MODE=offlineなのでログイン無しでも完走するが、その場合
  W&Bサーバ上には一切残らないことを明記
- wandb loginの手順と、ログイン状態の確認方法を追加
- WANDB_DIR=data/outputsを省略してはいけない理由を注記。
  raspi/common/wandb_logger.py の wandb.init() は dir を渡していないため、
  未指定だとwandbがカレントディレクトリ直下へwandb/を作る。data/の外に出ると
  gitignore対象外になりrun一式を失いやすい(ROI方式で実際に一度失っている)。
  なお2ライン側の実行コマンドには元からWANDB_DIRが入っていたため、
  コマンド自体の修正は不要だった。

6章のwandb syncの記述を補強:
- wandb login済みであることを前提として明示
- 同期していないrunはmanifestにwandb_run_idがあってもW&B上には存在しない点を
  注記し、区切りのよいところでまとめて同期するよう促した
ROI方式(feat/mike/89-bbox-analysis-within-roi の 8d0d042)と同じ4点を
2ライン方式側へも反映する。ネットワーク断時のwandb.init()の実測が根拠。

1. 一次記録はローカル成果物、W&Bは二次的な閲覧先(0章冒頭)
   manifests/はUSE_WANDBの値に関係なく書かれ、logs/events_<timestamp>.jsonには
   速度統計58キーがtiming_summaryとして保存される。W&Bを無効にしても方式の比較と
   再現に必要な数値は失われない。

2. WANDB_MODEはoffline固定(online不採用、0章)
   wandb 0.28.0で実測したところ、到達不能な宛先に対してonlineのwandb.init()が
   ブロックし続けた(接続拒否で4分超、DNS解決失敗で100秒超)。WANDB_INIT_TIMEOUTも
   Settings(init_timeout)も効かない。打ち切って例外を上げた場合、wandb.init()が
   try/exceptで包まれておらずExperimentLoggerの生成がmain.pyのtry:(314行目)より
   前にあるため、1フレームも処理せず終了しmanifestも残らない。実測表を残した。

3. 本番運用ではW&Bを有効にしない(0章)
   2ラインのmain.pyは検証と本番運用の両方で使い、W&Bは--wandb / USE_WANDB=trueで
   明示的に有効化したときだけ動く。本番運用ではどちらも付けない、と決定した。
   ROI方式のmain.pyがW&B非対応のままであるのと同じ理由による。

4. 精度検証runをW&Bへ残す(4章、推奨アクション#1の本体)
   4章「本実行(GT比較あり)」のコマンドには--wandbもUSE_WANDBも無く、count_error=0を
   確認する精度検証の本体がW&Bに残らなかった。USE_WANDB=true WANDB_MODE=offline
   WANDB_DIR=data/outputsを追加した。あわせて6章へ未同期run(*.wandb.syncedが無い
   ディレクトリ)の一覧コマンドを追加し、8章の合格基準にも同期の行を足した。

docs/wandb_integration_spec_v2.mdの§1.8(ネットワークの無い環境を第一級ユースケース
とする原則)へ、offline固定と本番運用でのフラグ非使用を決定として追記した。

init成功後に切断された場合の挙動は再現できておらず、未検証である旨を0章に明記した。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ROI方式(feat/mike/89-bbox-analysis-within-roi の 645f1c0)と同じ変更を、
2ライン方式のMAX_FRAME_GAPとCLEANUP_THRESHOLDへ適用する。

どちらもフレーム数で持っていたが、意味的には時間の量である(Line1とLine2の
通過を何秒以内なら対応付けるか、何秒見失ったら破棄するか)。.env.templateが
既に「3秒@30fps」「5秒@30fps」と注記していたとおり、意図は最初から秒だった。

フレーム数で固定すると、同じ設定値が撮影fpsによって別の長さを意味する。
90フレームは30fpsで3秒、10fpsでは9秒になる。検証に使ってきた動画は30fpsだが、
実機のRaspberry Piは10fps前後で動くため、実機では窓が意図の約3倍になっていた。

common/time_windows.py:
- ROI側と byte-identical に配置(frames_from_seconds とそのテスト21件)

detection/config.py:
- max_frame_gap / cleanup_threshold を max_frame_gap_sec / cleanup_threshold_sec
  へ変更。既定は3.0秒 / 5.0秒で、旧既定を30fpsで換算した値と一致する
- 旧env varが残ったまま新しい名前が無い場合はValueErrorで移行を促す。MARGIN→
  MARGIN_PXの前例に倣った。黙って無視すると窓の長さが既定値へ戻ってしまうため
- validateは秒に対して0以上を検査する

main.py:
- fps確定後にframes_from_secondsで変換し、VehicleTrackerへフレーム数を渡す
- 起動ログへフレーム数と秒数とfpsを併記する
- run_configへ秒(仕様)と変換後のフレーム数(実際の挙動)を両方記録。
  condition_keyに入るのはフレーム数のほうで、キー名は据え置いたため過去runとの
  比較の意味は変わらない

VehicleTrackerはフレーム基準のまま据え置いた。current_frameを受け取って動く以上、
内部の単位はフレームが自然であり、変換は設定の読み込み側の責務とした。
既存のtrackerテスト(max_frame_gap=3 等)もそのまま通る。

.env.template / line_setup/setup_lines.py の案内文、README、VERIFICATION.mdの
「確認できないこと」も秒表記へ更新した。

テスト: raspi/ 全体で188 passed(Gate 3時点161 → time_windows 21件と
config移行テスト6件を追加)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
save_to_envが.envファイル全体をハードコードのテンプレートで上書きしていた。
GUIでラインを引き直すたびに、他のキーがテンプレートの値へ戻る。

実害が2つあった。

1. 起動できない.envが生成される。テンプレートは廃止済みのCLEANUP_THRESHOLD=150を
   書き出すため、GUI実行後はConfig.from_envが移行エラーで停止する。77cb510で
   MAX_FRAME_GAPを秒基準へ移したとき、同じテンプレート内のCLEANUP_THRESHOLDを
   見落としていた。
2. 比較条件が失われる。テンプレートはMODEL_PATHをfine-tunedモデル、
   CONFIDENCE_THRESHOLDを0.3で書き出し、VEHICLE_CLASSESとIOU_THRESHOLDに至っては
   項目自体が無い。ROI方式と条件を揃えるために手で入れた値が消える。

roi-counterのroi_config.pyと同じ契約へ揃える。書き込むのはライン座標の10キーだけで、
他のキー・コメント・並び順は保持する。

- LINE_ENV_KEYS: 書き込み対象を明示
- build_line_env_values(points): 5点をキーと値へ変換(純ロジック)
- apply_line_values(existing, values): 既存本文へ反映(純ロジック)。既存キーは
  行ごと差し替え、無いキーは末尾のブロックへ追記する
- save_to_env: I/Oのみ。.envが無い場合は.env.templateを土台にする

I/Oと純ロジックを分離したのはroi_config.pyと同じ理由で、cv2をモックせずに
テストできるようにするため。

テスト: raspi/ 全体で198 passed(test_setup_lines_env.py 10件を追加)。
GUI実行後の.envでConfig.from_envが起動できること、比較条件が保持されること、
二重適用で重複しないことを確認する。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
このツールは.envのパスをハードコードしており、画角ごとに設定を分けている場合に
確認できなかった。新画角用のnewcam.envを追加した時点で、ライン配置を目視する
手段が失われていた。

main.pyとline_setup/setup_lines.pyは既に--envを持つ。同じ流儀へ揃える。

引数解析をargparseへ移したが、位置引数の並び(video / output / start_frame /
end_frame)はVERIFICATION.md 3章の記載と互換を保った。既存のコマンドはそのまま動く。

読み込んだ設定のパスを起動時に表示するようにした。どの画角の設定で確認しているかが
出力から分かる。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2ライン方式のmain.pyは1動画ずつしか扱わない。Gate 4の同条件計測では6動画を
回して結果を突き合わせる必要があるため、ROI方式のscripts/04_multi_video_mae.pyに
相当するものを用意する。

各runは独立したサブプロセスで走らせる。理由が2つある。

1. load_dotenvは既存の環境変数を上書きしない。同一プロセスで2つの.envを読むと
   後から読んだほうが無視され、画角ごとの設定切り替えが効かない。実際に確認した。
2. YOLOモデルとトラッカーの状態をrun間で持ち越さない。ROI方式が
   prepare_model_for_runで行っている初期化を、プロセス分離で保証する。

閾値スイープは行わない。2ライン方式には探索対象のパラメータが無く、
04_multi_video_mae.pyの組み合わせループに相当するものが存在しないため。

実行前チェックを先に通す。動画・設定・GTの存在を全動画分まとめて確認してから
1本目を始める。1本目を数十分かけて処理した後に3本目のGTが無いと分かる、という
失敗を避けるため。

comparison_keyの突合を実行直後に行う。ROI方式の確定runのmanifestから動画ごとの
comparison_keyを読み、一致しない動画があれば警告する。一致するrun同士でなければ
速度値を方式間で直接比較できない、というのがcomparison_keyの設計意図であり、
計測してから数日後に気付くのでは遅い。

出力はdata/outputs/{EXP_NAME}/{video_stem}/へ動画ごとに分け、summary.csvへ
台数・誤差・confidence内訳・速度統計・run識別子を集約する。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ucn-yushin
ucn-yushin marked this pull request as ready for review September 9, 2026 18:51
@ucn-yushin
ucn-yushin requested a lite review from Copilot and removed request for Copilot September 9, 2026 18:52
@ucn-yushin ucn-yushin changed the title [WIP] experiment/ucn/two-lines-detection experiment/ucn/two-lines-detection Sep 9, 2026
@ucn-yushin
ucn-yushin merged commit ba00a9e into develop Sep 9, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

1 participant