做视频隐私脱敏工具时,车牌/人脸追踪最怕的不是检测不准,而是检测断了之后怎么续。
我们的方案是检测+追踪两阶段:先用微调过的 YOLO 模型检出候选框,低置信度和重叠框在源头过滤;后续帧用 OpenCV 的 CSRT/KCF 跟踪器逐帧读取(不 seek),结果存成 [时间, cx, cy] 序列。预览和导出共用同一套按时间二分插值的取位函数。
这个架构有一个反直觉的坑:追踪器 ID 撞号会吞掉整段遮罩。
硬切场景下我们会重启追踪器,track id 从 1 重新编号;而轨迹过期时我们只从活跃队列里删、段数据故意保留到后处理。新的 tid=1 复活时如果直接用赋值覆盖,老 tid=1 的整段数据就丢了——实测一张被追踪 4.6 秒、76 帧真实检测的脸,最终一段遮罩都没输出。
修法不是合并两条轨(下游要求每个 key 单调,merge 会导致遮罩在两脸之间来回跳),而是「安置」:给被顶下来的老轨换个不会冲突的新 key 继续保留。
另一个隐蔽问题是追踪器过转场。
我们曾用灰度直方图、HSV、帧差三路信号做硬切判据,阈值分别是 0.40 / 0.72 / 0.33。但真实素材中,这三路在硬切点的实测值(如 0.173 / 0.304 / 0.284)全部低于阈值——转场根本没被检测到。
追踪器不感知画面内容,它只管跟纹理特征走。丢了检测后按 MAX_MISSED=20 步(约 1.33 秒)继续外推,alpha 降到下限 0.5 就停住,结果遮罩活生生飘到下一场去了。
我们的做法是加一层「软判据」(阈值 0.12 / 0.20 / 0.10,多路投票),只在判断「要不要继续外推」时使用;硬切判据不变(因为它决定是否清空追踪状态,代价更大)。
用 ffmpeg 的 select=gt(scene,0.2) 独立核对真实切点是验证手段。
写这段代码最花时间的不是一开始的设计,而是追踪断了之后各种corner case。检测模型可以换、跟踪器可以调参,但数据结构的不变量(每个遮罩轨单调、不交叉)必须在所有分支上都能守住。