Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
08d3f07
first commit
ucn-yushin Jun 19, 2026
745653e
[feat] 2ライン検知システムのディレクトリ構造を構築
ucn-yushin Jun 24, 2026
5931d85
[feat] 設定管理システムを実装(.env読み込み)
ucn-yushin Jun 24, 2026
a8e5fbb
[feat] 外積法によるライン交差検知ロジックを実装
ucn-yushin Jun 24, 2026
4f45508
[feat] 車両トラッキングと状態管理を実装(ハイブリッド方式)
ucn-yushin Jun 24, 2026
ea5f37e
[feat] GUIライン座標設定ツールを実装
ucn-yushin Jun 24, 2026
139a0a9
[feat] アノテーション動画生成機能を実装
ucn-yushin Jun 24, 2026
3a12c3c
[feat] イベントログ出力機能を実装(JSON/CSV)
ucn-yushin Jun 24, 2026
3c59680
[feat] メイン処理パイプラインを実装
ucn-yushin Jun 24, 2026
f76b64e
[docs] README.mdを作成(使用方法と設定手順)
ucn-yushin Jun 24, 2026
37a63c1
[chore] .gitignoreに2ライン検知システムの出力ファイルを追加
ucn-yushin Jun 24, 2026
02db0a4
[chore] uvを使った依存パッケージ管理に対応
ucn-yushin Jun 24, 2026
c8338a0
[feat] uv.lockにてプロジェクト内の依存関係を更新
ucn-yushin Jun 24, 2026
ede1468
[fix] 2ライン検知システムの出力ファイルを.gitignoreに追加
ucn-yushin Jun 24, 2026
43e4cca
[feat] lapパッケージを依存関係に追加
ucn-yushin Jun 25, 2026
b565959
feat: 車両クラス設定をConfigに追加し、YOLOトラッキングに反映
ucn-yushin Jun 25, 2026
ddb141c
feat: IOU閾値をConfigに追加し、ライン交差検知の方向を記録する機能を実装
ucn-yushin Jun 25, 2026
d33b0fe
fix: 2ライン検知システムの出力ファイルにdataフォルダを追加
ucn-yushin Jun 25, 2026
4df047f
fix: .gitignoreにdataフォルダとdocsフォルダを追加
ucn-yushin Jul 3, 2026
dc798c6
add: common/frame_stats.py - フレーム処理時間統計モジュールを追加
ucn-yushin Jul 3, 2026
0c59b55
add: common/wandb_logger.py - W&B実験ロガーとexp_key生成を追加
ucn-yushin Jul 3, 2026
3e6cced
add: common/tests - frame_statsとExperimentLoggerのテストを追加
ucn-yushin Jul 3, 2026
73ad351
fix: common/wandb_logger.py - LOG_INTERVAL_SEC検証とログ境界計算ヘルパーを追加
ucn-yushin Jul 10, 2026
a1a773a
fix: .gitignore - wandb_integration_spec_v2.mdをGit管理対象に追加
ucn-yushin Jul 10, 2026
a4a7b1f
fix: wandb_integration_spec_v2.md - 実装との差分を解消し仕様を確定
ucn-yushin Jul 10, 2026
65572c4
feat: 処理時間の共通計測仕様を追加
ucn-yushin Jul 11, 2026
a0e6d82
feat: 2ライン方式の処理時間計測を統一
ucn-yushin Jul 11, 2026
59fafa6
feat: run識別子と再現manifestを共通化
ucn-yushin Jul 13, 2026
1e8fe52
feat: 2ライン実験へrun識別情報を記録
ucn-yushin Jul 13, 2026
fbc1417
fix: .gitignoreから不要なエントリを削除
ucn-yushin Jul 14, 2026
33e7ebf
feat: ウィンドウ作成時に初期フレームを表示
ucn-yushin Jul 17, 2026
9257bd6
feat: GT読み込みとcount_error算出を共通化
ucn-yushin Aug 9, 2026
c8133ac
feat: 2ライン方式へGT比較を追加
ucn-yushin Aug 9, 2026
d97e3c7
feat: GT識別情報をcondition_keyへ反映
ucn-yushin Aug 9, 2026
324ae52
feat: GT比較結果をイベントログJSONへ保存
ucn-yushin Aug 9, 2026
e5dd37f
docs: 2ライン方式のGT比較手順を追記
ucn-yushin Aug 9, 2026
8642577
chore: .gitignoreにローカルディレクトリ用のルールを追加
ucn-yushin Aug 9, 2026
7951463
chore: pytest を dev dependency として明示宣言
ucn-yushin Aug 10, 2026
3d2eb72
build: ultralytics を 8.4.72 に固定
ucn-yushin Aug 10, 2026
ffa889e
fix: 2ライン交差判定を単位是正・有限線分・ヒステリシス方式へ作り直す
ucn-yushin Aug 10, 2026
c329240
feat: Line2通過後にconfidenceを確定
ucn-yushin Aug 11, 2026
50a7552
chore: margin_pxの既定値を3c計測結果に合わせる
ucn-yushin Aug 11, 2026
68a8900
fix: 2ライン同一フレーム両線交差の記録順を交点位置で決定
ucn-yushin Aug 16, 2026
98ab5db
fix: イベント識別子をtrack IDからevent IDへ変更
ucn-yushin Aug 16, 2026
b4b2045
chore: .vscode/settings.jsonを削除
ucn-yushin Aug 16, 2026
7d02fac
feat: GTスキーマへイベント単位のevents/tolerance_secを追加
ucn-yushin Aug 16, 2026
04099b0
feat: GTと予測イベントの突合モジュールevent_matching.pyを新設
ucn-yushin Aug 16, 2026
b37a1b1
feat: raspi/eval/を2ラインブランチへ移植
ucn-yushin Aug 16, 2026
81cc1a0
feat: イベント精度評価行の生成スクリプトを追加(2ライン)
ucn-yushin Aug 16, 2026
3c6b4a4
docs: 2ライン方式の検証手順をVERIFICATION.mdとして追加
ucn-yushin Aug 20, 2026
51a6b18
docs: 2ライン方式の検証手順へW&Bログインの前提を追記
ucn-yushin Aug 20, 2026
a93ce6a
docs: W&Bの運用方針を4点の決定として明文化(2ライン方式)
ucn-yushin Aug 25, 2026
77cb510
fix: 時間窓をフレーム基準から秒基準へ変更(2ライン方式)
ucn-yushin Aug 25, 2026
a5f4a5b
fix: setup_lines.pyがライン座標以外の.env設定を破壊しないようにする
ucn-yushin Aug 27, 2026
dec1453
fix: visualize_lines_and_vehicles.pyへ--envを追加する
ucn-yushin Aug 28, 2026
62365b1
feat: 複数動画を順に処理して比較用サマリーへ集約するランナーを追加
ucn-yushin Aug 28, 2026
d558ef2
feat: 動画ごとにROI方式と2ライン方式を交互実行するスクリプトを追加
ucn-yushin Sep 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,16 @@ __pycache__/
.env
raspi/result/prediction_visual.png
*.pt
.venv/
.venv/

