diff --git a/README.md b/README.md index 7548d71..fdad65d 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ python auto_remove_redlight.py 01.MOV 03.MOV 05.MOV | `speed_threshold` | `0` | 時速 ≤ 此值視為停止 | | `min_confidence` | `0.5` | OCR 信心低於此值視為雜訊 | | `smooth_window` | `5` | 中位數濾波視窗(取樣點數) | -| `merge_gap` | `3.0` | 相鄰紅燈段間隔小於此秒數即合併 | +| `depart_seconds` | `5.0` | 起步門檻(秒):兩段停車間「持續行駛」需超過此秒數才算真正起步而保留;否則(起步 N 秒內又停)視為仍在等待,連同短暫蠕動一起剪掉(處理車陣走走停停),也順便吸收 OCR 雜訊斷點 | | `cut_before_stop` | `2.0` | 進入紅燈端(速度到 0)移除起點再往前幾秒,連減速進站一起砍 | | `keep_after_stop` | `2.0` | 綠燈起步端(速度從 0 開始跑)移除終點提早幾秒,多留卡達起步畫面 | | `min_keep` | `0.8` | 保留片段短於此秒數即丟棄 | diff --git a/auto_remove_redlight.py b/auto_remove_redlight.py index 8bf9ded..fce8e50 100644 --- a/auto_remove_redlight.py +++ b/auto_remove_redlight.py @@ -101,7 +101,10 @@ MAX_SPEED = 300 # 合理時速上限,超過視為誤判(雜 # - 孤立的雜訊讀數會被周圍的 0 吃掉(中位數) # - 辨識失敗的空值會用前後最近的有效讀數補上 SMOOTH_WINDOW = 5 # 中位數濾波視窗(取樣點數,奇數)。5 = 看前後各 2 秒 -MERGE_GAP = 3.0 # 兩個紅燈移除段若間隔小於此秒數,視為同一段並合併 (秒) +DEPART_SECONDS = 5.0 # 起步門檻(秒): 兩段停車中間「持續行駛(GPS>0)」需超過此 + # 秒數,才算真正起步而保留;否則(起步 N 秒內又停)視為 + # 仍在等待,連同那段短暫蠕動一起剪掉(處理車陣走走停停)。 + # 也順便吸收 OCR 雜訊造成的短暫斷點。 # --- 剪輯 / 輸出參數 ---------------------------------------- # 紅燈移除段的頭尾「不對稱」微調: @@ -347,48 +350,59 @@ def find_keep_intervals(samples: List[Sample], duration: float, interval: float smoothed = smooth_speeds(samples, SMOOTH_WINDOW) stopped = [v <= SPEED_THRESHOLD for v in smoothed] - removals: List[Tuple[float, float]] = [] + # 1) 先抓出所有「原始連續停止」區段(不論長短) + raw_runs: List[List[float]] = [] i = 0 while i < n: if stopped[i]: - # 找出這一段連續停止的範圍 i..j j = i while j + 1 < n and stopped[j + 1]: j += 1 - run_start = samples[i].t - # 視為持續到「下一個取樣點」才結束,所以 +interval - run_end = min(samples[j].t + interval, duration) - run_dur = run_end - run_start - - # 是否一路停到「影片結尾」(結尾就是 GPS 0) - at_end = run_end >= duration - 1e-3 - - # 列為移除的條件: 達到紅燈門檻,或停到影片結尾(結尾停車一律砍掉) - if run_dur >= STOP_SECONDS or at_end: - # 進入紅燈端往前多砍 CUT_BEFORE_STOP 秒(連減速進站一起砍)。 - rstart = max(0.0, run_start - CUT_BEFORE_STOP) - if at_end: - # 停到影片結尾: 沒有起步要留 → 砍到底,不保留 KEEP_AFTER_STOP - rend = run_end - else: - # 一般紅燈: 綠燈起步端提早 KEEP_AFTER_STOP 秒結束(多留卡達起步) - rend = run_end - KEEP_AFTER_STOP - if rend > rstart: - removals.append((rstart, rend)) + run_end = min(samples[j].t + interval, duration) # 持續到下個取樣點 + raw_runs.append([run_start, run_end]) i = j + 1 else: i += 1 - # 合併「間隔很短」的相鄰移除段(平滑後仍可能殘留 1~2 格雜訊把一個紅燈切兩半) - if removals: - merged: List[Tuple[float, float]] = [removals[0]] - for (rs, re) in removals[1:]: - if rs - merged[-1][1] < MERGE_GAP: - merged[-1] = (merged[-1][0], re) # 接起來 + # 2) 合併「中間行駛(GPS>0)不超過 DEPART_SECONDS 秒」的相鄰停止段: + # 起步後 N 秒內又停 → 視為仍在等待(車陣走走停停),連那段短暫蠕動一起算停止。 + # 只有行駛「超過」DEPART_SECONDS 才算真正起步,該行駛段才會被保留。 + merged_runs: List[List[float]] = [] + for run in raw_runs: + if merged_runs and (run[0] - merged_runs[-1][1]) <= DEPART_SECONDS: + merged_runs[-1][1] = run[1] # 行駛 <= 門檻 → 併入前一段停止 + else: + merged_runs.append(list(run)) + + # 3) 套用門檻與頭尾偏移 → 產生移除區段 + removals: List[Tuple[float, float]] = [] + for (run_start, run_end) in merged_runs: + run_dur = run_end - run_start + at_end = run_end >= duration - 1e-3 # 是否一路停到影片結尾 + + # 列為移除: 達到紅燈門檻,或停到影片結尾(結尾停車一律砍掉) + if run_dur >= STOP_SECONDS or at_end: + # 進入紅燈端往前多砍 CUT_BEFORE_STOP 秒(連減速進站一起砍)。 + rstart = max(0.0, run_start - CUT_BEFORE_STOP) + if at_end: + # 停到影片結尾: 沒有起步要留 → 砍到底,不保留 KEEP_AFTER_STOP + rend = run_end else: - merged.append((rs, re)) - removals = merged + # 一般紅燈: 綠燈起步端提早 KEEP_AFTER_STOP 秒結束(多留卡達起步) + rend = run_end - KEEP_AFTER_STOP + if rend > rstart: + removals.append((rstart, rend)) + + # 4) 頭尾偏移後若仍有相鄰移除段重疊/相接,合併之 + if removals: + merged: List[List[float]] = [list(removals[0])] + for (rs, re) in removals[1:]: + if rs <= merged[-1][1]: + merged[-1][1] = max(merged[-1][1], re) + else: + merged.append([rs, re]) + removals = [(a, b) for a, b in merged] # keeps = 全片 [0, duration] 扣掉所有 removals keeps: List[Tuple[float, float]] = [] @@ -951,7 +965,7 @@ _CONFIG_KEYS = { "speed_threshold": "SPEED_THRESHOLD", "min_confidence": "MIN_CONFIDENCE", "smooth_window": "SMOOTH_WINDOW", - "merge_gap": "MERGE_GAP", + "depart_seconds": "DEPART_SECONDS", "cut_before_stop": "CUT_BEFORE_STOP", "keep_after_stop": "KEEP_AFTER_STOP", "min_keep": "MIN_KEEP", diff --git a/config.json b/config.json index 0ed04f1..0e67464 100644 --- a/config.json +++ b/config.json @@ -11,7 +11,7 @@ "speed_threshold": 0, "min_confidence": 0.5, "smooth_window": 5, - "merge_gap": 3.0, + "depart_seconds": 5.0, "cut_before_stop": 2.0, "keep_after_stop": 2.0, "min_keep": 0.8,