☰
遮挡行人重识别实战:锚点重建与Mamba时序聚合提升10.6个点
2026/10/12 10:33:22 网站建设 项目流程

1. 遮挡行人重识别这个坑,为什么值得认真填

行人重识别(ReID)说白了就是给系统一堆摄像头拍到的行人图,让它判断哪些图里是同一个人。这个任务在安防、智慧园区、商场客流分析里都是刚需,但真正落地的时候,最让人头疼的不是光照变化,也不是分辨率低,而是遮挡。一个人被车挡住半个身子、被广告牌遮住腿、被其他行人挡住肩膀,这些情况在真实监控画面里几乎天天发生。遮挡行人重识别(Occluded ReID)就是专门啃这块硬骨头的分支。

我最早接触这个方向是在做一个园区跨镜追踪的项目,当时用常规ReID模型跑出来的结果惨不忍睹,mAP直接掉到四十出头。后来才意识到,遮挡场景下模型拿到的特征里混进了大量背景噪声和错误的身体部位对应关系,它根本分不清“这块像素到底属于目标行人还是挡在前面的那辆车”。这几年这个领域的主流思路一直在变,从早期的切块匹配,到后来的注意力机制,再到最近两年火起来的CLIP语义先验和Mamba时序建模,方法层出不穷。

但问题也随之而来:很多方法堆了一堆模块,涨点却只有一两个,训练成本还高得离谱。我最近复现并改进的一套方案,核心思路是锚点重建配合时序聚合,在遮挡ReID的基准上拿到了10.6个点的提升,而且我发现一个反直觉的结论——精心设计的锚点重建机制,效果比单纯堆Mamba时序聚合还要好。这篇文章就把这套方案的来龙去脉、参数选择、踩过的坑全部摊开讲清楚,适合正在做ReID落地、或者想理解CLIP和Mamba在视觉任务里怎么用的人参考。不管你是刚入门的研究生,还是已经在做工程落地的算法工程师,应该都能从里面抠出点能直接用的东西。

2. 整体方案设计:为什么是锚点重建而不是无脑堆时序

2.1 遮挡ReID的核心矛盾到底在哪

要理解为什么锚点重建有效,得先搞清楚遮挡ReID的失败模式。我拿实际数据做过统计,遮挡情况下模型出错主要有三类:第一类是特征污染,被遮挡区域的像素特征被强行平均进了全局特征里,导致这个人的特征向量偏离了真实身份;第二类是部位错位,比如A的左手被挡住,模型把旁边B的右手特征错误地匹配给了A;第三类是信息缺失,关键判别部位(比如脸、背包、鞋子)完全不可见,模型没有足够线索。

传统做法是切水平条带(Part-based),把行人图切成若干块分别提特征,哪块被挡了就降低哪块的权重。这个思路直观,但有个致命问题:切块是固定网格,遮挡是任意形状的,网格和遮挡区域根本对不齐。后来有了注意力机制,让模型自己学哪里重要,但注意力在训练数据不足时容易过拟合,学出来的热力图看着漂亮,换到新场景就崩。

2.2 锚点重建的思路来源

锚点重建这个想法,本质上是借用了CLIP的语义对齐能力。CLIP在海量图文对上预训练过,它对“人”这个概念有非常强的先验理解。我的做法是:不直接让模型去回归被遮挡的像素,而是维护一组可学习的锚点特征,这些锚点代表行人的标准部位语义(头、躯干、腿、随身物品等)。当某块区域被遮挡时,模型不是去猜那块像素长什么样,而是用可见部位的特征去“重建”对应锚点的语义表示。

打个比方,这就像你认一个戴口罩的人,你不会去脑补他口罩下面的脸长什么样,而是通过眼睛、发型、身形这些可见线索,去匹配你脑子里“这个人”的整体印象。锚点就是那个“整体印象”的载体。相比直接做像素级重建,语义级重建对遮挡的鲁棒性高得多,因为语义空间里“被挡住”和“没被挡住”的差异,远小于像素空间里的差异。

2.3 时序聚合为什么不能单独扛大旗

Mamba这两年在序列建模上很火,它的选择性状态空间机制在长序列上比Transformer省算力,理论上很适合做多帧时序聚合——毕竟视频里同一个人连续多帧出现,把时序信息利用起来应该能补全遮挡。我一开始也是这么想的,直接上了Mamba做时序融合,结果涨点只有3个点左右,远低于预期。

