简介:多目标跟踪(MOT)是计算机视觉中连接单帧检测与跨帧关联的关键技术,在安防、生态观测等场景中,检测器负责定位目标,跟踪器则为每个目标赋予稳定ID并还原运动轨迹。实际落地时,远距离小目标检测往往成为系统瓶颈——飞鸟在画面中可能仅占几十像素,传统模型难以稳定召回,而目标高速运动、姿态剧烈变化又容易导致ID频繁切换。将YOLOv5与DeepSORT组合使用,能有效兼顾检测精度与跟踪连续性:YOLOv5通过高分辨率输入和针对性数据增强提升小目标召回率,DeepSORT则借助级联匹配与外观特征关联弥补运动预测的不足。本文从数据集标注、检测器训练、跟踪器调参到完整推理链路封装,结合逆光、遮挡、密集鸟群等工程难点,提供一套可直接复用的优化方案,适合从事驱鸟防护、飞鸟观测或农业巡检的开发者参考,帮助快速构建稳定可靠的飞鸟检测与跟踪系统。
1. 飞鸟视觉检测和跟踪:为什么 YOLOv5+DeepSORT 成了这一场景的默认答案
做机场驱鸟、变电站鸟害防治、光伏电站巡检或者农业鸟情观测的人,多半都有同一个感受:单帧检测框画出来一点都不难,难的是你要回答“刚才那只鸟是从哪飞过来的、在这片区域停了多久”。YOLOv5-Deepsort飞鸟视觉检测和跟踪这个标题,本质上是把一个短小精悍的检测器和一个跨帧数据关联模块串成完整链路:YOLOv5负责在单帧图像里给出飞鸟的框和置信度,DeepSORT 负责把这些框按目标的运动特征和外观特征串成带 ID 的轨迹。适合做安防驱鸟、生态观测和农业防护的工程人员直接复用来做自研系统,也适合刚入手多目标跟踪的开发者把它当成一个典型的小目标跟踪案例来学。这篇文章我把训练数据、检测器调参、跟踪器参数和落地排查一次性讲清楚,新手能照着跑通,老手也能在边界参数上找到对照。
2. 飞鸟检测器训练:小目标数据是整套系统的地基
2.1 飞鸟数据的采集与标注:小目标标注的标准决定检测上限
飞鸟检测和行人检测最大的差异不在模型结构,而在目标的像素分布。在 1080p 画面里,一只距离 50 米以上的鸟往往只有二三十个像素宽,占整张图的千分之一都不到。YOLOv5 的下采样倍数决定了它对这种极小目标非常不敏感,尤其是 P5 模型,其特征图上的一个网格可能对应原图 32x32 像素,小鸟在特征图上只有一个点甚至没有响应。
所以数据采集阶段就要明确一个原则:宁可要 2000 张包含大量远距离小目标的图像,也不要 8000 张全是近距离大目标的图像。我一般会让采集镜头同时覆盖俯视、平视和仰视三个角度,因为飞鸟的姿态和背景完全不同——平视时背景是天空,俯视时背景是草地或屋顶,仰视时鸟的肚皮和翅膀颜色又不一样。只用一个角度训练的模型,换场景基本必翻车。
标注工具用开源的 LabelImg 或者 LabelStudio 都行,类别名就写一个bird,不建议把鸟的种类拆成多个类,飞鸟检测通常是二分类问题。真正需要盯住的是标注框的贴合度:小目标标注时框稍微偏一点,IoU 损失就会剧烈波动,模型学到的是“大概在那个位置”而不是“精确在那个位置”。我遇到过某个项目的数据集里,远处飞鸟的框普遍比真实目标大 40%,训练出来的模型在 mAP 上很好看,但实际跑视频时每一帧的框都在目标边缘抖动,DeepSORT 的级联匹配跟着遭殃,ID 频繁切换。
这类目标做标注时还有一个容易被忽略的操作:不要把画面里模糊到完全无法辨认的像素点标进去。飞鸟数据集里混入大量 5x5 像素的噪点标注,模型会把注意力浪费在这些“幽灵目标”上。标注规范我会写成三条:框必须贴合目标可见轮廓;画面中无法确认是鸟的像素点不标;被遮挡超过 60% 的目标不标。这三条会让数据集小一点,但训练出来的模型稳定得多。
2.2 数据增强与 anchor 设置:别让模型只在干净天空下认识鸟
飞鸟数据的背景多样性是个大麻烦。同一只鸟在树林前、在建筑前、在云层前,视觉特征完全不同,而过拟合到特定背景是检测器训练最常见的隐性失败。我的增强策略是 YOLOv5 自带的 mosaic 和 copy-paste 主力,另外会手动加三类针对性增强:随机亮度扰动(模拟逆光场景)、随机色调偏移(模拟黄昏和清晨)、随机仿射变换(模拟镜头俯仰带来的形状变化)。HSV 增强的参数我会调得比默认值狠一点,因为飞鸟场景的拍摄时段跨度很大,日出日落的光线变化比行人检测剧烈得多。
anchor 设置是另一个不能偷懒的点。YOLOv5 在训练开始时会用 k-means 对训练集标注框做自适应聚类,自动生成适合当前数据分布的 anchor。问题是默认的聚类结果里,最大的三个 anchor 往往接近 200x200 像素,而飞鸟的框普遍在 15x15 到 60x60 之间,算出来的超参不一定合理。我习惯在训练前先单独跑一次 anchor 聚类脚本,观察输出结果里有没有覆盖 10~20 像素级别的 anchor 尺寸。没有的话就手动在模型配置层里添加一组小 anchor,并且把 stride 较小的特征图对应的 anchor 分配权重大一点。这一步能直接改善远距离小目标的召回率,属于投入产出比极高的操作。
2.3 训练命令与关键参数:一份能直接跑的配置
训练阶段我用的就是常见的 YOLOv5 训练脚本,数据和模型结构准备好之后,按下面的命令来跑:
python train.py \ --data bird.yaml \ --weights yolov5m.pt \ --img 1280 \ --batch 16 \ --epochs 200 \ --workers 8 \ --device 0 \ --hyp hyp.bird.yaml \ --cache ram参数说明:--img是训练分辨率,飞鸟场景我建议直接上 1280 而不是默认的 640。检测器的训练分辨率决定了它从图像里提取细节的能力,640 训练出来的模型对 30 像素以下的目标基本是放弃状态,1280 能把小目标的召回率拉高一个档次,代价是显存占用翻倍、训练时间变长,但对飞鸟场景来说这个代价值得。--weights用 m 规格的预训练权重,s 规格对极小目标的特征表达能力不够,l 和 x 规格在推理阶段对边缘设备不友好,m 是平衡点。--hyp指向自定义超参文件,我在下方单独说明。--cache ram把图像缓存到内存里,数据集小目标多时显卡利用率能提上去。
hyp.bird.yaml里我重点改了三个超参:fl_gamma从 0 改成 1.0,开启类别不平衡的焦点损失,飞鸟数据里背景样本数量远远多于目标样本,这个参数能压低大量简单负样本对损失的贡献;mosaic概率保留默认但把mixup从 0 改成 0.2,飞鸟图片之间做混合增强可以提升模型对目标部分遮挡的鲁棒性;hsv_h、hsv_s、hsv_v整体上调 20% 左右,应对逆光和晨昏拍摄。
fl_gamma: 1.0 mixup: 0.2 hsv_h: 0.02 hsv_s: 0.8 hsv_v: 0.5另外需要提醒的是损失函数里的box和cls损失权重。飞鸟目标是强定位敏感类型,我会把box损失权重从默认的 0.05 上调到 0.08。调完之后小目标框的位置精度会提升一点,但对跟踪模块的影响是巨大的——框抖动变小,DeepSORT 的 IoU 关联和外观特征裁剪都会更稳定。训练日志里重点盯 P_ 和 R_ 两列,当验证集上的 precision 持续上升而 recall 不涨时,说明模型正在收紧判别边界,这时候要小心过拟合到背景纹理上,可以提前用验证集做一次可视化检查。
2.4 怎么判断检测器能不能进入跟踪环节
很多人的习惯是只看 mAP,但 mAP 在飞鸟场景里参考价值有限。一张 1080p 图里如果只出现一只鸟,检测器预测了三个候选框但 nms 之后保留了一个正确的,AP 可能仍然很高,可对跟踪来说这个目标的位置抖动已经足以影响轨迹质量。我一般会额外看两个指标:小目标召回率,即面积小于 32x32 像素的目标中被正确检出的比例;以及预测框中心点在连续帧之间的位移标准差,这个值超过了目标宽度的三分之一,就说明位置不稳,进入跟踪阶段之后必须靠卡尔曼滤波去平滑,否则 ID Switch 会居高不下。
更直观的验证方式是把检测结果按每一帧画成视频,用 0.25 的置信度阈值跑一遍 30 秒的素材,眼睛盯住最远处的那只鸟,看它的框是否连续存在、是否频繁闪烁。闪烁的直接后果是 DeepSORT 里轨迹丢失,重新确认新轨迹之后 ID 必变。检测器能达到的标准我建议是:在 1080p 输入下,占图宽 1% 以上的飞鸟目标,连续 10 帧内检测框丢失不超过一帧。达到这个标准再往跟踪阶段走,否则后面所有工作都在补救检测器的烂摊子,性价比极低。
3. DeepSORT 在飞鸟跟踪里的角色:跨帧数据关联不是玄学
3.1 为什么单纯用 SORT 或 IoU 跟踪会失败
SORT 是最朴素的在线多目标跟踪算法,核心只有卡尔曼滤波加匈牙利匹配,匹配代价只看检测框和预测框的 IoU。行人场景下目标移动速度慢、边界框尺度变化平缓,SORT 表现不错。但飞鸟是典型的高速非线性运动目标:鸟煽动翅膀时框的宽高比在剧烈变化,拐弯时运动方向突变,而且飞行轨迹经常有数帧的完全遮挡(另一只鸟从镜头前掠过)。这三种情况会把 SORT 的 IoU 关联直接打爆,轨迹断裂和 ID 切换是家常便饭。
DeepSORT 在 SORT 的思路上加了两个关键模块:级联匹配和外观特征匹配。级联匹配优先处理最近一直被稳定跟踪的轨迹,避免新产生的检测框抢占老轨迹的 ID;外观特征匹配则用一个小型卷积网络把检测框内的图像压缩成一个特征向量,计算特征向量之间的余弦距离作为关联代价。这个设计对飞鸟场景最大的价值在于:当目标在短暂遮挡后重现时,即使运动预测已经偏离,外观特征仍然能把同一只鸟接回原轨迹。
3.2 飞鸟场景下特征提取器不能无脑用默认配置
DeepSORT 的经典实现里自带一个基于行人 ReID 数据集训练的特征提取器,这个网络在行人类别上表现尚可,但用在飞鸟上效果并不好。原因是飞鸟的外观看上去雷同,不同个体之间的特征差异远小于不同行人之间的差异,而且同一个个体在不同姿态下的外观变化又非常大。直接把行人 ReID 的特征拿来算余弦距离,匹配结果几乎等于随机。
我的做法是换用通用目标 ReID 思路:特征提取网络换成在更多自然物体类别上预训练过的小型 ResNet 或 MobileNet 结构,输入图像不是整个检测框,而是把检测框的中心区域裁剪出来做一次缩放,去掉大部分背景像素。因为飞鸟通常有细长的翅膀和较暗的躯干,背景占比太高会把特征向量主导成“背景的颜色”,而不是“鸟的形态”。这一步能显著降低 ID Switch,代价是特征提取耗时增加一些,但对于飞鸟这种目标密度不高(一般一帧不超过几十只)的场景,这个性价比非常高。
3.3 三个必调参数:max_cos_dist、max_age、n_init
DeepSORT 的参数意义和调参规则在飞鸟场景和行人场景完全不同,下面这张表是我反复试错后的经验值,直接照着设能少走很多路。
| 参数 | 行人场景常见值 | 飞鸟场景推荐值 | 原因 |
|---|---|---|---|
| max_cos_dist | 0.2 | 0.3~0.4 | 飞鸟姿态变化大,特征距离天然偏大 |
| max_age | 30 | 50~70 | 飞鸟飞出画面后可能再回来,保留轨迹时间长一点 |
| n_init | 3 | 2 | 飞鸟目标小,连续检测帧易中断,n_init 太大会导致轨迹永远无法确认 |
| nn_budget | 100 | 50 | 飞鸟特征库不需要太长,长了反而引入旧姿态干扰 |
max_cos_dist是外观匹配的门槛,距离小于该值才允许关联。飞鸟个体外观差异小,设太严很多真实匹配会被拒绝,导致跟踪器频繁新建轨迹;设太松又会把不同鸟的特征错误关联。我一般从 0.35 起步,观察视频里是否有 ID 合并,有就下调,没有就保持。max_age是轨迹丢失后保留的帧数,飞鸟飞出画面再折返是常态,但保留太长会在画面空闲区域留下多个残影框,要配合检测区域的置信度一起看。n_init控制一条轨迹需要连续匹配多少帧才被标记为确认状态,飞鸟场景建议 2,因为运动碎片化严重,要求连续三帧都匹配上往往等不到。
3.4 最小跟踪配置示例
直接改 DeepSORT 参数的代码通常集中在配置文件里,我用的是基于 Python 的跟踪器封装,核心初始化参数如下:
from deep_sort import DeepSort deepsort = DeepSort( model_path="checkpoints/bird_feature_extractor.torch", max_dist=0.35, max_iou_distance=0.7, max_age=60, n_init=2, nn_budget=50, use_cuda=True )参数说明:max_dist对应上面的max_cos_dist,这里设 0.35 是因为飞鸟比较极端的飞行姿态会让同一个体的特征距离超过 0.3。max_iou_distance控制在轨迹预测框和当前检测框的 IoU 低于 0.7 时强制切断关联,飞鸟高速运动时这个值不能设太高,否则同一个目标不同帧的框交叠不足会被误判为两个目标。use_cuda在边缘设备上建议设成 False,把特征提取放到 CPU 上跑,避免 GPU 显存被检测和跟踪同时挤爆。我在某个嵌入式设备上就曾经因为这里没关 CUDA,跑了两分钟显存溢出直接重启进程。
这个配置在大多数飞鸟场景下都能跑出比较稳的轨迹。真正需要继续调的场景是鸟群非常密集且互相交错的视频,那种场景下所有外观特征都会失效,运动特征权重必须调高,几乎没有一劳永逸的办法。
4. 把 YOLOv5 和 DeepSORT 串成完整推理链路:工程封装与性能取舍
4.1 串联逻辑:置信度过滤是检测器和跟踪器之间的唯一接口
检测和跟踪串接的关键在于一个明确的接口契约:YOLOv5 输出的原始框中,只有置信度超过阈值的框才会进入 DeepSORT。这个阈值不是越低越好,也不是越高越好——设太低会把大量背景误检喂给跟踪器,跟踪器会误以为来了很多鸟而疯狂创建轨迹;设太高又会让远处的弱势检测被过滤掉,轨迹连续性变得非常差。我通常设置两个阈值:进入跟踪器的置信度门槛是 0.3,纯可视化展示的置信度门槛是 0.25。中间这 0.05 的差值是为了让已经在跟踪状态的轨迹能用其历史位置预测来“接住”那些置信度波动到 0.3 以下的框,当卡尔曼预测和下一帧检测框再次匹配时,轨迹不会断。
接口里另一个重要数据是特征向量。DeepSORT 要求每个进入的目标带一个外观特征,这个特征在目标首次被检测到、且轨迹未确认时提取一次,后续帧只有当级联匹配失败、需要重新做外观匹配时才重新提取。很多初学者的实现错误是每一帧都对所有检测框提取特征,耗时直接翻三倍,对跟踪效果却没有半点提升。
4.2 完整推理主循环代码骨架
下面这段代码是用 Python 实现的 YOLOv5 与 DeepSORT 的串联推理主循环,输入是本地视频文件,输出是带轨迹 ID 的标注视频:
import cv2 import torch from models.experimental import attempt_load from utils.general import non_max_suppression, scale_coords from deep_sort import DeepSort # 加载检测器 detector = attempt_load("weights/bird_yolov5m.pt", map_location="cuda") detector.eval() # 初始化跟踪器,参数来自上一章的配置 tracker = DeepSort(model_path="checkpoints/bird_feat.torch", max_dist=0.35, max_iou_distance=0.7, max_age=60, n_init=2, nn_budget=50, use_cuda=True) cap = cv2.VideoCapture("input_bird_clip.mp4") writer = cv2.VideoWriter("output_tracked.mp4", cv2.VideoWriter_fourcc(*"mp4v"), 25, (1920, 1080)) frame_idx = 0 while True: ret, frame = cap.read() if not ret: break # 每 3 帧做一次检测,中间帧直接沿用上一轮的轨迹预测 if frame_idx % 3 == 0: img = torch.from_numpy(frame).permute(2, 0, 1).float() / 255.0 img = img.unsqueeze(0).to("cuda") with torch.no_grad(): pred = detector(img)[0] detections = non_max_suppression(pred, conf_thres=0.3, iou_thres=0.5)[0] bboxes = detections[:, :4].cpu().numpy() scores = detections[:, 4].cpu().numpy() # 转换坐标格式:xyxy -> cxcywh,DeepSORT 需要中心点宽高 cxcywh = xyxy_to_cxcywh(bboxes) track_boxes, track_ids, track_scores = tracker.update(cxcywh, scores, frame) # 可视化 for box, track_id in zip(track_boxes, track_ids): x1, y1, x2, y2 = box.astype(int) cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, f"ID:{track_id}", (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) writer.write(frame) frame_idx += 1 cap.release() writer.release()逻辑说明:这段代码里最值得关注的是跳帧检测——每 3 帧才跑一次 YOLOv5,中间两帧直接用 DeepSORT 内部卡尔曼滤波的预测结果补上。这背后的原理是飞鸟虽然运动快,但 3 帧之间的位移通常不超过目标自身尺寸的一倍,卡尔曼预测足够顶住。这样做能把推理 FPS 提升将近一倍,因为检测器开销是跟踪器的 5 到 8 倍,跳帧检测省掉的算力远大于跟踪器增加的开销。跳帧数不是越大越好,我的经验是普通摄像头 30fps 流最大跳 4 帧,超过 4 帧飞鸟转弯时预测框会严重偏离实际位置,轨迹会从目标上脱落。
代码里xyxy_to_cxwh是一个自己写的坐标转换函数,YOLOv5 输出的是左上角和右下角坐标,DeepSORT 需要的是中心点坐标加宽高。这是一个经常被忽略的细节,很多报错和轨迹偏移都来自坐标系没对齐。还要注意输入帧的尺寸要和训练时一致,检测前要做 letterbox 缩放,但跟踪器拿到的是原图坐标,所以scale_coords必须把模型输出映射回原图。
4.3 视频流接入与处理性能取舍
真实落地场景通常不是本地视频文件,而是 RTSP 网络摄像头或无人机挂载相机回传的流。RTSP 流的处理里我最常踩的坑是丢帧导致时间戳混乱。DeepSORT 内部的时间轴依赖输入帧的顺序,网络流抖动或者解码丢帧会让帧间隔不一致,卡尔曼滤波的预测步长完全错乱。我一般会在读取端做一层缓冲队列,解码器输出的帧先按时间戳排好序再送入跟踪器,同时对连续丢帧做检测,如果发现帧间隔超过 200ms,就把所有轨迹的max_age临时调大,避免因为网络卡顿导致全部轨迹断裂重来。
性能取舍上最重要的是分辨率策略:飞鸟远距离检测需要 1280 分辨率输入,但跟踪器的特征提取和边框回归完全可以在 640 分辨率下运行。我会做两级处理——检测器的输入是 1280x1280 的缩放图,跟踪器拿到的则是一份 640 宽的下采样副本。这样做既能保住小目标召回,又能省下特征提取的算力。另外在目标数量超过 30 的密集鸟群场景,我建议给跟踪器加一个单帧处理上限,超过上限时只优先处理置信度最高的前 N 个检测框,否则帧率会断崖式下跌,系统实时性就没了。
5. 跑起来之后的避坑清单:飞鸟检测跟踪的 5 个常见问题排查方向
5.1 远距离飞鸟频繁漏检,画面里能看见但结果里没有
现象:同一只鸟在画面里明显可见,但检测框断断续续,轨迹长度只有实际飞行的三分之一。 原因:模型对 20 像素以下的小目标召回率不足,加上推理时的输入分辨率被降到了 640,小目标在特征图上直接消失。 解决:推理时把输入分辨率升到 1280 或 1600,或者用切块推理——把 1920x1080 的原始帧切成四个 960x540 的块分别检测,再把结果合并。切块推理在小目标场景里效果立竿见影,代价是推理时间翻倍。如果目标行为主要是远距离巡航,优先切块;如果是中近距离多目标跟踪,优先提分辨率,两者不要同时开,T4 级显卡会扛不住。
5.2 ID 跳变严重:画面里只有一只鸟在飞,ID 却换了七八次
现象:轨迹稳定跟踪数秒后突然丢失,几帧之后在相近位置重新出现一个全新 ID,视频里看起来像“鸟被复制了”。 原因:飞鸟扇动翅膀时检测框宽高比变化剧烈,导致外观特征向量在相邻帧之间跳跃过大,超过了max_cos_dist的阈值;同时卡尔曼预测基于匀速运动模型,对突然转向的鸟预测位置与实际位置严重偏离子,IoU 也匹配不上。 解决:把max_cos_dist放宽到 0.4,同时观察特征提取器的输入,确认是不是把背景也裁进特征图里了;再检查 n_init,确认轨迹是否因为连续两帧检测失败而被当作新目标重新确认。最关键的一步是开启特征库平滑,即用最近 N 帧的特征均值代替单帧特征去做余弦距离计算,能明显压低特征抖动。
5.3 鸟群密集时 FPS 断崖式下降,从 30 帧直接跌到 8 帧
现象:单只鸟或三五只鸟时系统流畅,一旦几十只鸟同时出现在画面里,帧率立刻崩了。 原因:DeepSORT 的级联匹配和数据关联复杂度与目标数量呈平方级增长,特征提取和位置匹配全都堆在主线程里串行执行。 解决:把跳帧检测从 3 帧改成 5 帧,高密度场景下检测框密集,5 帧内的运动预测准确率仍然可以接受;再把nn_budget从 50 降到 30,减少特征比对次数;最后给跟踪器加目标数量上限,超过上限时只保留置信度最高的前 40 个框参与关联。如果还不行,就把特征提取放到独立的子线程里,检测和特征提取并行执行,帧率能再回一半以上。
5.4 飞鸟飞出画面再飞回来,无法接回原来的轨迹 ID
现象:跟踪器在目标离开画面后把轨迹标记为结束,目标重新入画时给出的是一个全新 ID,导致后续统计“总共出现了多少只鸟”时数据偏大。 原因:max_age设得太短,目标离开画面超过该帧数后轨迹就被清除了;或者nn_budget设得太短,旧特征被新特征挤出特征库,外观匹配无参照。 解决:把max_age调到 100 帧左右,对应约 4 秒的离屏时间;nn_budget调到 100,并且设置特征库淘汰策略为“优先淘汰最久未匹配的特征”,而不是普通队列的先进先出。另外要在目标重新入画时做区域先验——目标离屏前的位置和重新出现的位置如果距离在目标自身尺寸的 5 倍以内,优先和旧轨迹做一次自由匹配,这个规则能挽回不少场景下的 ID 连续性。
5.5 置信度阈值调低后轨迹变长但 ID 跟着变乱
现象:为了接住更多小目标把conf_thres从 0.3 降到 0.15,漏检少了,但画面里各种残影和虚假轨迹多了一大堆,ID 乱到没法看。 原因:低置信度检测里混入了大量背景误检,DeepSORT 把这些误检接收为全新的目标,创建了大量伪造轨迹,并且误检的目标在下一帧消失,触发新轨迹创建和旧轨迹删除的乒乓效应。 解决:把进入跟踪器的置信度门槛分成两档——首次创建轨迹需要 0.3 以上的置信度,但已确认的轨迹在卡尔曼预测位置周围小范围内允许 0.15 以上的检测框做关联。这本质上是让跟踪器更信任老轨迹、更警惕新轨迹,对反射噪声多的场景是很好的平衡。还要配合 n_init=3 一起用,新轨迹确认变严格,噪声轨迹就没有生存空间了。
6. 上线前先做一段端到端轨迹评估,再带上两个顺手的小技巧
检测和跟踪这套系统最大的陷阱就是“看起来能跑”。我见过某开发者自信地把系统接到现场,结果运行日志里 ID Switch 平均每十秒就发生几十次,但回放录像时人的注意力根本跟不过来。所以正式上线前一定要做一次端到端轨迹评估,找一段 30 秒到 60 秒的实际场景视频,手动标注里面每只鸟的轨迹真值,然后用三个指标卡关:MOTA(多目标跟踪准确率)低于 50% 不上线,ID Switch 总量低于真值轨迹数量的 20% 才放行,以及单目标跟踪时长占比——这个指标是看每一条被确认的轨迹中,有多少比例的帧是连续正确关联的。我用一个小脚本统计出这些数字,格式化输出成表格,哪一项红了就直接去调对应的参数,不要猜。
两个在实际项目里很好用的小技巧:第一是场景自适应置信度。飞鸟视频里有大量纯色天空区域,这个区域里即使很低的置信度也基本是目标;而背景有树林纹理的区域,需要更高的置信度才可信。做法是把画面按网格划分,统计每个网格区域在过去 100 帧内的平均虚检率,动态调整该区域的检测置信度门槛。虚检率高的区域把门槛从 0.3 提到 0.45,虚检率低的区域降到 0.2,这个技巧能把漏检和虚检同时压低。
第二是轨迹输出后处理。DeepSORT 输出的轨迹坐标存在量化抖动,直接叠加在检测框上会显得很散。我习惯加一个轻量级的一维卡尔曼滤波器只对轨迹坐标做平滑,注意别改成纯滞后滤波,会有明显拖尾感。平滑后的轨迹在回放里看起来专业很多,用于生成热力图或者统计停留时间也更准。
这套方案做下来,我最大的教训是:检测器和跟踪器必须分开验收。我有一次为了把检测 mAP 从 0.32 刷到 0.38,牺牲了位置精度,结果跟踪的 ID Switch 暴增了三倍,整个系统反而更差了。从那以后我养成一个习惯——先固定跟踪器参数,把检测器调到帧间稳定;再固定检测器,调跟踪器解决 ID 连续性问题。两边交互式地调,永远不要同时动手。希望这篇整理能帮你把飞鸟视觉检测和跟踪落得稳一点,少踩一些人已经替你踩过的坑。
本文还有配套的精品资源,点击获取