如果一个目标检测器能把人框得又准又稳那么跟踪环节的“给每个框分配固定ID”看起来就是重识别ReID的活。我最初在某个客流统计项目X里就是这么设计的先用ReID特征做帧间匹配离线评测集上Rank-1轻轻松松跑到90以上当时觉得跟踪已经解决了。结果到了密集人流、互相遮挡、光照忽明忽暗的真实录像里重识别把同一个人的ID切得稀碎把两个穿同款工服的路人焊成一个ID。我反复追问自己连ReID都不行那到底什么才真正可靠后来我才想明白问题不在“特征提取得不够好”而在“把特征匹配用在了错误的任务层级”。ReID的强项是跨相机、跨时间的身份检索而单相机多目标跟踪真正依赖的是一个更朴素也更硬核的先验同一个人的轨迹在时空中必须是连续的。从“找最像的人”到“找最合理的运动轨迹”这个思路转过来之后整个系统才真正稳了。这篇文章就聊聊我在这条路上踩过的坑、拆开的技术原理以及最终把“空间连续性重构”落地的实操过程。1. 项目缘起ReID的“满分”与实战的“零分”1.1 一个真实到让人崩溃的场景项目X是做商超重点区域的人流统计摄像头固定视角画面里最多的时候同时有二十来个人。最初的方案很单纯目标检测器每帧输出检测框我拿一个预训练的ReID模型对每个检测框做特征提取然后用余弦距离判断相邻帧之间哪些框是同一个人。前期的验证片段都是独立行人、遮挡少的画面效果出乎意料地好MOTA能到72IDF1到76我一度认为这个活儿已经干完了。高峰时段的录像给了我一记闷棍。先是闸机口两个人擦肩而过ID瞬间互换然后是收银台前排队的队列里一个弯腰拿东西的人被连续切成了三个ID最离谱的是两个穿同样黑色外套的顾客并排走了一小段跟踪系统直接把两个人合成了一条轨迹。我导出匹配日志逐帧排查发现同一目标相邻帧的特征余弦相似度居然经常低于不同目标之间的相似度也就是说ReID给出的“相似度排名”在拥挤场景里基本接近随机。这个现象不是个例而是一类系统性失效。它背后的原因很值得掰开揉碎讲清楚因为这就是ReID作为跟踪关联特征的“物理极限”。1.2 评测集上的“高分”为什么是幻觉先把ReID的经典评测协议摆出来看看。主流评测集的做法是给定一张查询图在候选图库中检索出同一个身份的其他图片用Rank-1、Rank-k、mAP衡量排名质量。查询图和候选图都是预先裁剪好的“干净”目标图片分辨率足够、完整度足够很多数据集里同一个人的多张图还覆盖了不同相机视角。这套协议本质上是“证件照比对”而跟踪里的关联是什么是从连续视频流里拿到一串形态不断变化的检测框——框可能偏了可能只框到半个人可能带上背景和遮挡物分辨率忽高忽低目标姿态可能在转身、弯腰、蹲下。这两个任务的目标函数完全不一样检索任务只要求“这次查询把正确目标排在前面”哪怕上一次排错了下一次还可以重新来跟踪任务要求“连续几十帧里ID不能乱”一次错误关联会在后续帧里被级联放大一个ID错乱可能直接带崩一整条轨迹。所以我后来跟人聊这类问题常说一句话别拿Rank-1来给跟踪方案壮胆Rank-1衡量的是“检索一次”的能力跟踪要求的是“连续对一百次”的能力。两者的门槛差距比很多人想象中大得多。2. 特征匹配极限ReID“看着很美”的四个原因2.1 全局特征本质上在表达“平均化的外表”ReID模型输出的是一个高维embedding向量主流的提取方式经过全局池化。全局池化的好处是对目标位置不敏感但坏处同样明显它天然地把空间细节“平均化”了。一个人身上真正稳定、可判别的线索比如背包、鞋型、肩宽、走路时的局部姿态恰恰是需要保留下空间结构的细粒度信息。一旦池化成全局向量这些信息相当部分被抹掉了。换个更直白的说法当一个人长期背对镜头或者画面里目标分辨率很低时ReID模型所能依赖的只剩“整体颜色大体轮廓”。在商超场景里夏季所有人都是短袖短裤冬季一排人全是深色外套这种“平均化的外表”撞车概率极高。我在排查日志里见过最夸张的一次同一个人的两个框余弦相似度0.52两个不同的人因为都背黑色双肩包相似度反而到了0.71。2.2 特征度量空间的不稳定性同人漂移与异人聚拢就算抛开全局池化的问题ReID特征本身在连续帧中也存在明显的漂移。视角变化、身体姿态变化、部分遮挡、光照波动都会让同一个人在不同帧里的embedding出现显著偏差。与此同时不同目标之间的特征又在“聚拢”——当两个人穿着相似、体型相近、又处在同样的光照环境下他们的特征向量会落在度量空间的邻近区域。当“同人漂移程度”大于“异人间距”时度量空间就出现了一个无法分类的中间地带。这可不是我拍脑袋下的结论。我做过一个简单实验虚构统计在项目X的录像中随机抽取200对“同一目标相邻帧”和200对“不同目标随机帧”分别计算特征余弦距离画出来两条分布曲线结果两条曲线有将近四分之一的面积是重叠的。也就是说哪怕把匹配阈值调得再好也有约四分之一的帧对在纯特征空间里根本无法可靠区分。这正是ReID的“特征匹配极限”最具象的表现不是模型训练得不够好是这个度量问题在信息层面就存在天花板。2.3 密集场景里的尺度灾难稀疏场景下目标往往较大ReID特征还勉强可用一旦进入密集场景目标尺寸分布拉开问题就更尖锐了。近处的人框高可能有三四百像素远处的人可能只有二十像素而ReID特征提取网络在极小目标上的输出质量衰减极其严重。我做过一次粗略统计虚构数据以检测框短边32像素为分界大于这个尺寸的目标相邻帧特征余弦均值约0.75短边小于32像素时这个均值直接掉到0.5以下基本就是随机水平。再加上密集场景里互相遮挡频繁检测框经常不是标准的“全身框”而是“上半身框”“半个身子加一截旁人”这种脏输入特征提取器自然输出一堆不可靠的向量。这块必须说清楚ReID的评测图片是标准裁剪而跟踪系统拿到的是检测器给的“毛坯框”。毛坯框的质量决定了特征提取的天花板而这个天花板在密集场景里低得惊人。2.4 “指标幻觉”Rank-1再高也承诺不了ID稳定很多团队一开始都栽在“指标幻觉”上。ReID模型在公开评测集上的mAP特别漂亮Rank-1甚至能到95以上大家就默认它一定适合做跟踪关联。但你真把模型接到在线跟踪里IDSW身份切换次数高到离谱。原因在我前面已经提到检索任务里排名即使出错损失是单次的跟踪任务里一次错误匹配会让后续帧全部带上错误的标签ID错误会在级联过程中自我强化直到两条轨迹交叉碰撞后双双崩溃。我甚至见过一个系统因为一次错误关联导致后续10帧里两个真实目标的框全部被绑定到错误轨迹上最后两条轨迹直接互换身份。表格对比一下会更直观任务成功标准一次失败的成本对特征的敏感度ReID检索查询排名把正确ID放最前单次错误可重试高MOT关联长时间ID不切换级联扩散轨迹崩坏极高MOT轨迹恢复遮挡后正确续接轨迹断裂ID重复中等所以不要再拿Rank-1来“背书”ReID在MOT里的表现。两个任务看着都跟“认出一个人”有关实际对特征稳定性的要求已经不在同一个量级。3. 空间连续性重构从“找最像的”到“找最合理的”3.1 比“长相”更硬核的先验轨迹时空连续既然纯靠外观不可靠那就得找更稳的线索。物理世界里有一个几乎不会失效的约束同一个目标在相邻两帧之间不可能瞬移。以25fps的摄像头为例一个正常步行速度的人每帧位移大概在10到30像素范围内快速奔跑也不过是几十像素。也就是说下一帧里“哪个人是老张”这件事外观只给了一个弱判断位置连续性给了一个强判断。这个先验在工程上落地最成熟的工具就是卡尔曼滤波。每条轨迹维护一个状态向量常见的匀速模型是[x, y, w, h, vx, vy, vw, vh]分别表示检测框的中心坐标、宽高以及它们对应的速度。每一帧先用恒定速度模型预测下一帧的框位置拿到当前帧检测框之后计算“预测框”和“实际框”之间的马氏距离或者IoU再决定是否关联。这里的关键点在于匹配用的不是“谁长得像”而是“谁在物理空间上最合理”。位置预测本身是平滑的对遮挡、光照变化、姿态变化统统免疫因为它根本不在意目标长什么样。3.2 关联方式的转变从距离矩阵到代价矩阵ReID路线下的关联是构造“外观特征距离矩阵”然后在矩阵上跑匈牙利匹配。空间连续性路线下的关联则换成“运动代价矩阵”矩阵元素是预测框与实际检测框的距离损失常用的有IoU负对数、马氏距离等。具体操作时我通常把代价设计成三部分加权运动代价马氏距离衡量预测状态与观测状态的统计距离。重叠代价IoU衡量预测框与实际框的空间重叠程度对高速目标的灵敏度不够但用于遮挡不严重的场景效率极高。外观代价ReID特征余弦距离作为前两者的补充项只在空间线索接近时才参与裁决。匹配顺序上优先用高置信度检测框去和预测轨迹做关联剩下的低分检测框再单独处理。为什么要这样排因为低分框往往是被遮挡、模糊、部分可见的目标如果用它们优先去匹配会把匹配的噪声放大。先匹配“确定的”再处理“不确定的”整体稳定性会好很多。3.3 轨迹状态机把“丢失”变成“可恢复”纯单帧匹配哪怕做得再好也扛不住长时间遮挡。真正的空间连续性重构要从单帧关联升级到轨迹状态管理。我常用的是一个三态状态机待确认态一条轨迹刚建立时并不急着“转正”必须在连续若干帧中都匹配上检测框才会进入确认态。这样做是为了避免把误检的框当成真目标导致满屏噪声轨迹。确认态正常跟踪状态。每一帧做预测、匹配、更新如果连续几帧匹配不上就会转入丢失态。丢失态轨迹暂时保留一段时间不立刻销毁。在保留窗口内运动模型继续预测它下一帧可能出现的位置一旦在预测位置附近重新出现检测框就尝试重新关联。这个状态机的核心思想是“延迟判决”不要求每一帧都立刻给出绝对正确的身份而是在一个时间窗口内用空间连续性把断裂轨迹接回来。这个改动带来的提升非常明显短遮挡场景下ID恢复率从不到一半提升到了七成以上。3.4 轨迹重构遮挡不是终点是拼接机会当一个人被完全遮挡了20帧、30帧再重新出现时单帧关联已经很难恢复身份了因为时间差太大运动预测的不确定性已经扩散。这时候要靠两段的“轨迹级拼接”这也就是我理解的“空间连续性重构”的完整形态。做法是把之前已经丢失的轨迹和新出现的轨迹头端做配对搜索。每条丢失轨迹保留它的末端状态包括最后位置、速度、方向每条新轨迹保留它头端几帧的位置序列。然后计算如果这两条是同一个目标新轨迹的头端应该落在丢失轨迹速度外推的哪个范围内方向也应该接近。我在项目X里实现时会在丢失轨迹的末端画一个外推搜索区域区域半径由保留时间和目标速度决定。新轨迹头端如果落进这个区域并且速度方向偏差小于某个阈值就判定为同一条轨迹执行拼接。拼接后整条轨迹的状态做一次反向平滑把丢失期间的位置补齐。这一步是真正意义上的“重构”——不再依赖外观去判断“谁是谁”而是依赖“两条轨迹在时空上能不能对得上”。4. 实操一个从ReID优先到空间优先的改造实录4.1 第一版方案检测器ReID特征关联项目X的第一版方案结构很简单检测器输出检测框ReID模型对每个框提取512维特征计算相邻帧两两检测框的余弦距离用匈牙利算法做最优匹配同时设置一个特征距离阈值0.3距离超过阈值的直接拒绝匹配。初始参数也参照了经典的级联匹配策略对于一个跟踪了较久的目标优先沿用现有的轨迹状态新轨迹只能匹配剩下的检测框。这套方案在测试片段聚集度低、遮挡少上的成绩是MOTA 72、IDF1 76、IDSW 120虚构数值看起来完全“可发布”。结果高峰时段一跑MOTA掉到60左右IDF1掉到65IDSW飙到2800几乎是灾难级别。现在回看这版方案问题根源很清晰整个系统只有外观一个关联依据一旦外观失效就全线崩溃。没有运动模型做前置约束没有轨迹状态管理做容错ReID的每一次错误匹配都是“硬错误”没有回头的机会。4.2 第一次改造把卡尔曼滤波和运动代价请回来第一版崩了之后我没法放弃检测跟踪的框架只能把关联链路整个重构。第一步就是给每条确认轨迹加一个卡尔曼滤波器状态为[x, y, w, h, vx, vy, vw, vh]采用等速度模型。每帧先用滤波器预测框位置然后算预测框和检测框的马氏距离和IoU组成运动代价矩阵。关键的改动在于把ReID特征从“唯一依据”降级为“辅助依据”。我设计了一个带权重的代价函数cost w_pose * 运动代价 w_appearance * 外观代价一开始我把w_pose设为0w_appearance设为1相当于还是纯ReID方案拿来做AB测试基线然后逐步增大运动权重的占比。实测下来的趋势虚构数据很能说明问题权重组合含义MOTAIDF1IDSWw_pose0, w_app1纯ReID60.265.12786w_pose0.3, w_app0.7运动辅助68.972.31420w_pose0.5, w_app0.5运动为主77.478.9385w_pose0.7, w_app0.3运动主导80.179.5212纯运动少量外观的组合在这个场景里明显碾压纯ReIDIDSW直接从2786降到两百多。这个结果让我很受触动真正常规跟踪场景中就不需要那么依赖外观。4.3 第二次改造轨迹状态机与断点续接运动代价带来的提升已经很大但长时间遮挡场景下仍然有大量“轨迹断裂后重新起新ID”的问题。于是第二次改造直接上状态机待确认、确认、丢失三态来管理轨迹生命周期。参数上我按两秒做丢失保留窗口——怼着25fps的帧率就是50帧。一条确认轨迹连续匹配失败后并不会立即销毁而是转为丢失态继续保留运动模型和外推位置在50帧的窗口内每次新检测框到来都会尝试在预测位置附近召回它。断点续接的逻辑则更为综合利用轨迹尾端的速度外推丢失期间的位置待新轨迹头端出现后计算它与尾端目标的速度方向差和距离差。如果两者差小于阈值我就判定为同一条目标直接拼接。拼完之后对整个轨迹段做一次平滑修正把断裂期间的位姿补上。这两层配合“置信度积分”机制轨迹每成功匹配一帧加1分连续丢失时每帧扣1分分数归零才彻底销毁。这解决了一个常见问题——因为担心“轨迹是假的”而过度销毁轨迹导致目标一被遮挡就立刻重新起ID。丢失寿命和置信度机制放进系统后IDSW又降了一个数量级。4.4 最终效果ReID退居“最后一道仲裁”两轮改造完成之后系统的最终成绩虚构数值是MOTA 83、IDF1 80、IDSW 60不到。在高峰时段ID稳定性已经远不是第一版能比的了。最让我意外的反而是ReID特征并没有完全弃用但在系统里的角色彻底变了。现在的匹配流程是这样的先用空间位置和预测框做初筛只保留“空间上高度可疑”的候选目标当几个候选在空间线索上都差不多时再用ReID特征做最终仲裁。比如两个人并排走预测框和检测框的IoU都说得过去速度也一致此时才让外观特征出来“投票”。一旦空间线索能给出一致结论外观就靠边站。这样设计的好处是ReID特征仍然在发挥它擅长的部分——区分“空间上分不开但身份上不同”的目标但不再承担它不可能完成的任务——仅凭外观在密集场景里稳定区分每个个体。5. 常见问题速查与选型建议5.1 我踩过的六个典型坑改造过程不是一帆风顺我把最近实践中遇到的典型坑整理出来希望能帮后来人绕开。坑1特征距离阈值设成硬值第一版里我把余弦距离阈值固定为0.3结果发现不同场景的最佳阈值波动很大。光照充足时0.3还太宽松遮挡频繁时0.3又太紧。后来我把硬阈值换成了动态门限用每条轨迹历史特征距离的均值加标准差作为阈值匹配更稳。坑2低帧率视频里运动模型也不稳之前接过一个5fps的低帧率视频卡尔曼预测的误差大得离谱。原因是等速度模型在长时间跨帧时对加速度的假设失效了目标在相邻两帧之间的位移既大又非线性。这种场景下我调大了卡尔曼的过程噪声让滤波器更快响应实测位置同时把外部重识别特征的权重重拾回来让外观去弥补运动模型的不足。坑3相机抖动让运动模型全线错乱云台或风吹导致的相机小幅震动会让“空间连续性”荡然无存因为目标在画面里的位置随机跳动。这时候必须先做全局运动补偿用背景特征点估计整帧的运动矩阵再把所有框坐标投影到补偿后的坐标系里然后才谈得上用卡尔曼预测。坑4遮挡一长轨迹就被销毁早期最大丢失寿命只有10帧人一被遮住超过半秒轨迹就没了。后面把这个寿命拉到50帧配合置信度积分明显改善了遮挡恢复率。但也不能无脑拉长拉太长轨迹会像没头苍蝇一样到处乱飘反而增加错误关联。坑5两条轨迹交汇时互相污染关联结果当两个人交错而过检测框重叠较大时匹配矩阵里会出现“两条轨迹抢同一个框”的情况。我加了互斥规则一个检测框只能绑定一条确认轨迹如果两条轨迹都被预测到同一个框附近系统会回退到上一帧重新评估而不是简单接受当前帧的匹配结果。坑6ReID模型在真实场景里过拟合预训练ReID模型下线评测成绩很好一到现场就废这就是典型的domain gap。后来我在项目X的现场采集了一批数据做有监督微调虽然离线评测提升不大在线IDSW却降了约三成。所以一定不要迷信“预训练模型直接拿来就能用”场景适配该做还得做。5.2 什么时候ReID仍然不可替代说了这么多ReID的极限但我不认为ReID应该被完全扔掉。有几个场景里外观特征恰恰是唯一可用信号跨相机跟踪目标离开A相机画面后可能几秒后才进入B相机空间连续性彻底断裂此时只能靠ReID判断是不是同一个人。长时间遮挡后恢复如果遮挡时间远超运动模型的外推置信区间仅凭空间线索已经不够需要外观特征做二次确认。反向验证在空间关联给出可疑结论时用外观特征进行复核能有效降低错误拼接率。我在最终方案里就是这样定位ReID的跨相机场景用ReID做主心骨单相机密集场景用空间连续性做主心骨两者都存在的场景用加权融合。技术与技术之间不是取代关系而是分工关系。5.3 如何快速判断你的项目到底需要哪种方案一个很实用的诊断方法我称之为“看IDSW高发帧段”。把系统跑完的视频里所有ID切换事件标出来看它们集中在哪种时刻如果IDSW大多发生在“两个外观相似目标擦肩而过”的地方说明系统在靠外观做判断然后被相似外观欺骗了。这种场景空间连续性才是正解。如果IDSW大多发生在“目标被遮挡一段时间后重新出现”的地方说明轨迹状态机和断点续接没做好重点应该在丢失态管理上。如果IDSW均匀分布、没有明显高发段大概率是检测器本身一塌糊涂频繁漏检、误检导致跟踪无从谈起先解决检测质量再谈跟踪。这个诊断方法完全不需要看复杂指标只需要几个小时的视频人工标注就能得到结论成本低、见效快。我在项目X里最深刻的体会是ReID不是不行而是被我放在了不合适的层级上。外观特征的价值在于补运动模型的空而不是反过来由它扛起整条跟踪链路。如果你正在为“跟踪总丢ID”头疼先别急着换更强更大的重识别模型找个下午把运动模型和状态机接进去跑一轮大概率会看到比换模型更直观的提升。最后再分享一个小技巧改造完成后建议在验证集上手动统计“轨迹断裂后的恢复率”也就是遮挡前轨迹ID与遮挡后轨迹ID相同的比例。只看MOTA/IDF1很容易被平均指标骗过去这个恢复率才是判断空间连续性重构到底做没做好的金标准。