☰
人脸防伪检测与活体检测:从ACER指标到Top3方案复现实战
2026/10/1 4:48:23 网站建设 项目流程

人脸防伪检测这活儿,比赛里拿名色是一回事,放到实际落地场景又是另一回事。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设为2,alpha设为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.7,ACER先降后升,0.5左右是效果最好的区间。

另外别忘了时间维度的增强:对视频序列做随机抽帧,有的用2帧,有的用4帧,有的用8帧。这样网络在不同帧数下都能工作,推理时也就不用严格卡一个固定帧数。

4. 代码复现全程与模型落地实操

4.1 数据预处理与训练验证流程

复现的第一步是把原始视频转换成训练所需的格式。我用的流程是:

  1. 用MTCNN或RetinaFace对每帧图像做人脸检测,拿到人脸框。
  2. 按人脸框裁剪,缩放到256×256。
  3. 为保证时序一致性,不用单帧检测结果,而是对视频前几帧检测到的人脸框做平滑,再应用到整段视频的所有帧。防止人脸框抖动导致输入不稳定。
  4. 对每帧生成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], dim=1) 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,用warmup+cosine decay。warmup大概5个epoch,让BN统计量先稳定下来。Batch Size:显存够的话用64,至少也要32。太小的话BN统计量波动很大,模型不容易收敛。优化器:AdamW,权重衰减设1e-4。SGD也可以用,但AdamW收敛更快,调参成本低。Focal Loss参数:gamma=2,alpha=0.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方案给了你一个非常好的起点,把代码跑通、把原理吃透,然后一定要带着自己的场景数据去重新调阈值、做增强、选模型。这套操作下来,你收获的不只是一个能跑的模型,而是一整套判断“什么是活体”的思维方式。

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

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

立即咨询