
人脸防伪检测这活儿比赛里拿名色是一回事放到实际落地场景又是另一回事。CVPR2019人脸防伪检测挑战赛是活体检测领域绕不开的一场硬仗任务要同时对付打印照片、视频重放、3D面具三类呈现攻击评测指标也不是普通图像分类那种accuracy而是综合了误报率和漏报率的ACER。当年Top3队伍的方案基本上把学术界对呈现攻击检测的理解浓缩进了论文、代码和模型权重里多颜色空间特征、时序建模、跨域泛化、细粒度纹理分析每一样拿出来都值得反复嚼。这篇文章我就按复现的视角把Top3方案里最有价值的设计拆清楚讲明白每个选择背后的动机再把训练、评估、部署环节里容易踩的坑一并整理出来。如果你是刚接触活体检测的研究生或者正在做刷脸支付、门禁机、银行App的人脸核验这套思路应该能直接帮你少走好几个月的弯路。文章不堆理论推导重点放在“为什么这样做”和“实际怎么落地”。1. 先理解任务本身三类攻击与ACER指标1.1 攻击方式的物理本质决定了技术路线挑战赛的任务定义很清晰给定一段人脸视频判断是真实活体还是某种呈现攻击。这里面有三类典型的攻击手段物理本质完全不同所以不能用一套特征打天下。第一类是打印照片攻击把一张人脸照片打印在A4纸上对着摄像头晃动。它的破绽在于纸张表面是二维平面缺乏真实人脸的立体结构而且打印油墨的反射特性、颜色光谱和真实皮肤有明显差异。只要摄像头分辨率够高纹理层面的差异就能被网络捕捉到。第二类是视频重放攻击用一个平板或手机播放目标人物的视频再对着摄像头。这类攻击的破绽在屏幕本身刷新率带来的摩尔纹、屏幕玻璃的反光、视频压缩留下的块效应以及画面边缘偶尔露出的屏幕边框。这些都属于“非自然人脸”的高频伪影。第三类是3D面具攻击用硅胶面具或者3D打印的立体面具戴在脸上。这个最难防因为人脸的三维结构是保真的纹理也接近皮肤。但面具材质和真实皮肤在微观反射、毛孔细节、特别是近红外波段的吸收特性上仍然有差别。Top3方案的共同点就是先意识到攻击的差异主要体现在物理介质上而不是人脸本身的语义内容。所以单纯把RGB图丢进分类网络虽然能拿到不错的训练集指标但换一个采集设备、换一种攻击介质模型立马崩。比赛的测试集恰恰就有这种跨域差异这也是为什么最后能排到Top3的方案都不是“裸二分类”。1.2 ACER指标怎么算为什么不能只看准确率比赛用的核心指标是ACER全称是Average Classification Error Rate由两部分组成APCER攻击呈现分类错误率把攻击样本误判为真实人脸的比例。这个指标对应的是安全漏洞漏一次可能就是一次资金损失。BPCER正常呈现分类错误率把真实人脸误判为攻击的比例。这个指标对应的是用户体验误拒太高用户会骂产品难用。ACER就是两者的平均值ACER (APCER BPCER) / 2。这个设计很聪明它强迫参赛者不能只追求“把攻击全部拦下”因为那样会把阈值设得极严BPCER爆表真实用户全被拦在门外。也不能只追求“让所有真实用户通过”否则攻击样本漏成筛子。正确的做法是在验证集上枚举不同阈值画出DET曲线取ACER最小时对应的那组参数。我自己复现时的经验是不要用最后一个epoch的模型直接评估而是把训练过程中每个epoch的模型在验证集上跑一遍ACER选最优的快照。比赛里很多队伍包括我自己都在这里栽过跟头——训练loss还在降但ACER已经反弹了说明模型开始过拟合攻击介质的细节而不是学习“活体”本身的共性。1.3 数据切分与跨域协议别忽视挑战赛的训练集和测试集在采集环境、摄像头型号、光照条件上有明显差异这就是典型的跨域评估。很多论文里会看到“留出协议”的说法意思是训练集里有一部分不参与训练专门用来模拟未知环境。复现的时候一定要重视这个跨域特性。如果只在训练集上做随机切分去调参你得到的阈值和增强策略很可能在测试集上无效。我最开始复现时用随机切分的验证集选阈值ACER看着只有1%出头结果换到另一个摄像头采集的测试集上直接飙到8%以上。后来改成按“不同环境留出”的方式做验证才和比赛的最终结果对得上。2. Top3方案的总体设计思路与选型逻辑2.1 为什么不能只靠RGB二分类2019年的时候很多人脸防伪论文的基线就是一个ResNet在RGB图上做二分类训练集能刷到99%的准确率换到测试集就拉胯。原因在于RGB三通道包含了过多的环境信息肤色、光照、背景、相机色彩还原能力。网络很容易偷懒比如记住了某个特定相机偏暖的色温或者训练集背景里特定的纹理而不是真正去区分“活体皮肤”和“打印纸张”。Top3方案在输入层面就做了改变。最常见的做法是把RGB图像拆成多个色彩空间并行输入典型组合是RGB保留原始颜色信息用于提取语义层面的特征。YCbCr把亮度分量Y和色度分量Cb/Cr分离。打印油墨和屏幕刷新在色度分量上的伪影比RGB空间更明显。HSV色相、饱和度、明度分离对光照变化更鲁棒能突出皮肤和纸张在饱和度上的差异。每个色彩空间单独过一个子网络再把特征拼起来做融合。这样做的本质是用不同的颜色表示方式强制网络关注不同的物理特征。打印照片的油墨在YCbCr空间会留下规律的色度条纹真实皮肤则不会屏幕重放的画面在HSV空间饱和度分布和真实环境光下的人脸完全不同。我还看到有的方案会在输入端叠加一个频域变换分支对图像做拉普拉斯或高斯差分提取高频细节。这是因为打印和屏幕重放会引入非自然的边缘锐度真实摄像头拍摄的皮肤边缘是柔和的。这个分支可以利用预计算的边缘图也可以把高频滤波当成一个可学习的卷积层加进网络。2.2 时序信息比单帧特征更值钱比赛提供的是视频数据但很多参赛队一开始只取单帧做分类。Top3方案几乎都用了时序信息区别只在怎么用。为什么时序重要因为真实活体人脸有自然的面部微动眨眼、嘴唇微微张合、头部无意识的晃动、呼吸带来的轻微形变。而攻击视频尤其是打印照片攻击往往是静止的或只有大幅度晃动视频重放攻击则是把屏幕上的画面原样播放帧间运动和真实摄像头拍摄的人脸运动模式完全不同。最常用的时序建模方式有几种帧级CNN特征 GRU把视频按固定间隔抽帧每帧过CNN提取特征向量然后按时间顺序输入GRU最后取最后一个隐状态或全部隐状态的平均做分类。时序统计池化把多个帧的特征做均值和标准差拼接用一阶和二阶统计量刻画动态变化。这个做法的好处是计算量小不需要训练循环网络部署时特别方便。光流分支额外计算相邻帧之间的光流图输入一个小的子网络。重放攻击的光流模式通常是整体平移周期性闪烁和真实人脸的局部微小形变区分度很高。我自己的复现结论是GRU方案在比赛指标上略好但训练和推理都更重时序统计池化在性能和计算量的权衡上更优尤其适合后续要落地到手机端或者边缘设备的场景。如果你不追求那零点几个百分点的ACER优势我强烈建议先用统计池化。2.3 主干网络选型Xception和ResNet的取舍Top3方案里出现频率最高的主干是Xception其次是ResNet50。Xception用的是深度可分离卷积同样的计算量下能提取更丰富的纹理特征而且参数量比ResNet50小得多。对于人脸防伪这种需要捕捉细粒度材质差异的任务Xception的“每一层都能单独学到空间相关特征”的特性非常合适。选型时还有两个实际考量第一输入分辨率不要太小。人脸防伪依赖的是材质纹理比如打印纸的网点、屏幕的摩尔纹这些是高频信息。如果按ImageNet习惯用224×224输入很多关键纹理已经被下采样抹掉了。Top3方案普遍把输入分辨率提到256×256甚至更高人脸区域裁剪后还要保证足够的分辨率。第二主干的输出特征要选中间层而不是最后一层。最后一层全局池化后的特征太抽象丢失了空间细节。更好的做法是取主干网络的倒数第二或第三个卷积模块输出的特征图保留空间分辨率再做后续融合。我用Xception做实验时发现相同训练配置下用倒数第二个block的输出特征比用最后一层的ACER低了将近1个百分点而且收敛更快。原因也简单防伪判别本质上是一个细粒度纹理问题空间位置信息比高度抽象的语义信息更重要。3. 核心模型结构与训练细节深度拆解3.1 多流CNN时序聚合的完整结构把Top3方案的思路整合起来一个可复现的完整模型结构大致长这样输入层视频帧序列每帧先做人脸检测和对齐裁剪出人脸区域缩放至256×256。对每一帧生成三个分支的输入RGB图、YCbCr图、HSV图另外可选一个拉普拉斯边缘图分支。特征提取层三个分支分别过一个Xception或ResNet共享参数或独立参数都可以。独立参数的优点是每个色彩空间学到自己特有的特征缺点是参数翻三倍。2019年比赛时大家为了冲指标都选独立参数但实际落地时共享参数往往更稳泛化也更好。特征融合层三个分支的特征图按通道拼接然后过一个1×1卷积降维。这一步的目的是让不同色彩空间的特征在通道维度上对齐同时给网络一个学习通道注意力权重的机会。我复现时加了一个轻量SE模块就是简单的全局平均池化两个全连接层ACER又有小幅度下降。时序聚合层对采样出的N个帧的特征序列做时序整合。用GRU的话取最后时刻的隐状态用统计池化的话计算特征向量的均值和标准差然后拼接。分类层一个全连接层输出2维logits或者1维标量分数接softmax或sigmoid。这个结构是当时Top3方案的“最大公约数”。具体到每个队伍的论文可能有各种细节差异比如融合方式是相加还是拼接时序模块是GRU还是LSTM但整体都是这个套路。3.2 损失函数与样本不均衡的处理人脸防伪训练集的正负样本比例通常不是极端的失衡但难易样本的分布很不均衡。打印攻击很容易被识别贡献的loss很快就趋近于零而3D面具攻击和某些高质量屏幕重放很难识别梯度信号不足。推荐的损失组合是交叉熵加上Focal Loss或者直接用Focal Loss替换交叉熵。Focal Loss的公式在原文里有详细定义核心思想是给容易样本降低权重给难样本加大权重。我在复现时把Focal Loss的gamma设为2alpha设为0.75比纯交叉熵在ACER上稳定低0.5个百分点左右。除了Focal Loss还可以叠加一个度量学习损失比如Center Loss或者Triplet Loss。目的很明确让真实人脸的特征在特征空间里聚拢让攻击样本和真实人脸拉开距离。单纯用分类损失网络只要学出一个线性可分的边界就算完事但边界的余量可能很小加上度量学习损失之后特征分布的紧凑性大幅提升换设备、换场景时的鲁棒性会好很多。训练时还有一个细节容易被忽略验证集的作用不只是选阈值还要用来早停。我建议在每个epoch结束后在验证集上计算ACER记录最优模型权重。不要等到训练完再统一挑因为loss最小的epoch大概率不是ACER最好的epoch。3.3 数据增强模拟攻击介质是关键人脸防伪的数据增强和其他视觉任务不太一样光做随机翻转、色彩抖动远远不够。Top3方案的论文里反复强调的一个观点是要主动模拟攻击介质的物理特性。我自己复现时加了这几类增强效果都很明显纸张纹理模拟在人脸区域随机叠加细微的噪点纹理、轻微的高斯模糊模拟打印照片在摄像头下的粗糙质感。可以用OpenCV生成随机噪声按小权重叠加到图像上。摩尔纹模拟对图像叠加周期性的正弦波纹模拟屏幕重放时摄像头拍到的摩尔纹。幅度和频率都要随机不然网络会记住固定的波纹模式。屏幕反光模拟在图像局部区域叠加高光渐变模拟玻璃屏反光。Random Erasing随机遮挡一部分人脸区域强迫网络不要依赖某个局部特征做判断。这些增强操作不能做得太过否则训练集和真实数据的分布差距会拉开。我做实验时把增强幅度从0.3逐步调大到0.7ACER先降后升0.5左右是效果最好的区间。另外别忘了时间维度的增强对视频序列做随机抽帧有的用2帧有的用4帧有的用8帧。这样网络在不同帧数下都能工作推理时也就不用严格卡一个固定帧数。4. 代码复现全程与模型落地实操4.1 数据预处理与训练验证流程复现的第一步是把原始视频转换成训练所需的格式。我用的流程是用MTCNN或RetinaFace对每帧图像做人脸检测拿到人脸框。按人脸框裁剪缩放到256×256。为保证时序一致性不用单帧检测结果而是对视频前几帧检测到的人脸框做平滑再应用到整段视频的所有帧。防止人脸框抖动导致输入不稳定。对每帧生成RGB、YCbCr、HSV三个色彩空间的输入。训练/验证数据切分我采用的方式是把视频ID作为划分单元同一人的所有视频只能出现在训练集或验证集中不能跨集。这样能避免网络偷看同一人的特征评估结果更可信。训练主循环的伪代码大致如下for epoch in range(num_epochs): for batch in train_loader: # batch包含rgb, ycbcr, hsv三个输入和一个标签 rgb_feat rgb_backbone(rgb) # [B, C, H, W] ycbcr_feat ycbcr_backbone(ycbcr) hsv_feat hsv_backbone(hsv) fused cat([rgb_feat, ycbcr_feat, hsv_feat], dim1) fused fusion_conv(fused) # 1x1卷积降维 fused se_module(fused) pooled temporal_stat_pooling(fused) # 均值标准差 logit classifier(pooled) loss focal_loss(logit, label) loss.backward() optimizer.step() val_acer evaluate(val_loader) if val_acer best_acer: save_checkpoint(model, epoch)这里要特别注意一个小坑如果用了批量归一化层视频帧的特征统计和单帧的特征统计是有差异的。训练时如果每个batch只放一段视频的帧BN统计量会不稳定。建议一个batch里混入多段视频的不同帧让BN统计量更接近真实分布。4.2 训练与评估的关键参数选择基于我复现时的大量实验以下是几个直接决定训练效果的关键参数输入分辨率256×256起步如果显存允许上到288×288会更好。低于224会让纹理信息损失严重。采样帧数训练时每段视频随机采样4到8帧测试时建议取16帧以上时序统计更稳定。学习率策略初始学习率3e-4用warmupcosine decay。warmup大概5个epoch让BN统计量先稳定下来。Batch Size显存够的话用64至少也要32。太小的话BN统计量波动很大模型不容易收敛。优化器AdamW权重衰减设1e-4。SGD也可以用但AdamW收敛更快调参成本低。Focal Loss参数gamma2alpha0.75。训练轮数30到50轮之间早停阈值设连续5个epoch验证集ACER不降就停。评估阶段要画DET曲线选阈值。具体做法是在验证集上对每个样本算出攻击概率分数然后从0到1枚举所有可能的分类阈值算每个阈值下的APCER和BPCER取二者平均值最小的阈值作为最终分类阈值。这个阈值要保存下来测试和部署时直接沿用。4.3 模型推理阶段的工程优化比赛方案要落地还得解决推理效率问题。2019年的模型在今天看来已经不算大但如果要在手机端或者闸机上跑仍然要做几层优化。帧间异步处理视频流是不间断的不能为了凑够8帧而让用户等8帧的时间。我的做法是维护一个滑动窗口持续把新帧的特征push进来同时pop掉最旧的帧每来一帧就做一次推理但分类结果取滑动窗口内所有帧的平均分数。这样既有时序信息的平滑效果又不增加额外延迟。时序模块替换如果GRU在目标设备上推理太慢把GRU换成时序统计池化平均推理时间能减少30%到40%ACER通常只损失0.3到0.5个百分点。这个权衡在大多数场景下都能接受。多尺度测试如果算力充裕可以对同一帧做多尺度裁剪比如256和224各跑一次结果取平均。能轻微提点但推理成本直接翻倍一般不建议。模型量化把Xception权重量化成INT8精度损失大概在0.5到1个百分点ACER以内但推理速度能快2到3倍。如果部署环境支持INT8性价比很高。5. 踩坑实录与常见问题速查5.1 高频问题与排查思路复现过程中我踩了不少坑整理成一张速查表按问题频率排的现象可能原因解决方案训练loss快速下降验证ACER却很高模型过拟合训练集的攻击介质细节加大数据增强幅度尤其是纹理模拟类增强改用Focal Loss降低易样本权重换一个摄像头采集的测试集就崩跨域泛化不足增加色彩空间分支、在YCbCr空间做光照归一化、用特征统计池化替代GRU同一段视频抽帧不同导致结果差异大单帧分类不够稳定模型对特定帧敏感推理时用滑动窗口平均分数或者加大采样帧数训练时BN统计量剧烈波动Batch内视频帧来源单一一个batch内混入不同视频的帧确保正负样本和不同视频都覆盖阈值在验证集选得很好测试集不work验证集与测试集的攻击分布差异大按采集环境做留出验证集避免随机切分导致“过拟合验证集”模型对打印攻击有效但对屏幕重放完全无效屏幕伪影特征没有被输入捕捉到检查是否有摩尔纹增强、是否用了足够高的输入分辨率、是否在YCbCr空间保留了色度高频信息这些问题的共同根源其实是“训练分布和测试分布不一致”。比赛设计本身就包含了这种不一致所以你复现时一定要把验证集切成“跨环境留出”的方式而不是随机切分。这样调出来的参数才真正对比赛有效。5.2 跨设备、跨场景的适配经验跨设备是活体检测最大的现实难题。训练时用的摄像头A和部署时摄像头B的成像特性完全不同色彩偏移、锐度、噪声水平都不一样。我实测过一种很有价值做法在训练时对图像做通道维度的随机增益扰动也就是每个通道随机乘上一个0.9到1.1之间的系数。这个改动很小但对跨设备鲁棒性的提升很明显因为它强迫网络不能依赖固定的颜色平衡关系。跨场景方面光照是最难搞的。训练集里是室内均匀光照部署环境可能是强逆光或者昏暗光线。建议在预处理阶段把人脸区域做自适应的光照归一化比如在YCbCr空间对Y通道做直方图均衡化。不要一上来就学很多论文里的复杂归一化方法先用直方图均衡化简单、稳定、有效。3D面具攻击往往在训练集数量很少导致模型在面具上完全没经验。如果手头能拿到少量面具样本哪怕只有几十段视频也应该单独分出来做微调。实在没有面具样本可以收集一些“戴了医用口罩、滑雪镜之类遮挡物”的人脸作为难负样本至少让模型学会“脸部有非正常遮挡时要警惕”。5.3 对2019年方案在当下环境下的再审视这套Top3方案放到今天来看有些部分已经过时但核心思想不过时。人脸检测现在是实时的、准确的模型也不需要用Xception这么大的主干MobileNetV3或EfficientNet-Lite在手机上跑得又快又稳。时序建模也被更高效的Temporal Shift Module或者直接用Vision Transformer的视频版替代掉了。但我个人觉得2019年方案里最值得继承的恰恰是“多色彩空间频域纹理时序动态”这个组合思路。后来的很多优秀工作比如用深度图做监督的、用双目近红外的、用剪贴不规则块重建的本质上都在做同一件事找到那些“人类眼睛不太注意但物理上无法伪造”的信号。人脸防伪这个方向永远在追赶攻击者。今天能拦住打印图片和屏幕重放明天可能出现高仿真的全息投影或者换脸视频。所以理解比赛方案的思维方式比记住某个具体的网络结构更有价值——你需要学会的是如何从“攻击介质的物理特性”出发设计特征而不是背下一个固定的模型。我自己做活体检测落地项目这几年最大的体会就是公开数据集刷分只是入场券真正拉开差距的是对采集环境、攻击手段和用户行为的理解。Top3方案给了你一个非常好的起点把代码跑通、把原理吃透然后一定要带着自己的场景数据去重新调阈值、做增强、选模型。这套操作下来你收获的不只是一个能跑的模型而是一整套判断“什么是活体”的思维方式。