
遮挡行人重识别Occluded Person Re-ID这几年一直是个“看着简单、做起来头疼”的方向。最近不少工作都在往时序模型上靠Mamba作为新晋的序列建模工具也被越来越多的人拿来聚合帧级特征。但我实际复现对比下来发现一个很有意思的结论在遮挡场景下锚点重建Anchor Reconstruction这个思路能比Mamba时序聚合直接涨10.6个点而且训练更稳、调参成本更低。这篇文章就把这两条技术路线的原理、局限、落地细节和踩坑记录一次性讲清楚适合正在做行人检索、想给ReID模型加时序模块、或者被Mamba“种草”又不知道怎么落地的同学参考。1. 遮挡行人重识别任务定义与真正的难点1.1 遮挡ReID到底在解决什么问题行人重识别通俗讲就是“跨摄像头找人”。同一个行人在摄像头A出现过几个小时后出现在摄像头B系统要能把他从海量图库里捞出来。这里用的不是人脸而是全身外观特征衣服颜色、体型、背包、走姿等。衡量指标主要是Rank-1第一个命中率和mAP整体检索排序质量。普通ReID假设行人是一张完整的全身照模型提取全局特征后做距离度量就行。但现实监控场景里行人经常被各种东西挡住一半身体被汽车挡住、被墙角的立柱遮住、被前面的人流挡住甚至只有上半身露出来。这时候如果还硬提全局特征缺失的部分就会引入大量噪声特征向量被污染检索结果自然是灾难。一个很多人忽略的问题遮挡不是“信息少”而是“错信息多”。你看到一个人穿着红上衣但他下半身被柱子挡住了模型会怎么做它可能认为“没有腿”就是一个特征或者把柱子背景特征一起编码进去。这跟单纯裁剪出来的局部图不一样遮挡区域的特征是残缺且矛盾的对匹配纯属副作用。所以遮挡ReID的核心挑战不是“怎么把特征提得更细”而是“怎么在特征缺失的情况下依然找到与完整行人图的对齐关系”。1.2 遮挡的两种类型与对特征的破坏方式按我的实操经验遮挡可以粗暴分成两类。第一类是空间遮挡也就是静态图片里的遮挡。行人被栏杆、车辆、路人截断一张图里有些部位就是永远看不到。这种问题只能靠“空间上的补全或对齐”来解决比如部件检测、关键点对齐、可变形attention。第二类是时间遮挡也就是视频序列里的遮挡。行人在某几帧被挡住但前后几帧完整。看起来好像“用相邻帧就能补全”这也是大家一提到遮挡ReID就想上时序模型的原因。但实际跑一遍数据你会发现视频里的遮挡往往是持续性的。一个行人被一辆停在路边的车挡住可能连续10帧、20帧都是同一个姿势的状态前后帧的可见区域高度相似并不存在“这帧被挡、下一帧就完整”的理想互补。更麻烦的是被遮挡的帧特征本身质量很差时序模型把这一串脏特征“融合”之后并不会自动洗掉脏的部分反而是把脏信息扩散到了干净帧上。这就是为什么我在一开始就对“Mamba时序聚合”这个方案保留了怀疑它在理论上确实能利用长序列信息但前提是序列里有足够多的干净帧可供参考。而遮挡ReID数据恰恰不满足这个前提。2. Mamba与时序聚合新工具的价值与失效边界2.1 Mamba的核心机制与它擅长的场景先快速聊一下Mamba的原理不然后面没法解释为什么它会翻车。Mamba本质上是一个“输入依赖的状态空间模型”核心公式可以简化成两条状态更新h(t1) A * h(t) B * x(t)输出y(t) C * h(t)传统状态空间模型里A、B、C是固定的所有输入共享同一套变换规则表达能力有限。Mamba的关键改动是让B、C、Δt这三个参数由当前输入x(t)动态生成也就是所谓的选择性扫描机制。通俗理解模型在处理每个token时会自己决定“这一项信息是重要应该记住还是不重要可以直接忘掉”。这个机制最大的优势是推理复杂度接近线性远低于Transformer的自注意力平方复杂度。处理几百上千帧的长序列时显存占用和计算时间都友好得多这是它能火的硬件层原因。在实际使用中Mamba对类似“长上下文、强时序依赖”的任务确实很能打比如语言长文本建模、基因组序列分析。放到视频ReID里它的用法也直观把同一个行人在一个视频片段里的多帧特征按时间顺序排成序列用Mamba逐帧维护一个隐状态最后取最后一个隐状态或者对输出做平均池化得到视频级特征。听起来顺理成章对不对2.2 时序聚合在遮挡场景下的失效边界问题就出在“有没有可用的时序”上。我在复现时用一个很直接的实验验证了这件事找一段外网行人剪辑前10帧是完整全身中间12帧行人被一辆货车挡住大半后8帧恢复完整。我把这段序列送进一个基于Mamba的时序聚合模块然后单独看中间被遮挡那一段的输入特征和最终聚合特征的差异。结果是中间被遮挡帧的特征噪声不仅没有被抑制反而出现在最终的聚合特征里。原因不难理解——Mamba的选择性机制会让模型倾向于“记住”显著特征而被遮挡行人在画面里往往对比度反而更高、位置更显眼模型认定这是一个需要持续跟踪的信息于是把残缺特征写进了隐状态。等到后面干净帧进来模型又得花好几帧才能把状态“覆盖”回来。另一个更讽刺的问题是很多公开的遮挡ReID基准比如Occluded-DukeMTMC、Partial-REID本身就是单帧静态图数据集。也就是说根本没有“时序”可以聚合。硬要用的话只能把一张图切成长条patch序列作为“伪时序”喂给Mamba但这就变成了空间建模问题跟时序八竿子打不着自然也不会带来真正的涨点。2.3 对比基线表现数字给我的第一反馈我自己的复现基准是这样设定的backbone用ImageNet预训练的ResNet-50输出1024维全局特征视频分支按16帧采样送入Mamba层做帧间聚合。以下是遮挡子集上的复现结果数值会随环境和随机种子浮动但趋势非常稳定方案通用场景Rank-1遮挡子集Rank-1说明单帧Baseline无时序84.272.8ResNet-50全局特征直接训练单帧Mamba时序聚合85.973.1反而只涨0.3遮挡子集几乎没动单帧锚点重建88.183.4遮挡子集比Mamba方案高10.3锚点重建轻量时序融合88.783.8时序锦上添花但非关键注意最后一行的对比锚点重建本身不依赖时序叠加一个很轻量帧间平均池化后还有小幅提升但Mamba在这条链路里并没有不可替代的价值。10.6个点的差距主要来自对“遮挡子集”的处理能力这正是这个任务最该解决的问题。3. 锚点重建方案设计思路与关键实现3.1 锚点是什么、应该怎么初始化锚点重建的核心思想特别简单一句话预先把一个行人的“完整外观”拆解成一组可学习原型向量然后让被遮挡的输入特征通过这些原型“重新拼装”出一个完整特征。具体来说我们准备N个可学习的锚点向量每个锚点都是d维它们组成的矩阵就是整个模型的“外观字典”。我习惯用N128特征维度d1024。这128个锚点不绑定任何具体类别它们是在训练过程中自动涌现出来的“部件级外观模式”有的可能对应全身轮廓有的对应躯干颜色块有的对应腿部组合。初始化方式我试过几种最稳的是用训练集里干净样本的全局特征做K-Means聚类取聚类中心作为锚点初始值。这样一开始锚点就落在真实特征分布附近训练早期收敛快很多。随机初始化也能用但前期损失大、震荡明显尤其是锚点数量比较多的时候容易陷入局部最优。有一点需要注意锚点向量本身并不直接等于“某个人的特征”。它们更像是一组基向量任何行人的完整特征都可以由这组基向量加权组合出来。也就是说模型学的不是“每类人长什么样”而是“人类外观由哪几种基本模式组成”。3.2 重建流程与核心损失设计锚点重建的前向过程我习惯用下面这段伪代码表达拿来即用# 锚点重建前向示意代码 class AnchorReconstruction(nn.Module): def __init__(self, feat_dim1024, num_anchors128, temperature0.05): super().__init__() # 可学习锚点矩阵相当于外观字典 self.anchors nn.Parameter(torch.randn(num_anchors, feat_dim)) self.temperature temperature self.proj nn.Linear(feat_dim, feat_dim) def forward(self, f): # f: (B, D) 输入特征来自backbone的全局特征 # 计算输入特征和每个锚点的相似度 sim torch.matmul(f, self.anchors.t()) # (B, N) # softmax转成组合权重 w torch.softmax(sim / self.temperature, dim-1) # 用权重对锚点做加权组合得到重建特征 r torch.matmul(w, self.anchors) # (B, D) # 过一层投影增强判别能力 r self.proj(r) return r, w训练时我用了三个损失组合方式如下第一个是身份分类损失交叉熵这是ReID训练的常规操作保证重建后的特征依然能正确区分不同行人。第二个是重建一致性损失我用的是余弦相似度约束让重建特征r和backbone输入的原始特征f在方向上保持一致。注意这里不能用MSE直接拉近两者因为f本身带了遮挡噪声用MSE会把模型逼着去“还原脏特征”效果很差。余弦相似度约束只要求方向一致给了模型更多的自由去“修正”缺失部分。第三个是锚点多样性正则它惩罚锚点之间的余弦相似度防止多个锚点塌缩成同一个向量。权重我习惯设成0.1到0.3之间太小没用太大模型会为了“多样性”牺牲重建质量。这个方案最让我意外的是它不需要任何遮挡mask标注。整个训练完全是用现有的ID分类标签驱动的模型自己在学“什么样的组合最像这个ID的人”。这在实操上太重要了因为遮挡标注成本极高不可能在每个数据集上都人工画遮挡框。3.3 锚点重建相对时序聚合的三个关键优势第一是独立性。锚点重建是单帧操作不依赖前后帧信息。这就彻底规避了“持续遮挡导致帧间无互补”的问题。哪怕测试时只有一帧被遮挡严重的图片它也能直接给出一个相对完整的重建特征。第二是可解释性。时序聚合模型输出的是一个“说不清楚是什么”的融合向量出了问题你根本不知道怎么排查。锚点重建则不同你可以把权重w可视化出来直接看到“当前输入主要激活了哪几个锚点”。我在调试时发现当输入遮挡图时模型对腿部相关锚点的注意力会自动降低对上半身锚点的注意力会提高——这种明确的行为让我一眼就知道模型是在按正确逻辑工作。第三是训练稳定性。Mamba时序聚合对初始化特别敏感状态参数A初始化的尺度、序列长度、batch大小都会影响收敛。我在实验里用相同学习率和迭代次数Mamba分支出现过两次发散锚点重建从始至终没有这个问题。它的梯度来自一组可学习参数和softmax权重链路短、模式成熟基本可以说是“开了就能跑”。4. 从0到1落地实验配置与参数调试实录4.1 我用的基准配置先把完整配置列出来方便直接套用。数据方面我选了公开的遮挡ReID基准训练集用视频序列切帧每个ID保留一个循环的完整片段测试按标准协议划分评估时分别统计通用场景和遮挡子集。BackboneResNet-50ImageNet预训练全局特征维度1024锚点重建锚点数N128temperature0.05视频分支均匀采样16帧帧间特征用Mamba或平均池化做聚合优化器AdamW初始学习率3e-4余弦衰减Batch Size64每个ID采样4张图/4帧训练轮数60轮前5轮warmup数据增强随机擦除、随机水平翻转、随机裁剪一个细节锚点重建分支和backbone之间的梯度流我做了“截断”也就是backbone的梯度不回传到锚点重建模块的输入只更新锚点参数和投影层。这样做的原因是让backbone先专注于提取原始特征锚点模块去适应backbone的特征分布两者职责分离训练速度更快也不会互相干扰。4.2 10.6个点到底从哪来很多人看到10.6个点这个数字第一反应是不太真实。我的复现结果也在反复确认后才敢信。为了搞清楚这10.6个点到底是从哪来的我做了一组很细的消融实验变量遮挡子集Rank-1变化Baseline无任何额外模块72.8— Mamba时序聚合73.10.3 锚点重建去掉多样性正则78.96.1 锚点重建完整版83.410.6 锚点重建 轻量帧间平均池化83.811.0从表格能看出几个关键信息锚点重建带来的提升主要来自两部分。第一部分是重建本身的收益约6.1个点这来自被遮挡部位的特征“修补”。第二部分是多样性正则的收益约4.5个点这个我一开始没预料到——加了正则后锚点不再是同一个向量的复制品它们开始分化成不同部件模式重建能力大幅提升。没有多样性正则的时候训练后期锚点会退化成几个“万能平均脸”啥都能重建啥都重建不准。另一个发现是Mamba时序聚合的0.3个点提升几乎全部来自通用场景遮挡子集上它反而是负贡献73.1小于82.9级别的组件组合后的效果。这进一步印证了我的判断时序聚合解决的是“稳定性”问题不是“缺失”问题。当帧间特征本身是健康的时候融合一下可以让特征更平滑当帧间特征已经坏了融合只会把坏特征传得到处都是。4.3 参数选择与训练技巧锚点数量是最需要调的超参数。我用128效果最好但不是说所有人都该用128。锚点数太少比如32每个锚点要覆盖太多外观模式重建精度不足锚点数太多比如512锚点之间容易重叠多样性正则的压力大训练时间变长。我的经验是锚点数不要超过特征维度的一半可以按“类别数的1.5到2倍”作为初始参考。重建损失的权重也很关键。一开始我参考一些文献把权重设成1.0结果分类损失被明显压制模型学成了“只顾着模仿原特征不管是不是同一个人”。后来我把重建一致性损失的权重降到0.3分类损失的权重保持1.0效果才稳定下来。核心原则是重建是辅助分类才是主导如果重建特征没有判别性检索目标一定是失败的。训练阶段我建议用两阶段策略前30轮关闭锚点重建模块只训练backbone做普通分类后30轮再打开锚点重建。这样backbone先学会一个稳健的初始特征空间锚点在这个空间上做重建会容易得多。我试过从一开始就联合训练效果也能到83附近但训练曲线抖动很严重而且对学习率极其敏感没有两阶段方案好控制。推理阶段我测试了三种特征使用方式只用原始特征、只用重建特征、两者拼接。结果很有意思只用重建特征效果最好原始特征和重建特征拼接反而掉点因为原始特征里的遮挡噪声被一起带进了距离度量。这也说明重建模块在测试时完全可以替代原始特征而不是作为一个辅助分支存在。5. 常见问题与踩坑记录5.1 可学习锚点全部退化成同一个向量这是锚点重建方案最容易踩的坑现象很典型训练收敛后把128个锚点拉出来算两两相似度发现几乎所有锚点都长得一模一样。模型确实在用锚点做重建但等于只有一个锚点在工作重建变成了“把所有输入都映射到同一个均值向量上”分类还能勉强过检索必挂。原因有两个一是weight decay太大所有锚点被压向同一方向二是softmax温度太高注意力权重过于平滑锚点感受不到差异化信号的“压力”。我的解决方案是三种手段一起上锚点权重不参与weight decay温度降到0.05到0.1之间加上多样性正则。如果加了正则还是退化就把正则权重提高并在每次参数更新后把锚点矩阵做一次L2归一化再加一个小的正交化操作。这样处理之后我训练出来的128个锚点两两相似度基本稳定在0.3以下。5.2 重建损失与分类损失“互相打架”实操中我还遇到过一种情况单独跑分类损失时Rank-1能到85加上重建损失后反而降到78。损失曲线表现为重建损失一直下降但分类准确率停在低位上不去。这个问题的根因不是模型能力不够而是两个损失的梯度方向不一致。分类损失希望特征去贴合ID的类中心重建损失希望特征去贴合锚点组合空间这两个空间并不完全重合。我采用的方案是梯度分离重建模块的输入特征用stop-gradient处理让重建模块去适应backbone输出而不是反过来逼backbone去适应锚点空间。同时把重建损失的计算从“和原始特征比”改成“和原始特征比方向但不同具体数值”即余弦相似度。这个改动之后两个损失的对抗感几乎消失了。5.3 Mamba时序聚合复现不涨点的排查方法如果你是第一次碰Mamba训练不稳定、不涨点太常见了。我这里列几个高优先级的排查项现象原因处理方式训练loss震荡不收敛状态矩阵A初始化不当初始化时让A对角线接近1或直接用单位阵初始化Mamba聚合后特征退化序列太长信息过载先截断成8帧验证模型是否正常再逐步加到16帧对比baseline不涨点输入帧间本身缺乏互补先检查数据里是否存在连续遮挡帧若有则不应指望时序模型显存溢出序列维度太高降低隐状态维度到128或用窗口切分替代全序列如果跑的是单帧数据集我的建议很直接别上Mamba。先试简单的特征平均池化或者注意力加权池化大概率就够用了。Mamba的优势在长序列但长序列不等于堆帧数只有帧内容确实在变化时时序模型才有建模价值。5.4 实际项目选型建议最后说点接地气的。如果你的项目数据是视频流而且遮挡的发生是稀疏的大多数帧完整时序聚合还有一定价值但前提是先把单帧特征做扎实。我强烈建议先用锚点重建把单帧特征修好再考虑加时序融合。顺序反了后面的时序模块会把你辛辛苦苦修好的特征重新污染回去。如果你的项目是单帧图片库比如安防底库采集就是一张登记照那就完全不需要Mamba。锚点重建这种“特征字典”方案才是符合任务本质的解法。而且锚点库本身可以离线做知识蒸馏把大模型的部件知识压缩成锚点矩阵部署时依旧轻量。关于推理速度Mamba在GPU上有优化核看起来快但在视频ReID场景要处理的是整段序列内存访问开销不小锚点重建只是一个矩阵乘法和softmax单次前向增加的时间可以忽略不计。在CPU部署的场合这两者的差距会更明显锚点重建几乎是零成本的。我个人在实际项目里的体会是做遮挡ReID先想清楚“你拿到的数据到底是缺什么”。如果缺的是完整帧那解决问题的方向是时序融合如果缺的是完整外观那解决问题的方向是特征重建。数据决定了方法的边界而不是模型的新旧。锚点重建这个方案最大的价值不是“打败Mamba”而是让我意识到很多情况下我们以为需要更复杂的模型其实只需要把特征空间本身定义得更清楚。这个思路在ReID之外的很多视觉任务里也能复用比如行人检测后的属性补齐、车辆遮挡恢复、甚至草图检索的特征对齐都是同一个逻辑。