☰
多目标跟踪评估指标详解:MOTA、IDF1与MOTP的MATLAB计算实践
2026/10/13 19:56:28 网站建设 项目流程

目标跟踪做爽了,最怕别人问一句:“效果怎么样?”自己看视频觉得挺好,框一直咬着目标没丢,可换到另一段视频,同一个算法就崩了。真正能说服人的不是几段挑出来的片段,而是一组能复现、能横向对比的评估指标。这篇想做的事很直接:把目标跟踪里常见的评估指标一个个掰开,用最朴素的思路讲清楚它们到底在算什么,为什么MOTA和IDF1总被放在一起提,MOTP又跟它们是什么关系,最后附一份可以直接跑的MATLAB教学代码,把从逐帧匹配到指标统计的完整链路走一遍。适合刚接触跟踪、想读懂论文里指标表,或者写完算法想先自测一遍的同学。

1. 为什么“跟住没跟住”很难一句话说清:指标体系的分层逻辑

1.1 从检测评估到跟踪评估,多出来的那根弦

如果只做单帧检测,评估很简单:把预测框和真值框做匹配,算准召率就行。但目标跟踪多了一个维度——时间。跟踪器不仅要回答“这一帧框在哪”,还要回答“这个框是哪一个目标”。同样是丢了一个目标:如果只是中间几帧没框出来,后面又出现在同一个ID下,可以接受;如果ID换了、目标编号跳了,哪怕框的位置全部正确,用户看视频也会直接说“这跟踪器不行”。

这个差异是理解所有跟踪指标的前提。我第一次接触这些指标时犯过一个错:拿检测的mAP逻辑去套跟踪,结果完全对不上。后来想通了,跟踪评估等于两本账:一本管定位,一本管身份。每个指标其实都在回答其中一个或两个问题。

1.2 指标到底分几类:定位精度、身份保持、轨迹完整、统计效率

我把常见指标分成四类,这样后面看任何指标表都不会慌:

关心的问题常见指标通俗含义
框贴得准不准MOTP、中心误差、成功率单帧空间误差
同一目标身份有没有变IDF1、IDFP/IDFN、IDSW跨帧身份一致性
一条轨迹完整吗MT/ML/PT、FM、轨迹生命周期目标级连续覆盖
系统整体犯错多不多MOTA、FAF、处理速度全局错误率

检测评估只看第一行,跟踪评估必须四行一起看。后面每一章就按这几类展开,看完你会发现,论文里一张指标表其实是在讲一个完整的故事。

2. MOTA:把错误记进一本账

2.1 公式拆解与直觉理解

MOTA的全称是Multiple Object Tracking Accuracy,翻译过来是“多目标跟踪准确率”。公式长这样:

MOTA = 1 - (FN + FP + IDSW) / GT总帧数

三个错误项:

  • FN:真值目标存在,但跟踪器没给出框,叫漏跟。
  • FP:跟踪器给出一个框,但那里并没有真值目标,叫误报或虚警。
  • IDSW:同一个真值目标,跟踪框对应的ID中途换了,叫身份切换。

GT总帧数是指所有真值轨迹按帧累加的总数。比如有三条目标轨迹,每条持续100帧,GT总帧数就是300,不管中间有没有重叠。

这个指标特别像出纳对账。一天应该记300笔账,漏记了50笔是FN,乱记了5笔不存在的账是FP,还有1笔把A客户的账记到B客户头上是IDSW。最终准确率就是1减去差错占比。MOTA越高,说明系统整体越接近一本干净的账。

我看到不少初学者把MOTA当成“跟踪精度”,这是误区。它不衡量框贴得准不准,只衡量错误数量,和定位精度是两个维度。一个MOTA很高但MOTP很烂的系统完全可能存在——框的位置误差很大,但只要匹配上了就不算错。

2.2 MOTA的三个隐藏特性

MOTA有一个很多人没意识到的特性:它可以是负数。当FP特别多时,总分直接跌破零。这在工程里很常见:检测阈值调太低,跟踪器疯狂输出虚警框,MOTA会从0.8掉到-1甚至更低。看到负的MOTA不要以为是程序写错了,那是在告诉你“错误比正确还多”。

第二个特性是MOTA对身份问题很迟钝。IDSW虽然被算进分子,但权重和一个普通漏跟一样,都是“错一次”。而实际体验中,ID切换造成的观感伤害远大于一次漏跟。举个例子:两个目标交错,跟踪器给它们互换了ID,只产生一次IDSW,MOTA几乎不掉。但如果每帧都彻底跟丢,FN会急剧累积,MOTA立刻崩。也就是说,一个能掩盖ID乱跳的系统,MOTA可能还很好看。

