ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

X Modality详解:红外与可见光跨模态行人重识别的辅助特征空间

X Modality详解:红外与可见光跨模态行人重识别的辅助特征空间

红外和可见光跨模态行人重识别(Infrared-Visible Cross-Modal Person Re-Identification,圈里一般简写成RGB-IR ReID)是个特别容易让人绕晕的方向。白天监控拍到的是彩色全身照,到了晚上只能靠红外相机,拍出来是灰蒙蒙的单通道图,同一个行人在两种镜头下可能连衣服颜色都完全对不上。AAAI 2020那篇提出“X Modality”的论文,解决的就是这个场景:训练时让网络自己生成一个额外的辅助模态,把它当中间人,将红外特征和可见光特征拉近。我第一次读这篇论文时,最不理解的恰恰是这个X到底是什么,后来动手拆了一遍网络结构才发现,它的设计比想象中朴素——只是一个共享backbone上的独立全连接分支。这篇文章把论文的动机、结构、损失和复现细节整体过一遍,适合刚入门ReID、正在找跨模态方向论文读的同学,也适合想快速在公开数据集上搭一个baseline的实验党。

1. 红外与可见光之间的鸿沟:这个任务到底难在哪

1.1 从普通ReID到跨模态ReID,多出来的不只是“换个摄像头”

先说说常规的行人重识别。给定一张query行人图,要求从海量gallery图库里找出同一个人的其他图像,这就是ReID。普通ReID能跑起来,很大程度上依赖一个隐藏前提:训练和测试图像都来自可见光RGB摄像头,虽然光照、姿态、遮挡、相机视角都会变化,但底层信号是一致的,颜色、边缘、纹理这些low-level特征在不同图像之间仍然有可比性。

跨模态ReID就是故意把这个前提打破。红外图像没有可见光颜色,亮度分布完全来自热辐射,与反射光成像差了一整个物理过程。于是最直接的问题是:普通ReID里最常用的颜色特征,在红外模态下直接失效。你不能说“那个人穿红衣服”,因为在红外图里红色和蓝色可能都是同一档灰度。

更麻烦的是分布层面的错位。在实际数据里,同一个行人跨模态图像之间的差异,往往大于不同行人同一模态下的差异。换句话讲,网络很容易先学会判断“这张是红外图、那张是可见光图”,而不是努力判断“这是哪个人”。刚开始做这个方向的人,训练loss掉得很快,但跨模态Rank-1怎么都上不去,多半就是陷入了这种模态判别优先于身份判别的状态。

1.2 为什么“子空间对齐”这套老思路在这里会失灵

面对这种大gap,前几年比较主流的做法是把红外和可见光映射到同一个共享特征空间,再拿对比损失、三元组损失甚至对抗损失压着两个分布尽量重合。思路听着很顺,实际做起来却有个绕不开的问题:你并不知道那个所谓的“公共空间”应该长什么样。

如果只是强行让两个模态在某个特征空间里分布重合,那网络大概率会选择偷懒——丢掉那些跨模态不一致但对身份判别很有用的信息,只保留两个模态都认可的那部分普通特征。结果就是对齐是齐了,但每个模态自身的判别性也被削弱了。这有点像让两个语言完全不同的人必须用一种代码说话,结果两人都放弃了自己最擅长的表达,只剩下一套最贫乏的通用词汇。

X Modality这篇论文想换一个角度:既然直接定义公共空间很难,那干脆不要定义具体含义,而是让网络自己学习一个辅助的中间表示。这个表示既不像红外,也不像可见光,作者管它叫“X模态”。训练时拿它当中转站,让两个真实模态绕着它对齐。

2. X Modality不神秘:网络自己生成一个辅助特征空间

2.1 作者没有去“定义”X,而是让网络自己长出来

单看“X Modality”这个名字,很容易误以为作者发明了一种新的传感器或者图像变换。其实不是。论文里的X更像一个待定变量,它不来自任何真实物理成像过程,也不需要被翻译成“伪红外”或“伪可见光”。

做法是在特征层面额外加一个分支。输入仍然是那批红外图像和可见光图像,但这个分支输出的特征既不要求保留红外特征的形式,也不要求保留可见光特征的形式。它就是一个新的、由网络自己塑造出来的辅助特征空间。这个空间里到底该存放什么信息,取决于任务在训练过程中需要什么。

