首页
/ supervision 中 DetectionsSmoother 深度解析:基于滑动窗口实现跨帧检测框平滑

supervision 中 DetectionsSmoother 深度解析:基于滑动窗口实现跨帧检测框平滑

2026-09-05 10:16:23作者:凌朦慧Richard

DetectionsSmoother 是 supervision(Roboflow 开源的计算机视觉后处理工具库,口号为 "We write your reusable computer vision tools")提供的检测平滑工具,位于检测工具集 sv.detection.tools 中。它针对视频追踪场景下目标检测框逐帧抖动的问题,为每个追踪目标维护一段固定长度的历史窗口,并对窗口内的坐标做均值平滑,从而让标注框、下游统计结果更加稳定。读完本文,你将掌握它的完整 API、逐帧更新与轨道管理的工作机制、置信度与旋转框(OBB)的特殊处理规则,以及如何把它接入 Ultralytics / RF-DETR 等检测 + 追踪流水线。

工具定位:平滑解决什么问题

在视频目标检测中,即使模型输出稳定,检测框的坐标也会因每帧预测误差而轻微跳动;若再叠加遮挡、短暂漏检再重现等情况,视觉上的"抖动"会更明显。DetectionsSmoother 的思路是:不再单独信任当前帧的检测框,而是取该追踪目标(track)最近 length 帧检测框的均值作为平滑后的输出

类定义见 DetectionsSmoother,并通过包级入口导出,可直接以 sv.DetectionsSmoother 使用,见 supervision 包初始化文件from supervision.detection.tools.smoother import DetectionsSmoother__all__ 中的 "DetectionsSmoother" 条目)。官方文档页入口为 docs/detection/tools/smoother.md,追踪流程的完整用法可在 追踪目标指南 的 "Bonus: Smoothing" 一节中看到。

官方文档给出了三条必须注意的前提约束:

  • 必须提供 tracker_id:平滑以追踪 ID 为单位组织历史,没有追踪 ID 就无法区分"同一个目标在不同帧的检测"。若检测到 tracker_id is Noneupdate_with_detections 会发出 SupervisionWarnings 警告并原样返回输入,跳过平滑(源码 smoother.py 第 148–155 行)。
  • 不兼容分割模型:该类只处理 xyxy 框(以及可选的旋转框角点),不适用于 mask 类输出。
  • 置信度不一致时的降级规则:当同一帧中部分追踪携带置信度、部分不携带时,该帧所有平滑检测的 confidence 会被统一置为 None,以避免合并时的字段冲突(源码 smoother.py 第 244–249 行)。

快速上手:最小示例与数值验证

官方文档附带的交互式示例(doctest)是理解其数学行为的最快方式:用 length=3 创建一个 smoother,送入两个带 tracker_id 的检测,观察平滑结果。

import numpy as np
import supervision as sv

smoother = sv.Detectionssmoother if False else sv.DetectionsSmoother(length=3)

detections_1 = sv.Detections(
    xyxy=np.array([[0, 0, 10, 10]]),
    confidence=np.array([0.5]),
    tracker_id=np.array([1]),
)
detections_2 = sv.Detections(
    xyxy=np.array([[2, 2, 12, 12]]),
    confidence=np.array([0.7]),
    tracker_id=np.array([1]),
)

smoothed = smoother.update_with_detections(detections_1)
print(smoothed.xyxy)        # array([[ 0.,  0., 10., 10.]]) —— 窗口内只有 1 帧,即原框
smoothed = smoother.update_with_detections(detections_2)
print(smoothed.xyxy)        # array([[ 1.,  1., 11., 11.]]) —— 两帧坐标的均值
print(smoothed.confidence)  # array([0.6])                 —— 两帧置信度的均值

可以看到两点规律:

  1. 框坐标是窗口内所有有效帧 xyxy 的逐元素均值(第一帧输出等于自身,第二帧是两帧均值 (0+2)/2, (0+2)/2, (10+12)/2, (10+12)/2)。
  2. 置信度同样在窗口内取均值,但只统计"携带置信度"的帧;若整个窗口都没有置信度则保持 None

完整流水线:接入检测 + 追踪

官方文档给出的标准用法是"检测模型 → 追踪器 → smoother → 标注器"四级流水线,以下示例基于 RF-DETR 与内置 ByteTrack:

import supervision as sv
from rfdetr import RFDETRMedium

video_info = sv.VideoInfo.from_video_path(video_path="<SOURCE_FILE_PATH>")
frame_generator = sv.get_video_frames_generator(source_path="<SOURCE_FILE_PATH>")

