简介研一图像处理期末大作业基于OpenCV的完整人脸识别项目面向计算机视觉初学者、高校研究生以及需要完成类似课设的开发者。项目覆盖人脸检测、特征提取、模型训练与身份识别核心流程。压缩包共9个文件含3个Python脚本分别承担数据集创建、模型训练、实时检测、2个XML级联分类器、1个YML训练数据文件及FaceBase.db人脸样本数据库整体仅1.03MB轻量且易于运行调试。目前已有1652人学习下载。代码模块划分清晰使用Haar级联分类器定位人脸通过LBPH等算法提取特征并训练模型生成的YML特征数据可直接用于身份匹配。附带的FaceBase.db存储了已采集的人脸样本方便复现整套流程或扩展自己的数据集。对深入理解OpenCV图像处理、人脸识别原理以及期末项目答辩展示均有实际帮助。1. 研一图像处理期末大作业为什么人脸识别成了最稳妥的选择每年研一期末图像处理课的大作业题目都绕不开那几个经典方向边缘检测、形态学处理、目标跟踪、人脸识别。如果你打开过学长留下的代码包会发现十个人里有六个交的是基于 OpenCV 的人脸识别——不是大家偷懒而是这个题目在工作量可见、原理好讲、答辩不容易被问倒三个维度上最均衡。OpenCV 自带 Haar Cascade、LBPH、EigenFace、FisherFace 这些现成人脸检测与识别模块代码量可以控制在一两百行内但讲原理时又能扯到图像灰度化、直方图均衡化、特征提取、分类器训练这些课程核心知识点正好覆盖期末答辩的考点。但能跑和能交是两码事。我见过太多人把 GitHub 上的 demo 下载下来换了几张测试图就提交结果答辩现场被老师一句LBPH 的半径和邻域点数为什么这么设问住。这篇笔记就是冲着这个痛点来的从环境搭建到特征提取原理再到完整代码和调参经验按一条能复现、能讲清楚的路走一遍。适合正在做图像处理大作业的研一学生也适合想用 OpenCV 快速搭一个人脸识别原型的开发者。2. 先把 OpenCV 的人脸识别体系拆开三种算法怎么选2.1 从 Haar Cascade 到 LBPHOpenCV 里到底有什么OpenCV 的人脸识别能力分两层第一层是人脸检测也就是从一张图里框出人脸的位置第二层是人脸识别也就是判断这张脸是谁。很多初学者把这两层混在一起导致代码写得乱七八糟。检测层 OpenCV 最常用的是 Haar Cascade 分类器原理是用 Haar-like 特征配合 AdaBoost 训练出一串弱分类器再用级联结构快速排除非人脸区域。这个方案的优点是模型文件是 XML不需要自己训练OpenCV 源码仓库里直接下载haarcascade_frontalface_default.xml就能用缺点是它对侧脸、遮挡、光照变化非常敏感稍微偏一点角度就框不住。识别层 OpenCV 内置三种算法EigenFace、FisherFace、LBPH。EigenFace 本质是 PCA 降维把人脸图像投影到特征空间用欧氏距离判断相似度FisherFace 是 LDA 线性判别分析追求类间距离最大、类内距离最小LBPH 则走的是局部纹理路线把图像分成小块每块提取局部二值模式直方图最后拼接成整张脸的特征向量。三者各有侧重EigenFace 和 FisherFace 对光照敏感但 FisherFace 在类别数多时表现更好LBPH 对光照鲁棒性最强也是目前作业里用得最多的方案。2.2 为什么 LBPH 是期末作业的最优解选 LBPH 不是因为它的识别率一定最高而是因为它在作业场景下最不容易翻车。期末大作业的数据集通常是自己用摄像头拍的每个人 10 到 20 张样本光照条件不统一人脸角度有偏差。这种数据喂给 EigenFace 和 FisherFace很可能因为训练集太小、光照差异太大导致识别率惨不忍睹。LBPH 的特征是基于局部邻域的二进制编码对整体光照变化不敏感而且 OpenCV 的face.LBPHFaceRecognizer_create()接口可以直接设置半径和邻域点数调参空间直观答辩时好解释。需要提醒的是OpenCV 的人脸识别模块不在主库里而是在opencv-contrib-python包里。如果你只装了opencv-python调用cv2.face会直接报错。这是期末作业里最常见的第一个坑后面避坑章节会详细展开。2.3 三个必调参数半径、邻域点数和阈值LBPH 的核心参数有三个理解它们才能应对答辩提问。第一个是半径radius默认 1表示中心像素与邻域像素的距离半径越大纹理特征越粗糙但抗噪能力越强第二个是邻域点数neighbors默认 8表示采样点的数量点数越多特征越精细但计算量也越大第三个是识别阈值threshold默认值是DBL_MAX实际使用时要手动设一个相似度阈值低于阈值才认为是同一个人。我一般会把radius设在 1 到 2 之间neighbors设在 8 到 12 之间。半径太大容易丢失细节纹理邻域点数太多在样本量不足时反而会过拟合。阈值要根据实际测试结果调大概在 50 到 80 之间这个值不是固定的跟训练集的质量和设备有关。3. 从零搭环境OpenCV 安装与摄像头数据采集的完整流程3.1 虚拟环境里装 opencv-contrib-python两条命令搞定不管你是用 Anaconda 还是原生 Python我都建议先建一个干净的虚拟环境再做这个项目避免和别的项目依赖冲突。创建虚拟环境用conda create -n face_recognition python3.9然后激活环境再装 OpenCV。这里有一个关键选择pip install opencv-contrib-python而不是pip install opencv-python。前者才包含cv2.face模块后者只包含主库。conda create -n face_recognition python3.9 -y conda activate face_recognition pip install opencv-contrib-python numpy matplotlib这段命令的逻辑是第一条创建 Python 3.9 的环境第二条激活它第三条安装 OpenCV 扩展版、NumPy 和 Matplotlib。NumPy 是 OpenCV 的底层依赖Matplotlib 用来显示图像方便调试。装完后在 Python 里执行import cv2; print(cv2.__version__); print(cv2.face)验证安装是否成功。如果cv2.face报AttributeError说明装成了普通版需要先卸载重装。3.2 用摄像头采集训练样本脚本里要注意的三个细节训练一个人脸识别模型第一步是拿到足够的样本。常见做法是用电脑摄像头拍每个人收集 15 到 20 张不同角度和光照下的正脸图。采集脚本的核心是循环读摄像头帧用 Haar Cascade 检测人脸把检测到的人脸区域缩放后保存成灰度图。import cv2 import os def collect_samples(person_name, sample_count20): save_dir f./dataset/{person_name} os.makedirs(save_dir, exist_okTrue) cap cv2.VideoCapture(0) face_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) count 0 while count sample_count: ret, frame cap.read() if not ret: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale( gray, scaleFactor1.1, minNeighbors5, minSize(100, 100) ) for (x, y, w, h) in faces: face_region gray[y:yh, x:xw] face_resized cv2.resize(face_region, (200, 200)) cv2.imwrite(f{save_dir}/{count:03d}.jpg, face_resized) count 1 cv2.rectangle(frame, (x, y), (xw, yh), (0, 255, 0), 2) cv2.imshow(Collecting Faces, frame) key cv2.waitKey(10) 0xFF if key 27: # ESC break cap.release() cv2.destroyAllWindows() print(fSaved {count} samples for {person_name}) collect_samples(zhang_san, 20)这段代码有三个细节值得注意。第一存图前先转灰度再 resize 成 200x200因为 LBPH 训练时输入图像尺寸不一致会导致特征向量维度对不上第二detectMultiScale的scaleFactor1.1表示每次缩放 10% 来检测不同尺度的人脸minNeighbors5表示一个区域至少被 5 个相邻窗口确认才算人脸这两个值直接影响检测的漏报和误报样本质量差时可以把minNeighbors降到 3第三每个样本文件名用序号格式000.jpg便于后续程序按顺序读取。3.3 训练数据与测试数据不能混在一起的目录组织方式目录结构看起来简单但很多人在这一步埋了雷。我建议的目录组织方式是这样的face_recognition_project/ ├── dataset/ │ ├── zhang_san/ │ │ ├── 000.jpg │ │ ├── 001.jpg │ │ └── ... │ └── li_si/ │ ├── 000.jpg │ └── ... ├── train.py ├── recognize.py └── collect_samples.py训练数据每个人留出 2 到 3 张不参与训练放到单独的测试目录用来验证模型的泛化能力这是期末答辩时可以说得出的加分点。如果所有样本都参与训练识别率会虚高答辩时被老师要求现场测试就容易露馅。4. 训练与识别LBPH 的完整代码和参数怎么设4.1 训练脚本读取数据集、生成标签、训练模型训练的核心是把每个人的照片和对应标签绑定标签从 0 开始编号。脚本遍历dataset目录下每个子文件夹文件夹名当作人的名字照片路径存到列表里对应的标签也存到另一个列表里然后调用LBPHFaceRecognizer的train方法。import cv2 import os import numpy as np def load_dataset(data_dir): images [] labels [] label_map {} current_label 0 for person_name in sorted(os.listdir(data_dir)): person_dir os.path.join(data_dir, person_name) if not os.path.isdir(person_dir): continue label_map[current_label] person_name for img_name in os.listdir(person_dir): img_path os.path.join(person_dir, img_name) img cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) if img is None: continue images.append(img) labels.append(current_label) current_label 1 return images, np.array(labels), label_map data_dir ./dataset images, labels, label_map load_dataset(data_dir) recognizer cv2.face.LBPHFaceRecognizer_create(radius1, neighbors8, threshold80) recognizer.train(images, labels) recognizer.save(face_model.yml) print(Trained with labels:, label_map)这段代码的逻辑分三步第一步用os.listdir遍历每个人的文件夹文件夹名就是人的名字第二步将每个名字映射到一个数字标签模型只认数字不认名字第三步调用train方法训练模型最后保存成 YAML 文件。为什么要重建label_map因为识别阶段返回的是数字标签你必须有一个映射表才能把数字转回人名这个映射表建议和模型文件一起保存。LBPHFaceRecognizer_create的四个参数值得说一下radius1取最小半径适合样本尺寸不大时的细节特征neighbors8是默认值对大多数数据集都够用threshold80设置距离阈值识别结果距离大于 80 就认为是陌生人。如果训练集只有两三个人、每人样本量偏少阈值可以放宽到 100 左右误识率会升高但至少能出结果。4.2 识别脚本摄像头实时识别加名字显示训练完成后的识别阶段读取摄像头画面检测人脸区域裁剪后缩放到和训练时相同的尺寸调用predict方法得到标签和置信度。import cv2 model_path face_model.yml label_map {0: zhang_san, 1: li_si} recognizer cv2.face.LBPHFaceRecognizer_create() recognizer.read(model_path) cap cv2.VideoCapture(0) face_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 face_cascade.detectMultiScale(gray, scaleFactor1.1, minNeighbors5) for (x, y, w, h) in faces: face_region gray[y:yh, x:xw] face_resized cv2.resize(face_region, (200, 200)) label, confidence recognizer.predict(face_resized) name label_map.get(label, unknown) if confidence 80: name stranger cv2.rectangle(frame, (x, y), (xw, yh), (0, 255, 0), 2) cv2.putText(frame, f{name} ({confidence:.1f}), (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) cv2.imshow(Face Recognition, frame) if cv2.waitKey(10) 0xFF 27: break cap.release() cv2.destroyAllWindows()这段代码的关键在predict的返回值理解上。predict返回一个元组(label, confidence)其中confidence在 LBPH 里表示的是距离值越小说明越相似而不是越大越相似。很多初学者把这个搞反以为 confidence 大代表识别准。实际判断逻辑是如果返回的距离大于训练时设的阈值就判定为陌生人。识别阶段的图像尺寸必须和训练阶段完全相同。训练时用的是 200x200识别时也要先cv2.resize到 200x200否则predict会因为特征向量维度不一致直接报错或者识别率骤降。4.3 用静态图片批量验证不依赖摄像头的测试方式摄像头实时识别受环境光、摄像头质量影响很大调试时不方便。我一般会先用一批静态测试图片离线验证模型确认准确率达标后再上摄像头。批量测试脚本的核心是读取测试图片、检测人脸、逐张预测最后统计识别正确的比例。import cv2 import os model_path face_model.yml test_dir ./test_images label_map {0: zhang_san, 1: li_si} recognizer cv2.face.LBPHFaceRecognizer_create() recognizer.read(model_path) face_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) correct 0 total 0 for img_name in os.listdir(test_dir): img_path os.path.join(test_dir, img_name) img cv2.imread(img_path) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale(gray, scaleFactor1.1, minNeighbors5) total 1 for (x, y, w, h) in faces: face_resized cv2.resize(gray[y:yh, x:xw], (200, 200)) label, confidence recognizer.predict(face_resized) if confidence 80: correct 1 print(fAccuracy: {correct}/{total} {correct / max(total, 1):.2%})这个脚本可以一次性评估模型在指定测试集上的表现每次改完参数后跑一遍数值变化一目了然。注意测试图片的命名不要包含人名信息否则你自己会潜意识地选图影响评估的客观性。5. 期末作业避坑指南五个最容易让识别翻车的问题5.1ModuleNotFoundError: No module named cv2.face现象代码执行到cv2.face.LBPHFaceRecognizer_create()时直接报AttributeError: module cv2 has no attribute face。原因装的是opencv-python而不是opencv-contrib-python人脸识别模块在扩展包里。解决先pip uninstall opencv-python再pip install opencv-contrib-python装完重启 Python 进程验证。5.2 人脸检测框不住侧脸和戴眼镜的脸现象摄像头前的人稍微侧一下头检测框就消失识别经常失败。原因haarcascade_frontalface_default.xml这个级联模型对正脸效果最好侧脸和遮挡场景不是它的强项。解决换用haarcascade_frontalface_alt2.xml或者haarcascade_profileface.xml做补充检测把minNeighbors从 5 降到 3 能减少漏检但误检率会上升采集训练样本时让人脸在画面里左右转动一点角度多拍不同姿态的样本。如果项目允许用 OpenCV 的 DNN 人脸检测器替换 Haar Cascade 是最彻底的方案但代码量会多不少。5.3 训练时报错Assertion failed或者尺寸不匹配现象recognizer.train(images, labels)时报错提示所有图片尺寸不一致或_src.size()断言失败。原因LBPH 要求训练图像是灰度图且尺寸一致如果你采集时生成的图片大小不统一训练时提取的特征维度就会冲突。解决在采集脚本里统一cv2.resize到固定尺寸这里用的 200x200如果已经有数据了写一段批量 resize 的脚本把所有训练图片重新处理一遍。5.4 识别结果总是显示 stranger现象自己对着摄像头识别窗口显示的名字是 stranger或者显示成另一个人的名字。原因训练样本太少、样本光照和测试环境差异太大或者阈值设置过严。解决先调阈值把threshold从 80 调到 100 或者更大如果还是不行问题在样本质量重新采集样本时保证光照均匀、人脸占画面比例大、每人多拍 20 张以上。样本数量从 10 张加到 20 张识别率往往有非常明显的提升。5.5 摄像头画面卡顿、识别延迟高现象摄像头实时画面有明显的滞后感程序运行 CPU 占用率很高。原因detectMultiScale的高分辨率图像检测本身就贵实时识别时每一帧都做全图检测再加上predict的特征提取普通笔记本很难流畅跑。解决先把帧缩小到 640x480 再检测代码里加一句frame cv2.resize(frame, (640, 480))检测到人脸后再把人脸区域单独处理减少不必要的计算。如果还卡把scaleFactor从 1.1 改成 1.2检测窗口变少计算量下降代价是可能漏掉小尺寸人脸。6. 进阶一步把识别结果输出成统计图表答辩时多一个亮点期末答辩时老师最看重的是你对整个流程的理解深度和工程完成度。如果只是打开摄像头识别一下显得单薄如果能把训练集大小对识别率的影响画成曲线或者把每个人的置信度分布展示出来答辩效果会完全不同。这块不需要额外安装框架用 OpenCV 加 Matplotlib 就能做。具体做法是准备三个不同规模的训练集比如每人 5 张、10 张、20 张分别训练三个模型用同一批测试图片测试各自的准确率把结果画成折线图。这个实验能直观回答老师样本量对识别率有什么影响的提问也能说明你理解 LBPH 的统计特性。画图代码大概 30 行核心是plt.plot加plt.savefig在答辩 PPT 里直接贴图就行。我自己的习惯是做完准确率统计后再打印一个混淆矩阵把每个人都标出来。混淆矩阵能让老师一眼看出模型把谁和谁搞混了接下来你就可以顺着讲这两个人的样本在采集时光照条件太接近导致特征空间距离过小这类具体问题比泛泛而谈光照影响识别有说服力得多。这块也是我认为期末作业里性价比最高的加分项。这套方案做下来代码量不超过 300 行原理能讲到 PCA、LDA、LBP 三个层面工程上有数据采集、模型训练、离线评估、实时识别四个完整环节期末答辩基本够用。如果时间充裕可以再往 DNN 人脸检测方向扩展但核心的 LBPH 流程先跑通、讲透比堆一堆跑不起来的模型靠谱得多。希望这篇笔记能帮你把大作业顺利落地少走几个我当年走过的弯路。本文还有配套的精品资源点击获取