ARTICLE DETAIL

资讯详情

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

TensorFlow人脸识别教程:从数据采集到CNN训练全流程解析

TensorFlow人脸识别教程:从数据采集到CNN训练全流程解析 简介基于TensorFlow构建的人脸识别神经网络毕业设计教程面向人工智能、计算机专业的学生及卷积神经网络入门者以个人照片与其他人脸照片组成正负样本集驱动模型学习面部特征并区分目标人物。资源包共六个文件核心为四个Python脚本分别负责目标人脸采集、反例人脸整理、卷积网络训练与识别判断另含说明文档和开源许可证整体约14KB结构紧凑。教程从运行环境搭建开始覆盖Windows和Linux系统并指导配置Python 3与TensorFlow环境完整演示了照片收集、模型训练到最终识别认人的全过程有助于理解卷积神经网络人脸识别的数据组织与网络设计思路。目前已有626人学习下载适合作为毕业设计或课程设计的参考项目。借助这组代码读者可以快速上手修改数据集与网络参数复现出一个人脸识别原型。1. 从卷积神经网络到人脸识别这份TensorFlow教程能带你跑通一条完整链路先说一个反直觉的结论这个项目能不能认出你八成取决于数据采集而不是网络结构。我用它做完毕业设计后最大的感受是真正折磨人的不是训练脚本写出多少行而是那两组图片集有没有老老实实准备好。这份基于TensorFlow训练的人脸识别神经网络教程本质是一个完整的二分类工程从摄像头采集自己的脸用网络上的人脸照片做参照训练一个卷积神经网络去判断当前画面里的人是不是我。它不依赖现成的人脸识别API所有环节都亲手实现非常适合毕业设计、课程设计以及想弄明白CNN从数据到模型全流程的初学者。如果你只想调个接口就出结果这个项目对你来说太重了但如果你想知道神经网络是怎么一步步认出一张脸的这套资源值得你照着跑一遍。2. 采集两组人脸数据get_my_faces.py与set_other_faces.py的分工人脸识别第一步不是写网络是凑数据。一个有意思的点是我们需要的是我的脸和不是我的脸两组图片这个项目把这两组数据当成二分类问题的正负样本。这里的关键在于负样本不是随便凑个数就行——它的质量和多样性能直接决定模型的泛化能力。我用这个教程做复现时前期花在数据收集和清洗上的时间比训练还多但换来的回报是训练时loss曲线非常干净几乎没遇到模型学歪的情况。2.1 get_my_faces.py用摄像头自动采集我的脸这个脚本的逻辑很直接调用OpenCV打开摄像头用Haar级联检测器找出画面里的人脸区域裁剪、灰度化、缩放到64x64像素然后保存到本地目录。它写的不是一个人脸识别模型而是一个自动数据采集工具。我一般会在运行前先确认摄像头编号笔记本自带摄像头通常是0外接USB摄像头在Windows上可能需要改成1。采集的时候保持头部在画面中央、缓慢转动角度方便后续模型学到不同姿态下的特征。import cv2 import os camera cv2.VideoCapture(0) # 0 表示默认摄像头打不开就试 1 detector cv2.CascadeClassifier(haarcascade_frontalface_default.xml) save_dir my_faces if not os.path.exists(save_dir): os.makedirs(save_dir) count 0 while count 500: # 目标采集 500 张 success, frame camera.read() if not success: continue gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces detector.detectMultiScale(gray, scaleFactor1.1, minNeighbors5, minSize(80, 80)) for (x, y, w, h) in faces: # 外扩 10% 再裁剪避免额头和下巴被切掉 x max(0, x - int(w * 0.1)) y max(0, y - int(h * 0.1)) w int(w * 1.2) h int(h * 1.2) face gray[y:yh, x:xw] face cv2.resize(face, (64, 64)) cv2.imwrite(f{save_dir}/{count}.jpg, face) count 1 cv2.imshow(capture, frame) if cv2.waitKey(30) 0xFF ord(q): break # 按 q 提前退出 camera.release() cv2.destroyAllWindows()这段代码有几个参数值得抠一下。scaleFactor1.1表示每次缩放比例值越小检测越精细但速度越慢minNeighbors5控制误检率值越大漏检越多但误报越少minSize(80, 80)让检测器忽略小于80x80的区域避免把远处的路人脸也存进来。连续采集时有个坑摄像头30帧一秒脸稍微动一下就会存出几十张几乎一样的图片这些重复样本会把模型带偏。常见做法是在每次写入前做一次帧间差异判断两张图的像素差小于阈值就跳过或者干脆手动删掉明显重复的部分。我习惯采集一轮后翻一遍目录把闭眼、模糊、带夸张表情的图挑出去——这些都是后面训练的隐形地雷。2.2 set_other_faces.py别人的脸怎么凑、怎么清理负样本脚本和正样本脚本是同一套处理管线差别只在输入源get_my_faces.py从摄像头读帧set_other_faces.py则遍历一个装满了普通照片的目录。负样本的来源有三种常见做法一是直接下载公开人脸数据集里的图片二是从网络图库批量抓取人脸照片三是找同学朋友帮忙拍。第一种最省事但要注意数据集的肖像许可和学术使用约定第二种来源杂、清洗成本高我一般用前两种混合保证负样本里有不同肤色、年龄、光照条件的人脸。import cv2 import os import glob detector cv2.CascadeClassifier(haarcascade_frontalface_default.xml) input_dir raw_other_faces # 原始照片目录 output_dir other_faces # 处理后的人脸目录 if not os.path.exists(output_dir): os.makedirs(output_dir) count 0 for img_path in glob.glob(f{input_dir}/*.jpg): img cv2.imread(img_path) if img is None: continue gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces detector.detectMultiScale(gray, scaleFactor1.2, minNeighbors5, minSize(80, 80)) for (x, y, w, h) in faces: x max(0, x - int(w * 0.1)) y max(0, y - int(h * 0.1)) face gray[y:yh, x:xw] face cv2.resize(face, (64, 64)) cv2.imwrite(f{output_dir}/{count}.jpg, face) count 1 if count 1500: # 负样本数量控制在 1500 张左右 break if count 1500: break这里有个数据洁癖问题负样本里绝对不能混进你自己的脸。处理网络图片时经常发生的情况是某张合影里恰好有个人长得跟你很像或者干脆就是你某次上传过的自拍——这会让模型在是不是我的边界上产生混乱。我吃过一次亏负样本里混了一张侧脸照片训练出来的模型对特定角度的侧脸完全失灵。从那以后我在跑set_other_faces.py之前会先人工扫一遍原始图片目录把有嫌疑的单人照全部移走。另外一个经验是负样本数量不要只卡在500张800到2000张会明显压住误报率因为模型需要见足够多的非我样本才能画出那条分界线。这个量级的数据集训练时间仍然以分钟计性价比非常高。2.3 目录结构与命名规范提前约定好后面少走弯路数据采集阶段结束最好统一目录结构和命名方式。train_faces.py读取的是固定目录如果你的文件名格式跟脚本预期不一致就得每次手动改路径。下面这个表格是我建议的布局来自README里的做法和我自己的习惯。目录/文件用途建议数量my_faces/正样本自己的人脸灰度图400~800张other_faces/负样本他人人脸灰度图800~2000张model/训练产出的checkpoint保存目录训练后自动生成haarcascade_frontalface_default.xml人脸检测器模型文件从OpenCV安装包复制正负样本的比例控制在1:2到1:3之间比较稳妥。正样本太少模型学不到你的特征负样本太少模型容易默认一切都是我。另外所有图片统一灰度、统一64x64分辨率这既是为了让网络输入维度固定也是为了让训练时的矩阵运算更快。如果你的正样本已经存成了彩色图可以在训练脚本里加一行cv2.imread(path, cv2.IMREAD_GRAYSCALE)强制转灰度效果一样但少占一半磁盘空间。3. 训练卷积神经网络train_faces.py的结构、参数与loss曲线数据齐了训练脚本是这套资源里最有含金量的一环。train_faces.py做的事情可以拆成四步读取两批图片并打上标签、做归一化、定义卷积神经网络结构、开始训练并周期性地保存模型。这个项目的网络结构选得比较克制没有堆几十层而是用几层卷积加池化把64x64的输入逐级压缩最后接全连接层输出是我/不是我的概率。对毕业设计来说这种结构恰到好处你能在答辩时讲清楚每一层在干什么又不会因为网络太深导致训练发散。3.1 网络结构设计为什么卷积层配全连接层就够用卷积神经网络的思路是逐层抽象第一层卷积核提取边缘、角度这类低层特征第二层卷积核把这些边缘组合成眼睛、鼻子、嘴的局部图案再往后几层学到的是更高级的面部结构组合。池化层在这中间做降采样压缩特征图的尺寸同时让模型对小的位移不那么敏感——人脸稍微偏几个像素池化能把这个偏移消化掉。我拆开这套教程的train_faces.py时注意到它没有用Batch Normalization而是靠小学习率和数据本身的稳定性来控制训练过程。这种做法在样本量不大时反而效果不错因为Batch Norm在小batch上统计均值方差容易抖动给调参增加不少烦恼。3.2 训练参数表与启动命令照抄之前先弄清楚每个值训练脚本里有一组参数是毕业设计答辩时大概率会被问到的学习率、batch size、训练步数、checkpoint保存间隔。下面这个表格是资源里给出的默认训练配置以及我对每个参数的理解。如果你只是想让模型跑起来直接沿用默认值如果你想要更好效果改动优先级从高到低是批量大小、训练步数、学习率。参数默认值作用调整建议learning_rate0.0001控制每步权重更新幅度过大导致loss震荡过小收敛太慢batch_size64每次喂给网络的样本数显存不足时降到32max_steps20000总训练轮数数据集大时可适当减少save_interval1000每多少步保存一次checkpoint建议1000方便出问题时回滚dropout_rate0.5全连接层随机丢弃比例过拟合时增大到0.6~0.7启动训练的命令很常规在项目根目录执行python train_faces.py如果你装了GPU版的TensorFlow训练速度会快不少没有GPU也不要紧这个体量的数据用CPU训练也就几十分钟。训练过程中终端会周期性打印当前的loss和准确率常见输出格式是step 1000, loss 0.32, acc 0.89这样的信息。我看终端输出时重点盯两个指标loss是不是在持续下降以及准确率有没有出现突然跳到95%以上的虚高。虚高通常意味着模型只认准了一小部分样本或者数据里存在大量重复帧。3.3 训练时盯紧loss曲线什么情况正常、什么情况要停下来训练开始后我最关心的不是准确率是loss的下降轨迹。正常情况是loss在前1000步内从0.7左右快速下探到0.3附近然后降速变得平缓最终平稳在0.05到0.1之间。如果loss曲线像过山车一样上下震荡第一步先检查学习率是不是太大了——0.001的级别在这个项目里几乎必然会震荡0.0001是常见的安全起点。如果loss下降慢得像蜗牛爬比如2000步了还在0.5以上徘徊检查一下数据归一化有没有做像素值除以255和除以255.0的区别虽然小但在反向传播里会被放大。过拟合是这个项目最常遇到的问题。模型把训练集里的几百张图背下来了换个人站在摄像头前也能被判定成我。判断过拟合有个土办法训练完成后拿一张你没进过训练集的照片去测。如果训练准确率95%以上、新照片准确率掉到50%附近说明模型在背题。应对手段有三个一是dropout开大一点二是把训练步数降下来三是回数据采集环节补一批新角度的自己。此时不要指望改网络结构能救回来样本多样性才是根本。训练脚本里还内置了模型保存机制一般每1000步存一次checkpoint出问题时可以回退到之前的模型——这是个很实用的设计省去了从头再训的时间。4. 实时判断是不是我is_my_face.py的推理逻辑与阈值设定训练得到模型文件只是第一步最终要运行的是is_my_face.py它加载之前保存的checkpoint重新打开摄像头对画面里的每一张人脸实时给出我或不是我的判断。这个脚本的代码量不大但有两个细节决定成败预处理方式必须和训练时保持一致以及判定阈值不是拍脑袋定的。4.1 加载模型与预处理的一致性一个环节对不上整个输出就废了推理脚本第一步是重建图结构并加载训练好的权重然后打开摄像头这块逻辑和get_my_faces.py几乎一致。真正的坑在预处理环节训练时你用的是灰度图、64x64、像素归一化到0~1那么推理时也必须走完全一样的管线少一个步骤输出就会偏。常见翻车是把摄像头直接读取的BGR彩色帧喂给了模型或者裁剪人脸后没做灰度转换——这种情况模型不会报错它会安静地输出一个错误概率让你完全猜不到问题出在哪。import cv2 import tensorflow as tf # 恢复训练时的计算图和变量 saver tf.train.import_meta_graph(model/model.meta) sess tf.Session() saver.restore(sess, tf.train.latest_checkpoint(model/)) # 获取训练时定义的输入占位符和输出tensor X sess.graph.get_tensor_by_name(input:0) logits sess.graph.get_tensor_by_name(output:0) camera cv2.VideoCapture(0) detector cv2.CascadeClassifier(haarcascade_frontalface_default.xml) while True: success, frame camera.read() if not success: continue gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces detector.detectMultiScale(gray, 1.1, 5, minSize(80, 80)) for (x, y, w, h) in faces: x max(0, x - int(w * 0.1)) y max(0, y - int(h * 0.1)) face cv2.resize(gray[y:yh, x:xw], (64, 64)) face face.reshape(1, 64, 64, 1) / 255.0 # 归一化必须与训练一致 prob tf.nn.sigmoid(logits) result sess.run(prob, feed_dict{X: face}) if result 0.5: label me else: label unknown cv2.rectangle(frame, (x, y), (xw, yh), (0, 255, 0), 2) cv2.putText(frame, label, (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) cv2.imshow(face recognition, frame) if cv2.waitKey(1) 0xFF ord(q): break这段代码最微妙的地方是result 0.5这个阈值。模型输出的0.5对应的是正负样本五五开的边界但实际场景中远没有这么理想——如果负样本里某个人的脸跟你特别像模型给出的概率可能一直压在0.55附近反复横跳。我会把阈值往上提到0.6或0.7用虚惊换误报的减少。这个阈值在文本里只是经验参考每人数据差异巨大最稳妥的办法是采一批额外的测试照片自己扫一遍概率分布再画定一条线。4.2 阈值设定从概率到是/否的那条分界线既然0.5不一定适用那阈值怎么标定常见做法是取一组我的照片和一组其他人的照片全部喂给模型记录它们输出的概率分布。如果两类概率分布重叠度很高说明模型没有学出区分度要回头查数据如果两类分布明显分开但中点不在0.5说明训练数据的正负样本比例失衡把阈值往分布间隙中间挪即可。这里值得注意的一个概念边界这个项目做的是人脸验证Face Verification判断这张脸是不是某个人而不是人脸检索Face Identification从一堆人脸库里找出这是谁。想扩展成認識多个人原始方案不太够用——每多认识一个人就意味着多一组正样本网络输出要从二分类改成多分类或者改成用特征向量做相似度匹配的Siamese结构改动量不小。4.3 从二分类到多认识几个人的代价毕业设计答辩时很可能会被问到一个问题如果我想让它认识三个人直接改标签可以吗答案是可以但效果会立刻打折——原因在于当前网络最后一层是二分类logits它的决策面是为了区分我和非我划出来的。增加一个人意味着决策面从一条变成多条模型的容量立刻捉襟见肘。更平滑的升级路线是保留现在这个模型作为基础把最后一层全连接前的特征向量提取出来对每个新认识的人单独训练一个分类器。这种特征提取浅层分类的做法既利用了卷积网络的特征表达能力又避开了重训整个网络的时间成本。5. 复现这个项目最容易踩的坑环境、数据与训练三块排查清单下面这份排查清单来自我自己复现时踩过的坑以及身边同学跑同样项目时问过我的一些问题。按照出现的频率排序TensorFlow版本坑排第一数据坑排第二网络结构和训练参数反而问题最少。5.1 环境坑TensorFlow 1.x与2.x的API差异现象运行train_faces.py直接报错AttributeError: module tensorflow has no attribute placeholder或者Session、feed_dict这些关键字全部飘红。原因这个教程的代码是在TensorFlow 1.x时代写的用的是tf.placeholder、tf.Session()这一类旧式写法。TensorFlow 2.0把这些API移到了tf.compat.v1命名空间下直接从tf.访问自然找不到属性。我之前还会见到有人装了2.10却被教程误导去pip install tensorflow1.15.0结果Python 3.9以上根本装不上这个老版本。解决两条路选一条。如果想省事在代码开头加一段import tensorflow.compat.v1 as tf; tf.disable_v2_behavior()让2.x模拟1.x的行为如果想彻底解决新建一个Python 3.7虚拟环境安装tensorflow1.15.0可以完整还原教程的原始运行环境。注意Windows上TensorFlow 1.x只有CPU版GPU版需要自行从源码编译对毕业设计来说CPU版足够。5.2 数据坑负样本里混入自己的脸正样本里没有脸现象训练出来的模型在大合照场景里把好几个人都识别成我或者反过来自己站在摄像头前模型却提示unknown。原因负样本目录里混进了含自己人脸的互联网照片模型学到了一个错误规律——这类肤色这个五官比例我。正样本采集过程中如果检测器短暂失灵把背景墙、桌面误判成人脸存下来模型学到的特征就变成了颜色或纹理而不是真实的脸部结构。解决在跑set_other_faces.py之前人工扫一遍原始图集看到长得像自己的照片直接删除跑完再随机抽查100张负样本确认无自己。正样本采集完也一样手动翻一遍my_faces目录把没有脸的、模糊的、只截到半边脸的图片删掉。这个步骤很枯燥我从第二次跑就老老实实执行了因为第一次偷懒导致模型翻车后返工的成本远高于检查目录花掉的十分钟。5.3 数据坑重复帧过多导致模型自我催眠现象训练日志里准确率升到98%但换个人来测立刻打回原形。原因get_my_faces.py在快速连拍模式下会存下大量只差几像素的重复帧。模型经过几千步训练后记住的不是人脸特征而是这些几乎相同的像素矩阵。解决采集时放慢节奏——每检测到人脸后等0.5秒再拍摄下一张或者干脆“拍一张手动变换一个角度再拍一张”。如果目录里已经有大量重复帧写一个哈希去重的小脚本把它们滤掉。判断重复帧的办法是计算两张图之间的平均像素差差值小于5就认为是重复。5.4 摄像头坑VideoCapture打开失败、权限被占用现象camera.read()一直返回False或者程序直接崩溃退出。原因Windows上常见原因有三个摄像头被系统相机应用占用、笔记本摄像头驱动被禁用、OpenCV的cap.set参数没有生效。我见过最隐蔽的一种情况是杀毒软件把摄像头驱动给静默禁用了导致OpenCV拿不到视频流。解决运行程序前先用系统自带相机测试摄像头是否正常代码里加上if not camera.isOpened():检查并打印提示如果笔记本自带摄像头打不开试一下把编号从0改成1。这些排查在Windows和Linux下都适用Linux上还可以用ls /dev/video*命令确认设备文件存在。5.5 训练效果坑loss不降或准确率虚高现象训练了5000步loss一直在0.4上下横跳准确率却冲着90%以上去了。原因loss不降通常不是网络结构的问题而是数据归一化没做或者学习率偏高。准确率虚高则有可能是正负样本比例失衡严重——正样本600张、负样本60张模型只要把一切输出都判成我就能拿到90%的准确率但这显然不是我们想要的。解决用0.0001左右的学习率重新跑检查/255是不是写成了/255.0把正负样本比例调整到1:2以上。训练结束后做一个冒烟测试单独用正样本目录跑一遍预测统计准确率上限——如果连自己的训练集都无法100%通过那模型本身就没有收敛好。6. 把认得我做得更稳数据增强与阈值校准两个小技巧训练结束不等于项目结束想让它在不同光照、不同角度的条件下都稳定工作我一般会在交付前多做两步。第一步是数据增强——在train_faces.py的读取阶段插入随机变换常见做法是水平翻转、亮度抖动、小幅旋转。水平翻转相当于把训练集翻了一倍而且人脸是近似对称的翻转不会破坏语义信息亮度抖动我用的是在原始像素值上乘以0.8到1.2的随机系数让模型不再对特定光线条件过度敏感。值得留意的是垂直翻转不能做因为人脸上下颠倒后特征分布完全变了模型会学到错误的结构关系。第二步是阈值校准。把模型训练好后我先拍30张自己的照片和30张同学的照片作为测试集分别输入模型记录概率值再画一条阈值-准确率曲线。这里的经验是错误接受率和错误拒绝率永远在打架阈值调高减少误报但可能漏掉自己调低则反过来。我一般会选择宁可不认也不错认的方向把阈值设在0.65左右用轻微的漏检率换取在陌生人面前不会误响应的安全性。我印象最深的一次教训是第一次跑完整个项目后兴冲冲拿去给室友演示结果室友站在摄像头前十秒模型毫无反应——因为他正好站在逆光位置整张脸处于阴影里。虽然训练时的数据采集阶段我在正常光线下拍了很多照片但模型从来没在低照度条件下见过我自然无法泛化。从那以后我每次跑完项目都会强制做一遍多光照测试拉窗帘、开台灯、背对窗户各站一次确认识别稳定后再收工。这个习惯帮我拦住了很多次数据采集不充分导致翻车的情况也让我后面在调阈值时有了更全面的参考依据希望帮到你。本文还有配套的精品资源点击获取
返回列表