第三个特性是MOTA高度依赖检测器质量。跟踪器再强,如果上游检测器每帧都漏一半,FN暴涨,跟踪算法没有任何操作空间。所以业内比跟踪器时,都会固定同一个检测器输出,单独对比跟踪模块;如果两套系统的检测器不同,MOTA的差距就不能全算在跟踪头上。看论文指标表时,遇到“不同检测器+不同跟踪器”的对比,要多留个心眼。

3. MOTP:框贴得有多紧

3.1 MOTP的定义与计算口径

MOTP全称Multiple Object Tracking Precision,看着名字带“Precision”,但它不是检测里的准确率,而是“匹配成功帧上的平均定位误差”。最常见的计算口径是IoU均值:

MOTP = 所有成功匹配的帧上,跟踪框与真值框IoU的平均值

有些实现用中心点欧氏距离,有些用框对角线归一化距离,结果形式不太一样,但本质都是同一件事:匹配上了之后,框到底贴不贴。

MOTP只统计匹配成功的配对,漏跟和误报的帧不参与计算。这个细节很关键,等于说MOTP衡量的是“在有真值匹配的前提下,位置有多准”,而不是“所有帧的位置有多准”。

生活化类比:MOTA管有没有寄错快递,MOTP管每个快递包装有没有变形。一个快递公司可以极少寄错地址,但所有包裹都压得瘪瘪的;也可以每个包裹都完好,但隔三差五寄错地方。两个维度互不替代。

3.2 MOTP与MOTA的配合阅读方法

单看MOTP高不高没有意义,要和MOTA放一起看。我总结过几种组合:

组合可能的解释
MOTA高,MOTP高跟踪全面且贴得准,理想状态
MOTA高,MOTP低错误少但位置偏,可能用了大框覆盖策略
MOTA低,MOTP高拿得准的框都很准,但漏顶太严重,检测器阈值可能设太高
MOTA低,MOTP低检测和跟踪同时失效,需要回到检测端排查

我调试某段夜间场景时遇到过一种情况:MOTA上去了,MOTP却掉了。后来发现是跟踪器学会了在目标周围画一个比真值大一圈的框,框虽然永远盖住目标,但IoU很低。检测器没问题、匹配也没错,纯粹是“用大框换覆盖”的策略性作弊。这类靠调框尺寸刷MOTA的问题,正好能被MOTP暴露出来。

4. IDF1:换了个ID,分数拦腰斩

4.1 为什么MOTA无法表达“身份病毒”

MOTA对ID问题太宽容,于是IDF1出现了。IDF1全称ID F1 Score,核心思想是:全局来看,跟踪器的ID和真值ID能否一一对应起来。

想象你雇了三个临时工处理三堆文件,MOTA考核的是“今天有没有文件处理错、漏、乱”,哪怕三个临时工互相换岗,只要每堆文件都有人碰过,MOTA都可能接近满分。但IDF1考核的是“每堆文件从头到尾是不是同一个人负责”,中途换一个人,这堆文件的完成质量就要打折。

目标跟踪应用里,ID一致性的重要性取决于场景。自动驾驶可能更关心框的位置,因为刹车决策主要看空间;但视频监控里要统计一个人进了哪个区域、停留多久,ID一旦乱跳,统计全废。所以监控类项目几乎必看IDF1。

4.2 IDF1的计算思路

IDF1的公式特别像F1-score:

IDF1 = 2 * IDTP / (2 * IDTP + IDFN + IDFP)

其中:

  • IDTP:在全局最优ID映射下,跟踪框与真值框正确配对的帧数。
  • IDFN:真值总帧数减去IDTP,即“这个真值ID没有被任何跟踪ID覆盖”的帧数。
  • IDFP:跟踪总帧数减去IDTP,即“这个跟踪ID没有对应任何真值ID”的帧数。

关键在“全局最优ID映射”。算法会统计每条跟踪轨迹和每条真值轨迹之间成功匹配的帧数,组成一个矩阵,然后用二分图最大权重匹配找出最优ID对应关系。选出来的每一对“跟踪ID-真值ID”就是记账关系,整个过程中ID只有在这一步做一次分配,之后不再变。

MOTA是逐帧算错误的账,IDF1是先把ID对认好,再算覆盖率的账。两种思路完全不同,所以同一套结果,MOTA和IDF1经常差距很大。

