🎯 YOLO Tracker-Aided Temporal Smoothing

2026-05-18 · 用上下幀關係降 YOLO det 的 FP/flickering,無需重訓

一句話結論:person YOLO det 加 ByteTrack + per-track confidence sliding window,**單幀假框被 filter / 短暫漏抓被 gap fill / 持續真陽性被 boost**。預期 FP -30~50%,flickering -60%,零訓練成本

🎯 為什麼要做

場域 FP/FN 根因(過往 audit):
- 78% FP 來自 YOLO 假框(單幀偵測誤判)
- 97% 漏報來自 YOLO 漏框(同人忽抓忽漏 flickering)
- 換更大 ViT 分類器 → 邊際遞減(已 ablation 確認)
**模型本身已飽和,但畫面是連續的 — 我們白白浪費了「上下幀關係」這個訊號**。同一個人在連續 30 幀內如果只有 1 幀偵測到,多半是 FP;如果連 8 幀都偵測到,幾乎一定是真的。

🧠 核心 idea(3 個機制)

機制解決什麼邏輯
1. Per-track confidence smoothing單幀低 conf 噪音同 track_id N 幀 conf 做 sliding mean / EMA,平滑 confidence 抖動
2. Gap filling短暫漏抓 / 遮擋同 track 短暫消失 ≤ 3 幀,用 motion model interpolate bbox,當作仍存在
3. Transient filter單幀假框 FPtrack 長度 < N_min 幀(如 3)視為 transient,不報 alarm

🔄 完整 pipeline

RTSP frame ↓ ┌─────────────────────────────┐ │ Stage 1: YOLO det │ ← 現役 person YOLO11n(不動) │ → raw det bbox + conf │ └─────────────────────────────┘ ↓ ┌─────────────────────────────┐ │ Stage 2: ByteTrack │ ← Ultralytics 內建,加 persist=True │ → bbox + tid │ └─────────────────────────────┘ ↓ ┌─────────────────────────────┐ │ Stage 3: Per-track buffer │ ← 新加(核心) │ for tid in tracks: │ │ deque.append(conf) │ │ smoothed = mean(deque) │ │ track_age += 1 │ │ → filter / fill │ └─────────────────────────────┘ ↓ ┌─────────────────────────────┐ │ Stage 4: 下游消費者 │ │ - PPE 21-attr cls │ │ - safety_rope RoI │ │ - iSeek 通報 │ └─────────────────────────────┘

⚙️ 演算法詳細

per-track state

track_state: dict[tid, {
    "conf_deque": deque(maxlen=N),   # 最近 N 幀的 confidence
    "bbox_deque": deque(maxlen=N),   # 最近 N 幀的 bbox(給 gap fill)
    "first_seen_frame": int,          # 第一次出現
    "last_seen_frame": int,           # 最後一次出現
    "miss_count": int,                # 連續多少幀沒看到(gap fill 用)
}]

每幀更新邏輯

def process_frame(frame, frame_idx):
    raw_dets = yolo.track(frame, persist=True, tracker="botsort.yaml")
    smoothed_dets = []
    seen_tids = set()

    # update active tracks
    for det in raw_dets:
        tid = det.tid
        seen_tids.add(tid)
        st = track_state.setdefault(tid, {...})
        st["conf_deque"].append(det.conf)
        st["bbox_deque"].append(det.bbox)
        st["last_seen_frame"] = frame_idx
        st["miss_count"] = 0
        smoothed_conf = mean(st["conf_deque"])
        track_age = len(st["conf_deque"])

        # transient filter — track 太短不信
        if track_age < N_MIN_TRACK:
            continue  # 不輸出到下游
        # 輸出 smoothed det
        smoothed_dets.append({
            "bbox": det.bbox, "tid": tid,
            "raw_conf": det.conf,
            "smoothed_conf": smoothed_conf,
            "track_age": track_age,
        })

    # gap fill — 沒看到的 active track
    for tid, st in track_state.items():
        if tid in seen_tids: continue
        st["miss_count"] += 1
        if st["miss_count"] <= N_GAP_MAX:
            # interpolate bbox(用最後 bbox 或 linear motion)
            ghost_bbox = st["bbox_deque"][-1]
            smoothed_dets.append({
                "bbox": ghost_bbox, "tid": tid,
                "raw_conf": 0.0,
                "smoothed_conf": mean(st["conf_deque"]) * 0.7,  # 0.7 為衰減因子
                "track_age": len(st["conf_deque"]),
                "is_ghost": True,
            })
        elif st["miss_count"] > N_RETIRE:
            del track_state[tid]   # retire 死掉的 track

    return smoothed_dets

