
简介一套基于TensorFlow的人脸识别完整源码包面向深度学习、计算机视觉方向的学生与开发者旨在帮助快速搭建从人脸检测、对齐到特征识别与验证的完整工程流程。压缩包共12个文件以Python脚本为主体涵盖训练、评估、数据读取等模块同时配有模型权重bin、编译缓存pyc与说明文档整体约140.52MB目录按功能划分清晰便于按需调用、二次开发与对照学习。源码围绕数据预处理、CNN模型构建、triplet loss/contrastive loss等损失函数设计以及模型训练、评估与部署思路展开能够帮助读者理解MTCNN人脸对齐、FaceNet特征映射等关键算法在实际项目中的落地方式。已有1090人学习下载适合具备Python基础、希望结合TensorFlow完成人脸识别项目的开发者。1. 这份基于TensorFlow的人脸识别源代码初看像一份改了一半的CIFAR-10官方示例cifar10_input.py、cifar10.py、cifar10_train.py这些文件都在但目录里的face_eval、face_train、face_data又透露出真实用途——作者把CIFAR-10的图像分类CNN搬到了人脸分类上还保留了官方样例的完整训练/评估链路。它不是FaceNet那种封装好的组件而是让人直接面对数据流、张量形状、loss曲线的底层代码。如果你正在做考勤门禁、人脸识别算法原型或者想用TensorFlow跑通一个图像分类项目这份源码值得拆开读。读懂它你就能理解为什么有人用几千行代码训练人脸模型也能自己动手改造成新的数据集。后面所有操作都围绕一个目标把cifar10开头的文件改成自己的face_*.py然后训出一个能区分不同人脸的模型。2. 源码结构与数据准备从CIFAR-10二进制到人脸图片2.1 目录和文件到底在做什么先看包里的关键文件很多人拿到手会被cifar10前缀和face前缀同时存在搞晕。我按执行顺序整理了一张表文件职责执行时机cifar10_input.py读取图片数据做归一化、打乱、batch化train/eval 前自动调用cifar10.py定义CNN网络结构、loss、训练算子train/eval 时导入face_train.py / cifar10_train.py训练主流程生成checkpoint手动启动face_eval.py / cifar10_eval.py / cifar10_eval_zhujiao.py加载checkpoint跑评估指标手动启动face_data/训练用图片数据目录手动准备face_train/训练生成的模型保存目录自动生成pycache/*.pycPython 3.6运行后生成的字节码缓存无需关注注意 README.md 里如果没有特殊说明face_data 下通常按“每个人一个子目录”组织图片子目录名就是标签。cifar10_input.py 原本假设数据是 CIFAR-10 的二进制 bin 格式所以第一件要改的事就是数据读取。2.2 cifar10_input.py 的数据读取逻辑标准 CIFAR-10 官方输入代码用tf.FixedLengthRecordReader读 3072 字节的特征加 1 字节标签然后 reshape 成 32x32x3。人脸图片很少是 32x32也不是二进制单记录所以我一般会把这个文件改成从文件列表读取 JPEG 的思路# 自定义读取人脸图片替换原来读取CIFAR-10 bin的部分 def read_face_image(filename_queue): reader tf.WholeFileReader() filename, content reader.read(filename_queue) image tf.image.decode_jpeg(content, channels3) image tf.image.resize_images(image, [64, 64]) image tf.cast(image, tf.float32) / 127.5 - 1.0 # 映射到[-1,1] label parse_label_from_filename(filename) # 从路径取标签id return image, label这段代码做了三件事用WholeFileReader把文件读成字节流用decode_jpeg解码成 RGB 三通道图像统一缩放到 64x64 并归一化到 [-1,1]。CIFAR-10 的原始像素范围是 [0,255]配合tf.image.per_image_standardization做单图标准化也可以但人脸图像光照复杂缩放到[-1,1]后对卷积权重的初始化更友好。有了单条样本之后还是沿用官方那套 batch 逻辑images, labels tf.train.shuffle_batch( [image, label], batch_sizeFLAGS.batch_size, num_threads8, capacitymin_queue_examples 3 * FLAGS.batch_size, min_after_dequeuemin_queue_examples)这里的min_after_dequeue控制队列里的最小样本数建议设置为 batch_size 的 8 到 10 倍。太小的话每个 batch 的随机性不足训练容易震荡太大会拖慢前面攒队列的时间。启动训练后进程会吃满内存才真正开始算 loss这是正常现象不是卡死。另外一个容易忽略的点是人脸图片的通道顺序decode_jpeg返回的是 RGB但 OpenCV 习惯用 BGR如果你之前用 cv2 做过预处理别把顺序搞混否则模型在训练时看到的颜色语义会失真。2.3 把 face_data 组织成 TensorFlow 能读的清单官方 CIFAR-10 是已经切好的训练集和测试集人脸项目没有。你需要自己先写一个小脚本生成 train.list 和 eval.list每行是“图片路径 标签id”find face_data -name *.jpg | awk -F/ {print $0, $2} face_data/train.listawk里$2取的是子目录名把它当成标签 id 的前提是你已经把每个人的图片分到了以 0、1、2…命名的文件夹。如果不喜欢 shell也可以用 Python 的glob做同样的事。注意类别编号要从 0 开始连续否则tf.nn.sparse_softmax_cross_entropy_with_logits的标签范围会报错。数据准备这部分只要做到“图片路径 数字标签”后面的模型不需要知道人脸是谁只需要知道有多少类。还有一点如果 face_data 里有损坏的图片decode_jpeg会直接抛异常建议在读取管线里加tf.image.decode_jpeg(content, channels3, try_recover_truncatedTrue)来容错。3. 卷积神经网络定义cifar10.py 里的模型结构3.1 网络拓扑与张量形状变化cifar10.py 的 inference 函数定义了一个经典CNN卷积 → 池化 → 卷积 → 池化 → 全连接 → softmax。针对人脸图片原版的输入是 24x24x3官方代码会先随机裁剪成 24x24如果你按上一章改成 64x64 输入各层输出尺寸变化如下层名操作输出形状conv15x5 卷积32核ReLU[batch, 64, 64, 32]pool13x3 最大池化步长2[batch, 32, 32, 32]norm1局部响应归一化 LRN同上conv25x5 卷积64核ReLU[batch, 32, 32, 64]pool23x3 最大池化步长2[batch, 16, 16, 64]norm2局部响应归一化 LRN同上local3全连接 384[batch, 384]local4全连接 192[batch, 192]logits全连接 num_classes[batch, num_classes]注意人脸识别是细粒度分类目标是在人种、姿态、年龄差异下区分身份所以浅层卷积核数量可以适当加大。原版 CIFAR-10 用 64 个卷积核就能跑出 86% 左右人脸任务如果类别不超过 20 人同样结构也够用。如果你的数据集有 50 人以上建议把 conv1 的核数改为 64conv2 改为 128否则特征容量不足训练集 loss 下不去。3.2 关键代码逐段拆解cifar10.py 中核心网络代码用tf.layers或tf.nn实现两种风格差别不大关键看变量作用域。以tf.nn版本为例with tf.variable_scope(conv1) as scope: kernel tf.get_variable(weights, [5, 5, 3, 32], initializertf.truncated_normal_initializer(stddev0.1)) conv tf.nn.conv2d(images, kernel, [1, 1, 1, 1], paddingSAME) biases tf.get_variable(biases, [32], initializertf.constant_initializer(0.0)) conv1 tf.nn.relu(tf.nn.bias_add(conv, biases), namescope.name)这段代码里有一个容易被忽略的细节truncated_normal_initializer(stddev0.1)把卷积核初始化为截断正态分布这比全零初始化有效得多。偏置初始化为 0.0 而不是 0.1在官方案例里也存在差异。如果偏置初始化为 0ReLU 前不会产生死神经元但在小数据集上可能收敛较慢。paddingSAME保证卷积后空间尺寸不变人脸的小尺寸细节不会过早被抹掉。如果你发现训练几轮后卷积核输出全是负数先检查这里是不是用了全零初始化。3.3 损失函数与训练算子的设计官方loss()用的是带权重衰减的交叉熵cross_entropy tf.nn.sparse_softmax_cross_entropy_with_logits( labelstf.squeeze(labels), logitslogits) cross_entropy_mean tf.reduce_mean(cross_entropy) tf.add_to_collection(losses, cross_entropy_mean) tf.add_to_collection(losses, FLAGS.weight_decay * tf.add_n(tf.get_collection(losses))) return tf.add_n(tf.get_collection(losses), nametotal_loss)这个设计比单独返回 loss 更工程化因为后续加 L2 正则时不需要改函数签名。weight_decay默认 0.004会在每个卷积核和全连接权重上叠加正则项。人脸识别任务里权重衰减不宜太大0.001 到 0.004 之间通常比较好过大会让模型欠拟合出现训练 loss 不降的现象。优化器选择上官方 CIFAR-10 示例用tf.train.AdamOptimizer(1e-3)这份源码大概率保留了这个设置。很多人会问为什么不用 FaceNet 的 triplet loss原因是这份源码是“分类式”人脸识别即把每个人当成一个类用 softmax 训练。对一个班级 30 人的考勤系统分类式足够对大规模陌生人的识别才需要换度量学习。理解这一点你就知道后续想换损失函数时应该动哪一部分。4. 训练和评估让损失曲线掉下来4.1 启动训练的命令与参数README 里如果没写启动方式通常跑法是python face_train.py --train_dir ./face_train --data_dir ./face_data --max_steps 20000 --batch_size 64关键参数含义如下表参数示例值调参建议learning_rate0.001先跑500步loss下降慢就减小到1e-4max_steps20000人脸数据量小5000步就能看趋势batch_size64显存不够减半注意学习率也要等比调整num_preprocess_threads84到16之间太高反而增加线程切换开销log_frequency10每10步打印一次loss便于快速筛选异常启动后终端每 10 步会打印一次 loss如果第一步 loss 就在类别数的自然对数附近比如 30 类时 log(30)≈3.4说明模型初始化正常。如果瞬间就是 0 或 1000先回去检查标签是否连续编码、图像像素是否越界。还有一个常见坑--data_dir指向 face_data 目录本身但源码里可能还会再拼接一下路径导致找不到文件观察一下报错里的路径就能发现。4.2 用 TensorBoard 监控训练过程源码里写了tf.summary.scalar(loss, loss)之类的话所以训练时自动会在 train_dir 下生成 events 文件。只需要tensorboard --logdir./face_train然后在浏览器打开 6006 端口。我一般不看 loss 曲线本身而是把 train 和 eval 的 summary 放到不同子目录用同一个--logdir启动这样能在一张图里看到训练集和验证集准确率的 gap。如果训练 loss 下降但验证准确率不动就是过拟合优先加数据增强如果两者都不动先检查 learning rate 是不是被默认参数覆盖了。TensorBoard 里还可以看卷积核的可视化图帮你判断第一层到底学到了边缘还是纹理。4.3 评估脚本谁说准确率要看日志face_eval.py 这类评估脚本的逻辑是恢复 checkpoint跑一遍验证集统计 top-1 准确率。核心是这一段with tf.Session() as sess: ckpt tf.train.get_checkpoint_state(FLAGS.eval_dir) if ckpt and ckpt.model_checkpoint_path: saver.restore(sess, ckpt.model_checkpoint_path) accuracy sess.run(eval_op, feed_dict{images: eval_images, labels: eval_labels})需要留意的坑评估脚本的输入 pipeline 必须和训练脚本共享同一个图像预处理逻辑包括缩放尺寸、归一化方式。很多人在训练时用resize(64,64)评估时直接喂原始大图导致第一个卷积层形状对不上报错。另外如果 eval 和 train 同时跑eval 脚本可能抓到训练中途的 checkpoint需要设置min_after_dequeue大一点或者用tf.train.SessionRunHook判断 checkpoint 的全局步数大于某个阈值再去评估。评估结果是一个整体准确率但人脸识别里更关心模型的拒识和误识分别是什么场景最好把每个类别的准确率单独打印出来。4.4 保留一个小的验证集人脸识别的数据往往只有几百张图随便切验证集容易让训练集类别不均衡。我常用 5 折交叉检验里的 1 折做验证并保证每个类别在验证集里至少有一张。分类准确率之外最好再记录一个每类平均准确率recall per class因为人脸识别实际使用中某些人脸在姿态和光照下的表现差异很大总准确率不够用。如果你发现某个人的图片总被识别成另一个人先去看训练集里这两个人的图片数量是否悬殊这是最直接的过拟合信号。5. 从CIFAR-10项目到产品级人脸识别四个实用改造5.1 数据增强让人脸不怕侧脸和光照在cifar10_input.py输入管线里加上随机翻转和颜色扰动是性价比最高的改造image tf.image.random_flip_left_right(image) image tf.image.random_brightness(image, max_delta0.2) image tf.image.random_contrast(image, lower0.8, upper1.2)这会让训练损失变高但验证准确率通常会涨几个点。注意人脸识别不适合随机裁剪因为人脸关键结构眼睛、嘴一旦被裁掉模型反而学到错误的特征。5.2 导出固化模型给TensorFlow Serving或门禁机训练好的 checkpoint 只是一堆变量部署时最好导出成 pb 文件。常见做法是使用tf.graph_util.convert_variables_to_constants把变量变成常量只保留前向推理图import tensorflow as tf from tensorflow.python.framework import graph_io sess tf.Session() saver.restore(sess, ./face_train/model.ckpt-20000) graph_def tf.get_default_graph().as_graph_def() output_names [logits] frozen tf.graph_util.convert_variables_to_constants( sess, graph_def, output_names) graph_io.write_graph(frozen, ./export, face_model.pb, as_textFalse)这样得到的 pb 文件在推理时不需要再管标签和归一化之外的细节门禁机上如果跑的是 TensorFlow Lite再转成 tflite 即可。转的时候注意把输入节点名字固定为images后面调用时统一用sess.run(logits:0, {images:0: img})。5.3 人脸对齐在分类前加一个MTCNN这份源码直接对原始人脸图片分类效果会受姿态影响。如果需要更稳的识别在数据入口先做 MTCNN 人脸 5 点关键点检测然后仿射变换把眼睛对齐到固定坐标再送入这个 CNN。代码片段import cv2 aligned custom_mtcnn_align(raw_image) aligned cv2.resize(aligned, (64, 64))MTCNN 本身也是 TensorFlow 模型可以跑在 CPU 上单张图片耗时大约 30-80ms比对模型整个推理时间还长所以在部署时通常把它和分类模型放在同一个进程里并使用线程池并发处理多路视频流。5.4 调参数时最容易踩的两个坑第一个坑是 batch_size 和学习率同步问题。CNN 对 batch 很敏感batch 从 64 加到 128学习率最好是原来的 1.5 到 2 倍否则收敛速度反而变慢。第二个坑是类别数变化。源码里输出层的节点数是从数据集的类别数算出来的如果你把 10 个人加到 50 个人需要重新初始化最后那层全连接而不是加载旧 checkpoint 继续训否则旧权重里没有新类别的维度。如果你真想上 FaceNet 那种端到端人脸识别这份源码的价值在于让你先理解数据流和评估流程再去看 FaceNet 的 embedding 输出时就不会被一大堆tf.map_fn和np.linalg.norm唬住。先把 MTCNN 对齐、分类阈值调好这份源码才能稳定工作。本文还有配套的精品资源点击获取