
简介这份资源是一份面向深度学习与计算机视觉入门者的卷积神经网络人脸识别示例代码以PDF形式呈现适合已掌握Python基础、希望从传统特征脸法过渡到CNN实战的开发者。内容围绕人脸检测、头像提取、数据集构建、模型搭建与训练展开涉及OpenCV、TensorFlow、Keras、scikit-learn等依赖库并给出Yale人脸库与自采照片混合训练的完整思路。资源包共1个PDF文件大小约722KB篇幅紧凑便于快速通读与对照实践。目前已有1442人学习下载说明其在入门群体中具有一定参考价值。读者可从中获得从环境配置、人脸裁剪到CNN分类识别的连贯示例理解卷积与池化在特征提取中的作用并借鉴数据收集、预处理与模型评估的工程组织方式为自行搭建人脸识别系统提供可复用的代码骨架与排错参考。1. 从特征脸到 CNN这套人脸识别示例代码到底能跑出什么如果你手头只有几十张自拍、一台普通笔记本却想搞明白卷积神经网络做人脸识别到底是怎么一回事这套示例代码就是为你准备的。它没有堆砌花哨的工程封装而是把「检测人脸 → 裁剪对齐 → 训练 CNN → 实时识别」这条链路完整摊开给你看。作者用 Yale 人脸库15 人、每人 11 张含光照和表情变化混入自己的照片用 Keras TensorFlow 搭了一个四层卷积的小网络最终在摄像头画面上实时框出人脸并打出分类标签。整套代码依赖 numpy、opencv-python、keras、tensorflow、scikit-learnPython 3.6 环境即可运行有 CUDA 显卡的话换 tensorflow-gpu 能明显缩短训练时间。它适合刚学完 CNN 原理、想找一个能跑通的小项目练手的人也适合想理解「人脸识别算法从特征脸到深度学习」这条演进线的从业者。代码里那些参数——卷积核数量、Dropout 比例、SGD 的学习率和冲量——都是可以直接改的改完看效果比看十篇原理文章都管用。2. 环境搭建与数据准备从 pip 安装到 Yale 人脸库混入自己的照片2.1 依赖安装与 Anaconda 的取舍这套代码的依赖清单很干净没有冷门库。直接 pip 安装即可pip3 install numpy pip3 install opencv-python pip3 install keras pip3 install scikit-learn pip3 install tensorflow # 如果有支持 CUDA 的 GPU换成 GPU 版本 pip3 install tensorflow-gpu这里有个选型问题值得说清楚Anaconda 确实能省去不少依赖冲突的麻烦它自带 Conda、Python 和 180 多个科学包在 Environment 面板里搜索模块、点 Apply 就能装。但它的下载文件很大如果你已经有 Python 包管理基础pip 逐条装反而更可控。我一般会在虚拟环境里跑这套代码避免 Keras 和 TensorFlow 的版本互相打架。Python 3.6 是作者验证过的版本但 Keras 和 TensorFlow 的版本兼容性比较敏感建议锁定 tensorflow1.x 配合对应的 keras 版本否则Conv2D的参数名可能对不上。2.2 人脸检测与头像提取的完整流程数据准备分两步先检测再裁剪。作者用的是 OpenCV 自带的 Haar 级联分类器haarcascade_frontalface_default.xml这个文件在 OpenCV 安装目录的data/haarcascades/下能找到。整个提取逻辑是遍历文件夹下所有图片 → 灰度化 → 检测人脸位置 → 按坐标切片保存。# _*_ coding:utf-8 _*_ import cv2 import os CASE_PATH haarcascade_frontalface_default.xml RAW_IMAGE_DIR me/ DATASET_DIR jm/ face_cascade cv2.CascadeClassifier(CASE_PATH) def save_feces(img, name, x, y, width, height): # 按检测框坐标切片注意 y-30 和 height30 是为了多留一点额头和下巴 image img[y:yheight, x:xwidth] cv2.imwrite(name, image) image_list os.listdir(RAW_IMAGE_DIR) count 166 for image_path in image_list: image cv2.imread(RAW_IMAGE_DIR image_path) gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale(gray, scaleFactor1.2, minNeighbors5, minSize(5, 5), ) for (x, y, width, height) in faces: save_feces(image, %ss%d.bmp % (DATASET_DIR, count), x, y - 30, width, height30) count 1detectMultiScale的三个参数直接决定检测质量scaleFactor1.2表示每次图像尺寸缩小 20%值越小检测越慢但越不容易漏minNeighbors5控制误检值越大越严格minSize(5,5)是最小人脸尺寸设太小会把背景噪点当人脸。y-30和height30是作者的血泪经验——Haar 检测框往往只框住五官额头和下巴容易被切掉手动扩一点能保留更多面部信息。Yale 库的图片是 100×100你混入的自拍尺寸不一所以下一步必须做尺寸统一。2.3 无形变 resize为什么不能直接暴力缩放直接把任意尺寸的图 resize 成 100×100人脸会被拉扁或拉长卷积核学到的特征就变形了。作者用了一个数字图像处理的常规手段先把短边涂黑补成正方形再缩放。def resize_without_deformation(image, size(100, 100)): height, width, _ image.shape longest_edge max(height, width) top, bottom, left, right 0, 0, 0, 0 if height longest_edge: height_diff longest_edge - height top int(height_diff / 2) bottom height_diff - top elif width longest_edge: width_diff longest_edge - width left int(width_diff / 2) right width_diff - left # 用黑色填充边界保持长宽比 image_with_border cv2.copyMakeBorder(image, top, bottom, left, right, cv2.BORDER_CONSTANT, value[0, 0, 0]) resized_image cv2.resize(image_with_border, size) return resized_imagecopyMakeBorder的value[0,0,0]就是填充黑色BORDER_CONSTANT表示用常数填充。这样处理后人脸在图像中的比例不变只是周围多了黑边。读取函数read_image把每张图转成np.int8数组标签用str(int((i-1)/11.0))生成——因为 Yale 库每人 11 张整除就能得到类别编号。这里标签是字符串后面 one-hot 编码时会自动处理。3. 网络结构与训练参数Keras 搭四层卷积的每一步在做什么3.1 从卷积到全连接逐层拆解模型构建Keras 的 Sequential 模型让搭网络像搭积木。作者的结构是两组「卷积-卷积-池化-Dropout」然后 Flatten、全连接、输出。from keras.layers import Conv2D, MaxPooling2D, Dense, Dropout, Flatten from keras.optimizers import SGD from keras.utils import np_utils from keras.models import Sequential import keras import numpy as np from sklearn.model_selection import train_test_split IMAGE_SIZE 100 raw_images, raw_labels read_image(size(IMAGE_SIZE, IMAGE_SIZE)) raw_images np.asarray(raw_images, dtypenp.float32) raw_labels np.asarray(raw_labels, dtypenp.int32) # one-hot 编码把类别标签变成等长的二进制向量避免大小关系误导模型 ont_hot_labels np_utils.to_categorical(raw_labels) # 7:3 划分训练集和测试集train_test_split 会自动打乱 train_input, valid_input, train_output, valid_output train_test_split( raw_images, ont_hot_labels, test_size0.3) # 归一化像素值除以 255落到 0~1 区间 train_input / 255.0 valid_input / 255.0 face_recognition_model Sequential() # 第一组卷积32 个 3×3 卷积核valid 模式不补边步长 1 face_recognition_model.add(Conv2D(32, 3, 3, border_modevalid, subsample(1, 1), dim_orderingtf, input_shape(IMAGE_SIZE, IMAGE_SIZE, 3), activationrelu)) face_recognition_model.add(Conv2D(32, 3, 3, border_modevalid, subsample(1, 1), dim_orderingtf, activationrelu)) face_recognition_model.add(MaxPooling2D(pool_size(2, 2))) face_recognition_model.add(Dropout(0.2)) # 第二组卷积64 个 3×3 卷积核 face_recognition_model.add(Conv2D(64, 3, 3, border_modevalid, subsample(1, 1), dim_orderingtf, activationrelu)) face_recognition_model.add(Conv2D(64, 3, 3, border_modevalid, subsample(1, 1), dim_orderingtf, activationrelu)) face_recognition_model.add(MaxPooling2D(pool_size(2, 2))) face_recognition_model.add(Dropout(0.2)) # Flatten 把多维特征图拍扁成一维才能送进全连接层 face_recognition_model.add(Flatten()) face_recognition_model.add(Dense(512, activationrelu)) face_recognition_model.add(Dropout(0.4)) # 输出层神经元数等于类别数sigmoid 输出每个类别的概率 face_recognition_model.add(Dense(len(ont_hot_labels[0]), activationsigmoid))border_modevalid意味着卷积后特征图会缩小100×100 经过两次 3×3 卷积和一次 2×2 池化尺寸逐步降到 22×22 左右。dim_orderingtf指定张量格式为(batch, height, width, channels)这是 TensorFlow 后端的默认格式。Dropout(0.2)在训练时随机丢弃 20% 神经元Dropout(0.4)在全连接层丢 40%目的是防止过拟合——小数据集上过拟合是头号敌人。输出层用 sigmoid 而不是 softmax作者原文如此但多分类任务其实 softmax 更标准这一点后面避坑章节会展开。3.2 SGD 优化器参数学习率、衰减与冲量的配合编译模型前先定义优化器。作者选了 SGD 而不是 Adam参数如下learning_rate 0.01 decay 1e-6 momentum 0.8 nesterov True sgd_optimizer SGD(lrlearning_rate, decaydecay, momentummomentum, nesterovnesterov) face_recognition_model.compile(losscategorical_crossentropy, optimizersgd_optimizer, metrics[accuracy]) batch_size 20 epochs 100 face_recognition_model.fit(train_input, train_output, epochsepochs, batch_sizebatch_size, shuffleTrue, validation_data(valid_input, valid_output)) print(face_recognition_model.evaluate(valid_input, valid_output, verbose0)) MODEL_PATH face_model.h5 face_recognition_model.save(MODEL_PATH)lr0.01是初始学习率decay1e-6让学习率随迭代次数缓慢下降防止后期震荡。momentum0.8引入冲量模拟物理惯性——梯度方向一致时加速方向反复时减速。nesterovTrue是 Nesterov 冲量先按冲量走一步再算梯度收敛更稳。损失函数用categorical_crossentropy配合 one-hot 标签使用如果标签是整数而非 one-hot应该用sparse_categorical_crossentropy。batch_size20表示每批 20 张图更新一次权重epochs100是完整遍历数据 100 次。训练完用save存成 HDF5 文件识别时直接load_model加载。4. 实时识别与效果验证摄像头读取、裁剪推理与打框标注4.1 识别脚本的完整逻辑训练好的模型要落到实时画面上流程是开摄像头 → 读帧 → 灰度化 → 检测人脸 → 裁剪并 resize → 送模型预测 → 画框写字。import cv2 import numpy as np import keras from keras.models import load_model CASE_PATH haarcascade_frontalface_default.xml face_cascade cv2.CascadeClassifier(CASE_PATH) face_recognition_model keras.Sequential() MODEL_PATH face_model.h5 face_recognition_model load_model(MODEL_PATH) cap cv2.VideoCapture(0) ret, image cap.read() gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale(gray, scaleFactor1.2, minNeighbors5, minSize(30, 30)) for (x, y, width, height) in faces: img image[y:yheight, x:xwidth] img resize_without_deformation(img) img img.reshape((1, 100, 100, 3)) img np.asarray(img, dtypenp.float32) img / 255.0 result face_recognition_model.predict_classes(img) cv2.rectangle(image, (x, y), (x width, y height), (0, 255, 0), 2) font cv2.FONT_HERSHEY_SIMPLEX if result[0] 15: cv2.putText(image, kangChi, (x, y-2), font, 0.7, (0, 255, 0), 2) else: cv2.putText(image, No.%d % result[0], (x, y-2), font, 0.7, (0, 255, 0), 2) cv2.imshow(, image) cv2.waitKey(0)predict_classes返回概率最大的类别索引。result[0] 15是作者给自己照片分配的类别号——Yale 库 15 个人编号 0 到 14自己的照片从 15 开始。reshape((1, 100, 100, 3))把单张图变成 batch 大小为 1 的四维张量这是 Keras 预测的固定输入格式。cv2.rectangle画绿框cv2.putText在框上方写标签。注意cv2.waitKey(0)只显示一帧实际实时识别应该放在while循环里持续读帧。4.2 训练时间与效果预期作者用「吃 2/3 个西瓜的时间」来形容训练时长按普通 CPU 估算大约 20 到 40 分钟GPU 能压到几分钟。Yale 库只有 165 张图加上自己的照片总共不到 200 张这个数据量下模型很容易过拟合——训练集准确率很高但测试集和实际摄像头画面上误识别不少。作者自己也提到「有一些误识别的情况出现」这是小数据集的正常表现。评估时看evaluate返回的 loss 和 accuracy如果验证集准确率远低于训练集说明过拟合了需要加数据增强或加大 Dropout。5. 避坑与排查这套代码跑起来最容易翻车的五个地方5.1 现象Conv2D报错unexpected keyword argument border_mode原因Keras 2.0 之后border_mode改名为paddingsubsample改名为stridesdim_ordering改名为data_format。作者代码基于旧版 Keras新版直接跑会报错。解决把border_modevalid改成paddingvalidsubsample(1,1)改成strides(1,1)dim_orderingtf改成data_formatchannels_last。或者锁定旧版 Keras但更推荐改代码适配新版。5.2 现象训练时 loss 一直是nan准确率不动原因学习率太大或数据没归一化。作者代码里做了train_input / 255.0但如果你自己改代码时漏了这步像素值 0 到 255 直接送进网络梯度爆炸loss 变 nan。解决确认归一化步骤在train_test_split之后、fit之前执行。如果归一化后还是 nan把学习率从 0.01 降到 0.001 试试。5.3 现象predict_classes报错AttributeError: Sequential object has no attribute predict_classes原因TensorFlow 2.6 之后predict_classes被移除了。作者用的旧版 Keras 有这个 API新版没有。解决用np.argmax(face_recognition_model.predict(img), axis-1)替代predict_classes。predict返回每个类别的概率向量argmax取最大概率的索引效果一样。5.4 现象摄像头画面卡死或cap.read()返回False原因摄像头被其他程序占用或者cv2.VideoCapture(0)的索引不对。有些笔记本内置摄像头是 0外接 USB 摄像头可能是 1。解决先确认没有其他程序开着摄像头然后试cv2.VideoCapture(1)或cv2.VideoCapture(-1)。另外waitKey(0)会阻塞实时识别要改成waitKey(1)并放进while True循环。5.5 现象识别结果全是同一个类别或者置信度很低原因数据不平衡。Yale 库 15 人每人 11 张你自己只混了几张进去模型倾向于预测样本多的类别。另外输出层用 sigmoid 做多分类不如 softmax 合适sigmoid 每个神经元独立判断不保证概率和为 1。解决增加自己的照片数量至少每人 10 张以上覆盖不同角度和光照。输出层激活函数改成softmax损失函数保持categorical_crossentropy不变。如果类别数很少也可以考虑用sparse_categorical_crossentropy配合整数标签省去 one-hot 编码。6. 进阶技巧用数据增强和模型微调把准确率再拉一截小数据集做人脸识别最有效的提升手段不是换更大的网络而是数据增强。Keras 的ImageDataGenerator可以在训练时实时对图片做旋转、平移、缩放、水平翻转相当于凭空变出几倍的数据量。我一般会这样接在fit之前from keras.preprocessing.image import ImageDataGenerator datagen ImageDataGenerator( rotation_range10, # 随机旋转 ±10 度 width_shift_range0.1, # 水平平移 10% height_shift_range0.1, # 垂直平移 10% zoom_range0.1, # 缩放 10% horizontal_flipTrue, # 水平翻转 fill_modenearest # 填充新像素用最近邻 ) datagen.fit(train_input) face_recognition_model.fit_generator(datagen.flow(train_input, train_output, batch_size20), steps_per_epochlen(train_input) / 20, epochs100, validation_data(valid_input, valid_output))rotation_range和width_shift_range模拟拍摄角度和位置变化horizontal_flip模拟左右镜像——人脸左右翻转后仍是对称的这个增强很安全。fill_modenearest保证旋转后边缘空白用最近像素填充不会出现黑边干扰。注意增强只对训练集做验证集保持原样否则评估结果不可信。另一个技巧是冻结卷积层做迁移学习。如果你不想从零训练可以加载一个在大型人脸数据集上预训练过的模型把前面的卷积层冻住只训练最后的全连接层。Keras 里用layer.trainable False逐层设置即可。这套示例代码的网络结构很浅迁移学习的收益有限但如果你把卷积层换成 VGG 或 ResNet 的前几层效果会明显不同。验证方法上除了看evaluate的准确率我习惯用混淆矩阵看每个类别的误判情况。sklearn.metrics.confusion_matrix传入真实标签和预测标签就能生成哪个类别老是被认错一目了然。如果某个类别误判特别多针对性补那个人的照片比盲目加数据更有效。从那以后我每次跑小数据集分类都强制先做一遍数据增强再训练不然验证集和训练集的准确率差距大得让人怀疑人生。希望帮到你。本文还有配套的精品资源点击获取