尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于TensorFlow与CNN的人脸识别毕业设计实战解析

基于TensorFlow与CNN的人脸识别毕业设计实战解析 简介这是一份基于TensorFlow训练人脸识别神经网络的毕业设计完整教程面向正在学习卷积神经网络、需要完成人脸识别方向课程设计或毕业设计的开发者。压缩包共6个文件主要由4个Python脚本、1个Markdown说明文档和1份开源许可文件构成脚本分工明确一个采集本人人脸图像一个采集其他人脸图像一个训练卷积神经网络一个用于新图片识别判断README还说明了Windows或Linux下Python 3.x与TensorFlow的安装运行方式。项目通过让网络学习两组不同人脸图片能够记住目标人物的面部特征并区分他人完整演示了从数据准备、模型训练到实际识别的全过程并给出了清晰的人脸数据集组织思路。资源包仅14KB已有625人浏览学习小巧却覆盖关键环节适合作为毕业设计的基础代码或TensorFlow入门实践。1. 为什么是 TensorFlow CNN这个毕业设计到底在训练什么先纠正一个常见的预期这套代码不是要做出一个能识别所有人的通用人脸识别系统它的目标非常收敛——让网络只认识“作者一个人”。训练时给它看两组照片一组是目标人物本人的脸部图像一组是与他无关的其他人的脸部图像最后用 TensorFlow 把卷积神经网络CNN训练成一个二分类器输入一张人脸输出“是本人”还是“不是本人”。项目里四个脚本的分工也很直白get_my_faces.py 负责采集本人的正样本set_other_faces.py 负责准备其他人的负样本train_faces.py 完成网络训练is_my_face.py 在训练结束后做实时识别。这种“单目标识别”本质上是二分类比通用人脸识别里的开集识别简单许多但已经把 CNN 从数据采集、预处理、训练到推理的完整链路跑通对需要快速上手深度学习的在校生和刚转 CV 的工程师都很合适。2. 先解决数据get_my_faces.py 与 set_other_faces.py 的样本生产流程人脸识别任务里数据集的质量直接决定模型上限后面调参只能在小范围内修补。这一章把两个数据采集脚本拆开讲顺带说明正负样本的组织方式和数量配比——这部分最容易被人忽略却恰恰是训练结果“像不像样”的分水岭。2.1 get_my_faces.py从摄像头到训练样本的切割流程这个脚本的标准动作是打开前置摄像头从视频流里抽帧对人脸区域做检测和裁剪统一缩放后写入 my_faces 目录。核心不是训练是把“一段视频”变成“一批尺寸一致的干净人脸图”。常用实现如下import cv2 import os # 保存目录和采样参数 save_root ./my_faces detect_interval 3 # 每 3 帧抽 1 帧避免相邻帧太相似 crop_size 64 # 统一缩放到 64x64 target_num 3000 # 期望的正样本数量 os.makedirs(save_root, exist_okTrue) cap cv2.VideoCapture(0) # 0 表示默认摄像头 # 用 OpenCV 内置的 Haar 级联人脸检测器 cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) count 0 while count target_num: ret, frame cap.read() if not ret: continue count 1 if count % detect_interval ! 0: continue gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces cascade.detectMultiScale( gray, scaleFactor1.1, minNeighbors5, minSize(64, 64) ) for i, (x, y, w, h) in enumerate(faces): face gray[y:y h, x:x w] face cv2.resize(face, (crop_size, crop_size)) cv2.imwrite( os.path.join(save_root, f{count}_{i}.jpg), face, [cv2.IMWRITE_JPEG_QUALITY, 95], ) cap.release()这段代码里最需要关注的不是循环而是detectMultiScale的三个参数。scaleFactor1.1表示检测窗口每次缩放 10%数值越小检测越精细但耗时越长实战中取 1.1 或 1.2 即可minNeighbors5表示一个候选框至少要被邻近区域重复检测到 5 次才被保留这个值调小会出现大量误检调大则会漏掉侧面或者模糊的人脸minSize在这里用来过滤掉距离摄像头过远的小尺寸误检区域。提示录制时不要保持一个姿势不动左右转头、抬头低头、前后移动都来一点得到的样本姿态多样性足够模型对新场景的适应性才会好。采集完成后花十分钟把明显闭眼、低头过度、模糊的照片删掉。这一批样本是后面所有工作的基础宁可少也不要脏。2.2 set_other_faces.py批量构建负样本集合负样本决定了模型的分类边界。负样本越贴近真实场景中“不是本人”的多样性训练出的模型就越不可能把陌生人误判成目标人物。set_other_faces.py 的通常做法是从一个公共人脸数据集或者收集到的无关人员照片目录里批量读取图片用同一个 Haar 检测器做人脸定位把裁剪缩放后的结果写入 other_faces 目录。import cv2 import glob import os src_dir ./lfw # 原始图像目录比如公开的人脸数据集 dst_dir ./other_faces # 负样本输出目录 crop_size 64 os.makedirs(dst_dir, exist_okTrue) cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) for idx, img_path in enumerate(glob.glob(os.path.join(src_dir, *.jpg))): img cv2.imread(img_path) if img is None: continue gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces cascade.detectMultiScale( gray, scaleFactor1.1, minNeighbors5, minSize(48, 48) ) for x, y, w, h in faces: face gray[y:y h, x:x w] face cv2.resize(face, (crop_size, crop_size)) cv2.imwrite(os.path.join(dst_dir, f{idx}_{x}_{y}.jpg), face)这套逻辑本身不难难点在于负样本的来源选择。常见的做法是从公开人脸数据集中筛一批不同年龄、肤色、光照条件的图片而不是随意抓取网络图。原因有二一是网络图的分辨率和人脸占比不稳定检测器可能裁不准二是公开数据集大多有相对统一的人脸对齐标准省去大量人工清洗。实际项目里我也会混入少量同场景下无关人员的前置摄像头照片让负样本和真实使用场景更接近。2.3 数据配比负样本数量不能比正样本少目录内容建议数量说明my_faces目标人物本人2000 ~ 5000 张单一人物姿态尽量多样other_faces无关人员5000 ~ 10000 张数量建议为正样本的 2~3 倍test_faces验证集正负各 200 张以上从上面两批中预留不参与训练负样本建议多于正样本原因是“不是本人”这个类别覆盖空间远大于“本人”。如果负样本只有正样本的一半训练出的模型会倾向于把所有输入都判断为“本人”因为训练阶段“本人”的先验概率被样本分布抬高了。准备数据时就把 test_faces 单独切出去后面训练和评估才有一个公平的裁判。3. train_faces.py卷积网络结构、损失函数与训练参数设计数据准备好之后进入训练环节。这个脚本承担三件事读取图片和标签、定义网络结构、迭代优化参数。下面按这三步展开主要讲清楚网络为什么这么搭以及每个超参数崩了之后看哪里。3.1 网络结构两层卷积加全连接单人二分类任务不需要非常深层的网络两三个卷积块足够。网络过深反而容易在样本量只有几千张的情况下过拟合训练集准确率接近 100%验证集却一塌糊涂。基准结构通常是import tensorflow as tf # TF 2.x 环境下兼容 v1 接口如果你的环境是 TF 1.x 则跳过前两行 if tf.__version__.startswith(2): tf tf.compat.v1 tf.disable_v2_behavior() IMAGE_SIZE 64 def cnn(x, keep_prob): # 输入形状: [batch, 64, 64]增加一个通道维度变成 [batch, 64, 64, 1] x_image tf.reshape(x, [-1, IMAGE_SIZE, IMAGE_SIZE, 1]) # 第一个卷积块5x5 卷积核 32 个ReLU 激活再接 2x2 最大池化 w1 tf.Variable(tf.truncated_normal([5, 5, 1, 32], stddev0.05)) b1 tf.Variable(tf.constant(0.1, shape[32])) h1 tf.nn.relu(tf.nn.conv2d(x_image, w1, strides[1, 1, 1, 1], paddingSAME) b1) h1_pool tf.nn.max_pool(h1, ksize[1, 2, 2, 1], strides[1, 2, 2, 1], paddingSAME) # 第二个卷积块5x5 卷积核 64 个 w2 tf.Variable(tf.truncated_normal([5, 5, 32, 64], stddev0.05)) b2 tf.Variable(tf.constant(0.1, shape[64])) h2 tf.nn.relu(tf.nn.conv2d(h1_pool, w2, strides[1, 1, 1, 1], paddingSAME) b2) h2_pool tf.nn.max_pool(h2, ksize[1, 2, 2, 1], strides[1, 2, 2, 1], paddingSAME) # 全连接层256 个神经元DRopout 防过拟合 w3 tf.Variable(tf.truncated_normal([16 * 16 * 64, 256], stddev0.05)) b3 tf.Variable(tf.constant(0.1, shape[256])) h2_flat tf.reshape(h2_pool, [-1, 16 * 16 * 64]) h3 tf.nn.relu(tf.matmul(h2_flat, w3) b3) h3_drop tf.nn.dropout(h3, keep_prob) # 输出层2 类经过 softmax 变成概率分布 w4 tf.Variable(tf.truncated_normal([256, 2], stddev0.05)) b4 tf.Variable(tf.constant(0.1, shape[2])) logits tf.matmul(h3_drop, w4) b4 return tf.nn.softmax(logits)卷积层使用paddingSAME是为了让人脸边缘的特征也有机会参与卷积不会因为尺寸收缩丢掉鬓角和下颚信息。池化窗口取 2x2、步长 2特征图尺寸减半计算量降为四分之一同时带来一个小小的平移不变性——头部轻微偏转时特征响应不至于剧烈变化。这里每个变量的stddev0.05是经过权衡的权重初始化标准差太大前向传播时激活值容易饱和反向传播的梯度会变得极小。3.2 损失函数与训练主流程分类任务的标配是 softmax 配合交叉熵。均方误差也可以做分类但在配合 softmax 输出时会收敛得异常慢因为输出层饱和区的梯度很小交叉熵在概率预测错误时误差大、梯度也大学习速度明显更快。def build_train_step(): x tf.placeholder(tf.float32, shape[None, IMAGE_SIZE * IMAGE_SIZE]) y_ tf.placeholder(tf.float32, shape[None, 2]) keep_prob tf.placeholder(tf.float32) y_pred cnn(x, keep_prob) # 加 1e-8 防止 log(0) 导致 loss 变成 nan cross_entropy tf.reduce_mean( -tf.reduce_sum(y_ * tf.log(y_pred 1e-8), axis1) ) train_step tf.train.AdamOptimizer(1e-4).minimize(cross_entropy) return x, y_, keep_prob, y_pred, train_stepAdamOptimizer(1e-4)是这一类小型 CNN 里最稳妥的选择。Adam 自带自适应学习率对初始学习率的敏感度比原始 SGD 低很多初学者不用反复试学习率也能得到一个可用的收敛轨迹。注意这里没有把图片本身和标签读取到内存的细节全部写出来实际处理时用小批量读取每批 64 张避免一次性把几千张图片全塞进feed_dict里撑爆内存。3.3 超参数参考表与训练异常诊断超参数建议值现象与排查方向初始学习率1e-4 ~ 3e-4loss 一直震荡不下降调小一个数量级重试batch_size32 ~ 128过大容易收敛到平的极小值过小则梯度噪声大训练轮数20 ~ 50看验证准确率是否还在上升不要盲目加轮dropout keep_prob训练 0.5 / 测试 1.0测试时忘记设为 1.0 会导致准确率暴跌训练过程中一个最常见的问题是 loss 变成nan。原因通常有两个一是tf.log(0)产生负无穷应对方法是在交叉熵里加一个极小量二是学习率设太大梯度更新跨过了最优区域。另一个问题是训练集准确率接近 100% 但验证集只有 80% 上下这说明模型把训练样本的细节背下来了优先增加负样本数量其次才是加大 dropout 比例。4. is_my_face.py模型恢复、实时推理与 softmax 阈值调优训练脚本产出的是 checkpoint 文件里面保存了所有变量的值。训练时模型在内存里跑而实际识别时需要一个独立的推理脚本把模型重新加载进来。is_my_face.py 的任务就是在摄像头画面里做检测把裁好的人脸放进网络输出一个是否匹配的判断。4.1 从 checkpoint 恢复模型保存和恢复是成对出现的。训练脚本最后会调用saver.save(sess, ./model/my_face_model.ckpt)推理脚本则要从同一个 checkpoint 恢复。注意必须保持网络定义一致TensorFlow 按变量名逐层恢复任何一层的名字或形状变了都会报错。saver tf.train.Saver() sess tf.Session() # 从指定路径加载训练好的权重 saver.restore(sess, ./model/my_face_model.ckpt) # 这段代码的输入输出结构必须与训练时一致 def is_my_face(face_array): face face_array.reshape(1, IMAGE_SIZE, IMAGE_SIZE, 1).astype(np.float32) face face / 255.0 prob sess.run(y_pred, feed_dict{x: face, keep_prob: 1.0})[0] return probfeed_dict里keep_prob必须显式传 1.0。训练时 dropout 会随机丢弃部分神经元推理时如果仍然按 0.5 运行每次前向传播的结果都会不同识别就会时灵时不灵。这是新手最容易犯的错误没有之一。4.2 从摄像头画面到最终判定实时推理管线要处理的不只是模型还有前置的检测环节。实现在训练时用 64x64 作为输入但在摄像头画面上如果拿 64x64 去检测人脸远处的人脸根本测不到近处的人脸又会裁掉一部分。所以推理阶段的检测尺寸应该比训练时更大def run_camera(): cap cv2.VideoCapture(0) cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) while True: ret, frame cap.read() if not ret: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 这里用更大的人脸尺寸做检测留出裁剪余地 faces cascade.detectMultiScale( gray, scaleFactor1.1, minNeighbors5, minSize(100, 100) ) for (x, y, w, h) in faces: face_roi gray[y:y h, x:x w] face_resized cv2.resize(face_roi, (IMAGE_SIZE, IMAGE_SIZE)) prob is_my_face(face_resized) label mine if prob[0] 0.8 else unknown cv2.rectangle(frame, (x, y), (x w, y h), (0, 255, 0), 2) cv2.putText(frame, f{label} {prob[0]:.2f}, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imshow(face, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()提示检测尺寸和缩放目标之间差距越大裁剪时引入的形变越明显。如果你的摄像头分辨率较高考虑把训练样本统一从 64x64 提升到 128x128能明显改善检测框与人脸边缘对不齐的问题。4.3 softmax 概率和阈值不是一回事网络输出的是两个类的概率向量例如[0.87, 0.13]含义是模型认为输入人脸是目标人物的概率为 0.87。直接把argmax结果作为判断依据会忽略置信度信息——样本只要不是目标人物就强行归入负类实际使用中很容易把侧脸、暗光下的人脸全部误判成“不认识”。所以推理逻辑里单独设一个阈值建议起点是 0.8之后根据使用场景调整如果希望减少“把陌生人认成自己”的情况阈值往上提到 0.9如果更在意不要漏掉本人阈值往下降到 0.7。每次调整后都重新拿 test_faces 跑一遍观察准确率和漏检率的变化。场景阈值方向可能代价门禁、解锁类强安全场景调高到 0.9本人误拒率上升考勤、辅助标记类调低到 0.6~0.7陌生人误纳率上升多数普通演示项目0.8 作为起始值再根据实测微调5. 从“只认识我”到多人脸扩展思路与验证技巧这个项目在原始设定下只识别一个人但很多实际场景要求“认识几个人”或者“知道这个人是谁”。把二分类网络扩展成多分类网络改动其实不大。最直接的做法是把输出层从 2 个单位改成 N1 个其中 N 是想要识别的目标人数多出的一个类别代表“未知”。对应地每个目标人物都要准备一组自己的正样本负样本仍然共用训练标签变成 one-hot 的多类向量。另一种做法是保持二分类网络不变为每个目标人物单独训练一个模型推理时逐个跑一遍做法简单但推理耗时线性增长人少时无所谓人多了就明显卡顿。第 5 章除了扩展方向还应该聊聊怎么确认模型真的可用。不要只盯着训练集准确率而要看 isolated test set 上的表现——和训练数据完全隔离的人脸照片才能真实反映模型在未知数据上的泛化能力。建议把 test_faces 里的正负样本都跑一遍计算两个更直观的指标误拒率自己是本人却被判负和误纳率不是本人却被判正。前者影响使用体验后者影响安全性。还可以把测试时输出的软概率都记录下来画一条阈值变化之下误拒率和误纳率的联动曲线选一个自己最能接受的平衡点。帧间投票是一个成本极低的工程技巧连续检测 5 帧只有其中 3 帧以上判定为“是本人”才输出最终结果。这能过滤掉单帧的抖动、表情变化和瞬时遮挡。配合深度学习框架的最新趋势也不妨把 TensorFlow 和 PyTorch 的当前版本做一次对比用同一套数据分别在两个框架里跑对比训练速度、显存占用和推理延迟——这种横向验证对理解框架差异很有帮助尤其是你要把这个项目往边缘设备上迁移的时候。本文还有配套的精品资源点击获取
返回列表