资讯详情 基于MTCNN实现人脸检测:完整代码与三级级联架构解析
📅 2026/10/16 6:45:24
简介这份资源提供基于MTCNN实现人脸检测的完整代码面向具备一定深度学习基础、希望深入理解级联卷积网络检测流程的开发者与研究人员。MTCNN通过P-Net、R-Net、O-Net三级网络依次完成候选框生成、边界框细化与关键点定位资源中的代码覆盖模型定义、检测推理、训练脚本及非极大值抑制等核心环节并附带Caffe模型与视频跟踪示例便于对照学习从训练到部署的完整链路。压缩包共80个文件以48个Python脚本为主体辅以prototxt网络配置、caffemodel权重、npy数据、ipynb教程笔记及少量图像与视频素材整体约23.13MB目录按网络、训练、工具与测试模块划分清晰。目前已有989人学习下载适合需要复现MTCNN、开展人脸检测实验或将其集成到人脸识别系统中的读者参考。1. 从一张合影里把人脸框出来MTCNN 到底能干什么前阵子帮朋友处理一批活动合影需求很朴素把每张照片里的人脸框出来裁成小图存档。我一开始想直接上 YOLO结果发现这批图里人脸尺寸差异极大近处的人脸占半张画面角落里的合影者只有几十像素。试了几个方案后还是回到 MTCNN——它对多尺度人脸的召回确实稳而且模型小、依赖少CPU 上也能跑。这份「基于 MTCNN 实现人脸检测完整代码」就是围绕这个场景整理的从图像预处理、三级网络级联推理到最终的人脸框和五点关键点输出整条链路都能直接跑。适合刚接触深度学习人脸检测的新手照着复现也适合需要快速集成检测模块的开发者拿去做二次开发。下面我按「能干什么 → 怎么跑 → 怎么改」的顺序把这份代码拆开讲。2. 三级级联架构拆解P-Net、R-Net、O-Net 各管什么MTCNN 全称 Multi-task Cascaded Convolutional Networks核心思路是「由粗到细、层层过滤」。它不像单阶段检测器那样一次出结果而是用三个网络串行工作前一级负责快速筛掉大量背景后一级负责精细回归。理解这个级联结构是看懂整份代码的前提。2.1 三个网络的分工与输入输出P-NetProposal Network是第一级输入任意尺寸图像经过全卷积后输出大量候选框和置信度。它的特点是快用滑动窗口的方式在特征图上密集采样配合非极大值抑制NMS先砍掉一批重叠框。R-NetRefine Network接收 P-Net 留下的候选框统一缩放到 24×24 再判断进一步过滤误检。O-NetOutput Network最后把关输入 48×48 的框输出最终的人脸概率和五点关键点坐标。网络输入尺寸主要输出作用P-Net任意图像金字塔候选框 置信度快速生成大量候选R-Net24×24精修框 置信度过滤明显误检O-Net48×48最终框 五点关键点输出最终结果代码里这三个网络通常定义在同一个模型文件中用类封装每个类包含卷积层、PReLU 激活和输出分支。P-Net 的输出分支有两个一个用于分类是否人脸一个用于边界框回归偏移量。R-Net 和 O-Net 在此基础上多一个关键点回归分支。2.2 图像金字塔与候选框生成MTCNN 处理多尺度人脸的关键在于图像金字塔。代码会按一定缩放因子常见是 0.709把原图缩小成一系列不同尺寸的副本每个副本都送进 P-Net。这样大脸在小图上被检测小脸在大图上被检测覆盖不同尺度。# 构建图像金字塔scale 为缩放因子 def pyramid(image, min_face_size20, scale0.709): pyramids [] m 12.0 / min_face_size # 12 是 P-Net 最小检测尺寸 min_length min(image.shape[0], image.shape[1]) cur_scale 1.0 while min_length * cur_scale 12: w int(image.shape[1] * cur_scale) h int(image.shape[0] * cur_scale) if w 12 or h 12: break pyramids.append(cv2.resize(image, (w, h))) cur_scale * scale return pyramids这段代码的逻辑是从原图开始每次乘以缩放因子直到最短边小于 12 像素为止。min_face_size控制最小可检测人脸设得越小金字塔层数越多、越慢但小脸召回越高。scale越小层数越密精度略升但耗时增加。实际调参时如果场景里人脸普遍偏小把min_face_size降到 15 左右会有明显改善。2.3 NMS 与边界框回归的配合P-Net 输出的候选框数量可能上万必须用 NMS 去重。代码里 NMS 通常按置信度排序保留最高分框再删掉与它 IoU 超过阈值的框。阈值一般设 0.5 到 0.7 之间太低会误删相邻人脸太高会留下大量重叠框。边界框回归的作用是修正候选框位置。P-Net 输出的框是粗糙的回归分支预测的是偏移量dx, dy, dw, dh把框往真实人脸位置拉。代码里这一步通常写成bbox_regress函数把偏移量按比例还原到原图坐标。注意回归后的框要裁剪到图像边界内否则后续裁剪会报错。# 对候选框做 NMSthreshold 为 IoU 阈值 def nms(boxes, threshold0.7): if len(boxes) 0: return [] boxes sorted(boxes, keylambda x: x[4], reverseTrue) keep [] while boxes: best boxes.pop(0) keep.append(best) boxes [b for b in boxes if iou(best, b) threshold] return keepiou函数计算两个框的交并比boxes里每个元素是[x1, y1, x2, y2, score]。这段 NMS 是纯 Python 实现候选框多时会有性能瓶颈后面讲优化时会提到怎么改。3. 从零跑通环境、权重与推理脚本架构清楚之后落地第一步是让代码跑起来。这份代码的依赖不算重但版本搭配有讲究尤其是 PyTorch 和 OpenCV 的兼容性。3.1 环境准备与依赖安装推荐用 Python 3.8 以上PyTorch 1.10 以上。核心依赖就四个torch、torchvision、opencv-python、numpy。如果要用 GPU 加速装对应 CUDA 版本的 torch 即可。我一般会建一个独立虚拟环境避免和系统里的包打架。# 创建虚拟环境并安装依赖 python -m venv mtcnn_env source mtcnn_env/bin/activate # Windows 用 mtcnn_env\Scripts\activate pip install torch torchvision opencv-python numpy装完后验证一下python -c import torch, cv2; print(torch.__version__, cv2.__version__)。如果 torch 版本低于 1.6某些张量操作会报错建议升级。OpenCV 用 4.x 即可3.x 在部分图像解码上有差异。3.2 权重文件加载与模型初始化代码里三个网络的权重通常放在weights/目录下文件名类似pnet.npy、rnet.npy、onet.npy也有用.pth格式的。加载时要注意权重和模型结构必须对应否则会报size mismatch。# 初始化三个网络并加载权重 import torch from model import PNet, RNet, ONet device torch.device(cuda if torch.cuda.is_available() else cpu) pnet PNet().to(device).eval() rnet RNet().to(device).eval() onet ONet().to(device).eval() pnet.load_state_dict(torch.load(weights/pnet.pth, map_locationdevice)) rnet.load_state_dict(torch.load(weights/rnet.pth, map_locationdevice)) onet.load_state_dict(torch.load(weights/onet.pth, map_locationdevice))eval()必须调用否则 BatchNorm 和 Dropout 会按训练模式走推理结果不稳定。map_location保证在 CPU 上也能加载 GPU 保存的权重。如果权重是.npy格式需要用 numpy 读取后逐层赋值代码里一般会封装一个load_npy_weights函数。3.3 单张图像推理全流程推理入口通常是一个detect函数接收图像路径或 numpy 数组返回人脸框和关键点列表。整个流程是读图 → 构建金字塔 → P-Net 推理 → NMS → R-Net 精修 → NMS → O-Net 输出 → 坐标还原。# 完整推理流程 def detect(image_path, min_face_size20, thresholds[0.6, 0.7, 0.7]): img cv2.imread(image_path) img_rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) pyramids pyramid(img_rgb, min_face_size) # 第一级 P-Net boxes [] for scaled in pyramids: boxes.extend(pnet_inference(pnet, scaled, scale_factor)) boxes nms(boxes, 0.7) # 第二级 R-Net boxes rnet_inference(rnet, img_rgb, boxes, thresholds[1]) boxes nms(boxes, 0.7) # 第三级 O-Net boxes, landmarks onet_inference(onet, img_rgb, boxes, thresholds[2]) boxes nms(boxes, 0.7) return boxes, landmarksthresholds三个值分别对应三级网络的置信度阈值。第一级设低一点0.6保证召回后两级设高一点0.7保证精度。如果发现漏检多把第一级降到 0.5如果误检多把后两级提到 0.8。scale_factor是金字塔每层的缩放比例用来把框坐标还原到原图。跑通后可以用cv2.rectangle和cv2.circle把框和关键点画出来存成新图。这一步能直观验证结果是否正确。4. 避坑与排查那些让我重跑三遍的细节这份代码逻辑不算复杂但实际跑的时候有几个坑我踩过不止一次。下面按「现象 → 原因 → 解决」列出来省得你再走弯路。4.1 报错 CUDA out of memory现象GPU 上跑大图时直接崩提示显存不足。原因图像金字塔层数多P-Net 一次性把所有层送进 GPU显存占用随图像尺寸平方增长。解决限制金字塔最大层数或者分批送 GPU。我一般会在pyramid里加一个max_layers参数超过就截断。另外推理时用torch.no_grad()包住能省不少显存。4.2 检测框偏移或尺寸不对现象画出来的框明显偏左或偏小。原因边界框回归的偏移量没有按金字塔缩放比例还原或者还原时用了错误的宽高基准。解决检查bbox_regress里是否用scale参数正确还原还原后再做一次边界裁剪。常见做法是把回归后的坐标除以当前金字塔层的缩放比例再映射回原图。4.3 小脸漏检严重现象合影里远处的人脸一个都没框出来。原因min_face_size设得太大金字塔没有覆盖到小脸尺度。解决把min_face_size降到 15 甚至 12同时把scale调到 0.6 左右增加层数。代价是速度变慢可以配合 GPU 加速。另一个办法是先对图像做一次上采样再检测。4.4 NMS 后相邻人脸被误删现象两个人站得很近时只框出一个。原因NMS 的 IoU 阈值太低把相邻人脸当成重叠框删了。解决把 NMS 阈值从 0.5 提到 0.7或者改用 Soft-NMS。代码里如果 NMS 是纯 Python 实现改阈值只需改一个参数但要注意阈值太高会留下重复框需要配合置信度过滤。4.5 关键点位置漂移现象五点关键点画出来不在眼睛、鼻子、嘴角上。原因O-Net 输出的关键点是相对于候选框的归一化坐标没有按框的宽高还原。解决检查onet_inference里关键点还原公式通常是x box_x1 landmark_x * box_w。如果框本身有偏移关键点也会跟着偏所以要先保证框回归正确。5. 二次开发把检测模块塞进自己的项目跑通之后多数人下一步是想把它集成到自己的应用里。这份代码的结构比较清晰改造点主要集中在推理封装、性能优化和输出格式三块。5.1 封装成可复用的检测类原始代码可能是脚本形式直接改起来不方便。我一般会包一个FaceDetector类把模型加载、推理、后处理都收进去对外只暴露detect(image)方法。这样换模型、调阈值都不用动业务代码。class FaceDetector: def __init__(self, weight_dirweights, devicecpu): self.device torch.device(device) self.pnet PNet().to(self.device).eval() self.rnet RNet().to(self.device).eval() self.onet ONet().to(self.device).eval() self._load_weights(weight_dir) def detect(self, image, min_face_size20, thresholds[0.6, 0.7, 0.7]): # 内部走完整三级推理 with torch.no_grad(): boxes, landmarks self._inference(image, min_face_size, thresholds) return boxes, landmarks_load_weights里做权重加载和格式兼容_inference里放三级推理逻辑。这样业务侧只需要detector FaceDetector(); boxes, pts detector.detect(img)两行。如果要做批量处理可以在类里加一个detect_batch方法把多张图拼成一个 batch 送 GPU吞吐能提升不少。5.2 性能优化从 CPU 到 GPU 的取舍MTCNN 在 CPU 上跑一张 1080P 图大概几百毫秒到一秒GPU 上能降到几十毫秒。如果只是离线处理一批图CPU 够用如果要实时视频流必须上 GPU。优化点有几个一是把 P-Net 的金字塔推理改成 batch减少循环开销二是用torch.jit.trace把模型转成 TorchScript推理速度能再提一截三是把 NMS 从纯 Python 换成 numpy 向量化实现候选框多时差距明显。# numpy 向量化 NMS比纯 Python 循环快很多 def nms_numpy(boxes, threshold0.7): if len(boxes) 0: return [] boxes np.array(boxes) x1, y1, x2, y2, scores boxes[:,0], boxes[:,1], boxes[:,2], boxes[:,3], boxes[:,4] areas (x2 - x1 1) * (y2 - y1 1) order scores.argsort()[::-1] keep [] while order.size 0: i order[0] keep.append(i) xx1 np.maximum(x1[i], x1[order[1:]]) yy1 np.maximum(y1[i], y1[order[1:]]) xx2 np.minimum(x2[i], x2[order[1:]]) yy2 np.minimum(y2[i], y2[order[1:]]) w np.maximum(0.0, xx2 - xx1 1) h np.maximum(0.0, yy2 - yy1 1) inter w * h iou inter / (areas[i] areas[order[1:]] - inter) inds np.where(iou threshold)[0] order order[inds 1] return boxes[keep].tolist()这段代码把 IoU 计算全部向量化避免了 Python 循环里逐个比较。order[inds 1]是因为order[1:]相对原数组偏移了一位。实测在几千个候选框时比纯 Python 版快五到十倍。5.3 输出格式对接从框到业务数据检测结果最终要对接业务。常见输出有两种一种是画框图直接存文件另一种是结构化数据比如 JSON 列表每个元素包含bbox和landmarks。如果下游是人脸识别通常还需要按框裁剪出人脸小图并做对齐——用五点关键点做仿射变换把眼睛和嘴角摆正。这一步代码里一般会提供一个align_face函数输入原图、关键点和目标尺寸输出对齐后的人脸图。改造时注意坐标顺序OpenCV 的rectangle要的是(x1, y1), (x2, y2)而有些标注格式是(x, y, w, h)转换时别搞反。我习惯在detect返回前统一成[x1, y1, x2, y2, score]业务侧要用哪种再转。从那以后我每次集成检测模块都会先拿一张多人合影跑一遍把框和关键点画出来肉眼确认再进业务逻辑。这个习惯帮我省了好几次「坐标顺序搞反导致裁剪全错」的返工。希望这份拆解能帮你把 MTCNN 顺利跑起来也欢迎在改造时按自己的场景调阈值和金字塔参数。本文还有配套的精品资源点击获取