model = RFDETRMedium()
tracker = sv.ByteTrack(frame_rate=video_info.fps)
smoother = sv.DetectionsSmoother()

box_annotator = sv.BoxAnnotator()

with sv.VideoSink("<TARGET_FILE_PATH>", video_info=video_info) as sink:
    for frame in frame_generator:
        detections = model.predict(frame[:, :, ::-1])
        detections = tracker.update_with_detections(detections)   # 先分配 tracker_id
        detections = smoother.update_with_detections(detections)  # 再做平滑

        annotated_frame = box_annotator.annotate(frame.copy(), detections)
        sink.write_frame(annotated_frame)

顺序很关键:必须先过追踪器拿到 tracker_id,再送入 smoother。同样的模式也出现在关键点追踪场景,见 docs/how_to/track_objects.md 中 Ultralytics / Inference 两套示例,均为 tracker.update_with_detections(...) 之后紧跟 smoother.update_with_detections(...),再交给 BoxAnnotatorTraceAnnotator 画框和轨迹。

一个需要留意的版本信息:示例中使用的内置 sv.ByteTrack 自 supervision-0.28.0 起标记为弃用,并计划在 0.31.0 移除(建议改用 trackers 包的 ByteTrackTracker,且更新方法由 update_with_detections() 改名为 update()),说明见 ByteTrack 类文档。本文示例忠实保留当前文档给出的写法,若你使用 0.28.0 之后的版本,建议将追踪器替换为外部实现,其余平滑代码不变。

API 详解

__init__(length: int = 5)

唯一构造参数:

  • length:参与平滑的最大帧数,即每个追踪历史的滑动窗口长度,默认 5

从源码看,length 通过 defaultdict(lambda: deque(maxlen=length)) 落实为每个追踪 ID 一条独立、有上限的 deque 队列,见 smoother.py 第 103–111 行。因此:

  • length 越大,平滑越强、输出滞后越大,框对目标真实位置的反应越慢;
  • length 越小(如 1–2),输出越贴近原始检测,但抖动抑制有限;
  • 每个轨道的历史相互独立,窗口满后最早帧自动滑出,不占用额外内存。

update_with_detections(detections) -> Detections

核心更新入口,每处理一帧调用一次。它在 smoother.py 第 140–174 行的实现分四步:

  1. 前置校验detections.tracker_id is None 时发出 SupervisionWarnings 警告并原样返回输入;
  2. 写入历史:逐条检测取出 tracker_id,用 detections.select(detection_idx) 复制出单目标 Detections 后追加进该轨道的队列(select 保证每帧历史都是独立副本,见 Detections.select);
  3. 缺席补记:对本帧未出现的历史轨道追加一个 None 占位;若某轨道整个窗口全是 None(长时间未再出现),则从缓存中删除该轨道,防止内存无限增长;
  4. 输出:调用 get_smoothed_detections(track_ids=当前帧活跃轨道),只输出本帧活跃轨道的平滑结果——缺席轨道保留历史但不输出"幽灵框"

其中第 3 步的活跃 ID 判断使用集合成员检查而非逐轨道扫描,这是仓库变更日志中记录的一次性能优化(无输出变化)。

get_track(track_id) -> Detections | None

返回单个轨道的平滑 Detections,规则(源码第 176–226 行):

  • 取窗口内所有非 None 帧,对 xyxy 逐元素求均值;
  • confidence 仅在携带它的帧上求均值;全部缺失时为 None
  • 其余字段(如 class_id)取自窗口中最早的有效帧;
  • 轨道未知或整窗为空时返回 None

get_smoothed_detections(track_ids=None) -> Detections

将多条轨道的平滑结果用 Detections.merge 合并为一个对象(见 Detections.merge)。两个细节值得注意:

  • track_ids 用于过滤"本帧活跃轨道":缺席轨道的历史仍保留在缓存中,只是不参与本次输出;
  • 若任一轨道的 confidenceNone,则全部平滑检测的 confidence 统一置 None——这是为了规避 Detections.merge 对可选字段"全有或全无"的合并约束(源码第 244–249 行的注释明确说明了这一点);
  • 结果为空时会补一个 tracker_id=np.array([], dtype=int),保证返回对象字段完整。

reset()

清空所有轨道的历史,但保留配置的 length,让同一个实例可以复用于不同视频流而不携带上一流的残帧。变更日志显示该方法是近期为与 TraceAnnotatorHeatMapAnnotator 保持接口一致而补充的。

深入实现:置信度、缺席轨道与旋转框

缺席与重现:历史不丢,输出不撒谎

