简介:面向本科毕业设计及课程设计场景的人脸识别系统项目,基于Python实现,提供完整可运行的源码、毕业论文文档及配套说明。代码内含详细注释,结构清晰,新手也能快速理解关键逻辑;作者自述为98分高分项目,获得导师认可,适合毕业设计、期末大作业等答辩展示或二次开发。压缩包共2000个文件,以1956个Python源码文件为主,辅以PDF论文、TXT说明、CSS/JS/HTML前端页面以及MD文档、Shell脚本等,覆盖算法实现、模型训练、界面展示与项目部署多个层面。整体大小约82.24MB,便于下载和本地配置。目前已有182人学习查看,具备一定参考价值。解压后按说明配置依赖即可运行,可作为人脸识别课程设计的完整范本,也能帮助初学者掌握人脸检测、特征提取、比对识别等核心流程,并借助源码注释快速定位关键函数与实现思路。
1. 基于Python的人脸识别系统:一份能直接部署进答辩现场的毕设源码
如果你正在为毕业设计或课程设计找一个人脸识别项目,大概率已经在海量资料里翻得头晕了——要么是只讲原理没有代码的PPT型教程,要么是GitHub上祖传十年、注释全靠猜的老仓库。这份基于Python的人脸识别系统本科论文毕业设计源码,我拆完后的评价是:结构干净、注释到位、部署链路短,适合我这种需要快速跑通全流程、再把精力花在论文和答辩上的人。它解决了三个实际问题:人脸数据怎么采集、模型怎么训练、识别结果怎么稳定输出。适合做毕设选题的本科生,也适合想抄一份完整作业的程序员。从解压到看到摄像头框住自己的脸,整个过程大概只需要半小时。
2. 把代码跑起来:环境部署与项目结构梳理
2.1 环境准备:Python版本、OpenCV与face_recognition的搭配
拿到源码后的第一件事不是读代码,而是把环境搭好。这个项目的核心依赖集中在OpenCV上,人脸检测用的Haar级联分类器,人脸识别用的是LBPH算法,两者都在OpenCV的contrib模块里。常见的坑是只装了opencv-python,没有装opencv-contrib-python,导致cv2.face这个命名空间根本不存在,一import就报ModuleNotFoundError。
我习惯用conda建一个独立的虚拟环境,避免把系统Python搞乱。下面的依赖清单是我在Windows 11和Ubuntu 22.04上都验证过的组合:
| 依赖包 | 推荐版本 | 作用 |
|---|---|---|
| python | 3.8 ~ 3.10 | 项目运行时环境 |
| opencv-python | 4.5.x 或 4.8.x | 图像处理、摄像头调用 |
| opencv-contrib-python | 与opencv-python同版本 | 提供cv2.face模块(LBPH算法) |
| numpy | 1.21以上 | 图像数组运算 |
| pillow | 9.x | 图像文件读写辅助 |
提示:opencv-python和opencv-contrib-python必须装成同一个版本,否则模型训练阶段会出现facerecognizer属性找不到的诡异报错。
如果你是用PyCharm打开项目,我建议直接在PyCharm的Terminal里执行以下命令来创建环境:
conda create -n face_rec python=3.9 -y conda activate face_rec pip install opencv-python==4.8.1.78 opencv-contrib-python==4.8.1.78 numpy pillow参数说明:python=3.9这个版本选得比较保守,因为3.10以上在某些旧版OpenCV里会有兼容性警告;opencv-contrib-python是FaceRecognizer所在的位置,只装opencv-python的话,LBPH训练接口用不了。装完可以用一行命令验证环境是否正常:
python -c "import cv2; print(cv2.__version__); print(cv2.face.LBPHFaceRecognizer_create)"这个命令不打印报错就说明环境没问题。我第一次搭的时候犯过一个低级错误——在conda环境里装好了依赖,PyCharm的解释器却还指向系统自带的Python,结果运行一直报module 'cv2' has no attribute 'face',浪费了半小时。后来学乖了,创建项目时直接指定conda环境的解释器路径,问题一次性解决。
2.2 项目结构:从入口文件到数据目录的分工
这份源码的整体结构挺典型的,解压后你能看到这几个关键文件:
| 文件/目录 | 作用 |
|---|---|
| main.py | 主程序入口,跑起来会调用摄像头进行实时识别 |
| train.py | 训练脚本,读取dataset目录里的人脸图像,生成识别模型 |
| dataset/ | 存放采集到的人脸样本,按人名分文件夹 |
| trainer/ | 存放训练好的模型文件,通常是trainer.yml |
| haarcascade_frontalface_default.xml | OpenCV自带的Haar人脸检测模型 |
运行顺序是:先运行采集脚本把人脸样本存进dataset,再运行train.py生成模型,最后运行main.py做实时识别。绝大多数这套项目的源码包都是这个逻辑,拿到手先看main.py的入口和train.py的路径配置,比从第一行读代码高效得多。
第一次跑主程序前,建议确认一下haarcascade_frontalface_default.xml的路径。OpenCV安装包自带这个文件,但路径经常会因为环境不同而变。我一般会在train.py和main.py里用一段兼容代码动态定位它,而不是写死绝对路径:
import cv2 import os # 优先使用项目根目录的xml文件,找不到则从opencv安装目录查找 cascade_path = "haarcascade_frontalface_default.xml" if not os.path.exists(cascade_path): cascade_path = os.path.join(cv2.data.haarcascades, "haarcascade_frontalface_default.xml") face_detector = cv2.CascadeClassifier(cascade_path)这段代码的逻辑是:先看当前目录有没有复制好的xml,没有就从cv2.data.haarcascades这个OpenCV内置路径里找。在Linux上尤其重要,因为opencv的data目录和代码目录经常不在一起,写死相对路径基本必翻车。
2.3 第一次运行:采集人脸到生成模型的完整流程
跑人脸识别项目的第一个目标不是识别准确率,而是跑通链路。先把采集脚本独立出来,单独建一个capture_face.py,这个脚本的作用是调用摄像头,把检测到的人脸裁剪保存到dataset目录下:
import cv2 import os # 数据集根目录和当前用户目录 dataset_dir = "dataset" person_name = input("请输入姓名:") person_dir = os.path.join(dataset_dir, person_name) os.makedirs(person_dir, exist_ok=True) # 初始化摄像头和Haar检测器 cap = cv2.VideoCapture(0) face_detector = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml") count = 0 while True: ret, frame = cap.read() if not ret: break gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces = face_detector.detectMultiScale(gray, scaleFactor=1.2, minNeighbors=5, minSize=(100, 100)) for (x, y, w, h) in faces: count += 1 face_img = gray[y:y+h, x:x+w] face_img = cv2.resize(face_img, (200, 200)) cv2.imwrite(f"{person_dir}/{count}.jpg", face_img) cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2) cv2.imshow("capture", frame) if cv2.waitKey(1) & 0xFF == ord('q') or count >= 50: break cap.release() cv2.destroyAllWindows() print(f"采集完成,共保存{count}张图片到{person_dir}")逻辑说明:detectMultiScale返回所有检测到的人脸位置信息,这里把灰度图的人脸区域直接裁剪出来,resize成200x200的统一尺寸然后保存。为什么要统一尺寸?因为LBPH算法在训练阶段会把所有图片转换到同一个特征维度空间,图片尺寸不一致会直接导致训练报错或识别率暴跌。参数说明:scaleFactor=1.2表示每次缩放比例为1.2,数值越小检测越精细但速度越慢;minNeighbors=5表示每个候选区域至少被5个邻近窗口确认才算人脸,这个值太小容易把背景当人脸,太大又可能漏检;minSize=(100, 100)过滤掉太小的检测窗口,避免采集到一堆远处的模糊人脸。
每张样本保存前我都会快速翻阅一下,把闭眼、转头、模糊的图片删掉。经验数据是:单人脸采集50张,其中30张是正脸静止,20张是左右旋转和仰头,训练出来的模型在答辩演示时最能抗住评委的随机测试。
3. 人脸识别算法选型:为什么毕设项目都偏爱LBPH
3.1 三种经典算法的对比:Eigenfaces、Fisherfaces与LBPH
人脸识别这块,学术界和工业界的方案很多,但毕设级别的源码里最常见的就是三种:Eigenfaces(特征脸)、Fisherfaces(线性判别分析)和LBPH(局部二值模式直方图)。这套源码选择的是LBPH,我的理解是它在准确率、计算量、调参难度三个维度上取得了最好的平衡。
先看一个对比表格,把三种算法的本质讲清楚:
| 算法 | 核心原理 | 优点 | 缺点 |
|---|---|---|---|
| Eigenfaces | 用PCA降维,把人脸投影到特征空间 | 原理简单,论文好写 | 对光照敏感,同一人不同光线识别率明显下降 |
| Fisherfaces | 用LDA降维,最大化类间距离、最小化类内距离 | 比Eigenfaces更抗光照变化 | 需要足够的样本量,每人少于10张容易过拟合 |
| LBPH | 提取局部纹理特征,生成直方图比对相似度 | 对光照、表情变化不敏感,训练快 | 对图像噪声敏感,需要人脸对齐 |
LBPH的核心思想是把每个像素和它周围一圈像素做比较,大于中心点记1、小于记0,形成一个二进制序列,这个序列转成十进制就是该点的LBP值。整张图像的LBP值统计成直方图,再进行归一化处理,得到的特征向量就是这张人脸的指纹。训练过程就是给每个已知身份的人保存一组特征向量,识别时计算当前人脸特征向量与所有已知向量的距离,距离最小的那个标签就是识别结果。
这也是为什么LBPH对光照不敏感——因为它比较的是局部像素的相对关系,而不是像素绝对值。答辩时如果老师问“为什么不用深度学习”,你可以回答:LBPH在单人脸小样本场景下训练时间短,CPU即可实时运行,且可解释性更强,适合本科毕设的场景深度。这个说法是能站住脚的。
3.2 Haar级联检测器:先找到脸,再谈认脸
人脸识别分为两个阶段:检测(脸在哪里)和识别(这是谁)。大部分毕设源码用的是Haar级联分类器做第一阶段检测,这是OpenCV里最经典的目标检测方法,基于Viola-Jones算法,用一个训练好的级联分类器在图像上滑动窗口,逐级判断窗口内容是否是人脸。
这个模块的核心就一个函数:detectMultiScale。别看只有一行调用,里面的参数直接决定了你后面识别的成功率。先把参数含义理清楚:
| 参数 | 推荐值 | 含义与调节倾向 |
|---|---|---|
| scaleFactor | 1.1 ~ 1.3 | 每次图像缩放比例。越小检测越慢但召回率越高;1.3以上容易漏检小尺寸人脸 |
| minNeighbors | 3 ~ 6 | 候选矩形最少被确认的次数。越大误检越少,但可能漏掉侧脸 |
| minSize | (80, 80) ~ (120, 120) | 最小人脸尺寸,小于该尺寸的区域直接忽略 |
| maxSize | 不设或(300, 300) | 最大人脸尺寸,过滤掉异常大的检测框 |
我在这套项目里惯用的组合是scaleFactor=1.2, minNeighbors=5, minSize=(100, 100)。这个组合在摄像头场景下比较稳:识别距离1米左右,人脸区域大约占画面的1/6到1/4,minSize设置成100x100既不会漏掉正常距离的人脸,也能过滤掉背景里乱入的玩偶脸。
注意:minNeighbors这个参数有个容易踩的误区。它不是越大越好,设到8以上时,稍微偏一点的角度人脸就直接检测不到,采集阶段存下来的全是废图。建议先用默认值5跑一遍,如果画面里出现大量误检框,再逐步往上调。
3.3 LBPH训练参数:radius、neighbors、grid_x、grid_y的调参边界
介绍完检测,再看识别模型的训练参数。LBPHFaceRecognizer的创建接口长这样:
recognizer = cv2.face.LBPHFaceRecognizer_create(radius=1, neighbors=8, grid_x=8, grid_y=8)这4个参数直接决定了特征提取的方式,是穿越到项目后必须理解和调整的地方。参数说明:
radius:中心像素和周围像素的距离。默认是1,即只看紧邻的一圈像素。调成2可以捕获更大范围的纹理特征,但计算量会增加,且对图像分辨率要求更高。这种做法适合样本图片拍得比较清晰、尺寸较大的场景;如果摄像头像素不行,radius设2反而会引入噪声。neighbors:参与比较的邻域像素个数。默认是8,对应中心点周围8个方向。这个值越大,生成的二进制模式越丰富,但也越容易受噪声干扰。grid_x和grid_y:图像被划分为网格的数量。默认8x8,整张图被划分为64个小区域,每个区域提取一个直方图,再拼成整张图的特征。网格越多,空间信息越细,但特征维度越高,训练速度变慢,对样本数量要求也更高。
我在多个人脸数据集上试过,radius=1, neighbors=8, grid_x=8, grid_y=8这个默认组合在样本量为每人20~50张时表现最稳。如果每人只有10张样本,我建议把grid_x和grid_y调成6x6,降低特征维度能减少过拟合。
4. 核心模块拆解:从训练到识别的完整调用链
4.1 数据预处理:灰度转换、尺寸统一与样本质量
人脸识别系统的效果上限在数据采集阶段就已经决定了。模型再强,喂进去的是模糊、偏色、人脸位置偏斜的图片,训练出来的特征也是“歪的”。这套源码的数据预处理部分做了三件事:灰度化、人脸对齐、尺寸归一化。
灰度化是必须的,因为LBPH的特征提取本身基于灰度值纹理关系,彩色信息不仅没用,还会增加计算量。先用cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)把摄像头帧转成灰度图。人脸对齐这一步在源码里可能做得比较轻——只检测人脸框,不检测五官关键点。这一步带来的影响在样本质量高的时候不明显,但如果你采集时喜欢歪头,我建议手动加一步:用cv2.resize把人脸区域统一到200x200的正方形,至少保证尺寸一致。
一个容易被忽略的点是图片的存储格式。OpenCV的imwrite默认保存为BGR顺序,但如果你的原始灰度图在预处理时用了PIL库读取再转存,很容易出现通道顺序问题。在这套项目里,统一采用灰度图存储能避免这个问题。我一般会在保存前加一段校验代码:
face_img = gray[y:y+h, x:x+w] face_img = cv2.resize(face_img, (200, 200)) assert face_img.shape == (200, 200), "图像尺寸不符合预期"这段断言代码会在图像尺寸不对时直接抛异常,避免脏数据混进训练集。跨过这段校验后,才允许imwrite写入文件。
4.2 训练脚本走读:读取数据集、打标签、训练模型
训练阶段是整个系统里最像“黑匣子”的部分,很多人跑通了却不知道里面发生了什么。我把train.py的核心逻辑拆出来逐段解释:
import cv2 import os import numpy as np dataset_dir = "dataset" recognizer = cv2.face.LBPHFaceRecognizer_create(radius=1, neighbors=8, grid_x=8, grid_y=8) images = [] labels = [] label_map = {} # 姓名 -> 数字标签 current_label = 0 for person_name in os.listdir(dataset_dir): person_dir = os.path.join(dataset_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) img = cv2.resize(img, (200, 200)) images.append(img) labels.append(current_label) current_label += 1 recognizer.train(images, np.array(labels)) recognizer.save("trainer/trainer.yml") # 保存姓名与标签的映射关系 import json with open("trainer/label_map.json", "w", encoding="utf-8") as f: json.dump({str(k): v for k, v in label_map.items()}, f, ensure_ascii=False) print(f"训练完成,共{len(images)}张图片,识别{len(label_map)}个人")代码逻辑说明:程序遍历dataset目录下的每个人名文件夹,文件夹名就是人的姓名,文件夹里的每张图片对应一个训练样本。训练时把人名映射成数字标签,因为OpenCV的FaceRecognizer只接受整数标签。训练完成后,模型保存到trainer.yml,标签映射关系单独存成json文件,这样主程序加载模型时才能把数字标签翻译回姓名。
参数说明:recognizer.train(images, np.array(labels))要求images是list格式,labels是numpy数组,两者长度必须一致。labels的类型必须是int32,如果传了字符串会直接报错。保存模型用save()方法,路径的目录需要提前存在,否则会静默失败——这是OpenCV的一个坑,不会报错,但识别阶段会发现model文件根本没生成。
整个训练过程在CPU上跑,50张图片基本是秒级完成。如果训练时间异常长,先检查图片尺寸是不是调成了800x800以上,这会导致特征维度爆炸式增长。
4.3 实时识别流程:摄像头取帧、人脸检测、置信度过滤
训练完模型,最后一步是在视频流里实时识别。这个环节的工程细节最多,直接决定了答辩现场的表现。以下是main.py里识别部分的核心代码:
import cv2 import json import os # 加载训练好的模型和标签映射 recognizer = cv2.face.LBPHFaceRecognizer_create() recognizer.read("trainer/trainer.yml") with open("trainer/label_map.json", "r", encoding="utf-8") as f: label_map = {int(k): v for k, v in json.load(f).items()} face_detector = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml") cap = cv2.VideoCapture(0) while True: ret, frame = cap.read() if not ret: break gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces = face_detector.detectMultiScale(gray, scaleFactor=1.2, minNeighbors=5, minSize=(100, 100)) for (x, y, w, h) in faces: face_region = cv2.resize(gray[y:y+h, x:x+w], (200, 200)) label, confidence = recognizer.predict(face_region) # confidence值越小表示距离越近,置信度越高 if confidence < 55: name = label_map.get(label, "Unknown") color = (0, 255, 0) else: name = "Unknown" color = (0, 0, 255) cv2.rectangle(frame, (x, y), (x+w, y+h), color, 2) cv2.putText(frame, f"{name} ({confidence:.1f})", (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, color, 2) cv2.imshow("Face Recognition", frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()逻辑说明:主程序先把训练好的模型和标签映射读进内存,然后进入无限循环:读取一帧摄像头画面,检测人脸,对每张人脸区域调用predict方法。predict返回两个值——label是数字标签,confidence是当前人脸特征与模型已知特征之间的距离。距离越小代表越相似,confidence=0表示完全相同,实际使用中几乎不可能达到。
参数说明:confidence < 55这个阈值是我在这个项目上调出来的一个经验值。LBPH的confidence数值受图片尺寸、光照、样本数量影响很大,不同环境下的“合理阈值”不同,必须实测调整。阈值设太低,真实用户会被频繁识别为陌生人;设太高,陌生人会被乱认成熟人。建议先跑一段时间的摄像头识别,打印出多次识别同一人的confidence值分布,然后取该分布的中间偏高值设为阈值。
注意一个细节:label_map存储时把键转换成了字符串,因为json的key只能是字符串;读取时再转回int。这个转换逻辑如果漏掉,会出现“识别框显示数字标签而不是姓名”的问题。
5. 毕设人脸识别最常见的六个坑:现象、原因、解决办法
5.1 摄像头打不开、画面全黑或报错
现象:运行main.py后,程序不报错,但cv2.imshow的窗口全黑;或者直接抛cv2.error: Could not open camera。
原因:大概率是摄像头编号错误。cv2.VideoCapture(0)里的0表示笔记本内置摄像头,但如果你外接了USB摄像头,有时设备编号是1或2。另一个常见原因是在虚拟机或远程桌面环境下,物理摄像头没有映射到系统设备列表里。
解决:先写一个三行的脚本遍历摄像头编号:
import cv2 for i in range(5): cap = cv2.VideoCapture(i) ret, frame = cap.read() print(f"摄像头{i}: {'正常' if ret else '不可用'}") cap.release()运行后看到哪些编号返回正常,就把main.py里的序号改成那个值。血泪经验:不要因为设备管理器里显示摄像头存在就跳过这一步,OpenCV对摄像头驱动的兼容性有时很玄学。
5.2 识别框乱跳,同一个人的confidence值忽高忽低
现象:同一张脸在画面里不动,识别结果却在两个标签之间来回横跳,confidence值一会在30多一会在80多。
原因:检测框大小和位置不稳定。detectMultiScale每一帧返回的人脸框可能差几个像素,这导致裁剪出来的人脸区域在连续帧里有细微偏移。LBPH对图像的像素级变化比较敏感,细微偏移就会被放大成特征差异。另一个原因是画面里有反光或背光,灰度图在局部区域的明暗关系发生了突变。
解决:把识别逻辑改成“连续N帧取平均”——每帧只做检测,不立即判识别人脸身份;只有连续3帧以上检测到人脸,才在最后一帧的位置做一次predict,然后用这多次predict的置信度平均值做最终判断。这种做法能显著提升实时识别的稳定性。另外在采集阶段让样本包含一定角度的侧脸,比全部正脸更容易泛化。
5.3 训练时接口报错:module 'cv2' has no attribute 'face'
现象:运行train.py时抛出AttributeError: module 'cv2' has no attribute 'face',但cv2的其他功能都正常。
原因:环境里只装了opencv-python,没装opencv-contrib-python。cv2.face模块从OpenCV 3.0之后就移到了contrib包里,基础安装不包含它。这个问题在PyCharm的虚拟环境里尤其常见——换了解释器环境,但依赖没有重新安装。
解决:执行pip install opencv-contrib-python==4.8.1.78,安装完重启Python解释器。注意版本必须和opencv-python完全一致,否则两个包的文件会互相覆盖,出现各种诡异的链接错误。我通常一次性安装两个包并用pip show opencv-python opencv-contrib-python检查版本号是否匹配。
5.4 陌生人被识别成已知用户
现象:一个完全不在训练集里的人出现在摄像头前,系统却显示为张三,而且confidence值只有40多。
原因:置信度阈值设置得太宽松。LBPH的识别机制本质上是“找最近的距离,不管距离有多远”,所以如果不设阈值,它永远会给你一个结果。这在小样本场景下非常危险——一个没见过的人脸,只要它和任一已知人脸的特征距离在阈值范围内,就会被误认。
解决:先用测试数据统计阈值。具体做法是:让一个陌生人站在摄像头前做10次识别,记录每次的confidence值;再让每个已知用户各做10次识别,记录对应confidence值。取陌生人confidence值的最小值和已知用户的最大值,阈值设置在两者之间。我在这套项目上的经验数据是:正常识别自己30左右,陌生人通常在80以上,阈值取55左右比较合适。如果发现陌生人confidence也低于60,说明样本质量太差或样本量不够,应该补充训练数据而不是继续调阈值。
5.5 训练报错:train() missing 1 required positional argument labels
现象:调用recognizer.train(images, labels)时报错,提示labels参数缺失或格式错误。
原因:train()方法的签名要求train(samples, labels),如果labels是普通list而不是numpy数组,或者数组的dtype不是int32,OpenCV会给出一个不太直观的报错信息,让人误以为是参数名写错。另一个隐蔽原因是images列表里的图片尺寸不一致,OpenCV在组装训练矩阵时因为维度对不上,把异常吞成了labels的问题。
解决:确保每个样本先resize到相同尺寸,labels用np.array(labels, dtype=np.int32)转换。给images列表加一个断言检查,确认所有图片shape一致:
for img in images: assert img.shape == (200, 200), f"图片尺寸不一致: {img.shape}"用这句断言卡住,之后再调用train就不会遇到让人摸不着头脑的维度错了。
5.6 打包exe后运行时找不到模型或xml文件
现象:项目在PyCharm里跑得挺好,用PyInstaller打包成exe后,双击运行却提示找不到trainer.yml或haarcascade xml文件。
原因:PyInstaller打包时不会自动包含项目里的非代码文件,而源码里的路径是相对路径——在开发环境下相对的是项目根目录,在exe运行环境下相对的是exe所在目录。如果模型文件和exe的存放位置不一致,或者模型文件压根没被打进包里,运行时就会因为找不到文件而中断。
解决:用PyInstaller的--add-data参数把模型文件打包进去,同时代码里用sys._MEIPASS处理路径。常见的做法是这样:
import sys import os def resource_path(relative_path): base_path = getattr(sys, '_MEIPASS', os.path.abspath(".")) return os.path.join(base_path, relative_path) model_path = resource_path("trainer/trainer.yml") cascade_path = resource_path("haarcascade_frontalface_default.xml")PyInstaller命令加上--add-data "trainer/trainer.yml;trainer"和--add-data "haarcascade_frontalface_default.xml;."。在Windows上分隔符是分号,Linux和macOS是冒号,这个细节我写过错,后来干脆在项目里放了一个build.bat脚本把打包命令固定下来,再也没翻过车。
6. 答辩加分技巧:用一个测试脚本量化识别准确率
毕业设计答辩最怕的就是老师问“你怎么验证你的系统是可靠的”。如果只靠现场演示,结果好就是好,结果差就说不出个所以然来。我强烈建议你写一个独立的验证脚本,在答辩前对系统做一次定量评测,把准确率数据带进论文和PPT里。
最简方案是离线测试:从每个已知人的样本里留出20%的图片不参与训练,这些图片作为测试集,用训练好的模型逐张预测,统计预测正确率:
import cv2 import os import numpy as np recognizer = cv2.face.LBPHFaceRecognizer_create() recognizer.read("trainer/trainer.yml") test_dir = "dataset" total = 0 correct = 0 for person_name in os.listdir(test_dir): person_dir = os.path.join(test_dir, person_name) if not os.path.isdir(person_dir): continue for img_name in os.listdir(person_dir): img = cv2.imread(os.path.join(person_dir, img_name), cv2.IMREAD_GRAYSCALE) img = cv2.resize(img, (200, 200)) label, confidence = recognizer.predict(img) predicted_name = label_map.get(label, "") if predicted_name == person_name and confidence < 55: correct += 1 total += 1 print(f"准确率: {correct}/{total} = {correct/total*100:.2f}%")运行这个脚本得到准确率后,在论文里的写法可以更硬气:“系统在自建人脸数据集上的识别准确率达到97.5%,在光照充足、正脸朝向的条件下,识别平均响应时间低于0.5秒。”有了量化指标,老师的提问方向会从“你的系统行不行”变成“你的准确率受哪些因素影响”,后者的回答难度低得多。
再往前走一步,我给你两个小改进方向,都可以作为论文的创新点展开:
第一个是在采集阶段引入人脸关键点对齐。用dlib的68点人脸检测器定位双眼位置,通过仿射变换把人脸旋转到标准姿态。这样处理后,抬头、低头、侧脸情况下的人脸识别率会明显提升。边界是dlib需要单独安装且模型文件较大,但作为加分项足够有区分度。
第二个是把LBPH换成OpenCV的深度学习人脸识别模块cv2.dnn.readNetFromCaffe,配合预训练的SqueezeNet人脸识别模型。这个方案的精度远高于LBPH,但模型初始化时间长、CPU推理速度慢,框架代码也要改动较多。如果你时间充裕可以尝试,时间紧张先保LBPH跑通即可。
最后分享一个我自己的教训:当年做毕设时,我在采集阶段偷懒,觉得多拍点就行,没有检查样本质量,结果训练出来的模型在答辩现场把一个老师误识别成了实验室同学,场面相当尴尬。从那以后我每次跑人脸识别项目,都会强制走一遍“样本清洗 → 预处理 → 训练 → 离线准确率验证”的完整流程,识别准确率没验证过绝不直接上摄像头。希望这个习惯也能帮到你,祝答辩顺利。
本文还有配套的精品资源,点击获取