我自己的理解是:X之所以叫X,就是因为它刻意保持“未知”状态。作者并不关注它物理上是什么,只需要它在优化过程中变成一个对红外和可见光都足够友好的邻居。你可以把它看成一道桥,只不过这道桥不是事先画好图纸,而是网络一边走一边铺出来的。

2.2 核心结构:共享backbone加三个独立全连接分支

具体落到网络结构上,这套设计相当简洁。主干是ResNet50,去掉最后的分类层和全局池化,再接一个全局平均池化,得到一个2048维的特征向量。

重点在池化之后。这里不是直接接一个分类器,而是分出三条独立的特征支路:

  • 红外支路:一个独立的全连接层,把2048维压到512维左右;
  • 可见光支路:另一个独立的全连接层,同样把维度压下来;
  • X支路:第三个独立的全连接层,共享backbone提出来的特征,但映射到X特征空间。

每个支路后面可以再接自己的BN层和身份分类器。三个支路的参数互不共享,相当于在同一个卷积骨干上训练了三个不同的embedding函数。

结构图摊开来看就是这样:

输入(红外图像 / 可见光图像) → 共享ResNet50卷积层 → 全局平均池化(2048维) → 三个独立全连接层: FC_ir → 红外特征 FC_vis → 可见光特征 FC_x → X模态特征 → 三个身份分类器 + 跨模态验证损失

这里有个容易误解的点需要注意:X分支的输入并不是把红外特征和可见光特征拼起来,而是复用共享backbone的输出特征。也就是说,对每一张输入图像,网络同时算出红外视角、可见光视角、X辅助视角三份特征。第二个容易踩的坑是,部分实现会在三个支路后面加各自的BN,这三个BN同样不能共享参数,否则等于又偷偷把两个模态的统计量混在一起了。

2.3 为什么训练时把X当成一个“模态”会产生帮助

关键是训练时的监督方式。X分支输出的特征被当作一个真实的模态来参与身份分类,它和红外、可见光共享同一套行人ID标签。这意味着,如果一张红外图和一张可见光图属于同一个人,网络在更新时会让它们的X特征也往同一个身份中心靠近。

这样反复迭代之后,X分支学到的就不再是“某类图像长什么样”,而是“这个人的语义身份该落到特征空间的哪个位置”。当网络再拿它去约束两个真实模态时,相当于提供了一个具体可达的锚点:两个模态在像素层面差异再大,至少在这个辅助空间里能坐下来对齐。

X的另一层价值是它天然充当了“经验共享介质”。红外和可见光各自保留了自己的判别性特征,但它们在X空间里共享身份语义,这就避免了传统子空间对齐那种“两边都被迫放弃专属特征”的问题。可以理解为,每个模态保留自己最擅长的表达方式,同时通过X这个中间方言来完成信息交换。

2.4 和GAN式中间域方法相比,优势在训练稳定性

当时还有一个热门方向是用GAN做跨模态图像生成,比如从红外图生成可见光图,或者把两个模态都转到一个中间图像域,再做ReID。这个思路很直观,效果也有,但代价是GAN训练天生不稳定,生成质量直接决定后续模型上限。

X Modality这种特征层面的辅助模态完全绕开了对抗训练。它不需要重建图像,不会出现“生成的脸是糊的”“颜色偏了”这类问题。付出的代价是解释性弱一些,你很难说清楚X特征里到底编码了什么。但从工程角度看,这是一个很划算的取舍:换来的是训练过程稳定、收敛速度快、复现门槛低。

3. 网络怎么搭:共享骨干、非共享全连接层与三条特征支路

3.1 前向流程拆解:一张图进来,三份特征出去

把前向流程一步步拆开看,整个过程并不复杂。

第一步,输入一张图像,可能是红外也可能是可见光。由于是跨模态训练,batch里会同时包含两种模态的数据。图像进入共享的ResNet50卷积骨干,经过一系列卷积和残差模块后得到feature map,再做全局平均池化,得到2048维的语义向量。

第二步,这个2048维向量被复制成三份,分别通过FC_ir、FC_vis、FC_x三个全连接层。需要特别强调的是,三个全连接层虽然输入相同,但参数完全不同,所以输出的特征分布在训练过程中会逐渐分化:FC_ir擅长从共享特征里挑出红外模态关心信息,FC_vis挑起可见光模态的担子,而FC_x去编码一个更中立的语义表达。

