
简介基于Python机器学习的人脸识别期末大作业项目适合高校学生作为课程设计或期末大作业参考。项目已获导师指导并被评为97分高分包含完整可运行的源码、课程报告与项目说明覆盖人脸识别、性别检测等典型应用场景代码注释清晰目录结构明朗下载后无需修改即可运行。压缩包共10个文件其中6个Python脚本负责训练、测试与实时识别3个PDF为课堂汇报、中期报告与最终报告另有1个Markdown说明文件整体大小约3.93MB。目前已有428人学习下载可作为机器学习综合实践的良好范例。资源提供了从数据预处理、模型构建到可视化展示的完整流程并附带BP神经网络实现性别检测的扩展实验能够帮助学习者快速复现人脸识别系统理解特征提取与分类器设计的关键方法是兼顾课程汇报与代码实践的优质资料。1. 为什么把课程设计选成人脸识别人脸识别是机器学习课程作业里性价比最高的题。它不需要在本科阶段硬磕大规模卷积网络而是用 OpenCV 的 Haar Cascade 先把人脸框出来再交给 BP 神经网络做性别或身份分类检测、特征、训练、评估四个环节全部覆盖完整跑通只需要一个摄像头和一台普通笔记本。这份拿到 97 分的大作业源码恰好是按这个套路组织的里面既有 photo/video 两套输入链路也有 BP 网络做性别检测的完整预测代码还有可直接提交的课程报告和课堂汇报材料适合正在做课程设计、期末大作业或者想找一个可复现人脸识别 baseline 的读者。下面按文件结构、BP 原理、代码走读、调参与验收的顺序展开每段代码都能抄下来直接改。2. 项目文件结构与 BP 神经网络的输入输出链路2.1 解压后每个文件是干什么的先把 zip 解压开逐个对文件用途做标注。文件名类型用途photo_sex_rec.py图片 性别读入单张图片检测人脸并预测性别photo_lib_sex_rec.py图片 建库对一批图片建立人脸特征库并执行识别video_sex_rec.py视频/摄像头 性别逐帧检测实时性别分类video_lib_sex_rec.py视频 建库视频流中与预建库比对身份photo_test.py测试脚本单张图片快速验证模型文件和权重文件是否可用video_face_rec.py视频 人脸检测只画人脸框不做分类README.md说明文档运行顺序、环境依赖与目录说明课堂汇报.pdf答辩材料答辩用演示稿最终报告.pdf / 工作报告.pdf课程报告算法描述、实验数据与结果分析脚本命名本身就在拆解工作流photo 是静态图片检测video 是摄像头或视频流检测lib 后缀代表需要先构建人脸库sex 是性别分类face_rec 是纯检测。命名越规范报告里画系统架构图越省事老师一眼能看出模块划分。photo_test.py 是调试入口先跑它确认权重没加载错再去看视频链路能省掉大部分环境问题排查时间。2.2 BP 神经网络的输入特征与网络设计2.2.1 人脸区域归一化与特征向量拼接BP 网络的输入层节点数等于特征维度。如果把整帧视频画面直接送进去几十万维的输入会让权重矩阵膨胀训练极慢且极易过拟合。所以标准做法是先靠 Haar 级联把背景信息剔除只保留人脸框再压缩成固定尺寸的小图。常见做法是 resize 到 32x32 或 64x64 的灰度图输入维度是 1024 或 4096。我一般把像素值直接除以 255 缩放到 [0,1]而不是做标准化。sigmoid 的输出域也在 (0,1) 区间两边尺度对齐之后网络初始权重在 ±0.5 附近时前向输出不会一开始就顶到饱和区梯度消失的概率低很多。如果想把准确率再往上拉一点可以把特征扩展为像素向量加灰度均值加灰度方差形成一个 1026 维的输入这个拼接在一行 concatenate 里就能完成。import cv2 import numpy as np def extract_feature(roi_gray, target_size(32, 32)): # 统一尺寸避免不同分辨率下人脸区域维度不一致 roi cv2.resize(roi_gray, target_size) # 像素归一化到 0~1匹配 sigmoid 输出域 pixels roi.reshape(-1) / 255.0 # 附加全局光照信息辅助 BP 区分明暗差异 extra np.array([roi.mean(), roi.std()]) return np.hstack([pixels, extra])逻辑说明第一行 resize 把所有检测框强制压成相同尺寸这是批量训练的前提reshape(-1) 把二维灰度图拉平成一行得到 1024 维向量mean 和 std 分别描述人脸区域整体亮度和对比度属于最简单的全局特征。参数说明target_size 越小训练越快但 16x16 会丢掉眉毛、嘴唇等性别相关细节一般不要低于 24均值特征对人脸偏暗或过曝的场景有一定补偿作用实测在混合光照数据上能把性别准确率提升 2~3 个百分点。2.2.2 三层 BP 的前向传播、损失与参数选择性别检测本质是二分类。典型的网络结构是输入层 1024 个节点、隐藏层 64 个节点、输出层 1 个节点隐藏层激活函数用 sigmoid输出层也走 sigmoid损失函数用交叉熵而不是均方误差。交叉熵在预测完全错误时梯度更大对课程设计这种几百条样本的小数据集收敛更干脆。import numpy as np def sigmoid(x): return 1.0 / (1.0 np.exp(-x)) def sigmoid_deriv(x): return x * (1.0 - x) class BPNet: def __init__(self, n_in1026, n_hidden64, n_out1, lr0.01): self.lr lr # 小随机初始化避免权重对称0.5 是经验缩放值 self.w1 np.random.randn(n_in, n_hidden) * 0.5 self.b1 np.zeros(n_hidden) self.w2 np.random.randn(n_hidden, n_out) * 0.5 self.b2 np.zeros(n_out) def forward(self, X): # 输入经过隐藏层再压缩到单输出节点 self.z1 X.dot(self.w1) self.b1 self.a1 sigmoid(self.z1) self.z2 self.a1.dot(self.w2) self.b2 self.a2 sigmoid(self.z2) return self.a2 def backward(self, X, y, pred): # 批量梯度下降m 是当前批次样本数 m X.shape[0] d2 (pred - y) * sigmoid_deriv(self.a2) d1 d2.dot(self.w2.T) * sigmoid_deriv(self.a1) self.w2 - self.lr * self.a1.T.dot(d2) / m self.b2 - self.lr * d2.mean(axis0) self.w1 - self.lr * X.T.dot(d1) / m self.b1 - self.lr * d1.mean(axis0)逻辑说明forward 把 1026 维输入压缩到 64 维隐藏层再映射到 1 个输出节点输出值越接近 1 判定为某一性别越接近 0 判定为另一性别。backward 里 d2 和 d1 是交叉熵误差对输出层、隐藏层的梯度除以 m 是为了让更新步长与 batch 大小解耦。参数说明n_hidden 太小网络学不到有效特征太大在几百条样本上必然过拟合32 到 128 都可接受lr 从 0.01 起步训练时 loss 震荡就降到 0.001w1 的初始化缩放系数 0.5 是适配 sigmoid 的常见选择换成 1.0 容易让隐藏层输出一开始就饱和。参数推荐值取值依据n_hidden32 ~ 128节点过少欠拟合过多过拟合lr0.01 起调loss 震荡则降为 0.001epochs100 ~ 300小数据集再增加无实际收益batch全部样本或 64numpy 内存足够不需刻意设小2.3 训练数据组织、权重保存与恢复课程设计的隐藏加分项是数据来源交代清楚。报告里必须写明训练集、验证集、测试集的拆分比例并贴几张预处理后的样本图。常见做法是从 UTKFace 或 IMDB-WIKI 这类公开人脸数据集里按性别归档也可以收集同学正面照手动标注但报告数据页要打码或说明已征得同意。本项目没有把原始训练集放进 zipREADME 给出的是推荐目录组织方式train/male、train/female、test/male、test/female。按这个目录存放图片训练脚本读取时最省事。训练完的权重用 numpy 直接落盘预测脚本不依赖训练数据也能独立运行。# 训练结束后保存全部权重到单个 npz 文件 np.savez(bp_sex.npz, w1w1, b1b1, w2w2, b2b2) def load_net(pathbp_sex.npz): data np.load(path) net BPNet() # 把保存的参数逐项恢复到网络实例 net.w1, net.b1 data[w1], data[b1] net.w2, net.b2 data[w2], data[b2] return net逻辑说明savez 把四个权重矩阵打包成一个 npz 文件磁盘占用通常不到 200KBload_net 返回的是一个已经能直接 forward 的 BPNet 实例photo 和 video 脚本共用这个入口。参数说明npz 是压缩格式加载前不要手动解压成单个文件如果报告里对比了 BP 与 CNN 的性能差异建议在同一份测试集上记录准确率和单帧耗时即使只是文字说明也能明显提升答辩时的方法对比深度。3. 图片与视频双链路走读photo_sex_rec.py 与 video_sex_rec.py3.1 图片静态检测的完整调用链图片链路的大致执行流程是解析命令行参数、读图、转灰度、Haar 检测人脸、对每个检测框裁剪缩放、送入 BP 预测、画框打标签、写回结果。这类工程的检测函数通常封装成一个统一的 detect_and_predict视频链路只是把这个函数套进帧循环里。import cv2 import numpy as np face_cascade cv2.CascadeClassifier(cv2.data.haarcascades haarcascade_frontalface_default.xml) def detect_and_predict(img, net, target_size(32, 32), threshold0.5): gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # scaleFactor 控制图像缩放步长minNeighbors 控制候选框确认数 faces face_cascade.detectMultiScale(gray, scaleFactor1.1, minNeighbors5, minSize(24, 24)) for (x, y, w, h) in faces: roi gray[y:yh, x:xw] feature extract_feature(roi, target_size) feature feature.reshape(1, -1) pred net.forward(feature)[0][0] label male if pred threshold else female # 在原始彩色图上绘制矩形与标签 cv2.rectangle(img, (x, y), (xw, yh), (0, 255, 0), 2) cv2.putText(img, label, (x, y - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) return img逻辑说明detectMultiScale 返回若干个人脸矩形坐标代码对每个矩形做一次特征提取与预测所以一张合影里出现多张脸时循环会分别给每个脸打上性别标签。reshape(1, -1) 是把一维特征包装成一行样本因为 BPNet.forward 期望输入是二维矩阵。参数说明scaleFactor1.1 是精度和速度的平衡点改大检测变快但容易漏检minNeighbors5 表示候选框至少要得到 5 个相邻框支持才确认为人脸误检多时调到 8漏检多时调到 3minSize(24, 24) 过滤掉小噪点框做合影识别时如果人脸较小要调小到 (20, 20)否则输出结果会漏掉后排人脸。photo_sex_rec.py 的 main 入口一般就是拼装上面的函数。比较稳妥的命令行写法是支持图片路径和权重路径两个参数例如 python photo_sex_rec.py --image test.jpg --model bp_sex.npz。threshold 最理想的做法是从训练集的正负样本比例推算样本均衡时直接用 0.5现场演示时为了减少性别标签来回跳我通常固定为 0.6。3.2 视频逐帧检测与库模式比对的差异3.2.1 lib 模式先建特征库再算距离photo_lib_sex_rec.py 和 video_lib_sex_rec.py 的核心思路是先对一批已知人物的图片提取人脸区域特征保存为特征库检测时把当前人脸的特征与库中逐一计算欧氏距离取距离最小且低于阈值的那条作为识别结果。这样识别的是“这个人是谁”而不是只分男女。def build_library(image_paths, target_size(64, 64)): library [] for p in image_paths: img cv2.imread(p) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale(gray, scaleFactor1.1, minNeighbors5) if len(faces) 0: continue x, y, w, h faces[0] roi cv2.resize(gray[y:yh, x:xw], target_size) # 入库存特征也做归一化保证与查询特征同一量纲 library.append(roi.reshape(-1) / 255.0) return np.array(library) def match_face(lib, query, dist_thresh0.6): # 逐行计算欧氏距离取最近且低于阈值的编号 dists np.linalg.norm(lib - query, axis1) idx np.argmin(dists) return idx, dists[idx] if dists[idx] dist_thresh else -1逻辑说明build_library 对每一张入库图片取第一个检测到的人脸作为目标统一缩放到 64x64match_face 用 numpy 的广播机制一次性算出查询特征与库中所有人脸的距离argmin 拿到最近邻索引。参数说明建库尺寸用 64x64 而没有沿用性别分类的 32x32是因为身份比对需要更细的纹理信息模糊到 32 会明显拉高不同人之间的相似度dist_thresh0.6 是拒绝阈值用于把陌生人挡在识别结果之外具体数值和库的大小强相关库里有 10 个人和 50 个人时同样的距离含义不同提交前要用带干扰背景的样本重新标定。3.2.2 实时模式摄像头采集与逐帧预测video_sex_rec.py 做的事情就是循环读摄像头帧。一个常见问题是默认分辨率过高导致 Haar 检测耗时过长笔记本自带摄像头 640x480 是比较划算的档位。cap cv2.VideoCapture(0) # 外接摄像头索引可能是 1找不到画面时从 1 开始试 cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) while True: ret, frame cap.read() if not ret: break frame detect_and_predict(frame, net, threshold0.6) cv2.imshow(face sex rec, frame) # waitKey(1) 等待 1ms返回键盘输入的 ASCII 码 if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()逻辑说明cap.read 每次取一帧 BGR 图像ret 为 False 表示摄像头被占用或索引错误此时需要切换 VideoCapture 的索引参数。detect_and_predict 在函数内部完成灰度转换和 Haar 检测所以摄像头取到的原始彩色帧直接传入即可。参数说明waitKey(1) 的 1 表示每帧最多等待 1 毫秒数字越小循环越快 0xFF 是取低 8 位避免不同平台上特殊按键返回多字节码导致判断失效。如果帧率明显偏低优先把缩放帧宽降到 480而不是一味调小 minSize因为 minSize 过小会引入大量误检框耗时反而更高。3.3 三种典型失败场景的排查顺序代码能跑通但结果不对时按三个方向排查。第一种是检测框为空先确认 cascade 文件加载路径是否正确再用 photo_test.py 跑一张近距离正脸图如果仍然无框把 minNeighbors 调到 3 试一次。第二种是性别预测结果恒为 0.5 附近几乎可以肯定是网络权重没加载成功此时 forward 使用的还是随机初始化参数检查 load_net 调用后 w1 是否被替换。第三种是视频画面卡顿先去掉 cv2.imshow 单跑耗时测试定位瓶颈是在 Haar 检测还是 BP 前向传播常见瓶颈是前者。4. 环境搭建、训练收敛与跨平台踩坑4.1 依赖安装与摄像头索引确认项目基于 Python 3.7 以上版本和 OpenCV运行前安装这几个包就够。sklearn 用不上时可以不装但训练脚本通常会用 train_test_split 划分数据集装齐更稳妥。pip install opencv-python numpy matplotlib scikit-learnopencv-python 自带 Haar 级联模型文件numpy 提供矩阵运算sklearn 用于划分数据集和生成分类报告。如果本身在 conda 环境里运行用 conda install -c conda-forge opencv 也是常见方式两者不会冲突但不要同时混装多个渠道的 opencv避免出现 libopencv 版本覆盖问题。摄像头索引的判断建议写个小循环依次尝试 0、1、2打印每个索引是否能拿到帧。for idx in range(3): cap cv2.VideoCapture(idx) ret, frame cap.read() print(idx, ret, frame.shape if ret else no frame) cap.release()逻辑说明笔记本内置摄像头一般是 0外接 USB 摄像头经常变成 1 或 2这个循环能把可用索引一次性试出来比反复改参数再运行整个视频脚本高效。4.2 训练收敛的判断与早停策略训练时只看最终准确率是不够的要同时盯 loss 曲线和验证集准确率。正常情况是 loss 单调下降后趋于平稳验证集准确率小幅波动如果 loss 震荡先把 lr 从 0.01 降到 0.001如果 accuracy 停在 50% 附近先检查标签是否打反再看归一化是否遗漏。性别数据一张张标注很容易出现目录名和标签不一致的情况。早停是课程设计里容易被忽略但很加分的小策略best_acc 0.0 patience 30 wait 0 for epoch in range(300): train_one_epoch() # 实际代码里是分批训练循环 acc evaluate_val() if acc best_acc: best_acc acc wait 0 save_weights() else: wait 1 if wait patience: print(fearly stop at epoch {epoch}, best_acc{best_acc:.4f}) break逻辑说明连续 30 个 epoch 验证集准确率没有刷新就停止训练并保留历史最优权重。这样既防止过拟合也节省调参时的重复等待时间。参数说明patience 视数据集规模调整几百张样本时 20~30 足够如果数据量到了几千张可以放宽到 50save_weights 只保存 best_acc 对应的那版权重不要在所有 epoch 结束后再覆盖。4.3 跨平台运行时的常见差异这份项目在 Windows、macOS、Linux 上表现不同主要有三个注意点。第一cv2.imshow 在纯 SSH 服务器上无法弹出窗口无桌面系统下要把 imshow 换成 imwrite把检测结果写回文件再查看。第二macOS 上摄像头权限需要在系统设置里单独授权终端应用否则 VideoCapture 返回的 ret 一直是 False。第三OpenCV 4.x 里 cv2.data.haarcascades 路径是固定的但如果读者换用了 OpenCV 3.x这个常量不存在要手动指定 xml 文件的绝对路径。Linux 服务器上最常见的错误是缺少 GUI 依赖库运行 demo 时报 cv2.error 且提示 libGL.so.1 找不到。解决办法是安装 libgl1-mesa-glx比如 Debian 系发行版执行 apt-get install -y libgl1-mesa-glx不需要重新编译 OpenCV。5. 答辩验收的三个加分细节混淆矩阵、演示阈值与预录视频5.1 用混淆矩阵替代单一准确率答辩时只报一个“准确率 91%”很难展开提问。用 sklearn 生成混淆矩阵把错判集中在哪里直接可视化老师能顺着具体样例继续追问而你有充分准备。生成方式很简单在测试集预测结束后调用 metrics.confusion_matrix然后用 matplotlib 画成热力图横纵轴分别标注 male/female对角线上数字越大代表分类越可靠。报告里配合矩阵写一句“女性被误判为男性的比例明显高于反向误判”紧接着解释原因训练集中女性样本偏少或者女性测试样本里存在刘海遮挡和侧脸。这种分析比单纯贴一个 accuracy 数值有信息量得多也容易撑起一个答辩小节。5.2 现场演示前固定阈值与曝光现场答辩的灯光往往不是你能控制的摄像头白平衡会在不同角度自动漂移性别预测结果可能在同一张脸上跳来跳去。演示前把 detect_and_predict 的 threshold 参数固定为 0.6让预测只在置信度足够时切换标签能有效减少抖动。另外在现场演示脚本里关闭自动曝光固定为手动曝光和固定白平衡OpenCV 里通过 cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 0.25) 可以调整但不同摄像头的支持程度不同最好在答辩前一晚实拍三段不同方向的光线做验证。5.3 30 秒预录 demo 的录制参数无论现场演示多顺利都建议预录一段 30 秒的视频作为兜底。录制时覆盖三种场景单人正脸、多人同框、戴眼镜暗光每段 10 秒并在画面角落叠加当前 FPS。帧率是老师最常追问的指标叠加后无需口头解释。场景分辨率目标 FPS关注点单人正脸960x72025性别标签稳定多人同框960x72020多人框同时出现戴眼镜暗光640x48015Haar 是否漏检录屏时用 OBS 或系统自带录屏都行关键是把演示窗口和 FPS 计数器一起录进去。视频文件答辩前放在与源码同目录的 demo 文件夹里万一现场摄像头驱动冲突直接播放这段素材也能完整展示整个检测链路的工作状态这是整个项目档次的直接证明。本文还有配套的精品资源点击获取