ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

CVPR2019人脸防伪检测挑战赛Top3方案:多模态与时序建模实战解读

CVPR2019人脸防伪检测挑战赛Top3方案:多模态与时序建模实战解读 2019年CVPR人脸防伪检测挑战赛的Top3方案我前后仔细啃过好几遍也复现过其中不少代码。今天想把这场比赛的背景、核心网络结构、训练细节和实际踩坑经验整理成一份完整的解读一方面帮做算法研究的朋友省去翻论文的时间另一方面给做产品落地的朋友一些可参考的思路。人脸防伪检测这个方向说直白点就是判断面前这张脸到底是真人的脸还是照片、视频、面具或者屏幕翻拍。随着人脸识别在支付、门禁、手机解锁里越来越普及这类攻击手段也变得越来越精细。CVPR2019那届挑战赛正好赶上行业从学术研究向工程落地过渡的关键节点Top3方案基本代表了当时最实用的一批技术路线代码开源程度也比较高很适合用来做基线复现和技术选型。这篇文章适合三类人读一是刚入门人脸防伪检测的研究生需要一个靠谱的起点二是做实名认证、金融风控、安防闸机的算法工程师想了解Top3方案里哪些trick能迁移到业务里三是纯粹对多模态学习和域泛化感兴趣的读者可以从一个具体比赛中看到这些技术如何协同工作。我会把任务定义、数据模态、网络设计、训练配置、问题排查一条线讲清楚不绕弯子。1. 先搞清楚任务人脸防伪检测的痛点和挑战赛的赛制1.1 人脸防伪检测到底是什么人脸防伪检测英文一般叫Face Anti-spoofing也有人叫Presentation Attack Detection。它做的是判断人脸识别系统面前的对象是否来自真实用户本人而不是打印照片、手机屏幕翻拍、3D面具或者视频回放。很多人容易把它和人脸活体检测混淆实际上活体检测更强调“这个人是不是活的”而防伪检测更关注“呈现给摄像头的媒介是否合法”。在学术数据集里最常见的攻击类型是打印攻击、屏幕重放攻击和面具攻击。这个任务为什么难因为攻击形式和真实人脸在二维图像上非常接近。一张高清晰度的屏幕照片在普通RGB摄像头下纹理细节可能和人脸几乎一样。算法需要找出那些人类用肉眼不太容易注意到的线索比如屏幕的摩尔纹、打印纸的反射、边缘的平面性、双目图像的视差或者红外反射的差异。这些线索往往极其微弱而且受摄像头型号、光照、角度影响非常大所以单一模态很难覆盖所有攻击类型。CVPR2019人脸防伪检测挑战赛的核心意义就是把这些真实问题摆到台面上用相对统一的评测协议来比较不同方案。比赛提供的训练集通常包含多种传感器模态同时要求模型对未见过的环境和攻击方式有一定泛化能力。这个设定非常贴近实际因为部署到新设备时你不可能重新采集大量真实攻击数据。1.2 CVPR2019挑战赛的数据与评测方式据公开资料这届挑战赛主要基于OULU-NPU和SiW这类数据集评测上强调跨场景和跨攻击类型的泛化。参赛者既可以用RGB单模态也可以使用深度图、红外图以及它们组合的多模态输入。多数Top团队的最终方案都选择了多模态原因很简单单一RGB在跨域情况下掉点太严重。比赛通常采用ACER作为主要指标即错误接受率和错误拒绝率的平均值。这个指标要求模型在“放行真人”和“拦截攻击”之间取得平衡单看AUC或者单看AP都不够全面。我在实际复现时也发现很多模型在训练集上AP能到99%但ACER却不理想原因就是真实类与攻击类之间的决策阈值很难同时满足两类错误。评测协议的细节很关键。有些赛道要求帧级打分有些要求视频级投票。Top3方案通常会做视频级的时序融合因为单帧判断很容易受到偶发噪声的干扰而连续的几帧中攻击特征往往更稳定。这里可以理解为人脸像是一个不断流动的信号攻击媒介带来的伪影在时间维度上会有规律地出现比如屏幕刷新闪烁、手持打印照片的轻微抖动。1.3 Top3方案的整体画像从公开的论文和代码来看Top3方案并不是那种特别花哨的网络反而非常务实。它们都有几个共同点第一使用预训练的轻量级骨干网络而不是从头训练超大模型第二把RGB、深度、红外等模态在特征层做融合而不是简单在输入层拼接第三引入长短时记忆网络或者3D卷积来处理视频帧序列第四非常重视数据增强和域随机化。我整理了一张表方便对照理解Top3方案里几个关键维度的常见选择维度常见做法我的理解输入模态RGB Depth IR 多模态多模态互补比单模态鲁棒骨干网络ResNet18、MobileNetV2、EfficientNet参数量适中方便训练和部署时序建模LSTMs、ConvLSTM、3D卷积利用帧间一致性抑制突发噪声注意力机制空间注意力、通道注意力、帧注意力让模型聚焦人脸关键区域损失函数交叉熵 三元组或中心损失同时保证分类和特征判别性训练策略多尺度裁剪、颜色抖动、光照扰动模拟真实环境差异这些选择背后有一个共同逻辑防伪检测不是单纯的图像分类它同时需要模型具备细粒度纹理感知和时序状态感知。所以单纯靠加深网络很难解决反而是“合适的骨干 多模态 时序”这条路更稳。2. 拆解Top3方案的核心技术路线2.1 多模态输入RGB、深度与红外怎么融合多模态是这届比赛最明显的分水岭。RGB能提供颜色和纹理但对屏幕翻拍、打印照片的区分能力有限深度图能直接反映物体表面是平面还是立体对打印攻击和屏幕攻击特别有效红外图则能捕捉活体皮肤与纸张、屏幕的热辐射差异对光照变化不敏感。融合方式很讲究。直接在输入层把三个模态拼成一个多通道张量会让卷积核同时处理来自不同传感器的数据但模态之间的噪声和分辨率差异会互相干扰。Top3方案里更常见的做法是在每个模态分别提取特征然后在中间层做融合。比如RGB分支和深度分支各自经过几个残差块得到大小相同的特征图再通过拼接或者加权求和合并。这样做的好处是每个分支可以先学习各自模态的低层特征高层再去做语义层面的互补。具体融合点通常选在网络的最后几个下采样阶段。太早融合低层特征还没分开太晚融合可学习的特征粒度不够。我复现时的经验是在ResNet18的第二个和第三个残差块之后各做一次融合比只在最后一层融合效果更好尤其对细小的屏幕摩尔纹和纸面反射更敏感。2.2 骨干网络选型轻量级还是大模型比赛里常用的骨干是ResNet18、ResNet34和MobileNetV2。很多人会问为什么不直接上ResNet50或者更深的模型效果不是更好吗。这里有个现实约束比赛评测通常限制推理速度而且人脸防伪模型需要嵌入到实时视频流里太重的骨干在边缘设备上跑不动。更重要的是防伪检测需要关注高频纹理细节过深的骨干可能会在多次下采样中把这些细节滤掉。MobileNetV2的好处是计算量小但需要仔细调参。ResNet18相对中庸有成熟的预训练模型迁移学习非常方便。Top3方案里也有用EfficientNet-B0或者B1的能效比很突出但复现时要特别注意输入分辨率。我建议你从ResNet18开始先把数据流程和训练闭环跑通再切换到MobileNetV2做部署优化。另外骨干网络的输入分辨率值得专门调一下。很多人习惯用224x224但对防伪检测来说人脸区域往往只占画面的一小部分224x224可能丢失太多细节。不少Top方案会把输入切到256或者288并且配合中心裁剪和人脸对齐。这样模型能看到更多皮肤纹理和屏幕光栅特征ACER会明显下降。2.3 时序与注意力为什么单帧不够用单帧模型最大的问题是不稳定。一张图上的光斑、反光、噪点都可能被误判成攻击信号而且打印照片和屏幕翻拍在静止状态下单帧特征确实很难区分。引入时序信息后模型可以观察人脸区域在一段时间内的变化模式。真实人脸的微表情、眨眼、头部自然晃动都会带来规律的帧间差异而攻击媒介通常是静止的或者只有整体的平面运动。实现时序建模的常见做法有两种。一种是先用2D卷积逐帧提取空间特征再把特征序列送入LSTM或者GRU最后取最后一个时间步的输出做分类。另一种是直接把多帧堆叠成3D张量用3D卷积或者(21)D卷积建模。Top3方案里两者都有但从复现难度来看2D卷积LSTM更稳定收敛更快也更容易调试。注意力机制在时序建模里也很有用。空间注意力可以让模型只关注鼻子、眼睛周围这些活体特征明显的区域而不是整个背景通道注意力可以动态调整RGB特征和深度特征的权重帧注意力则是让模型自动选出信息量最大的几帧降低模糊帧对结果的干扰。我在实验中发现帧注意力对视频级ACER的改善非常明显尤其在有人脸遮挡或者短暂出画的情况下。2.4 域泛化从实验室到真实场景的关键一跃跨域是防伪检测落地最头疼的问题。在训练集上表现优异的模型换一个摄像头、换一种光照、换一种攻击方式准确率可能直线下降。Top3方案之所以突出很大程度上是因为它们在域泛化上做得更好而不是单纯在测试集上刷分。域泛化的手段大致分三类。第一类是数据层面通过色彩抖动、高斯模糊、随机遮挡、Cutout等模拟不同成像条件第二类是特征层面使用对抗训练或者梯度反转让模型学习与域无关的特征第三类是损失函数层面引入域分类损失或者最大均值差异约束让不同域的特征分布尽量对齐。我个人不太建议初学者一上来就做对抗训练收敛不稳定排查困难。先把数据增强做足再把批大小调大配合MixUp或者CutMix这类简单有效的方式就能拿到不错的跨域结果。数据增强的幅度要循序渐进太猛会让模型学不到有效纹理太弱又容易过拟合。实际项目中可以先用标准增强然后根据验证集跨域误差逐步加大。3. 论文代码复现从数据集准备到训练评估3.1 环境与数据准备复现一个比赛方案环境踩坑往往比网络设计更浪费时间。我的建议是固定一套环境不要频繁升级依赖。PyTorch 1.8到2.x都可以但要注意CUDA和GPU驱动的匹配。训练人脸防伪模型主要吃显存一个ResNet18加上三模态输入和视频序列如果用32的batch size8GB显存比较紧张建议至少准备16GB或者降低帧数和分辨率。数据组织方式会影响数据加载效率。可以把每个视频拆成固定长度的帧序列做成索引文件而不是把所有帧都读进内存。比赛数据一般会提供人脸框或者landmark复现时先做人脸对齐把脸裁剪到统一尺寸再用裁剪后的图做训练。这一步千万别省对齐后模型收敛速度和最终精度都会有明显提升。我通常会把数据集划分成训练集、验证集和测试集。验证集里包含和训练集不同的场景这样能尽早看到模型是否过拟合。评测时人脸框的检测质量也值得关注如果人脸框有抖动可以加一点时序平滑否则帧级预测会非常不稳定。你可以先把人脸检测模型固定住用相同的框去裁剪训练集和测试集保证实验可对比。3.2 网络结构的代码示意下面给出一个简化版的PyTorch实现思路它结合了多模态分支和时序LSTM是我觉得最适合作为起点的结构。这个结构不是某个Top3方案的完整代码但核心设计一致。import torch import torch.nn as nn import torchvision.models as models class SpatialBranch(nn.Module): def __init__(self, backbone_nameresnet18, out_dim512): super().__init__() if backbone_name resnet18: self.backbone models.resnet18(pretrainedTrue) self.backbone.fc nn.Identity() feat_dim 512 elif backbone_name mobilenet_v2: self.backbone models.mobilenet_v2(pretrainedTrue) self.backbone.classifier[1] nn.Identity() feat_dim 1280 self.fc nn.Sequential( nn.Linear(feat_dim, 256), nn.BatchNorm1d(256), nn.ReLU(inplaceTrue), nn.Linear(256, out_dim) ) def forward(self, x): feat self.backbone(x) return self.fc(feat) class AntiSpoofModel(nn.Module): def __init__(self, num_classes2, use_depthTrue, use_irFalse): super().__init__() self.rgb_branch SpatialBranch(resnet18, out_dim256) self.depth_branch SpatialBranch(resnet18, out_dim256) if use_depth else None self.ir_branch SpatialBranch(resnet18, out_dim256) if use_ir else None self.lstm nn.LSTM( input_size256 * (1 int(use_depth) int(use_ir)), hidden_size256, num_layers1, batch_firstTrue, bidirectionalTrue ) self.classifier nn.Sequential( nn.Linear(256 * 2, 128), nn.ReLU(inplaceTrue), nn.Dropout(0.5), nn.Linear(128, num_classes) ) def forward(self, rgb_frames, depth_framesNone, ir_framesNone): # rgb_frames: [B, T, C, H, W] B, T, C, H, W rgb_frames.shape rgb_feats [self.rgb_branch(rgb_frames[:, t]) for t in range(T)] features [rgb_feats[t]] if self.depth_branch is not None: features.append(self.depth_branch(depth_frames[:, t])) if self.ir_branch is not None: features.append(self.ir_branch(ir_frames[:, t])) fused_feats [torch.cat(f, dim-1) for f in zip(*features)] fused_feats torch.stack(fused_feats, dim1) # [B, T, D] lstm_out, _ self.lstm(fused_feats) lstm_out lstm_out[:, -1, :] # 取最后一个时间步 logits self.classifier(lstm_out) return logits这段代码有几个细节需要说明。第一每个模态分支共享相同的骨干结构但不共享权重因为RGB、深度和IR的特征分布差异很大。第二LSTM用了双向结构可以同时参考前后帧信息比单向LSTM更稳但推理时对实时性有一点影响。第三分类器里加了Dropout比赛模型经常会在视频帧上过拟合Dropout能明显改善泛化。3.3 训练配置与关键参数训练配置我建议用AdamW优化器初始学习率1e-4配合warmup和余弦退火。批量大小尽量拉大如果显存不够可以减少时间步长或者降低分辨率。损失函数可以用交叉熵加一个辅助的对比损失对比损失可以帮助模型把真实人脸和攻击人脸在特征空间里拉开距离而且对跨域有一定帮助。一个非常重要的参数是帧采样方式。训练时不要只取视频的前几帧应该随机抽取连续片段比如每段8帧每隔2帧采样一次。这样做可以模拟真实视频流中的各种状态。验证和测试时可以采用滑动窗口对整段视频打分再把所有窗口得分平均最终得到视频级分数这样会比只看一帧稳定很多。数据增强的顺序也有讲究。先做随机裁剪和水平翻转再做颜色抖动和光照扰动。不要使用旋转超过15度的增强因为人脸防伪检测中姿态剧烈变化反而会引入歧义。Cutout和RandomErasing可以少量使用概率设在0.2左右。深度图的增强要和RGB保持空间一致性比如同一个随机裁剪区域都要作用到三个模态上否则模态之间会对不齐。3.4 评估指标与结果解读训练完模型不要只盯着测试集准确率。在比赛复现中我建议分别计算真实视频的错误拒绝率和攻击视频的错误接受率然后计算ACER。很多在线下表现不错的模型在线上会因为阈值设置不合理而翻车所以你需要从验证集上找到等错误率对应的阈值再用这个阈值去评估测试集。另外模型输出的分数分布非常值得画出来看。如果真实人脸和攻击人脸的分数分布有很大重叠说明模型并没有真正学到判别性特征这时候需要调整损失函数或者引入更难的数据样本。如果两个分布分开得比较好但ACER仍然高通常问题出在个别攻击类型上比如面具攻击或者极端光照下的屏幕攻击可以单独统计每个子集的准确率找出短板。我在复现时还发现多模态模型如果测试时某个模态缺失性能会明显下降。比赛里数据比较干净但落地时摄像头可能只有RGB没有深度或者红外。所以训练时要有意识地做模态丢弃以一定概率随机把深度或红外分支置零让模型学会在模态缺失情况下依然输出合理结果。4. 实际踩坑常见问题与排查技巧4.1 模型在训练集上爆表、测试集上翻车这是复现比赛代码最常见的问题。导致这个现象的原因通常是训练集和测试集的分布差异太大模型记住了训练集里的背景、摄像头噪声或者人脸框位置。我的排查流程是先看训练集和验证集的可视化特征分布用t-SNE把特征画出来如果各个域之间分得很开说明模型的域泛化能力不足。解决办法可以从增强入手。把颜色抖动范围加大加入随机亮度和对比度扰动再做高斯模糊模拟失焦。另一个有效手段是做多尺度训练同一张图用不同分辨率输入让模型不再依赖某个固定的尺度。对抗域泛化技术虽然更高级但调参成本很高建议先用这些基础手段把差距补上来。还有一个容易被忽视的问题数据泄露。人脸对齐时候如果使用了测试集视频的人脸框做校准算是一种间接泄露。比赛代码里一般不会犯这种错误但自己采集数据时很容易踩。严格做法是只用训练集拟合人脸检测器或者用完全固定的检测器处理所有数据。4.2 深度和红外模态缺失怎么办实际部署时很多场景只有普通摄像头没有深度传感器和红外传感器。这时你只能靠RGB单模态模型但可以先用比赛数据训练一个多模态教师模型再用单模态学生模型去蒸馏把深度和红外模态学到的知识迁移到RGB特征中。这种方法在比赛方案里也有变体实现起来不算复杂。具体做法是冻结训练好的多模态模型让RGB学生模型去模仿它在中间层输出的特征图损失函数用均方误差。这样做比直接用RGB做有监督训练效果更好因为学生模型能够间接学到深度模态对平面攻击的判别线索。我在实验中蒸馏模型能把单模态ACER降低2到4个百分点代价是训练过程需要同时运行教师和学生网络显存占用更高。如果要求实时部署还可以在输入端做轻量级的伪深度估计用一个小网络从RGB预测深度图再把预测深度作为辅助输入。但要注意预测的深度图噪声较大和真深度图分布不一样需要重新用数据增强适配。4.3 光照、遮挡和换脸攻击怎么处理光照变化是防伪检测的老大难。强光会让屏幕翻拍上的摩尔纹淹没暗光会让RGB图噪点增加。常规做法是训练时对亮度做随机弹性变化还可以用光照归一化算法对输入做预处理。但要注意归一化算法本身可能丢掉关键的反射线索需要验证是否有效再决定是否保留。遮挡问题常见于口罩、帽子和手部遮挡。Top3模型一般通过行人脸的局部区域特征来做判断比如只关注眼睛和额头周围。训练时可以用随机遮挡和Cutout来模拟这种情况让模型不至于因为局部看不到就盲目判负。还有一种思路是引入基于人脸关键点的局部注意力把关键点周围的特征单独抽出来做辅助分类。换脸攻击在CVPR2019之后才慢慢多起来当年的比赛涉及较少。但如果你想扩展可以用人脸交换工具生成一些换脸数据加入训练集中配合频域特征或者像素级重建误差来检测。频域特征可以通过傅里叶变换得到换脸图像往往在高频细节上有明显差异这个方向值得单独做实验。4.4 模型压缩与实时推流部署比赛方案里的模型如果原封不动搬到端侧大概率跑不动。我在部署时通常走三步第一步把骨干从ResNet18换成MobileNetV3-Small精度损失不大速度提升明显第二步把LSTM改成单向的只保留两帧的时序信息或者直接用时间维度的平均池化第三步做INT8量化量化后对比ACER变化如果掉了太多就保留中间层为FP16只对卷积层量化。实时推流还有一个细节帧率。模型不需要对每一帧都推理可以用一个滑动窗口每处理完一帧的结果后利用相邻帧的相似性做结果平滑。判断“真人”时可以放宽阈值判断“攻击”时再用更严格的阈值这样能减少对真实用户的误拒同时保持对攻击的拦截率。加速时还可以用TensorRT或者ONNXRuntime。转换过程中要注意LSTM的算子兼容性有些框架对RNN支持不太好如果遇到问题可以先把时序部分拆出来用CPU计算只把空间卷积部分放到GPU或者NPU上。4.5 常见问题速查表现象可能原因解决办法训练集AP很高测试集ACER差过拟合、域差异大加强数据增强、加入对抗域泛化或蒸馏深度图优化效果不明显深度图和RGB对齐不准检查人脸框同步确保空间对齐一致LSTM训练不收敛序列长度过短、学习率太高增加时序长度到8或16降低学习率推理时延高双向LSTM或大分辨率输入换成单向LSTM、降低输入分辨率、量化模态缺失后性能崩训练时没有模态丢弃在输入前随机置零某个模态分支视频级结果抖动大帧级预测不稳定用滑窗平均或加帧注意力模型屏幕翻拍检测率低摩尔纹特征不明显提高输入分辨率增加屏幕翻拍数据增强这张表基本覆盖了我复现和部署过程里遇到的大多数问题。如果你遇到某个具体的怪现象建议从数据管道开始排查先确认输入张量是否正确再逐步查看每一层特征输出很多时候是预处理顺序出了错而不是网络结构有问题。5. 从比赛到落地我复盘后的几条经验5.1 别迷信单点技巧先搭好数据闭环复现Top3方案最大的收获是意识到一个工程项目的成功往往不是靠某个惊艳的模块而是靠一条完整、稳定的数据闭环。从数据采集、人脸对齐、模态配准到模型训练、阈值选择、端侧部署任何一环松掉整体效果都会打折扣。比赛方案里的注意力、对比损失、多模态融合都是锦上添花基础数据质量和评测协议才是地基。我见过很多团队在数据采集上非常随意随便找几段视频就开始训练结果模型在实验环境跑得很好一到线上就失效。正确的做法是建立一套持续更新的攻击样本库包括不同打印机、不同手机屏幕、不同面具材质。这样模型才能真正学到区分“真实皮肤”与“非生物介质”的本质特征而不是记住某些特定样本。5.2 基线模型的质量往往决定上限很多参赛者喜欢堆模块但Top3方案几乎都先跑通一个简单的RGBResNet18基线再逐步加入深度、时序和注意力。基线模型的意义不只是提供初始分数更是你判断每一步改进是否有用的参照。我在复现时每次改动只会调节一个变量用ACER对比记录这样能很清楚地知道什么trick有效什么trick只是过拟合。另外预训练权重非常重要。从ImageNet预训练开始微调比随机初始化训练快很多效果也稳定得多。如果训练数据量不大不妨试试先在公开的大规模人脸反欺诈数据集上预训练一轮再在目标场景上微调。这种方法在Top3方案的实现中也很常见能有效缓解小数据下的过拟合问题。5.3 后续扩展主动防御与多模态融合人脸防伪检测并不会因为一个比赛就结束。现在已经有很多方向值得继续做比如利用随机光照或者主动投影来增强活体特征让攻击媒介很难模仿动态光影变化再比如融合毫米波雷达或者热成像从更深层的物理特性上区分真假人脸。比赛里学到的基础网络和融合思路迁移到这些新模态上依然成立。如果你正在做相关课题建议把Top3方案作为起点而不是终点。先复现再剪枝最后针对自己的数据特点做定向优化。这个顺序十多年里反复被验证有效也是我写这篇解读最想传达的东西。保持对数据和场景的敏感比执着于某一个模型结构更能带来长期收益。
返回列表