# 2ライン検知システムの出力ファイル
data

# ドキュメント
docs/
docs/*
!raspi/line_detection/docs/
!raspi/line_detection/docs/wandb_integration_spec_v2.md

# ローカルディレクトリ
**/*.local
3 changes: 0 additions & 3 deletions .vscode/settings.json

This file was deleted.

23 changes: 22 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,25 @@ version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []
dependencies = [
# YOLOv8とトラッキング
"ultralytics==8.4.72",
# 動画処理
"opencv-python>=4.8.1",
# 設定管理
"python-dotenv>=1.0.0",
# 数値演算
"numpy>=1.24.4,<2.0",
# 深層学習バックエンド
"torch>=2.2.2",
"torchvision>=0.17.2",
# データ処理
"pandas>=2.2.1",
"lap>=0.5.13",
"wandb>=0.28.0",
]

[dependency-groups]
dev = [
"pytest>=9.1.1",
]
Empty file added raspi/common/__init__.py
Empty file.
125 changes: 125 additions & 0 deletions raspi/common/event_matching.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
"""GTと予測イベントの突合、および精度指標の算出。"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Sequence


@dataclass(frozen=True)
class PredictedEvent:
"""検出方式が出力した1件の予測イベント。"""

event_id: str
direction: str # "IN" or "OUT"
t_sec: float


@dataclass(frozen=True)
class GtEvent:
"""GTの1物理イベント。raspi.common.ground_truth.GtEvent と同型。"""

event_id: str
direction: str
t_sec: float


@dataclass(frozen=True)
class MatchResult:
tp: int
fp: int
fn: int
tp_in: int
fp_in: int
fn_in: int
tp_out: int
fp_out: int
fn_out: int
precision: float | None
recall: float | None
f1: float | None


def _match_direction(
predicted: Sequence[PredictedEvent],
gt: Sequence[GtEvent],
direction: str,
tolerance_sec: float,
) -> tuple[int, int, int]:
predicted_in_direction = [
(index, event) for index, event in enumerate(predicted) if event.direction == direction
]
gt_in_direction = [
(index, event) for index, event in enumerate(gt) if event.direction == direction
]

candidates = []
for predicted_index, predicted_event in predicted_in_direction:
for gt_index, gt_event in gt_in_direction:
time_difference = abs(predicted_event.t_sec - gt_event.t_sec)
if time_difference <= tolerance_sec:
candidates.append(
(
time_difference,
gt_event.event_id,
predicted_event.event_id,
predicted_index,
gt_index,
)
)

candidates.sort()
consumed_predicted: set[int] = set()
consumed_gt: set[int] = set()
tp = 0

for _, _, _, predicted_index, gt_index in candidates:
if predicted_index in consumed_predicted or gt_index in consumed_gt:
continue
consumed_predicted.add(predicted_index)
consumed_gt.add(gt_index)
tp += 1

fp = len(predicted_in_direction) - tp
fn = len(gt_in_direction) - tp
return tp, fp, fn


def match_events(
predicted: Sequence[PredictedEvent],
gt: Sequence[GtEvent],
tolerance_sec: float,
) -> MatchResult:
"""予測イベントを方向別にGTへ1対1で突合し、全体の指標を返す。"""

tp_in, fp_in, fn_in = _match_direction(predicted, gt, "IN", tolerance_sec)
tp_out, fp_out, fn_out = _match_direction(predicted, gt, "OUT", tolerance_sec)

tp = tp_in + tp_out
fp = fp_in + fp_out
fn = fn_in + fn_out

precision = tp / (tp + fp) if tp + fp > 0 else None
recall = tp / (tp + fn) if tp + fn > 0 else None

if precision is None and recall is None:
f1 = None
elif precision is None or recall is None or precision + recall == 0:
f1 = 0.0
else:
f1 = 2 * precision * recall / (precision + recall)

return MatchResult(
tp=tp,
fp=fp,
fn=fn,
tp_in=tp_in,
fp_in=fp_in,
fn_in=fn_in,
tp_out=tp_out,
fp_out=fp_out,
fn_out=fn_out,
precision=precision,
recall=recall,
f1=f1,
)
130 changes: 130 additions & 0 deletions raspi/common/frame_stats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
"""
フレーム処理時間の統計ユーティリティ(両検出ロジック共通)

