ARTICLE DETAIL

资讯详情

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

CNN人脸识别实战:从112×112输入到PyTorch示例代码全解析

CNN人脸识别实战:从112×112输入到PyTorch示例代码全解析 简介这份资源是一份面向深度学习初学者与计算机视觉入门者的卷积神经网络人脸识别示例代码以PDF形式呈现帮助读者理解如何用CNN完成从人脸检测到分类识别的完整流程。压缩包内共1个PDF文件约722KB内容围绕数据收集、预处理、模型搭建、训练与识别展开涉及Keras与TensorFlow后端、OpenCV人脸检测、Yale人脸库等具体实践环节。已有1442人学习下载说明该示例在入门人群中具有一定参考价值。读者可从中获得一套可复用的CNN人脸识别实现思路包括如何用Haar级联分类器裁剪人脸、如何构建卷积层与池化层提取特征、如何用全连接层完成分类以及训练后如何对新图片进行识别并标注结果。对于想从特征脸法等传统算法过渡到深度学习方案的开发者这份代码示例能提供清晰的工程落地参考与排错思路。1. 用 CNN 做人脸识别从一张 112×112 的图到能跑通的示例代码很多人第一次接触人脸识别脑子里想的是调个库、传张图、返回名字三步走结果真动手才发现模型输入到底要多大尺寸人脸检测和识别是不是一回事为什么我拿网上抄来的 CNN 示例代码在 LFW 上准确率能到 99%换成自己公司门禁拍的照片就集体翻车这篇笔记就围绕使用卷积神经网络CNN做人脸识别的示例代码这个标题把从数据准备、网络结构、训练参数到推理部署的完整链路拆开讲清楚。适合两类人一是刚学完 CNN 基础、想找个能跑通的完整项目练手的同学二是已经在做人脸识别门禁机、考勤机这类落地产品需要回头把识别模块的细节抠明白的工程师。下面所有代码都是可复现的最小实现不依赖任何闭源 SDK。2. 先分清检测与识别CNN 在人脸链路里到底管哪一段2.1 人脸识别不是单个 CNN 模型而是一条流水线新手最容易踩的认知坑是把人脸识别当成一个端到端的 CNN。实际工业链路至少分四段人脸检测找到图里有没有脸、在哪、关键点对齐把歪头、侧脸摆正、特征提取CNN 真正干活的地方、特征比对算距离或分类。CNN 主要承担的是第三段——把一张对齐后的人脸图映射成一个固定长度的特征向量比如 512 维。检测阶段现在主流用 RetinaFace、SCRFD 这类基于 CNN 的检测器对齐用 5 点或 68 点关键点做仿射变换比对则用余弦相似度或欧氏距离。为什么要把这件事说清楚因为很多示例代码只给你第三段你直接拿原始照片喂进去模型当然崩。我一般会跟团队里新人强调识别准确率上不去八成问题出在检测和对齐而不是 CNN 本身。所以下面给的示例代码我会把对齐这一步也补上哪怕用最简单的五点仿射。2.2 选型自己训 CNN 还是用预训练骨干微调标题说的是示例代码那就要回答一个现实问题从零训一个 CNN 做人脸识别可行吗答案是——小数据集上可以跑通但想达到可用精度必须用预训练骨干。原因很直接人脸识别是细粒度任务类间差异极小两个人脸长得像类内差异极大同一个人光照、角度、表情变化大。从零训需要百万级 ID、千万级图片普通人拿不到。常见做法是拿 ResNet、MobileNet、IR-SE 这些骨干在 MS1M、Glint360K 这类公开人脸集上预训练好的权重然后做微调或直接当特征提取器。示例代码里我会用 MobileNetV2 做骨干原因是它参数量小、CPU 上也能跑适合门禁机、行空板这类边缘设备。如果你追求精度换成 IR-ResNet50 或 ArcFace 的官方骨干即可代码结构不变。骨干网络参数量输入尺寸适合场景备注MobileNetV2~3.5M112×112边缘设备、门禁机速度快精度够用ResNet50~25M112×112服务器端精度高显存吃紧IR-SE50~43M112×112高精度比对ArcFace 常用MobileFaceNet~1M112×112移动端专为人脸设计提示输入尺寸 112×112 是 ArcFace 系列的标准不要随意改成 224×224除非你重新训。预训练权重和输入尺寸是绑定的。3. 数据准备与对齐示例代码能不能跑通八成看这一步3.1 用 MTCNN 或 RetinaFace 做检测加五点对齐假设你手上有一批原始照片每张照片里可能有多个人脸。第一步是检测并裁剪。下面这段代码用 facenet-pytorch 里的 MTCNN 做检测和对齐输出 112×112 的标准人脸图。这是示例代码里最容易被忽略、但最关键的一步。import cv2 import numpy as np from facenet_pytorch import MTCNN from PIL import Image # 初始化 MTCNNimage_size 设为 112 与后续 CNN 输入对齐 mtcnn MTCNN(image_size112, margin0, keep_allFalse, post_processTrue) def align_face(img_path): img Image.open(img_path).convert(RGB) # 返回对齐后的张量形状 [3, 112, 112]值域 [-1, 1] face mtcnn(img) if face is None: return None # 转成 numpy方便后续保存或送入自定义模型 face_np face.permute(1, 2, 0).numpy() face_np ((face_np 1) * 127.5).astype(np.uint8) return face_np if __name__ __main__: aligned align_face(test.jpg) if aligned is not None: cv2.imwrite(aligned.jpg, cv2.cvtColor(aligned, cv2.COLOR_RGB2BGR))逻辑说明MTCNN 内部做了检测、关键点回归和仿射变换image_size112保证输出尺寸和 CNN 输入一致margin0表示不额外扩边。post_processTrue会把像素归一化到 [-1, 1]这是很多预训练模型的输入要求。参数上keep_allFalse表示只保留置信度最高的一张脸做人脸识别比对时通常一张图一个人。如果你要做多人脸场景改成keep_allTrue返回的是列表。3.2 构造训练集每个 ID 一个文件夹划分要按人划分示例代码要能训起来数据组织必须规范。常见做法是每个身份一个文件夹文件夹名就是标签。但这里有个血泪经验训练集和验证集必须按身份划分不能按图片随机划分。否则同一个人既出现在训练集又出现在验证集准确率虚高到 99%上线就翻车。# 目录结构示例 dataset/ ├── id_0001/ │ ├── 001.jpg │ ├── 002.jpg ├── id_0002/ │ ├── 001.jpg └── ... # 按身份划分7:3 python split_dataset.py --root dataset --ratio 0.7 --out train.txt val.txtsplit_dataset.py的核心逻辑是先拿到所有身份列表打乱后按比例切分身份再把对应身份下所有图片写入 train.txt 或 val.txt。这样验证集里的身份在训练时完全没见过评估结果才可信。参数ratio控制训练集身份占比小数据集建议 0.8大数据集 0.7 即可。注意如果你用的是公开数据集如 LFW它本身有标准的 6000 对验证协议不要自己乱切直接用官方 pair.txt否则结果没法跟别人比。4. 用 PyTorch 搭一个能跑的 CNN 人脸识别示例4.1 骨干网络加 ArcFace 头为什么不用普通 Softmax普通分类 CNN 用 Softmax 加交叉熵在人脸识别上效果一般因为 Softmax 只要求类间可分不要求类内紧凑。人脸识别需要的是同一个人的特征向量尽量聚在一起不同人尽量分开。ArcFace 通过在角度空间加 margin强制类内紧凑、类间分离是目前最主流的损失函数。下面给出完整模型定义骨干用 MobileNetV2头部换成 ArcFace。代码可以直接抄。import torch import torch.nn as nn import torch.nn.functional as F from torchvision import models class ArcFace(nn.Module): def __init__(self, in_features, num_classes, s30.0, m0.50): super().__init__() self.s s self.m m self.weight nn.Parameter(torch.randn(num_classes, in_features)) nn.init.xavier_uniform_(self.weight) def forward(self, x, labelNone): # 特征和权重都做 L2 归一化转成余弦相似度 x F.normalize(x) w F.normalize(self.weight) cos_theta F.linear(x, w).clamp(-1 1e-7, 1 - 1e-7) if label is None: return cos_theta # 加角度 margin theta torch.acos(cos_theta) target_logit torch.cos(theta self.m) one_hot torch.zeros_like(cos_theta) one_hot.scatter_(1, label.view(-1, 1), 1.0) logits one_hot * target_logit (1 - one_hot) * cos_theta return logits * self.s class FaceCNN(nn.Module): def __init__(self, num_classes, emb_dim512): super().__init__() backbone models.mobilenet_v2(pretrainedTrue) self.features backbone.features self.pool nn.AdaptiveAvgPool2d(1) self.bn nn.BatchNorm1d(1280) self.fc nn.Linear(1280, emb_dim) self.arc ArcFace(emb_dim, num_classes) def forward(self, x, labelNone): x self.features(x) x self.pool(x).flatten(1) x self.bn(x) emb self.fc(x) if label is not None: return self.arc(emb, label) return F.normalize(emb)逻辑说明features是 MobileNetV2 的卷积部分输出 1280 维。pool做全局平均池化bn加批归一化稳定训练fc降到 512 维特征。ArcFace里s30是缩放因子m0.5是角度 margin这两个参数是人脸识别里最常调的。s太小梯度弱太大训练不稳m越大类间越分离但太大收敛困难。一般从 s30、m0.5 起步小数据集把 m 降到 0.3。4.2 训练循环与三个必调参数训练代码本身不复杂但有几个参数直接决定成败。下面给出核心训练循环。from torch.utils.data import DataLoader from torchvision import transforms from dataset import FaceDataset transform transforms.Compose([ transforms.Resize((112, 112)), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5]), ]) train_set FaceDataset(train.txt, transform) train_loader DataLoader(train_set, batch_size128, shuffleTrue, num_workers4) model FaceCNN(num_classestrain_set.num_classes).cuda() optimizer torch.optim.SGD(model.parameters(), lr0.01, momentum0.9, weight_decay5e-4) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max20) for epoch in range(20): model.train() for imgs, labels in train_loader: imgs, labels imgs.cuda(), labels.cuda() logits model(imgs, labels) loss F.cross_entropy(logits, labels) optimizer.zero_grad() loss.backward() optimizer.step() scheduler.step() print(fepoch {epoch}, loss {loss.item():.4f})三个必调参数第一batch_size人脸识别建议 128 起步太小 BN 统计不稳显存不够就降到 64 并同步降 lr。第二lrSGD 用 0.01 到 0.1Adam 用 1e-3配合余弦退火。第三weight_decay5e-4 是常用值防止过拟合。另外RandomHorizontalFlip对人脸要慎用因为左右脸不对称翻转可能引入噪声小数据集可以关掉。提示训练时 loss 降到 1 以下不代表模型可用一定要在按身份划分的验证集上算准确率。人脸识别看的是验证集上的 TARFAR不是训练 loss。5. 推理与比对把 CNN 特征用起来的两种方式5.1 闭集分类直接取 ArcFace 输出最大类如果你的场景是固定人员比如公司 100 人考勤那训练时 num_classes100推理时直接取 logits 最大的类即可。这种方式简单但新增人员必须重新训练适合人员稳定的门禁机。model.eval() with torch.no_grad(): emb model(img_tensor.cuda()) # 返回归一化后的 512 维特征 logits model.arc(emb) # 与所有类中心算余弦 pred logits.argmax(dim1)5.2 开集比对算余弦相似度设阈值更通用的是开集比对不分类而是把两张脸都提特征算余弦相似度大于阈值就认为是同一人。这种方式新增人员只需注册一张照片不用重训。阈值一般设 0.5 到 0.6具体看你的 FAR 要求。def cosine_sim(a, b): a F.normalize(a, dim1) b F.normalize(b, dim1) return (a * b).sum(dim1) sim cosine_sim(emb1, emb2) if sim 0.55: print(same person)阈值怎么定拿一批已知同人和不同人的对画 ROC 曲线选你业务能接受的 FAR 对应的阈值。门禁场景 FAR 要极低阈值往 0.6 以上调手机解锁可以放宽到 0.5。6. 避坑与排查示例代码跑不通时先看这五条现象一训练 loss 一直不降卡在 8 左右。原因通常是 ArcFace 的 s 或 m 设太大或者学习率太高。解决先把 m 降到 0.2、s 降到 16确认能降下来再逐步加回去。现象二验证集准确率 99%上线全错。原因是训练验证按图片随机划分同人泄漏。解决改成按身份划分重新评估。这是最常见的翻车点。现象三推理时同一张图两次特征差很多。原因是模型没切 eval 模式BN 还在用 batch 统计。解决推理前加model.eval()并用torch.no_grad()。现象四对齐后的人脸是歪的或裁掉半张脸。原因是 MTCNN 的 margin 设太小或者原图人脸太小。解决margin 设 0.2 到 0.3检测前把原图短边缩到 640 以上。现象五CPU 上推理慢到没法用。原因是用了 ResNet50 这类大骨干。解决换 MobileNetV2 或 MobileFaceNet并用 ONNX Runtime 或 TensorRT 加速112×112 输入下 CPU 单张可以做到 20ms 以内。7. 进阶技巧用 ONNX 导出把 CNN 人脸识别塞进门禁机示例代码在服务器上跑通只是第一步真正落地往往要部署到门禁机、行空板这类边缘设备。我的习惯是训练完立刻导出 ONNX用 ONNX Runtime 做推理比 PyTorch 直接跑快 2 到 3 倍而且不依赖 Python 环境。import torch.onnx model.eval() dummy torch.randn(1, 3, 112, 112).cuda() torch.onnx.export( model, dummy, face_cnn.onnx, input_names[input], output_names[embedding], dynamic_axes{input: {0: batch}, embedding: {0: batch}}, opset_version11 )导出时注意两点一是model.eval()必须加否则 BN 和 Dropout 会带进计算图二是dynamic_axes把 batch 维设成动态方便批量比对。导出后用 onnxruntime 验证一遍输出和 PyTorch 是否一致误差应在 1e-4 以内。import onnxruntime as ort import numpy as np sess ort.InferenceSession(face_cnn.onnx) inp np.random.randn(1, 3, 112, 112).astype(np.float32) out sess.run(None, {input: inp})[0] print(out.shape) # (1, 512)验证方法拿同一张对齐后的人脸图分别过 PyTorch 和 ONNX算两个 512 维向量的余弦相似度应该大于 0.999。如果差很多检查输入归一化是否一致——PyTorch 里用了 Normalize([0.5]*3, [0.5]*3)ONNX 推理前也要做同样的预处理这一步漏掉是血泪教训。最后说个我自己的习惯每次改完模型结构或损失函数先在一个 10 人的小数据集上跑 5 个 epoch确认 loss 能降到 0.1 以下、验证集能到 90% 以上再上全量数据。这样能省下大量等训练的时间也能快速定位是代码 bug 还是数据问题。人脸识别这个方向CNN 结构本身已经比较成熟真正拉开差距的是数据清洗、对齐质量和阈值策略。希望帮到你。本文还有配套的精品资源点击获取
返回列表