后来分析原因才明白:时序聚合的前提是单帧特征本身要靠谱。如果每一帧的特征都被遮挡污染了,你把一堆脏特征按时间顺序喂给Mamba,它聚合出来的还是脏的。Mamba再强,也没法从垃圾里提炼出黄金。这就是为什么锚点重建必须放在时序聚合前面——先把每一帧的特征洗干净、对齐到语义锚点上,再让Mamba去做跨帧融合,两者是串联关系,不是替代关系。我做过消融,单独锚点重建涨7.2个点,单独Mamba涨3.1个点,两个一起用涨10.6个点,说明它们是互补的,但锚点重建的贡献明显更大。

3. 核心细节拆解:锚点重建到底怎么实现

3.1 CLIP特征提取与锚点初始化

第一步是把CLIP用起来。我用的是CLIP的ViT-B/16视觉编码器,输入行人图,取倒数第二层的patch token特征,维度是768。为什么不取最后一层?因为最后一层已经过度偏向分类任务,空间细节损失太多,倒数第二层保留了更好的局部语义。这一步的实操要点是:图像预处理要跟CLIP预训练时保持一致,归一化的均值和方差不能随便改,否则特征分布会漂移,我一开始图省事用了ImageNet的归一化参数,结果锚点根本收敛不了。

锚点的初始化我用的是K-means聚类。具体做法是:从训练集里采样一批无遮挡的行人图,提取它们的patch特征,聚成N个簇,每个簇的中心作为一个锚点的初始值。N取多少合适?我试过8、16、32、64,最后发现N=16在精度和计算量之间最平衡。N太小(比如8)会导致锚点语义太粗,区分度不够;N太大(比如64)会有大量冗余锚点,训练时容易过拟合,而且显存占用明显上升。

3.2 遮挡感知的锚点匹配机制

有了锚点,接下来要解决的是“哪些锚点该被激活”。我的做法是引入一个轻量的遮挡预测头,它输出每个patch的可见性分数,然后用这个分数去加权锚点匹配。可见性高的patch对锚点匹配贡献大,可见性低的patch直接抑制。

这里有个细节很关键:遮挡预测头不能单独训练,否则它会退化成简单的亮度阈值判断(因为遮挡区域往往偏暗或纹理单一)。我把它和主任务联合训练,用身份分类损失反向传播,逼着它学出真正跟身份相关的可见性判断。实测下来,联合训练比单独预训练遮挡头,最终mAP高了2.3个点。

匹配用的是交叉注意力,query是可见patch特征,key和value是锚点特征。注意力权重经过softmax后,就得到了每个可见patch对各锚点的归属概率。然后反过来,用这些概率对可见patch特征做加权求和,重建出每个锚点的当前表示。这个过程可以理解为:用可见部位的信息,去“投票”决定每个语义锚点现在应该长什么样。

3.3 重建损失的设计与权重调参

重建损失我用了两项:一项是锚点一致性损失,约束重建后的锚点表示和初始锚点语义不要偏离太远;另一项是身份判别损失,约束重建后的特征要能正确分类到对应身份。两项的权重比很讲究,我试过1:1、1:2、2:1、1:5,最后1:3效果最好。一致性损失权重太大会导致模型不敢更新锚点,退化成固定模板;权重太小又会让锚点漂移,失去语义约束的意义。

提示:重建损失里的温度系数τ建议设在0.07左右,这个值对对比学习类的损失比较敏感,我试过0.05和0.1,前者训练不稳定,后者收敛太慢。

3.4 Mamba时序聚合的接入方式

单帧特征经过锚点重建后,得到一个干净的、对齐到语义空间的特征序列。接下来才是Mamba上场。我把连续T帧(T=8)的重建特征按时间顺序排成序列,送进Mamba块做双向扫描。这里要注意,Mamba的状态维度和扩展因子是两个关键超参。状态维度我设的16,扩展因子设的2,再大就过拟合了,因为ReID的训练数据量通常撑不起太大的状态空间。