W&B の summary に入れる速度指標(p95 / effective_fps / realtime_ok 等)を
1 フレームごとの処理時間リストから算出する。
"""

from typing import Any, Dict, List, Sequence

import numpy as np


STAT_SUFFIXES = ("min", "max", "mean", "p50", "p95", "p99", "total")


def compute_series_stats(values: Sequence[float], prefix: str) -> Dict[str, float]:
"""任意のミリ秒系列を、指定した prefix の統計キーへ展開する。"""

if not values:
return {f"{prefix}_{suffix}": 0.0 for suffix in STAT_SUFFIXES}

arr = np.asarray(values, dtype=float)
return {
f"{prefix}_min": float(arr.min()),
f"{prefix}_max": float(arr.max()),
f"{prefix}_mean": float(arr.mean()),
f"{prefix}_p50": float(np.percentile(arr, 50)),
f"{prefix}_p95": float(np.percentile(arr, 95)),
f"{prefix}_p99": float(np.percentile(arr, 99)),
f"{prefix}_total": float(arr.sum()),
}


def _timing_value(record: Any, key: str) -> float:
if isinstance(record, dict):
return float(record[key])
return float(getattr(record, key))


def compute_timing_stats(records: Sequence[Any], source_fps: float) -> Dict[str, Any]:
"""timing schema v2 のフェーズ別統計とリアルタイム指標を算出する。"""

phase_names = (
"read_ms",
"inference_tracking_ms",
"counting_logic_ms",
"core_ms",
"output_ms",
"end_to_end_ms",
)
stats: Dict[str, Any] = {}
for phase in phase_names:
stats.update(compute_series_stats([_timing_value(r, phase) for r in records], phase))

core_mean = stats["core_ms_mean"]
end_to_end_mean = stats["end_to_end_ms_mean"]
stats["core_effective_fps"] = 1000.0 / core_mean if core_mean > 0 else 0.0
stats["end_to_end_effective_fps"] = (
1000.0 / end_to_end_mean if end_to_end_mean > 0 else 0.0
)

budget_ms = 1000.0 / source_fps if source_fps > 0 else 0.0
miss_count = (
sum(_timing_value(r, "end_to_end_ms") > budget_ms for r in records)
if budget_ms > 0
else 0
)
stats["frame_budget_ms"] = budget_ms
stats["deadline_miss_count"] = int(miss_count)
stats["deadline_miss_rate"] = float(miss_count / len(records)) if records else 0.0
stats["core_realtime_ok"] = (
bool(stats["core_effective_fps"] >= source_fps) if source_fps > 0 else False
)
stats["end_to_end_realtime_ok"] = (
bool(records and source_fps > 0 and miss_count == 0)
)

# timing schema v1 互換。新しい比較・リアルタイム判定には上記の明示名を使う。
for suffix in STAT_SUFFIXES:
stats[f"frame_ms_{suffix}"] = stats[f"core_ms_{suffix}"]
stats["total_ms"] = stats["core_ms_total"]
stats["effective_fps"] = stats["core_effective_fps"]
stats["realtime_ok"] = stats["core_realtime_ok"]
return stats


def compute_frame_stats(frame_ms_list: List[float], source_fps: float) -> Dict:
"""
フレーム処理時間リストから速度統計を算出する。