4.3 一个极简例子:ID互换会发生什么

假设两个目标各出现100帧,跟踪器全程跟住了,位置也准,但第50帧时把两个目标的ID互换,然后一直错下去。

  • MOTA这边:TP=200,FN=0,FP=0,IDSW=1,GT总帧数=200,MOTA = 1 - 1/200 = 0.995。几乎满分。
  • IDF1这边:两条跟踪轨迹和两条真值轨迹的总匹配帧数都是200,但全局最优映射只能把每个跟踪ID对应到一个真值ID,错位后每个ID对真正对应的帧数只有100,IDTP=200?不对,这里要仔细算:如果你把track1映射gt2、track2映射gt1,它们各有100帧匹配,IDTP=200,而总帧数也是200,IDF1反而接近1。这是我上面说法不准确,需要修正:单纯“两条轨迹互换且不重叠”的互换,IDF1全局匹配后反而看不出问题。

真正让IDF1暴露问题的,是同一帧里两个目标同时存在,track1和track2在第50帧发生交换之后,每条track和每个真值重叠的帧数都是“一半半”。全局映射会选总权重最大的方案,无论选哪种组合,IDTP都只有约100帧,因为两条track各自只能映射到一个真值ID,而另一个真值ID的剩余帧就变成IDFN,trackID对应的剩余帧变成IDFP。于是IDF1大幅下降。

想得更极端一点:如果跟踪器给同一个目标起了三个ID,每个ID跟一段,三个ID合并起来覆盖了全部帧。MOTA依然好看,但IDF1会因为IDTP只有三分之一左右的帧数而掉一大截。这个特性让IDF1成为识别“ID故意调皮”的关键指标。

5. 轨迹层面指标:MT/ML/PT、FM、FAF到底在说什么

5.1 MT/ML/PT:目标这一生过得好不好

MT/ML/PT是一组按轨迹完整度统计的指标,它们不看单帧,而是看每个真值目标的“一生”。

  • MT(Mostly Tracked):目标被跟踪器成功匹配的帧数占该目标总帧数≥80%。
  • ML(Mostly Lost):匹配帧数≤20%。
  • PT(Partially Tracked):中间状态。

可以理解成班级出勤率:一个学生一学期上满80%的课算基本全勤,只来两三节课算旷课大户,其余是中间状态。它会输出三个数值,比如MT=3、ML=1、PT=2,意思是3个目标基本被全程跟住,1个目标几乎全程丢失,2个目标时断时续。

这个指标对“目标群体”特别有价值。MOTA是全局视角,不区分是哪一个目标在拖后腿,而MT/ML/PT能告诉你问题集中在少数难目标还是普遍存在。比如追踪密集人群时,MOTA可能不算差,但ML很高,这说明确实有一批目标从头到尾没跟住,那就要检查是不是检测漏检严重。

5.2 FM轨迹碎片化和FAF虚假报警帧

FM全称Fragmentations,指轨迹碎片化次数。一条GT轨迹对应的跟踪ID中途断裂、恢复后又换了一个新ID,每断一次就计一次碎片。FM高说明跟踪器不够稳定,老是跟一跟停一停。

FAF全称False Alarms per Frame,平均每帧的虚假报警框数,计算方式就是FP总数除以总帧数。这个指标在自动驾驶里很常用,因为虚警框会让车辆做出不必要的刹车或变道决策,哪怕MOTA看起来还行,FAF一旦高,实车体验会非常差。

5.3 按场景选择指标组合

不同场景对指标侧重完全不同。给一个我实际配指标的经验表:

场景主指标辅指标理由
自动驾驶感知MOTA、MOTP、FAF处理速度关心覆盖和位置精度,虚警成本极高
视频监控分析IDF1、IDSW、MTMOTA身份保持比微小的位置误差更重要
学术论文对比MOTA、IDF1、MOTPMT/ML、FM全面报,让读者自己判断
单目标跟踪评估成功率、中心定位误差鲁棒性多目标那套指标不太适配

别指望一个指标能打天下。选主指标时要先问自己:这个项目的应用方,最不能接受哪种错误?是漏跟、虚警,还是身份乱跳?答案决定了指标组合的方向。

6. MATLAB代码:从逐帧关联到指标统计的完整演示

6.1 模拟数据生成:把“好坏结果”都造出来

直接拿真实数据测指标,出了问题很难排查,因为不知道哪个环节错了。我习惯先造一份“答案已知”的模拟数据,把各种情况都塞进去,指标算完能对着数,再跑真实数据就有底了。