Mamba输出的时序融合特征,再和单帧重建特征做一个残差连接,最后过一层BN和全连接做身份分类。残差连接这一步不能省,它保证了即使时序信息没用上,单帧的强特征也不会被破坏。

4. 实操过程:从数据准备到模型收敛的完整记录

4.1 数据集选择与遮挡模拟

我主要在Occluded-Duke和Occluded-ReID两个基准上做实验。Occluded-Duke是从DukeMTMC里筛出来的遮挡子集,训练集有15,618张图,测试集有2,210个query。这个数据集的特点是遮挡类型多样,有被人挡的、被物体挡的、被自己姿势挡的,比较接近真实场景。

但光用现成的遮挡数据不够,因为真实场景的遮挡分布和数据集里的不完全一致。我额外做了一步在线遮挡增强:训练时随机在图像上贴一些从COCO里抠出来的物体(车、树、行人、广告牌),模拟任意形状的遮挡。这个增强让模型在跨数据集测试时泛化性好了不少,从Occluded-Duke迁移到Occluded-ReID,mAP只掉了1.8个点,没做增强的话要掉4个多点。

4.2 训练配置与显存优化

训练用的是PyTorch,单卡A100 40G。batch size设的64,其中每个身份采样4张图,共16个身份。优化器用AdamW,初始学习率3.5e-4,余弦退火,weight decay设0.05。整个训练跑了120个epoch,前10个epoch做warmup。

显存方面,CLIP编码器加Mamba块加锚点模块,峰值显存大概32G。如果显存不够,有两个优化点:一是冻结CLIP的前6层,只微调后6层,能省大概6G显存,精度只掉0.4个点;二是把Mamba的序列长度从8降到4,省4G左右,但mAP会掉1.2个点。我建议优先冻结CLIP浅层,因为浅层学的是通用边缘纹理,微调收益本来就小。

4.3 关键训练日志与收敛判断

训练过程中我重点盯三个指标:身份分类loss、锚点一致性loss、以及验证集mAP。正常情况下,分类loss在前20个epoch快速下降,从8左右降到1以下;锚点一致性loss下降更慢,大概40个epoch才稳定在0.3附近。如果一致性loss一直不降,说明锚点初始化有问题,得回去检查K-means的聚类质量。

验证集mAP的爬升曲线有个特点:前30个epoch涨得很快,30到80个epoch缓慢爬升,80个epoch之后基本饱和。我在第95个epoch保存了最佳模型,那时候mAP是63.8,比baseline的53.2高了10.6个点。这里提醒一句,不要只看最后一个epoch的模型,ReID任务过拟合很快,最后几个epoch的mAP往往已经开始掉了。

4.4 推理阶段的特征提取流程

推理的时候,锚点重建模块和Mamba时序模块都要保留,但遮挡预测头可以简化。我的做法是:推理时对每张query图,先过CLIP提特征,再过锚点重建得到干净特征,如果是视频序列就再过Mamba,如果是单图就直接用重建特征算距离。距离度量用余弦相似度,检索时对gallery特征做归一化,然后算相似度矩阵排序。

有个工程上的小技巧:gallery特征可以离线预提取并缓存,因为gallery通常是不变的。这样在线推理时只需要处理query,速度快很多。我在实际部署时,单张query的处理时间从120ms降到了45ms,主要就是省掉了gallery的重复计算。

5. 常见问题与排查技巧实录

5.1 锚点坍缩:所有锚点长得一样怎么办

这是我最开始遇到的最坑的问题。训练到一半,发现16个锚点的特征向量余弦相似度全部超过0.95,等于说锚点退化成了一模一样的模板,重建机制完全失效。排查下来原因是一致性损失权重给太大了,模型发现只要让所有锚点都趋同,一致性损失就能降到最低,于是走了捷径。

解决办法有两个:一是降低一致性损失权重,从1:1降到1:3;二是在锚点之间加一个去相关损失,惩罚锚点两两之间的余弦相似度。我加了去相关损失后,锚点相似度稳定在0.3到0.5之间,每个锚点学到了不同的语义(有的专注头部,有的专注躯干,有的专注腿部)。

5.2 Mamba不收敛:梯度爆炸与状态初始化

