新增起步門檻 depart_seconds: 處理車陣走走停停
- 兩段停車間的行駛(GPS>0)若不超過 depart_seconds(預設5秒),視為仍在等待, 連同那段短暫蠕動一起剪掉;唯有行駛超過5秒才算真正起步而保留(含整段) - 合併改在原始停止段層級進行,取代舊的 merge_gap(post-padding 合併) - 短暫走停的多個小停(各不到 STOP_SECONDS)合併後可一起構成紅燈被移除 - 已用合成情境驗證: 3秒走停被剪、13秒行駛保留、結尾停車砍到底 本次調整由 Claude Opus 4.8 (1M context) 協助開發與處理。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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` | 保留片段短於此秒數即丟棄 |
|
||||
|
||||
+46
-32
@@ -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",
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user