做目标跟踪最怕什么?模型调好了,检测框也在跳,但每个框是谁根本没搞清——ID频繁切换、目标跟丢、前后帧对不上号。早几年想解决这个问题,要么上DeepSort,要么啃一堆关联算法的论文,代码写起来头都大。现在有YOLOv8配合ByteTrack,事情就变得非常直接:检测归检测,跟踪归跟踪,各管各的,跑起来只要几行代码,速度还快得感人。我在水下鱼群检测的项目里把这套组合用了很久,平均下来一杯咖啡的功夫就能搭完整个跟踪流程。这篇文章把你需要知道的都写清楚,从原理到代码,再附带一套鱼群场景下的调参经验,照着做基本不会翻车。
1. 为什么是YOLOv8+ByteTrack:跟踪问题的本质拆解
1.1 检测解决"是什么",跟踪解决"是谁"
先说清楚一个很多人混淆的概念:目标检测和目标跟踪根本是两件事。YOLOv8这类检测器,干的是"这一帧图像里有哪些目标、分别在什么位置";而跟踪要回答的问题是"上一帧的A目标,这一帧跑哪去了"。逐帧检测结果只是散落的框,没有身份信息,把帧与帧之间的目标关联起来,才是多目标跟踪的核心任务。
一个直观的生活化类比:检测就像每帧拍一张班级合影,你知道照片里坐着几个人;跟踪则需要你在录像里从头到尾锁定"穿红衣服那个同学"始终是他,不能因为镜头晃动或者他换了个座位,就把他当成另一个人。
所以一个完整的MOT(Multi-Object Tracking)系统,至少要包含检测器和关联算法两部分。检测器负责提供高质量的框,关联算法负责把这些框在时间维度上串起来。
1.2 ByteTrack的杀手锏:不丢弃低分框
ByteTrack这名字你可能听过,它来自字节跳动2021年的工作,论文名字就叫《ByteTrack: Multi-Object Tracking by Associating Every Detection Box》。理解它的核心思想只需要一句话:之前的跟踪算法把低置信度的检测框直接丢掉了,ByteTrack一个都不丢。
按常理,检测器输出的低分框大概率是背景或遮挡目标,丢掉似乎没问题。但ByteTrack的作者观察到一个关键现象:被遮挡的目标、快速移动的目标、体型很小的目标,检测分数天然偏低,而这些恰恰是跟踪最容易跟丢的对象。把它们一律删掉,等于主动放弃最难的样本。
ByteTrack的做法是分两步走:先用高分数框做一次匹配,处理那些清晰的、遮挡不严重的目标;然后用低分数框再做一次匹配,专门找回那些因为遮挡或运动模糊而得分偏低的目标。两次匹配结合,跟踪的鲁棒性一下就上来了。这个设计极度优雅,因为它没有增加任何训练开销,挂在任何检测器后面都能用。
1.3 为什么选YOLOv8而不选其他检测器
YOLOv8是Ultralytics出的检测框架,相比前代,它在结构上做了不少调整,比如换掉了anchor-based的设计,改成了anchor-free,意味着少了很多调anchor的超参数。当时我考虑过Faster R-CNN,精度确实稳,但帧率太难看,水下的实时监控场景完全吃不消。也考虑过YOLOv5,虽然生态成熟,但v8在训练收敛速度和部署灵活性上更有优势,特别是用hook来做自定义训练分析时,v8的接口设计更顺手。
选型本质是个权衡问题:精度要高、速度要快、二次开发要容易、社区要活跃。YOLOv8在这四项上都很均衡,尤其配合ByteTrack时,它的检测框质量高、漏检少,能把ByteTrack低分框关联的优势充分发挥出来。实测在水下鱼群场景,YOLOv8s的检测效果已经够用,推理速度在GTX 1660 Ti上还能跑到30fps以上,完全是实用级别。
2. 5分钟快速搭好跟踪环境:YOLOv8+ByteTrack实操
2.1 环境准备与安装避坑
既然标题说5分钟搞定,那环境这块必须顺畅。先说下我常用的组合:Python 3.9 + PyTorch 1.13 + CUDA 11.7,这套搭配在2024年的生态里依然非常稳定,踩坑最少。
安装核心依赖时,建议用国内镜像源,否则下到一半断掉会很闹心:
pip install ultralytics -i https://pypi.tuna.tsinghua.edu.cn/simple pip install byte_tracker如果装离线版或私有化环境,你只需要拿到YOLOv8的权重文件和ByteTrack的tracker代码,手动复制进项目里也能跑。ByteTrack官方仓库(github.com/ifzhang/ByteTrack)里提供了完整的tracker实现,核心就几个py文件:byte_tracker.py、matching.py、kalman_filter.py,你甚至可以只拷贝这几个文件到任意项目,只要接口对上就能用,零依赖负担。
2.2 最小可用代码:检测+跟踪+可视化
搭一个最小可跑的示例其实非常简单。下面这段代码,就是整套流程的骨架,我平时做原型验证就是用这段逻辑,只是在不同场景里换换参数:
import cv2 import torch from ultralytics import YOLO from byte_tracker import BYTETracker # 加载检测器权重 model = YOLO("yolov8s.pt") # 换成自己训练的鱼群模型即可 # 初始化ByteTrack tracker_args = { "track_thresh": 0.5, # 高分数阈值 "match_thresh": 0.8, # 匹配时的IoU阈值 "track_buffer": 30, # 跟踪丢失后保留帧数 "frame_rate": 30, # 输入帧率,影响卡尔曼预测 } tracker = BYTETracker(tracker_args, frame_rate=30) cap = cv2.VideoCapture("fish_video.mp4") while True: ret, frame = cap.read() if not ret: break # 1. YOLOv8检测 results = model(frame, verbose=False)[0] dets = results.boxes.data.cpu().numpy() # x1,y1,x2,y2,score,class # 2. 整理格式(ByteTrack只关心坐标和分数,不需要类别) online_targets = tracker.update(dets, (frame.shape[0], frame.shape[1]), (frame.shape[0], frame.shape[1])) # 3. 绘制跟踪结果 for t in online_targets: bbox = list(map(int, t.tlwh)) # top-left x, y, width, height track_id = t.track_id cv2.rectangle(frame, (bbox[0], bbox[1]), (bbox[0] + bbox[2], bbox[1] + bbox[3]), (0, 255, 0), 2) cv2.putText(frame, f"ID:{track_id}", (bbox[0], bbox[1] - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imshow("YOLOv8+ByteTrack MOT", frame) if cv2.waitKey(1) & 0xFF == ord("q"): break cap.release() cv2.destroyAllWindows()这段代码核心就三步:检测 -> 更新tracker -> 画框。你会发现跟踪结果里没有类别信息,只有track_id和框,因为ByteTrack本身是跟类别无关的,它只管"这个东西在上一帧出现过没有、这一帧跑到哪了"。
2.3 检测结果到跟踪输入的格式转换
上面代码最需要留神的地方,就是检测器输出到tracker输入的格式转换。YOLOv8的results.boxes.data返回的是[N, 6]的张量,每行是[x1, y1, x2, y2, conf, cls]。而ByteTrack的update方法接收的检测框数组要求是[N, 5](或[N, 6])的格式,但坐标必须是**[x1, y1, x2, y2, score]**,类别可带可不带。
如果你直接把YOLOv8的输出原样丢给tracker,肯定会报错或者行为异常。我调试的时候踩过这个坑,一开始没做切片转换,tracker拿到的数据维度不对,匹配逻辑全乱了。
安全写法是手动剥出坐标和置信度:
# dets[:, :4] 是左上角+右下角坐标,dets[:, 4] 是置信度 tracker_input = np.column_stack([dets[:, :4], dets[:, 4]])这里还有个小细节:ByteTrack用x1y1x2y2还是tlwh(top-left x, y, width, height)取决于具体版本。官方仓库里的STrack类内部会把tlwh作为标准状态,但update入口一般接收x1y1x2y2再做转换。稳妥的做法是看下你引入的源码,在byte_tracker.py里搜一下np.asarray(detections)附近,确认坐标定义,免得后面画框对不准。
3. 鱼群检测实战:这个场景难在哪
3.1 水下环境对检测和跟踪的双重考验
鱼群检测和普通的行人跟踪、车辆跟踪差别非常大,可以说是MOT领域的"地狱模式"。水下环境首先就把检测器搞得很崩溃:水质浑浊导致对比度低,光照随水深和天气剧烈变化,鱼体表面光滑反光,背景里还有密集的细小气泡,这些都会让检测器误报。
更麻烦的是鱼群的行为特性。鱼群是高度聚集的,目标之间频繁遮挡;鱼身细小、长宽比奇怪,有的鱼游速极快,相邻几帧之间的位移非常大,甚至有运动模糊;同一帧里可能有几十条甚至上百条鱼,每条鱼的特征又高度相似——对跟踪算法来说,这等于一群人穿着同样的衣服快速来回跑,还要分清谁是谁。
UFLD(水下鱼群检测数据集)这类公开数据集的标注难度也说明问题:很多鱼只有几个像素大小,即便人眼都很难确认身份,更别说让算法自动关联。
3.2 检测器训练策略:先让YOLOv8"看清鱼"
鱼群检测的跟踪效果好不好,70%取决于检测器够不够好。如果检测器本身频繁漏检、误检,后续的ByteTrack再聪明也没用——巧妇难为无米之炊。
在训练YOLOv8鱼群检测模型时,我总结了一套自己的策略:
- 数据增强是王道:水下视频的帧连续性很强,如果直接按帧抽图训练,模型会过拟合到特定背景上。我习惯做HSV颜色扰动、随机裁剪缩放和MixUp,模拟光照变化和密集场景。另外加上Mosaic增强(YOLOv8内置),让模型见到更多不同背景组合。
- 输入尺寸别贪大:鱼小的确需要分辨率,但分辨率拉高意味着显存和推理时间上涨。实验下来
imgsz=640和imgsz=1280在小鱼检测上的mAP差得很明显,但部署时帧率也被拉低。实际项目里建议先用640预训练,再在1280上做少量epoch的微调,得到一个"高精度验证版"和"快速部署版"灵活选择。 - 类别别分太细:如果鱼种间长得像,强行细分只会增加检测难度,导致跟踪时ID频繁跳变。业务上不需要识别品种的话,干脆统一当"fish"来检测,跟踪会稳定得多。
训练命令也不复杂,Ultralytics官方封装得很友好:
yolo detect train data=fish.yaml model=yolov8s.pt epochs=150 imgsz=640 batch=16 device=03.3 跟踪参数初始化:不同鱼种的初始配置
拿到训练好的检测模型后,ByteTrack的参数也需要针对鱼群特性做初始化。我常用的初始配置如下,然后根据实际效果微调:
| 参数 | 初始值 | 设定理由 |
|---|---|---|
| track_thresh | 0.25 | 鱼类得分天然偏低,太高会把小鱼全过滤掉 |
| match_thresh | 0.85 | 鱼群密集,严格匹配能减少错配 |
| track_buffer | 50 | 鱼被遮挡或游出画面时间较长,需要更长记忆 |
| frame_rate | 30 | 以实际视频帧率为准,如果源是慢速水下相机则调低 |
这套配置的核心思想就是:低阈值保召回,高IoU阈值防错配,长Buffer保身份。实际效果好不好,必须靠视频实测来检验,因为指标只能告诉你平均表现,真正的痛点都在细节里。
4. 调参秘籍:让鱼群跟踪不丢ID、不乱跳
4.1 从检测器的"置信度阈值"入手
好多人在调跟踪参数时,习惯直接改ByteTrack的阈值,改了发现没用。但ByteTrack吃的是检测器的输出,如果检测器本身输出的框就乱七八糟,跟踪怎么调都白搭。
鱼群场景里,检测器的置信度阈值有它特殊的地方。常见目标检测会把conf_thres设成0.5甚至更高,但鱼群不行——小鱼、半遮挡的鱼、快速游动导致运动模糊的鱼,检测分数普遍只有0.2到0.4。如果你设了0.5,这些鱼压根不会出现在检测结果里,ByteTrack再怎么会"找回低分框"也无计可施。
我的经验是:鱼群检测跑推理时,conf_thres设到0.1到0.2左右,iou_thres(NMS的IoU阈值)设到0.5到0.6。先把所有可能的鱼都框出来,让跟踪器去筛选。你可能会担心低分数框太多会造成大量误检,但别忘了,ByteTrack的关联逻辑天然能筛掉那些时间上不稳定的误检——真的目标有运动轨迹,会持续出现;假的框往往是孤立的,跟踪器很快就把它丢弃了。
在YOLOv8推理代码里这样控制:
results = model(frame, conf=0.15, iou=0.5, verbose=False)[0]4.2 ByteTrack核心阈值:track_thresh和match_thresh的道与术
ByteTrack最核心的两个参数是track_thresh和match_thresh,很多同学在这两个参数之间反复横跳,不知道到底调谁。
理解这两个参数,要回到字节原始论文背后的第一性原理。track_thresh是"高分数框"和"低分数框"的分界线:高于它的检测框进入第一阶段匹配,低于它的进入第二阶段匹配。所以它决定了算法对待"低分框"的态度——设得越低,进入高优先级匹配的框越多,跟踪对低分目标越敏感;设得越高,低分框越多,第二阶段匹配压力越大。
而match_thresh是关联阶段判断"两个框是否属于同一个人"的IoU阈值。鱼群场景,由于鱼小、密集,相邻帧同一目标位置变化也不大,IoU通常还是能保持一定的数值。但问题在于:鱼群靠太近时,A鱼的框和B鱼的框IoU也非常高,就容易张冠李戴。
我实际调参的经验数值是这样的:
- 鱼比较稀疏、个体大、运动慢:
track_thresh=0.4,match_thresh=0.8,效果不错。 - 鱼群密集、个体小、游动快:把
track_thresh降到0.2,保证小鱼、低分鱼也能进入跟踪;match_thresh升到0.9,宁可少匹配几个,也不要错配导致ID跳变。 - 水质特别浑浊、误检多:优先降
track_thresh到0.1,但同步把match_thresh升到0.95,这样能最大限度压制误检框干扰跟踪。
4.3 使用ByteTrack官方指标:怎么评估跟踪效果
调了半天参数,总得有个标准来衡量效果对不对。多目标跟踪的评估指标跟检测不一样,检测看mAP,跟踪还得看ID相关的指标。
常用的几个指标:
- MOTA(Multiple Object Tracking Accuracy):综合衡量误检、漏检和ID切换的指标,越高越好。MOTA = 1 - (False Positives + False Negatives + ID Switches) / Ground Truth Objects。
- IDF1:评价身份保持能力的F1分数,越高说明ID切换越少。鱼群场景里这个指标尤其重要,因为你的核心需求是"每条鱼的身份不搞混"。
- ID Switch (IDs):总切换次数,越低越好。这个指标最直观,调参时直接看视频里ID有没有乱跳就行。
如果想算这些指标,推荐用TrackEval库配合GT标注集。鱼群数据集的GT标注比检测集麻烦许多:光给每条鱼画框还不够,还得在每一帧里保持同一个ID号。实际操作中,我会用半自动标注先跑一版检测结果,再手工修正ID关联,将工作量降到最低。
调参时不用把这些指标全盯死,我一般只看两个数:MOTA和IDs。MOTA太低说明整体跟踪质量不行;IDs太多说明身份搞混严重。两者还要结合着看,因为有可能MOTA挺高但IDs也高——说明系统经常把鱼跟丢又重新找回,虽然框没怎么漏,但身份已经乱了。
4.4 运动信息加进来:用卡尔曼滤波预测鱼的位置
ByteTrack内置了卡尔曼滤波来做运动预测,这个模块对鱼群场景非常重要。卡尔曼滤波的本质就是一个"懂物理常识"的平滑器:它根据目标上一帧的位置、速度,预测这一帧大概在什么位置,然后在预测位置附近寻找真实检测框。
鱼的运动虽然在快速游动中看起来乱,但相邻两帧(间隔仅几十毫秒)内的运动轨迹是平滑的、有惯性的。卡尔曼滤波利用这种时间连续性,可以解决很多检测器遗漏的问题:某帧检测器漏检了一条鱼,但只要上一帧的位置和速度已知,卡尔曼滤波能估计出一个比较靠谱的预测框,ByteTrack就可以拿这个预测框维持跟踪,等下一帧检测器重新检测到它。
调卡尔曼滤波相关参数,主要是track_buffer这个跟生命周期有关的参数。track_buffer代表目标丢失多少帧之后才宣告死亡(移除track)。鱼被遮挡常见于大鱼游过小鱼,或鱼群扎堆时,被遮挡时间可能长达几帧到十几帧,这时候如果把track_buffer设成默认的30帧(在30fps下约为1秒),对小目标的持久跟踪非常有利。
不过track_buffer也不是越大越好。设太大,那些已经游出画面、永远回不来的鱼会长时间占用track资源,导致计算量变大,还可能跟新出现的鱼误匹配,造成ID混乱。鱼群密集场景,我建议track_buffer在30到50之间调节。
5. 鱼群场景踩坑实录与常用问题排查
5.1 ID反复横跳怎么破
ID Switch(ID切换)是鱼群跟踪里最让人抓狂的问题。前几帧还好好标着ID=5的鱼,下一秒变成ID=9了。原因不外乎两个:第一,检测器中间漏检了这条鱼,导致跟踪链断了,等鱼再次被检测出来时,tracker认定它是新目标,分配了新ID;第二,两条鱼靠太近,IoU重叠过高,匹配时B鱼把A鱼的轨迹抢走了。
排查这个问题,我会先看检测器的漏检情况。把检测结果可视化出来,判断"中间几帧鱼到底有没有被检测到"。如果确实漏检,问题在检测器,需要降低conf或增强模型训练;如果检测框一直在,但ID还是切换,那问题在跟踪关联,需要调高match_thresh。
还有一个容易被忽略的原因:画面里的鱼如果太小,检测框只有二三十个像素,那么框位置的轻微抖动,都可能导致前后帧IoU低于阈值。这在调match_thresh时要注意,设得太高(比如0.95以上),小鱼反而更容易丢。
5.2 把背景误检成正样本,该怎么压制
水下场景的误检来源多种多样:漂浮的杂质、水草摆动、气泡聚集,甚至光照变化产生的阴影,都可能被YOLOv8误认为鱼。这类误检经过ByteTrack后,因为它们在连续帧中位置相对固定,还带着看似合理的"运动特征",偶尔会形成非常顽固的假轨迹。
我处理这个问题的顺序,是先检查检测器的类别区分度。如果鱼的形态和背景差异大,建议在网络输出层面增加背景类别训练样本,或者人工标注一批"难负样本"(全是气泡、杂质的帧),放进训练集,让模型记住这些东西不是鱼。
如果训练环节没法改动,只能在推理端想办法。一种有效做法是给跟踪结果加一个"置信度积分器":一条轨迹连续N帧都有高分检测支撑,才正式对外显示;如果只是偶尔出现一次,就过滤掉。这个后续处理在工程上很有效,比反复调阈值更可靠。
5.3 小鱼目标检测不到,有什么补救办法
小鱼检测不到,不可能只靠调跟踪参数解决。毕竟跟踪是对检测结果的二次加工,输入就没有,输出自然没有。
我对小鱼场景有这几个实战经验:
提升输入分辨率:用
imgsz=1280,或者干脆把画面切块,比如把一个1080p画面切成左上、右上、左下、右下四块,每块放大检测,检测完再映射回原坐标。这个方法能明显提升小目标召回,但推理时间也成倍增加,需要根据场景取舍。利用帧间信息做超分或插帧:对视频流做简单的光流配准,把多帧图像融合成一张高信噪比的图再检测。水下场景光线差、噪声大,这个方法对提升小鱼置信度很有效,但工程复杂度偏高,不适合快速原型。
换个更大的YOLOv8模型:从
yolov8n换成yolov8m会有效果,但不至于爆炸式提升。鱼群场景里的收益主要来自训练数据和数据增强,而不是单纯堆模型参数量。
5.4 跟踪结果不稳定,画面抖动怎么办
这里的抖动,是指跟踪框在视频里"跳来跳去",明明目标没怎么动,框却忽大忽小、忽左忽右。多半是检测框本身不稳定:鱼游动时姿态变化,导致检测框的宽高比变动剧烈。
一种做法是在跟踪器输出后再加一个平滑后处理,比如EMA(指数移动平均)或Savitzky-Golay滤波器。实际操作中,我给跟踪框的坐标做一阶低通滤波,效果立竿见影:
smoothed_xy = alpha * current_xy + (1 - alpha) * previous_xyalpha取0.3到0.5之间比较合适。如果取太小,框的反应太慢,实时性变差;取太大,平滑效果又不明显。
另一种更根本的方案,是让卡尔曼滤波多干活。ByteTrack内部有卡尔曼滤波的状态更新,可以通过调高过程噪声协方差,让滤波输出更平滑,但这也意味着位置响应变慢。对于快速游动的鱼,"平滑"和"灵敏"永远是一对矛盾,只能看你业务更偏向哪一头。
5.5 鱼群检测调参速查表
最后整理一张速查表,把我平时最常用到的参数调整思路放这里,方便你直接对照:
| 问题表现 | 优先调整点 | 调整方向 |
|---|---|---|
| ID切换频繁 | match_thresh | 提高(0.8 -> 0.9) |
| 目标频繁丢失 | track_thresh | 降低(0.4 -> 0.2) |
| 误检产生假轨迹 | match_thresh + track_thresh | 同时提高,严格匹配 |
| 小鱼检测不到 | 检测器conf / 输入分辨率 | 降低conf / 提高imgsz |
| 遮挡后跟不回 | track_buffer | 提高(30 -> 50) |
| 跟踪框抖动 | 后处理平滑 | 引入EMA或降低alpha |
| 低分框干扰严重 | NMS的iou_thres | 降低(0.6 -> 0.5) |
这张表可以当成调参的基本原则。实际项目中参数都不是孤立的,改一个经常会连带影响另一个,建议一次只调一个变量,然后观察视频效果,别急着同时改好几个参数,不然出了问题根本不知道哪步改坏的。
6. 更进一步的扩展方向
6.1 用TensorRT加速部署到边缘设备
跟踪效果稳定后,接下来面对的往往是算力问题。鱼群监控经常要在水下相机旁边的边缘设备上运行,比如RK3588这类国产开发板,算力比PC弱不少,YOLOv8s推理可能都显得吃力。
这个时候就要用TensorRT加速了。流程大概是:把YOLOv8的PyTorch权重导出为ONNX,再用TensorRT的trtexec工具生成engine文件,推理时加载engine跑。C++部署时,TensorRT 8.6配合YOLOv8的常见解法是把预处理(resize、归一化)和NMS都写在自定义CUDA代码里,减少host-device数据拷贝。
这个环节要留意的问题主要是版本匹配:TensorRT的版本对ONNX算子支持度不同,YOLOv8用到的某些算子(比如SiLU激活)在老版本TensorRT里可能被替换成性能较差的实现。我通常会在导出ONNX时开启opset=12或更高,并且保存后用onnxruntime先验证一遍精度一致性,再进TensorRT。
6.2 画损失函数曲线,用hook调训练过程
YOLOv8在训练过程中会输出日志,但如果想细粒度监控训练状态,Ultralytics也预留了hook机制。通过钩子函数可以方便地拿到每一层的梯度、激活值分布,从而分析训练是否收敛。
鱼群检测场景里,我一般关注两个损失曲线:cls_loss和box_loss。如果cls_loss降得很低但box_loss持续震荡,大概率是数据标注框不够准——鱼群边界模糊,标注本身波动大。这时候再怎么调训练参数效果都有限,最该做的是回头把标注质量提上去。
画损失函数曲线其实很简洁,可以用ultralytics训练时自动生成的results.png,也可以把训练日志里的loss数据导出后用matplotlib画。我在分析时还会同时对比训练集和验证集的loss差距,判断是否过拟合。鱼群场景数据量通常不大,过拟合很常见,这时可以通过调低epochs或增加数据增强来缓解。
6.3 从MOT到更高级分析:航线、密度与行为
跟踪只是第一步,跟踪结果的价值在于下游分析。拿到每条鱼的轨迹后,可以做很多事情:轨迹聚类看鱼群洄游路线;按时间统计某个区域的鱼群密度变化;分析个体游速、转向频率,辅助研究鱼类行为。
举个例子,用鱼群的轨迹数据做热力图,能直观看到鱼群在养殖网箱里的分布是否均匀。如果某个角落永远没有鱼过去,说明那里的水质或饲料投放可能有问题。这些应用让MOT系统从"看得见"提升到了"看得懂"的层次。
7. 个人实操心得
做了多个水下鱼群项目之后,我对YOLOv8+ByteTrack这套组合有几点比较深的体会。第一个是:跟踪效果的天花板,由检测器决定;ByteTrack再强,也只是把检测结果的潜力充分释放出来。所以做多目标跟踪项目,花大力气在检测端永远是值得的。第二个是:调参之前先可视化,把检测框和跟踪结果同时画出来看几段视频,比盯着指标数字调更高效。指标能告诉你好不好,但只有视频能告诉你到底哪里出了问题。
还有一个小技巧:录制测试视频时,尽量多录几个不同时段、不同光照条件的片段。鱼群项目里,晴天正午和阴天傍晚的水下画面差别极大,只用一段视频调参往往会在另一段视频上翻车。多场景测试,才能让参数真正具备鲁棒性。