测试 tests/detection/tools/test_smoother.pytest_smoother_reappearing_track_keeps_history 验证了这一行为:第 1 帧有检测、第 2 帧空(轨道缺席)、第 3 帧目标重新出现。期望结果是——第 2 帧平滑输出长度为 0(不输出幽灵框),而第 3 帧输出的坐标 [1, 1, 11, 11] 与置信度 0.6跨越缺席帧、对第 1 帧与第 3 帧历史求均值得到的。这说明 None 占位只用于淘汰整窗空轨道,并不打断平滑连续性,对"短暂漏检后重现"的目标尤其友好。test_smoother_does_not_emit_missing_tracks 则从另一角度断言了"缺席不输出"这一点。

置信度的三种窗口情形

test_smoother_confidence_scenarios 用参数化用例覆盖了三种情形:

窗口内置信度分布 平滑结果
两帧均有(0.5、0.7) 均值 0.6
两帧均无 None
混合(0.5、无) 仅对存在的帧取均值,得 0.5

test_smoother_window_full_averages_all_frames 进一步验证满窗(length=3,坐标 0/3/6、置信度 0.3/0.6/0.9)时是对全部 3 帧而非最近 2 帧求平均,输出恰为中间帧的坐标 [3, 3, 13, 13] 与置信度 0.6——这正是"均值平滑 = 移动平均滤波"的直观体现。

旋转框(OBB)的平滑

仓库较新的一个能力是:当 Detections.data 中带有旋转框角点(ORIENTED_BOX_COORDINATES)时,smoother 会把角点与轴对齐框一起平滑,保证两者描述同一位置。实现要点:

  • 仅当窗口内所有有效帧都携带形状一致的角点时才平滑角点,否则丢弃该字段、回退为普通 xyxy 平滑(test_mixed_oriented_box_metadata_is_dropped 验证混合窗口不残留陈旧 OBB 数据);
  • 求均值前先做角点对齐:不同帧的角点起点与绕行方向可能不同,_align_oriented_cornerssmoother.py 第 13–25 行)通过枚举 4 个循环移位 × 2 个方向(正序/逆序)共 8 种候选,选取与参考帧欧氏距离最小的对齐方式,test_corner_order_is_aligned_before_averaging 专门验证了循环移位等价角点不会把正方形"搅成"交叉形;
  • 平滑角点算出后,xyxy 由角点包络(xyxyxyxy_to_xyxy)重新派生,保证两者严格一致(test_corners_agree_with_the_smoothed_boxtest_rotated_rectangle_keeps_xyxy_and_obb_envelopes_consistent)。测试类注释说明这修复的问题:此前其余字段都拷贝自窗口最早帧,导致角点与平滑后的轴对齐框位置互相矛盾;
  • 普通轴对齐检测不受影响,不会凭空多出 OBB 键(test_detections_without_oriented_boxes_are_unaffected)。

重置语义

test_reset_clears_track_history 验证 reset() 之后输出不再受旧框污染(新轨道首帧输出即原始框);test_reset_preserves_window_length 验证重置后新轨道的 deque.maxlen 仍等于构造时的 length(2),即重置只清数据、不清配置。

实践建议与适用边界

综合源码与测试,使用 DetectionsSmoother 时的几点建议:

  1. length 与帧率的换算:默认 5 帧,在 30 fps 视频中约覆盖 1/6 秒的历史。目标运动越快,越应减小 length 以免标注框"拖在"目标后面;目标基本静止(如固定设备计数)时则可适当加大。
  2. 追踪器是前置依赖:没有稳定 tracker_id 时平滑无从谈起,建议先调好追踪器参数(如激活阈值、丢失缓冲帧数)再叠加平滑。
  3. 注意输出滞后:均值平滑本质上引入滞后,若下游需要"当前时刻"的精确位置(如测速),应评估滞后带来的偏差,或缩短窗口。
  4. 不适用场景:分割 mask 输出(官方明确不兼容)、以及需要逐帧实时响应的低延迟场景。
  5. 多路复用:同一实例复用于不同流前调用 reset(),避免上一流的轨迹污染新一流的平滑结果。

DetectionsSmoother 以极小的 API 面(构造、逐帧更新、按轨查询、重置)实现了"滑窗均值 + 缺席容忍 + 字段一致性"三件事,是 supervision 检测工具集中用于压制视频标注抖动的标准组件;其每帧 O(n_tracks) 的更新开销主要来自活跃 ID 的集合判断与各轨道一次窗口均值,窗口长度 length 又通常很小,因此叠加进检测管线的成本可以忽略。

参考路径

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
983
503
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384