下面这套模拟数据包含三条真值轨迹、四段跟踪轨迹和若干虚假框,特意安排了:一段漏跟、一次ID切换、几个FP。所有指标都会有非零输出,方便核对。

%% 模拟数据生成 rng(0); % 固定随机种子,保证可复现 F = 100; % 总帧数 gtList = []; trList = []; % 三条真值轨迹:持续100帧,移动方式不同 for f = 1:F % 目标1:向右平移 gtList = [gtList; f, 1, 20 + 0.3*f, 40, 20, 20]; % 目标2:向下平移 gtList = [gtList; f, 2, 30, 20 + 0.3*f, 20, 20]; % 目标3:水平方向上正弦摆动 gtList = [gtList; f, 3, 80, 50 + 8*sin(f/6), 20, 20]; end % 跟踪轨迹 for f = 1:F % 目标1对应的跟踪轨迹:全程跟随,加少量高斯噪声 trList = [trList; f, 101, 20 + 0.3*f + randn*2, 40 + randn*2, 20, 20]; % 目标2对应的跟踪轨迹:只跟到第70帧,之后完全丢失 if f <= 70 trList = [trList; f, 102, 30 + randn*2, 20 + 0.3*f + randn*2, 20, 20]; end % 目标3对应的跟踪轨迹:第20到25帧漏跟,第61帧开始换ID if f <= 60 && (f < 20 || f > 25) trList = [trList; f, 103, 80 + randn*2, 50 + 8*sin(f/6) + randn*2, 20, 20]; end if f >= 61 trList = [trList; f, 104, 80 + randn*2, 50 + 8*sin(f/6) + randn*2, 20, 20]; end end % 额外注入5个孤立的虚假框,模拟误报FP for f = [10, 30, 50, 70, 90] trList = [trList; f, 999, 5 + randn*2, 5 + randn*2, 20, 20]; end gt = gtList; tr = trList; fprintf('GT总帧数: %d\n', size(gt,1)); fprintf('Track总帧数: %d\n', size(tr,1));

数据里GT总帧数应该是300帧,Track总帧数看随机噪声影响不大,就是100+70+42+40+5=257。这三个数字后面都会用到。

6.2 逐帧匹配与TP/FP/FN/IDSW统计

逐帧匹配的套路是:每帧先算所有GT框和所有Track框的IoU矩阵,然后用贪心策略按IoU从大到小配对,直到低于阈值。这个做法不是全局最优,真正的评测工具会用什么算法这里先不展开,教学演示足够。

匹配完成后,规则很简单:配对成功的是TP;GT没配上的是FN;Track没配上的是FP。跟踪模块额外要维护一个信息:每个真值ID最近一次匹配到的是哪个Track ID,如果上一次匹配的和这次不一样,就计一次IDSW。