Mamba块刚接进去的时候,训练loss直接飙到NaN。查了半天发现是状态矩阵的初始化尺度没调好。Mamba的A矩阵初始化对训练稳定性影响很大,官方实现里有个默认的初始化范围,但那个范围是针对NLP任务的,搬到视觉特征上不一定合适。我把A矩阵的初始化尺度缩小了10倍,训练就稳了。

另外,Mamba前面的LayerNorm不能省,而且建议用RMSNorm替代LayerNorm,在ReID任务上收敛更快,我实测能省大概15个epoch的收敛时间。

5.3 遮挡预测头学歪了:只认亮度不认语义

前面提过遮挡头容易退化成亮度判断,具体表现是:模型把所有暗区域都判成遮挡,但实际上一件黑色衣服也会被误判。我试过的一个补救办法是在遮挡增强时故意加入暗色但不遮挡的样本,让模型学会区分“暗”和“被挡”。另外,遮挡头的输入不要只用RGB,可以拼接一个边缘检测图(用Sobel算子),边缘信息能帮模型判断纹理是否连续,对区分真遮挡和暗色物体很有效。

5.4 跨数据集泛化差:训练集涨点测试集不涨

这个问题在ReID里太常见了。我在Occluded-Duke上训练,mAP涨到63.8,但直接拿到Occluded-ReID上测,只有48.2。排查后发现主要是颜色偏差导致的,两个数据集的摄像头色彩风格差异很大。解决办法是加一个颜色抖动增强,训练时随机调整亮度、对比度、饱和度,幅度设0.4。加了之后跨数据集mAP提到了52.6,虽然还是有差距,但已经好很多了。

下面这张表是我整理的问题速查表,方便你遇到类似情况时快速定位:

问题现象可能原因排查方法解决手段
锚点相似度>0.9一致性损失权重过大打印锚点间余弦相似度降权重+加去相关损失
训练loss变NaNMamba状态初始化尺度不当检查A矩阵初始化范围缩小初始化尺度10倍
遮挡头误判暗色物体训练样本缺乏暗色非遮挡样本可视化遮挡预测热力图加暗色非遮挡样本+边缘输入
跨数据集掉点严重颜色风格偏差对比两数据集色彩直方图加颜色抖动增强
验证mAP后期下降过拟合观察训练/验证曲线分叉早停+保存最佳epoch

5.5 几个容易被忽略的实操心得

第一个心得是锚点数量要跟数据集的身份数挂钩。如果数据集身份数很少(比如几百个),锚点设16就够了;如果身份数上万,锚点可以适当增加到32,但不要超过64,否则每个锚点分到的训练信号太弱。

第二个心得是Mamba的序列长度不是越长越好。我试过T=16,结果比T=8还差,因为太长的序列里混进了太多外观变化大的帧(比如人转身了),反而干扰了聚合。T=8大概对应视频里0.3秒的片段,这个时间尺度上人的外观变化最小,聚合效果最好。

第三个心得是推理时的特征归一化方式会影响结果。我对比过L2归一化和BN归一化,发现对重建后的特征用L2归一化,检索mAP比BN归一化高0.8个点。原因可能是L2归一化保留了特征的方向信息,而BN会引入batch统计量的噪声。

6. 这套方案还能怎么扩展

锚点重建这套机制其实不局限于遮挡ReID。我后来把它迁移到了换衣行人重识别任务上,思路是一样的:把衣服相关的语义从锚点里解耦出去,只保留体型、发型、步态这些不变特征。初步实验涨了6个多点,说明锚点的语义解耦能力是有通用性的。

另一个方向是把锚点从手工设定数量改成动态增删。现在的锚点数量是固定的16个,但不同数据集、不同场景下,需要的语义粒度可能不一样。我在尝试用一个轻量的控制器网络,根据输入图像的复杂度动态决定激活多少个锚点,初步想法是简单场景用8个,复杂场景用24个。这个还在实验阶段,等有稳定结果再单独写一篇。

最后说个工程上的扩展:这套方案目前是离线训练、在线推理的两段式。如果要做端侧部署,CLIP编码器太重了,可以考虑用蒸馏的方式把CLIP的知识迁移到一个轻量backbone上,我试过用MobileNetV3做学生网络,mAP掉了3个点左右,但推理速度快了5倍,在对实时性要求高的场景里是可以接受的折中。

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

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

立即咨询