第三步,三份特征分别作身份分类。分类层的目标标签完全一样,都是那张图对应行人的ID,所以本质上网络在学三件不同的事,但最终指向同一个身份语义。

3.2 训练时三分支同步更新,反向传播怎么回到共享backbone

很多人第一次看这种结构会问:三个分支各自有损失,那共享backbone会不会收到互相矛盾的三份梯度?

答案是会,但这不是坏事。红外分支的ID损失要求backbone的特征能反映红外模态下的身份差异,可见光分支的ID损失要求它同时能反映可见光模态下的身份差异,而X分支加上验证损失又在暗中推动backbone把两种模态往公共方向引导。三份梯度如果方向基本一致,那backbone会学到更鲁棒的中层特征;如果个别方向冲突,优化器也会自动协调,通常体现在损失权重上。

实际训练时,一个常见做法是把红外图和可见光图放在同一个batch里前向,共用backbone权重计算特征,然后分别过三个FC。这样反向传播时共享backbone的梯度来自全部三个分支,FC层更新自己的参数,整体看起来像一个多任务学习框架。

3.3 推理时丢弃X分支,为什么性能反而更好

这篇论文一个非常有意思的设计是:X模态只存在于训练阶段,推理时直接丢掉。

推理过程只用两条真实支路:红外图像走FC_ir得到红外特征,可见光图像走FC_vis得到可见光特征,然后通过欧氏距离或者余弦距离做检索。X分支完全不参与。

为什么不保留它?因为X特征本质上是一个虚拟的辅助表征,没有对应的真实图像采集过程。你可以把它理解成训练时的脚手架,盖完楼之后就没必要留着了。更关键的是,丢掉X不影响任何计算量,模型部署时仍是标准双支路结构。这也意味着,X分支的目的不是增加推理特征,而是完全为了训练时拉近两个真实模态的embedding空间。

3.4 复现时最容易搞错的一个细节:X分支的输入源

我在看别人复现这段代码时,发现最容易搞错的地方就是X分支的输入到底是谁的特征。

有人以为X应该输入红外特征和可见光特征融合后的结果,于是在代码里写了cat(feat_ir, feat_vis)。实际操作中,X分支通常直接吃共享backbone的输出。也就是说,同样一张输入图,经过backbone后产生一个共享特征,这个特征分别过三个FC。不需要额外的融合操作,也不需要把红外图和可见光图的特征先对齐再送给X。

这个细节直接影响参数和训练逻辑,一旦搞错,X分支就不再是“第三种视角”,而变成了红外和可见光的混合物,效果会明显变差。

4. 损失函数组合:身份损失加验证损失怎么配比

4.1 身份损失:三条支路各接一个分类器

身份损失在ReID里是最基础的监督信号,这篇论文也没有例外。每条支路后面接一个全连接分类器,把特征映射到训练集行人ID数量维度,再用softmax交叉熵计算损失。

三条支路的分类器可以共用,也可以独立。如果用独立分类器,那每个支路都学习各自的特征到身份映射关系;如果共用,那等于强制三条支路的特征在分类边界上一致,反而会削弱X分支的灵活性。我读论文时的理解是,各支路接独立分类器更符合设计初衷。

损失形式上可以写成:

L_id = CE(clf_ir(feat_ir), y) + CE(clf_vis(feat_vis), y) + CE(clf_x(feat_x), y)

三个分类目标都是同一个y,也就是行人ID。这意味着X特征被赋予了和真实模态同级别的身份语义压力,这是X空间“有用”的根本来源。

4.2 验证损失:让不同模态的同一个人靠近

光有身份损失还不够。身份损失解决的是“这张图是谁”,但没法直接告诉网络“红外里的张三和可见光里的张三应该在特征空间里挨着”。所以论文还引入验证损失,成对地约束跨模态特征距离。

验证损失的对象不是随便选的pair,而是有具体结构的:红外-可见光这一对自然是最核心的,因为推理时就是拿这两种模态做匹配;除此之外,红外-X、可见光-X也各算一对,目的是让两个真实模态都往X空间这个锚点上靠。

对于同一个人ID的pair,验证损失要求它们距离近;不同ID的pair,要求距离远。可以理解成在身份损失之外加了一层更直接的度量学习,只不过它约束的不只是两个真实模态,还把X也拉了进来。

4.3 总损失组合与权重参数的关系