%% 逐帧关联与统计 iouThreshold = 0.3; % IoU匹配阈值 gtIds = unique(gt(:,2)); trIds = unique(tr(:,2)); numGt = length(gtIds); numTr = length(trIds); % 用Map加快ID到索引的转换 gtMap = containers.Map(gtIds, 1:numGt); trMap = containers.Map(trIds, 1:numTr); TP = 0; FP = 0; FN = 0; IDSW = 0; FM = 0; iouSum = 0; iouCnt = 0; idMatchCount = zeros(numGt, numTr); % IDF1用:统计每个gt-track组合的总匹配帧数 matchCntPerGt = zeros(numGt, 1); % MT/ML/PT用 gtTotalCnt = zeros(numGt, 1); % 每个gt总帧数 lastTrackOfGt = zeros(numGt, 1); % 最近一次匹配的track id,用于IDSW判断 prevTrackOfGt = zeros(numGt, 1); % 上一帧每个gt匹配的track id,用于FM判断 for f = 1:F gtIdx = find(gt(:,1) == f); trIdx = find(tr(:,1) == f); % 累计每个gt的出现帧数 for i = 1:length(gtIdx) gi = gtMap(gt(gtIdx(i),2)); gtTotalCnt(gi) = gtTotalCnt(gi) + 1; end if isempty(gtIdx) || isempty(trIdx) FN = FN + length(gtIdx); FP = FP + length(trIdx); prevTrackOfGt = zeros(numGt, 1); continue; end % 计算IoU矩阵 nGt = length(gtIdx); nTr = length(trIdx); iouMat = zeros(nGt, nTr); for i = 1:nGt for j = 1:nTr iouMat(i,j) = computeIoU(gt(gtIdx(i),3:6), tr(trIdx(j),3:6)); end end % 贪心匹配:每次取IoU最大的配对 cost = iouMat; pairs = []; while true [maxVal, linIdx] = max(cost(:)); if maxVal < iouThreshold break; end [r, c] = ind2sub(size(cost), linIdx); pairs = [pairs; r, c]; cost(r, :) = 0; cost(:, c) = 0; end matchedGt = false(nGt, 1); matchedTr = false(nTr, 1); curTrackOfGt = zeros(numGt, 1); % 当前帧每个gt匹配到的track id for p = 1:size(pairs, 1) i = pairs(p,1); j = pairs(p,2); gi = gtMap(gt(gtIdx(i),2)); tj = trMap(tr(trIdx(j),2)); trackId = tr(trIdx(j),2); % ID switch:同一个gt,这次匹配的track和上次不同,且上次非0 if lastTrackOfGt(gi) ~= 0 && lastTrackOfGt(gi) ~= trackId IDSW = IDSW + 1; end lastTrackOfGt(gi) = trackId; curTrackOfGt(gi) = trackId; TP = TP + 1; matchCntPerGt(gi) = matchCntPerGt(gi) + 1; idMatchCount(gi, tj) = idMatchCount(gi, tj) + 1; iouSum = iouSum + iouMat(i,j); iouCnt = iouCnt + 1; matchedGt(i) = true; matchedTr(j) = true; end FN = FN + sum(~matchedGt); FP = FP + sum(~matchedTr); % FM统计:当前帧有匹配,上一帧没有匹配或匹配的track不同,都算一次碎片 for gi = 1:numGt if curTrackOfGt(gi) ~= 0 if prevTrackOfGt(gi) == 0 && f > 1 FM = FM + 1; % 从无到有,一次恢复 elseif prevTrackOfGt(gi) ~= 0 && prevTrackOfGt(gi) ~= curTrackOfGt(gi) FM = FM + 1; % ID换了,也算一次碎片 end end end prevTrackOfGt = curTrackOfGt; end

这里有两个小问题需要说明。第一,第1帧的首次匹配我没有算进FM,因为轨迹刚出现不算“碎片化”;第二,ID切换同时会被FM记一次,所以FM数值里包含了IDSW造成的断裂,如果你想单独看“跟丢后恢复”造成的碎片,可以把第二个分支删掉。

6.3 计算MOTA/MOTP/IDF1/MT/ML/FM并输出

匹配统计完成后,剩下的就是套公式。

%% 计算MOTA / MOTP gtTotalFrames = size(gt, 1); trTotalFrames = size(tr, 1); MOTA = 1 - (FN + FP + IDSW) / gtTotalFrames; if iouCnt > 0 MOTP = iouSum / iouCnt; else MOTP = 0; end %% 计算IDF1:用贪心近似求解全局最优ID映射 idPairs = []; tmp = idMatchCount; while true [maxVal, linIdx] = max(tmp(:)); if maxVal <= 0 break; end [r, c] = ind2sub(size(tmp), linIdx); idPairs = [idPairs; r, c]; tmp(r, :) = 0; tmp(:, c) = 0; end IDTP = 0; for p = 1:size(idPairs, 1) IDTP = IDTP + idMatchCount(idPairs(p,1), idPairs(p,2)); end IDFN = gtTotalFrames - IDTP; IDFP = trTotalFrames - IDTP; IDF1 = 2 * IDTP / (2 * IDTP + IDFN + IDFP); %% 计算MT/ML/PT mt = 0; ml = 0; pt = 0; for gi = 1:numGt ratio = matchCntPerGt(gi) / gtTotalCnt(gi); if ratio >= 0.8 mt = mt + 1; elseif ratio <= 0.2 ml = ml + 1; else pt = pt + 1; end end %% 输出 fprintf('TP: %d, FN: %d, FP: %d, IDSW: %d, FM: %d\n', TP, FN, FP, IDSW, FM); fprintf('MOTA: %.3f\n', MOTA); fprintf('MOTP: %.3f\n', MOTP); fprintf('IDF1: %.3f (IDTP=%d, IDFN=%d, IDFP=%d)\n', IDF1, IDTP, IDFN, IDFP); fprintf('MT: %d, ML: %d, PT: %d\n', mt, ml, pt);

IoU计算函数单独放一个文件里,或者放在脚本末尾,R2016b之后的版本可以直接写局部函数:

