ARTICLE DETAIL

资讯详情

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

人脸静默活体检测:从MTCNN检测到活体分类的完整实践

人脸静默活体检测:从MTCNN检测到活体分类的完整实践 简介这是面向计算机相关专业学生与深度学习初学者的完整人脸静默活体检测项目覆盖真实场景下的照片、屏幕翻拍等非活体攻击识别适合用于毕业设计、期末大作业或项目实战练习。压缩包共8个文件包含Python源码、PyTorch/TensorFlow模型文件h5、pb、说明文档及演示视频其中src目录提供mtcnn与人脸检测相关脚本并配有png结果图与mp4运行录屏便于对照效果验证。资源整体约10.82MB结构清晰源码已经本地编译与严格调试可直接运行。目前已有72人学习项目由导师指导并评审通过得分98分难度适中且经助教审定既能满足课程设计交付要求也能帮助学习者快速理解静默活体检测的模型组织、推理流程与结果可视化方法是一份高完成度的参考实现。1. 人脸静默活体检测为什么一张照片骗得过人脸识别骗不过这个项目最近好几个做毕业设计和期末大作业的同学拿着同一份项目来找我基于深度学习与Python的人脸静默活体检测算法。问得最多的一个问题就是——人脸识别我懂活体检测到底在检测什么我一般先让他们拿自己的手机试一下把一张高分辨率照片对着摄像头很多系统确实能解锁这就是所谓的照片攻击。而静默活体检测要做的就是在这个环节把照片、视频、屏幕翻拍这些假脸拦下来用户不需要配合眨眼或摇头帧一进来直接给出是真脸还是假脸的判定。这份项目源码的核心是“MTCNN人脸检测 深度活体分类”两段式架构代码量不大但工程链路完整非常适合做课程设计、毕业设计复现或者给刚入门深度学习视觉方向的学习者当练手项目。2. 静默活体检测的原理MTCNN把人脸框出来FAS模型判真假2.1 两段式推理链路检测和分类各干各的活把静默活体检测拆成两个子任务来做是工程上最稳的解法。第一段负责“脸在哪里”第二段负责“这张脸是不是活人的脸”。两段各干各的互不干扰定位问题的时候非常省心。第一段用的MTCNNMulti-task Cascaded Convolutional Networks是一种经典的三级联检测网络由P-Net、R-Net、O-Net三个子网络串起来。P-Net在图像金字塔上快速产出大量候选框先粗筛一轮把明显不是人脸的窗口扔掉R-Net对候选框做精修把大部分负样本滤掉O-Net负责输出最终的人脸框和五个关键点左眼、右眼、鼻尖、左嘴角、右嘴角。这套流程的好处在于每一级网络都比较轻量在CPU上也能跑到可用的速度而且输入输出格式非常统一。对于拿到一份现成源码的场景来说MTCNN不管是自己封装还是调用现成库边界都很清晰。这份项目里把MTCNN单独封装成了src/mtcnn.py底层加载的是model/mtcnn.pb冻结图模型说明作者把检测这一步做成了一个可独立调用的模块后面换活体分类模型时不需要动检测部分的代码。检测输出的人脸框坐标是整数还是浮点、是否需要先放大一圈再裁剪这些细节都集中在mtcnn.py里改起来很方便。第二段的fas.h5是一个二分类卷积网络输入是人脸检测框裁剪出来的局部图像输出是这个区域的“活体分数”。分数越高越可能是真人越低越可能是照片或屏幕。这里要特别强调一个细节送入活体模型的并不是整帧图像而是MTCNN框出来的那一小块人脸。如果直接把整帧送进活体模型背景里的纹理和物体对分类干扰非常大误报率会明显上升。所以demo.py里一定有人脸裁剪这一步这也是静默活体检测和普通图像分类最大的差别。顺着这个思路再看预处理。模型训练时用的是RGB三通道图像而OpenCV默认读进来的是BGR这个通道顺序不转换模型看到的颜色分布完全是反的。尺寸也要对齐假设模型训练时输入是112×112推理时就不能resize到224×224否则卷积感受野完全错位分数会漂移到你无法判断的程度。归一化方式同样关键是除以255还是减均值除以标准差必须和训练时保持一致。这些细节单看都不难但串在一起任何一个地方对不上结果就是黑匣子分数忽高忽低你还不知道问题出在哪。2.2 静默 vs 动作活体为什么选“静默”方案动作活体检测是另一条常见路线让用户按提示眨眼、张嘴、左右转头算法在连续帧里捕捉这些动作是否真实。听起来似乎更直观但翻拍视频其实也能模仿动作——拿一段录好的真人视频对着摄像头眨眼、转头全都有动作活体照样被骗。真要防住视频攻击还得结合深度图、红外图或者光流分析那复杂度就上来了。静默活体的优势在于三点。第一是无感不需要指令配合用户走过来帧一进来就出判定适合闸机、门禁、手机解锁这类讲究体验的场景。第二是低延迟单帧推理即可不需要攒一段连续帧再分析。第三是部署简单不需要额外的姿态估计模块、光流计算模块一个模型文件就能跑。但静默也有自己的难点。真脸和假脸在图像上的差异非常细微主要藏在纹理细节里。屏幕翻拍会有摩尔纹打印照片会有纸张纹理和阴影手机屏录制的视频再回放会有频闪条纹。这些特征卷积网络是可以学习的前提是人脸裁剪足够稳定且训练集里这类样本足够多。这个项目的data目录里放了一个fake_screen.png和对应的结果图fake_screen_res.png就是为了让使用者直观看到屏幕翻拍样本进来之后活体分数被压到了多少、检测框把假脸框在了哪里。选型建议上我一般推荐课程设计和期末大作业优先做静默方案。理由很现实动作活体需要连续帧标注和时间维度的建模数据制作成本成倍增加而静默方案的数据集组织和训练流程本质上就是一个普通二分类任务对新手友好得多。跑通之后如果还想加分再在静默方案之上叠加眨眼检测工程上完全来得及后面进阶部分会细说。2.3 MTCNN关键点除了框人还能做人脸对齐MTCNN返回的五个关键点不是摆设很多人只用人脸框把关键点坐标直接丢掉这很浪费。标准做法是把两只眼睛连线的角度算出来做人脸对齐把歪着脸的帧先旋转到水平方向再送入活体模型。旋转后的面部纹理分布更接近模型训练时看到的分布误判率能下降不少。我在调这个项目时最先动的一招就是加人脸对齐。实现也很简单拿到左右眼坐标之后用atan2算出旋转角再用OpenCV的仿射变换把整帧旋转一下重新跑一遍MTCNN取框。代价是每帧多了一次图像旋转计算但换来的是活体分数在头部轻微偏转时不会剧烈抖动。这个稳定性在答辩演示时特别重要你总不希望评委歪一下头系统就把真人判成假脸吧。3. 把环境跑起来依赖版本、目录结构和模型加载顺序3.1 依赖清单与安装先看Keras和TensorFlow的版本默契拿到这份源码先别急着跑demo第一步是确认Python版本和深度学习框架版本能不能互相兼容。这类项目最常见的运行组合是Python 3.6到3.8配合TensorFlow 2.x主线Keras用tensorflow.keras而不是独立安装的原生Keras。fas.h5是带结构一起保存的模型文件用load_model加载即可但版本错配时它报的错最隐蔽后面避坑章节会展开。安装依赖建议走国内镜像源否则下载耗时很长。TensorFlow 2.x安装好之后tensorflow.keras.models.load_model(model/fas.h5)就能直接把模型加载进来。需要注意的一点是如果h5文件是用旧版Keras保存的在新版本里加载偶尔会出现Layer相关的警告。此时不要慌先确认没有自定义层如果有把tensorflow与keras的版本锁定在2.6到2.8之间这个区间对这类毕业设计项目的兼容性最好。MTCNN部分依赖的库少一些numpy、opencv-python、tensorflow外加项目自带的src/mtcnn.py就能跑。opencv-python安装时要注意版本4.5以上的版本对VideoCapture的接口没有破坏性变更但4.8之后部分API标记为deprecated只要代码里没用到就不会报错。另外建议装一个h5py后面检查fas.h5文件完整性时用得上。3.2 目录结构解读每个文件到底在干嘛把资源的目录结构列出来是最直观的打开方式。核心文件一共不到十个拆开看每一层都很清楚src/mtcnn.py # MTCNN检测器封装加载mtcnn.pb demo.py # 主演示脚本摄像头/图片推理入口 data/fake_screen.png # 屏幕翻拍攻击样本 data/fake_screen_res.png # 翻拍样本的检测结果图 data/blink_detection_demo.mp4 # 眨眼检测演示视频 model/mtcnn.pb # MTCNN冻结图模型 model/fas.h5 # 活体分类模型 README.md # 运行说明从文件构成能明显看出这个项目的设计思路先用mtcnn.pb完成人脸检测再用fas.h5完成活体分类。fake_screen.png是攻击样本对应的fake_screen_res.png是结果图这个成对的文件很适合用来做初步验证——代码跑通之后先不放摄像头直接用这两张图验证检测和分类两条链路是否正常。blink_detection_demo.mp4是眨眼检测的演示素材说明作者在静默活体的基础上还扩展过动作活体的部分。demo.py是整份资源最该精读的文件。它把MTCNN初始化、活体模型加载、摄像头循环、画框画文字这些步骤串在一起代码量不大但工程链路的骨架都在里面。你后续要做的任何改动不管是加眨眼检测、换模型文件还是改判定阈值都以这个文件为入口。3.3 模型加载顺序先检测后分类中间夹一个人脸裁剪demo.py的模型加载顺序是固定的先初始化MTCNN再加载活体模型两个模型都不大初始化时间可以忽略。但有一个细节要注意MTCNN的pb文件加载时用的是tf.compat.v1的Graph和GraphDef解析如果运行环境是TensorFlow 2.x且没有关闭v2行为加载pb时会报错或拿不到预期输出。常规做法是在代码最开头加上这两行import tensorflow.compat.v1 as tf tf.disable_v2_behavior()加了这两行之后GraphDef解析和Session运行才能按老接口工作。很多同学在这个地方卡住报错信息要么是Attempting to use uninitialized value要么是Op type not registered根因基本都是v2行为没关干净。顺带一提如果你用的是TensorFlow 2.10以上版本tf.compat.v1仍然可用不用过度担心兼容性。加载顺序之所以要固定是因为活体分类依赖MTCNN的输出后一个模型的输入是前一个模型的输出这决定了初始化也必须按这个顺序。反过来先加载活体模型再加载MTCNN也能跑但代码可读性会变差排查问题时多绕一层没必要。4. 跑通demo.py从视频帧到活体分数的完整推理链路4.1 核心代码逐段拆解检测、裁剪、分类三步走把demo.py的主流程用可运行的简化代码还原出来加上关键注释方便逐行理解import cv2 import numpy as np import tensorflow.compat.v1 as tf from tensorflow.keras.models import load_model from src.mtcnn import MTCNN tf.disable_v2_behavior() # 初始化两个模型MTCNN负责检测人脸fas.h5负责判断真假 detector MTCNN() fas_model load_model(model/fas.h5) def detect_liveness(frame): # 第一步MTCNN检测人脸返回人脸框和关键点 boxes, landmarks detector.detect(frame) if len(boxes) 0: return None, 0.0 # 取检测到的第一个人脸框坐标转整数 x1, y1, x2, y2 [int(v) for v in boxes[0]] # 第二步裁剪人脸区域 face frame[y1:y2, x1:x2] if face.size 0: return None, 0.0 # 第三步预处理通道顺序与归一化必须与训练一致 face cv2.cvtColor(face, cv2.COLOR_BGR2RGB) face cv2.resize(face, (112, 112)) face face.astype(float32) / 255.0 # 第四步送入活体模型打分 pred fas_model.predict(face[None, ...])[0][0] return (x1, y1, x2, y2), pred # 摄像头循环每帧读入、检测、判定、画框 cap cv2.VideoCapture(0) while True: ret, frame cap.read() if not ret: break box, score detect_liveness(frame) if box is not None: label LIVE %.3f % score if score 0.5 else FAKE %.3f % score cv2.rectangle(frame, (box[0], box[1]), (box[2], box[3]), (0, 255, 0), 2) cv2.putText(frame, label, (box[0], box[1] - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) cv2.imshow(Liveness, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()逻辑说明detect返回的boxes是形状为(n, 4)的数组每行是x1, y1, x2, y2对应人脸框左上角和右下角坐标。landmarks是形状为(n, 5, 2)的关键点数组后面做眨眼检测时要用。拿到框之后直接对图像数组做切片就是裁剪操作。face.size等于0的情况一定要判断否则后续resize会直接崩溃这是一个容易忽略的边界条件。预处理部分的三个操作cvtColor转RGB、resize到112×112、astype后除以255归一化顺序不能乱也不能省。预测时传入face[None, ...]是为了增加一个batch维度因为Keras模型的输入要求是四维张量(N, H, W, C)单张图也要扩维。输出的pred是一个浮点数代表活体分数。参数说明活体判定阈值取0.5是一个常见起点。如果你在强光或弱光环境下测试建议先跑一段打印出分数分布再决定要不要调。翻拍样本误判成真脸就往上调比如0.65真人总被判假就往下调。调阈值是最便宜的方案比重训模型便宜得多但治标不治本分数分布严重重叠时还是要回头检查预处理。4.2 demo.py里可以改的三个参数阈值、最小人脸尺寸、检测间隔第一个是活体判定阈值默认0.5这个参数直接影响最终判定结果建议做成环境变量或配置文件而不是硬编码在循环里。第二个是MTCNN的最小人脸尺寸minsize默认通常是40代表小于这个尺寸的人脸不会进入检测流程。想让识别距离更远可以降到20但代价是候选框数量变多计算量上升、速度下降。第三个是检测间隔摄像头模式下每一帧都跑完整MTCNN很浪费常见做法是每3到5帧检测一次中间帧沿用上一帧的人脸框位置这样可以显著提升FPS。调试时有个很实用的习惯把每一帧的活体分数打印出来同时把检测框画在图上存成一张图片真脸和假脸各收集几十张观察分数分布是否可分。如果真脸分数集中在0.55到0.6假脸集中在0.2到0.4那阈值设在0.5很安全鲁棒性不错。如果两个分布有明显重叠区那问题不在阈值而在预处理或人脸裁剪质量需要回到代码里查通道顺序、归一化方式对人脸框有没有适当外扩。5. 避坑静默活体检测最常见的六个翻车现场5.1 现象load_model报错模型文件损坏或op找不到原因fas.h5保存时使用的Keras版本与当前运行版本不一致常见于新版TensorFlow把旧版的自定义层或激活函数移动到了别的命名空间加载时找不到对应操作。解决先确认README里标注的依赖版本把tensorflow锁定在2.6到2.8之间。如果版本没问题再用h5py打开h5文件检查里面是否有weights和model_config能正常打开说明文件没坏问题一定出在框架版本上。5.2 现象屏幕翻拍照片在demo里被判成真人原因静默模型学到的是纹理差异当翻拍距离远、摩尔纹被磨平或者翻拍屏光线很均匀时假脸纹理和真脸在低频信息上非常接近分类器容易混淆。另外阈值设太低也会把边缘样本放进来。解决打印出分数把阈值从0.5提高到0.65再测一轮。如果还是误判检查人脸裁剪区域是否把大片背景带进了模型背景纹理会影响分类结果。裁剪时适当放宽人脸框的上边界把额头露出来因为额头区域的皮肤纹理是活体检测的重要线索。5.3 现象摄像头黑屏或者程序直接退出没有任何报错原因笔记本自带摄像头在OpenCV里的设备索引不一定是0插了外接摄像头之后索引可能变成1或2VideoCapture(0)打开了不存在的设备ret一直是False。解决在启动循环前打印cap.isOpened()如果返回False把cv2.VideoCapture(0)改成cv2.VideoCapture(1)再试。还有一种情况是索引没错但摄像头被其他程序占用关掉所有占用摄像头的软件再跑一遍。处理好之后仍打不开检查系统是否有摄像头权限限制。5.4 现象帧率非常低只有3到5 FPS完全无法实时原因MTCNN三级联网络本身并不算重但每一帧都做完整的人脸搜索并且需要构建图像金字塔在纯CPU环境或低配笔记本上计算成本被成倍放大。解决每3帧检测一次中间帧直接沿用上一帧检测到的人脸框坐标。再把送入MTCNN的图像按宽度缩放到640小图的图像金字塔层数少检测速度提升非常明显。这两个优化叠加帧率通常能翻倍。代价是快速移动时人脸框会有点滞后但静默活体检测的应用场景里人脸通常不会剧烈移动这个折中很划算。5.5 现象真脸总被误判为假脸特别是在暗环境下原因模型是在较均匀光照下训练的暗光下人脸亮度低、纹理信息少活体分数整体被压低。另一个隐蔽原因是预处理不一致cv2.cvtColor转RGB没做或归一化方式与训练时不同输入分布偏移后分数自然不准。解决先检查预处理三个步骤是否完整COLOR_BGR2RGB转换、resize到模型输入尺寸、除以255归一化。预处理没问题之后再看光照对人脸区域做一次直方图均衡化会增强暗光下的纹理对比度分数往往能提上来不少。如果还不行别硬调阈值收集一批暗光真脸样本做数据增强后重新微调模型更靠谱。5.6 现象同样的代码别人电脑上跑得很顺自己机器上直接报显存不足原因TensorFlow默认申请全部可用显存如果电脑上还有其他程序占用了显存加载模型时就会OOM。这个现象在共享GPU或入门级显卡的机器上尤其常见。解决在导入TensorFlow之后、加载模型之前设置显存按需增长import tensorflow as tf gpus tf.config.experimental.list_physical_devices(GPU) if gpus: try: tf.config.experimental.set_memory_growth(gpus[0], True) except RuntimeError as e: print(e)这样设置之后模型只申请实际需要的显存在2G显存的机器上也能跑起来代价是推理速度略慢。如果加了这行代码还是OOM那就是模型本身超出了显存容量考虑把输入图像分辨率降低或换CPU推理。6. 进阶在静默活体上叠加眨眼检测把项目从80分做到95分6.1 用MTCNN关键点计算EAR实现眨眼计数静默活体不需要用户配合但很多课程设计和毕设想展示“更完整的活体检测方案”最常见的扩展就是叠加眨眼检测。项目里自带的blink_detection_demo.mp4正好是现成的演示素材说明这条路作者已经趟过了。眨眼检测不需要额外训练模型用MTCNN输出的双眼关键点坐标就够了。眼睛纵横比EAREye Aspect Ratio的核心思路是计算上下眼睑距离与左右眼角的比值人正常睁眼时EAR稳定在0.3左右眨眼瞬间会掉到0.2以下连续几帧低于阈值就算一次眨眼。def eye_aspect_ratio(eye_points): # eye_points是单只眼睛的6个关键点坐标 vertical_1 ((eye_points[1][0] - eye_points[5][0]) ** 2 (eye_points[1][1] - eye_points[5][1]) ** 2) ** 0.5 vertical_2 ((eye_points[2][0] - eye_points[4][0]) ** 2 (eye_points[2][1] - eye_points[4][1]) ** 2) ** 0.5 horizontal ((eye_points[0][0] - eye_points[3][0]) ** 2 (eye_points[0][1] - eye_points[3][1]) ** 2) ** 0.5 return (vertical_1 vertical_2) / (2.0 * horizontal 1e-6)计算逻辑说明每只眼睛用6个关键点描述第0和第3个点是左右眼角第1、2、4、5个点是上下眼睑边缘点。EAR的分子取两段垂直距离的平均分母是水平距离加1e-6是为了防止除零。这个比值在眨眼瞬间会快速下降通过连续帧的EAR曲线可以准确识别一次眨眼。实际接入时把静默活体的单帧分数和眨眼检测叠加成两级判定先过静默活体分数达标后再看一段时间内是否有正常眨眼行为。屏幕翻拍视频虽然能骗过动作检测但很难同时骗过静默纹理检测高分辨率照片能骗过静默检测但照片不会眨眼。两级判定各堵一个方向整体安全性比单用其中任何一个都高不少。6.2 换自己的数据集训练活体模型如果不想一直用现成的fas.h5可以自己训练一个活体分类模型。数据组织方式很简单建两个文件夹正样本放真人摄像头画面负样本放屏幕翻拍、打印照片、平板录制视频的截图。每个样本先用MTCNN裁剪到人脸区域统一缩放、归一化用Keras的ImageDataGenerator做随机亮度、对比度、翻转增强然后训练一个简化的卷积网络就行。模型结构不用太复杂静默活体是二分类任务数据集质量远比网络深度重要。有一点要特别提醒负样本要尽量贴近你的真实使用场景。如果打算部署在门禁闸机上负样本就要拍闸机位置的屏幕翻拍如果只在实验室摄像头下演示那负样本用手机屏幕翻拍就够。换一种翻拍设备、换一种光照条件分数差异可能会很明显这也是静默活体检测数据集的通用难点提前做好心理准备。从那以后我每次调活体检测都强制自己先打印分数分布、把每个误判帧存成图片看一遍再决定是调阈值、调预处理还是换数据集而不是一上来就重训模型。这个习惯帮我省了很多时间也少走了很多弯路希望帮到你。本文还有配套的精品资源点击获取
返回列表