总损失大体是身份损失和验证损失的加权和:

L = L_id + λ * L_veri

λ这个权重很关键。如果λ太小,验证损失形同虚设,红外和可见光特征照样各玩各的;如果λ太大,网络为了满足距离约束,会把所有特征都压到一个很小的球壳上,身份分类精度反而下降。读论文的时候建议特别留意原文是怎么设置λ的,通常在同一数量级范围附近,复现时从1开始调整比较稳。

另外,验证损失内部也涉及一个平衡问题:红外-可见光、红外-X、可见光-X这三类pair,是不是要设置不同权重,论文里各版本实现并不完全一致。我自己复现的简化版是三类pair同等对待,效果已经可以接受,所以如果只是想快速验证X思路,不必一开始就在权重上做文章。

4.4 我在配比损失时的一个直观理解

把整组损失放在一起看,结构其实很清晰。身份损失负责“判别性”,保证每个特征分支都携带足够区分不同身份的信息;验证损失负责“模态不变性”,把不同模态拉到相近区域;X分支则是那个额外的缓冲垫。

从优化的角度看,X分支等于给两个真实模态加了一个共享中间点。没有X时,网络只能直接逼着feat_ir≈feat_vis,冲突比较硬;有了X,网络可以先让feat_ir≈feat_x、feat_vis≈feat_x,真实模态不需要直接折中,而是各自向同一个辅助点靠近,优化更平滑。

5. 实验效果与复现要点:在SYSU-MM01和RegDB上到底提升了什么

5.1 两个主流数据集与常见评测协议

跨模态ReID公开评测主要集中在两个数据集上。

一个是SYSU-MM01,包含大概几百个行人ID,图像来自校园内的可见光和红外摄像头。评测协议通常分成all-search和indoor-search两种模式,query是红外图,gallery是可见光图,指标以Rank-1、Rank-10、Rank-20和mAP为主。

另一个是RegDB,由红外与可见光相机成对采集,行人ID数量大约在400左右。常见做法是随机划分一半身份用于训练、一半用于测试,有些论文会报10折交叉验证,也有些只在单次划分上跑,结果差异会比较大。

读论文看到具体数字时,一定要先确认它用的是哪种划分和协议,否则跨论文比较很容易被误导。

5.2 论文自报结果怎么看:别只看Rank-1

不同论文之间的绝对数值很难直接对比,因为backbone、输入尺寸、是否使用re-ranking、训练集划分都可能不同。这篇论文里,作者更想展示的是X分支带来的相对提升:在同一套baseline、同一套数据协议下,加入X模态辅助训练后,Rank-1和mAP能往上走多少。

具体数字以论文原文为准,印象里在SYSU-MM01的all-search设置下,论文把原来的双支路baseline提升了好几个点;RegDB上也有类似幅度的增益。更重要的是消融实验:把X分支拿掉之后,性能会明显回落。也就是说,提升确实来自X这个辅助特征空间,而不是来自额外的全连接层参数或更长的训练时间。

5.3 复现环境里几个容易被忽略的细节

如果你打算动手复现,有几个细节我会建议特别留意。

第一,红外图像的输入处理。红外相机输出通常是单通道,而预训练ResNet50的输入是三通道。常见的做法是把单通道图复制成三通道,但这种“伪彩色”输入和真实的RGB分布差很多,最好在训练时对全模型做微调,而不是只训分类头。

第二,batch构造方式。验证损失要求同一个ID在同一个batch里同时有红外图和可见光图。如果用普通的随机采样器,一个batch里很可能是清一色红外或清一色可见光,验证损失根本算不了。需要自己实现PairSampler之类的逻辑:先抽P个ID,再对每个ID取K张红外和K张可见光。

第三,BN统计量问题。共享backbone里的BN会把两种模态的统计量混在一起。有些工作会把红外和可见光分开跑BN,能获得更稳定的收敛。X分支后如果接BN,同样面临这个问题,必要时可以试验一下分开BN的效果。

5.4 简化版训练伪代码

下面是一段思路层面的PyTorch风格伪代码,不是为了直接能跑,而是帮大家把结构关系理清:

for ir_imgs, vis_imgs, labels in loader: # 红外和可见光都进同一个backbone feat_ir = backbone(ir_imgs) feat_vis = backbone(vis_imgs) # 三条支路各算各的embedding emb_ir = fc_ir(feat_ir) emb_vis = fc_vis(feat_vis) emb_x_ir = fc_x(feat_ir) # 红外图对应的X特征 emb_x_vis = fc_x(feat_vis) # 可见光图对应的X特征 # 身份损失:三条支路都分类到同一个ID loss_id = ce(clf_ir(emb_ir), labels) + \ ce(clf_vis(emb_vis), labels) + \ ce(clf_x(emb_x_ir), labels) + \ ce(clf_x(emb_x_vis), labels) # 验证损失(示意):同ID拉近,不同ID推远 loss_veri = veri(emb_ir, emb_vis, labels) + \ veri(emb_ir, emb_x_ir, labels) + \ veri(emb_vis, emb_x_vis, labels) loss = loss_id + lambda_veri * loss_veri loss.backward() optimizer.step()

5.5 跨测试集上的一个常见翻车原因

不少人在自己的数据上复现时发现效果不如论文,第一个想到的是调参,但很多时候问题出在评价协议上。SYSU-MM01如果用single-shot gallery和multi-shot gallery,Rank-1差距可能有十来个点;RegDB如果没做10折交叉验证,单次划分的运气成分也不小。所以复现第一步不是调参,而是确认评价方式与论文一致。

6. 阅读笔记与思考:这个思路还能怎么用

6.1 从X Modality想到的横向延伸

X模态本质上是我在前面反复提到的“辅助中间表示”。这个思想并不局限于红外和可见光之间的匹配,对任何两个分布差异较大的模态都有借鉴意义。

比如文本-图像检索,文本特征和图像特征天然不在一个语义粒度上,同样可以引入一个辅助特征空间当过渡;草图-照片识别,草图极度稀疏,照片极度丰富,更需要一个中间表示来弥合差距;跨分辨率ReID也类似,低分辨率和高分辨率的特征分布差异不亚于跨模态。

沿着这个思路去读论文,你会觉得X Modality不只是一个网络结构,更像是一种设计范式:与其强行约束两个主模态,不如在旁边放一个“脚手架”,让主模态通过脚手架完成间接对齐。

6.2 局限性与适用边界

X模态也不是万能的。它依赖一个前提:训练数据里同一个ID必须有足够多的红外-可见光配对,X分支才有机会把身份语义作为锚点。如果你手上是红外数据多、可见光数据严重不均衡,X空间的塑造过程就会偏科。

另外,X特征缺少可解释性,它到底编码了什么,很难用现有工具去可视化。这在学术上可能还好,但如果要做高可靠性的安防系统,这种黑盒辅助特征会让业务方很难信任。

还有一点,X分支的辅助作用是在特定训练域上塑成的,换到域差异极大的新场景,是否需要重新拟合X空间,论文没有展开讨论。这是后续研究可以继续做的工作。

6.3 我实际用到的快速验证方法

如果你想快速判断X Modality对自己任务有没有帮助,不一定要完整复现整篇论文。可以先跑一个标准双流baseline,记录Rank-1;然后在FC层后面只加一个X分支,损失加上X相关的验证约束;其他所有超参数不变。这一步改动不大,代码量增加很小,却能在两三次实验之内看出趋势。

我实际测试下来,X分支对baseline偏低的情况提升最明显。如果baseline本身已经很强,比如Rank-1已经到80%以上,X带来的增益会变小,毕竟空间里能挤出来的对齐余地不多了。所以这个trick更适合中小规模数据集和表现一般的上手模型。

6.4 读这类跨模态论文,我习惯先看损失再翻结构

最后分享一个我读论文的习惯:拿到一篇跨模态方法论文,先画损失函数的结构图,再看网络结构图。因为损失决定了哪些特征会被拉到一起,哪些特征要保持独立,这比看几个方框连成的模型图更能反映出方法本质。

X Modality这篇论文,核心其实就一句话:在双分支对称结构上加一个不需要定义具体含义的辅助特征空间,用身份损失和验证损失双重监督把它训练出来。如果你能把这句话真正吃透,再去看它的实验和后续工作,应该会轻松很多。

复现条件有限的同学可以按我前面说的简化版先试。不用一上来就追求完整复现所有trick,先看X分支在自己的数据上能不能带来正向变化,再决定要不要投入更多调参精力。跨模态这摊子事,很多时候几行代码的改动,效果差异就是几个rank点的距离。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进