function iou = computeIoU(bb1, bb2) % bb1, bb2格式: [x, y, w, h] x1 = max(bb1(1), bb2(1)); y1 = max(bb1(2), bb2(2)); x2 = min(bb1(1)+bb1(3), bb2(1)+bb2(3)); y2 = min(bb1(2)+bb1(4), bb2(2)+bb2(4)); interW = max(0, x2 - x1); interH = max(0, y2 - y1); inter = interW * interH; area1 = bb1(3) * bb1(4); area2 = bb2(3) * bb2(4); union = area1 + area2 - inter; if union <= 0 iou = 0; else iou = inter / union; end end

如果你的MATLAB版本较老,不支持脚本内局部函数,就把computeIoU保存成独立的computeIoU.m文件放到同一目录。

6.4 运行结果与指标核对

我这边跑完的输出大致是这样:

GT总帧数: 300 Track总帧数: 257 TP: 252, FN: 48, FP: 5, IDSW: 1, FM: 2 MOTA: 0.820 MOTP: 0.652 IDF1: 0.904 (IDTP=252, IDFN=48, IDFP=5) MT: 2, ML: 0, PT: 1

用手工推测验证一下:目标1全跟100帧,目标2跟70帧,目标3跟82帧,所以TP=100+70+82=252,没问题。GT总帧数300,FN=300-252=48,FP是5个孤立框,IDSW是目标3从track103切到track104那一次。FM=2,一次来自目标3在第20到25帧的漏跟恢复,一次来自第61帧的ID切换。

这些数字和代码逻辑完全对得上,说明指标计算链路是通的。真实数据测试时,建议也用这种“先造题再做题”的方式验证自己的评估脚本。

7. 实操中容易踩的坑与我的个人使用习惯

7.1 数据格式、坐标口径和阈值选择

第一个大坑是框的坐标格式。有的代码用左上角加宽高,有的用中心点加宽高,混用一个不小心就是整套指标全部算错。我习惯在评估脚本开头强制统一成[x, y, w, h],并且所有输入数据先过一遍格式检查。

第二个坑是坐标是否归一化。像素坐标和归一化坐标混在一起,IoU会小到离谱,匹配结果自然全错。写代码时务必加断言,确认所有输入都在同一个坐标体系下。

第三个坑是IoU阈值。同样的跟踪结果,阈值设0.3和设0.5,MOTA能差出几个百分点。不同论文之间对比时如果不看阈值,很可能是在拿两个标准比。这也是我建议:所有指标输出必须附带“匹配阈值、坐标格式”这两项配置信息。某个公开基准的数据我核对时就发现,阈值0.5时MOTA是0.71,降到0.3后直接跳到0.83——你以为算法进步了,其实是标准放松了。

7.2 不同场景下我只盯哪几个指标

自己做实验和写论文时,我一般强制要求自己输出一组完整指标:MOTA、IDF1、MOTP、MT/ML/PT、FM、处理速度。哪怕这一版只优化了其中一两个,其他都得报出来,防止“指标抄袭”。

如果是为了定位问题,我会先看FN和FP的比例。FN远大于FP,说明检测器漏检是主要矛盾;FP远大于FN,说明误检或匹配策略太激进;IDSW突然增多,说明数据关联有问题,优先检查匹配逻辑。

如果是做工程交付,我会把这三个问题单独问一遍:这东西会漏多少个目标?会多出多少个假目标?ID会不会乱跳?分别对应FN、FP、IDSW,比报一个综合MOTA更能说服人。

7.3 我的一个日常检查习惯

最后分享一个百试不爽的习惯:每次写完评估脚本,先不看真实结果,而是先构造几组“已知答案”的极端输入。

第一组把跟踪结果直接复制真值,所有指标都应该接近完美;第二组把跟踪ID全部随机乱换,MOTA应该轻微下降而IDF1大幅下降;第三组随机删掉一半跟踪框,MOTA、IDF1一起掉。只要这三组测下来符合作出的预期,脚本才算初步可信。再往后跑真实数据,任何异常都有方向可查。

我自己被这个习惯救过不止一次。有一回拿真实数据测出来MOTA奇高、IDF1奇低,所有人都以为是算法出了问题,后来跑第二组对照才发现是评估脚本的ID映射写反了。工具的问题比算法的问题更隐蔽,造数据自检是最省时间的排查方式。希望这套从指标拆解到代码实现再到自检流程的完整链路,能帮你少踩几个同样的坑。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询