Args:
frame_ms_list: 1 フレームあたりの処理時間(ミリ秒)のリスト
source_fps: 入力動画の FPS(realtime 判定の基準)

Returns:
dict: 速度統計。空リスト時は全て 0 / realtime_ok=False を返し例外を出さない。
- frame_ms_min / frame_ms_max / frame_ms_mean
- frame_ms_p50 / frame_ms_p95 / frame_ms_p99
- total_ms
- effective_fps (= 1000 / frame_ms_mean)
- realtime_ok (= effective_fps >= source_fps)
"""
if not frame_ms_list:
return {
"frame_ms_min": 0.0,
"frame_ms_max": 0.0,
"frame_ms_mean": 0.0,
"frame_ms_p50": 0.0,
"frame_ms_p95": 0.0,
"frame_ms_p99": 0.0,
"total_ms": 0.0,
"effective_fps": 0.0,
"realtime_ok": False,
}

arr = np.asarray(frame_ms_list, dtype=float)
mean = float(arr.mean())
effective_fps = 1000.0 / mean if mean > 0 else 0.0

return {
"frame_ms_min": float(arr.min()),
"frame_ms_max": float(arr.max()),
"frame_ms_mean": mean,
"frame_ms_p50": float(np.percentile(arr, 50)),
"frame_ms_p95": float(np.percentile(arr, 95)),
"frame_ms_p99": float(np.percentile(arr, 99)),
"total_ms": float(arr.sum()),
"effective_fps": effective_fps,
"realtime_ok": bool(effective_fps >= source_fps) if source_fps > 0 else False,
}
Loading