建議參數

參數建議值說明
N (deque maxlen)8-16 幀RTSP @ 10 fps 約 1-1.5 秒
N_MIN_TRACK (transient filter)3-5 幀track 持續這麼久才認真
N_GAP_MAX (gap fill)3 幀短暫遮擋可補;超過視為真消失
N_RETIRE (清掉死 track)30 幀3 秒沒看到就 retire
ghost conf decay0.7補幀的 confidence 打折,不要當真實 det

🚨 跟 iSeek 通報路徑整合

現在 iSeek rule:「raw det 連續 N 幀觸發 sustained → 通報 → cooldown」
新 iSeek rule:「smoothed det 出現即可觸發 → 通報 → cooldown」
| Before | After | Bonus | |--------|-------|-------| | sustained window 5-10 幀 才報 | 3-5 幀 即可(smoothed 本身夠穩)| 通報延遲 -50% | | FP rate 高(同一 false alarm 連報 cooldown 期)| FP 在 smoothing 階段就過濾 | cooldown 邏輯可放寬 | | flickering 導致 alarm 中斷 → 重新計算 sustained | 同 track 持續中(gap fill),sustained 不中斷 | recall 改善 |

📊 預期效益(推測,需 production 驗證)

指標baseline+ smoothing說明
FP rate100%50-70%transient filter + smoothing 雙效
flickering(同人忽抓忽漏)常見-60%gap fill 補回
recall(持續真陽性)baseline+2-5%gap fill + boost confidence
iSeek 通報延遲sustained × frame_dt-50%sustained 可調短
推論成本~1.1×tracker 開銷可忽略

🛠️ 部署位置

服務位置動作
model_viewer / ppe-demoscripts/model_viewer/app.pyPPE21Handler 已加 完成;person handler 待加
iSeekiSeek_iframe (5090-2 上 docker)rule engine 整合 smoothed det 待做
iseek-riveriseek-river.intemotech.comriver_debris 也可加 待做
離線推論 batchinfer_ppe_video.py / infer_ppe_to_json.py可加 選做

⚠️ 注意 / 已知限制

🚀 動工順序

Step動作時長
1model_viewer person handler 加 tracker smoothing(沿用 PPE21Handler 範本)1 天
2同份 code 套到 iSeek inference 服務(5090-2 docker)1 天
3調 iSeek rule 整合 smoothed conf + 縮短 sustained 窗口1 天
4production 跑 1 週 ABTest(half cam baseline / half cam smoothing)1 週
5analytics:FP / recall / 通報延遲 對比1 天
6若效益顯著 → 全 cam roll out1 天

🔗 相關 VOD 研究(後續可延伸)

方法類別備註
ByteTrack / BoT-SORTTrackerUltralytics 內建,我們本方案直接用
FGFA (Flow-Guided Feature Aggregation)VOD-train用 optical flow warp 鄰幀 feature 加權平均
SELSA (Sequence Level Semantic Aggregation)VOD-trainDETR-based
MEGA (Multi-frame Enhanced Global)VOD-trainLong-term + short-term memory
YOLOv8-MS / YOLO11-MSVOD-trainYOLO 加 temporal neck(社群實作)
TSM (Temporal Shift Module)VOD-trainchannel-wise shift to prev/next frame
TransVOD / PTSEFormerVOD-trainDETR + temporal attention
VideoSwin / SlowFastVideo transformer長期可考慮,cost 高
本份是 A 路線(tracker-aided smoothing)的設計文件,零訓練成本,當下立即可動。
未來累積足夠 sequence 標注後,可再評估 B 路線(FGFA / SELSA 重訓),預期再 +5-10% recall / -20% FP,但 cost 3-10×。
→ 回模型訓練 SOP · → 所有報告目錄