ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

基于OpenCV与LBPH的人脸识别系统课程设计全解析

基于OpenCV与LBPH的人脸识别系统课程设计全解析 简介面向机器视觉课程及期末大作业场景这套基于Python与OpenCV的人脸识别项目提供了完整的源代码与文档说明开发者可由此了解人脸数据采集、模型训练到实时识别的全流程代码内注释丰富对新手较为友好。压缩包共9个文件包含3个Python脚本负责检测、训练与数据采集、OpenCV级联检测器的XML配置、训练生成的YML模型、SQLite风格的DB人脸数据库、Markdown说明文档等整体体积仅1.03MB部署简单适合本地快速跑通。目前已有524人学习下载反馈良好项目结构清晰系统功能完善界面直观易用既可作为课程设计或期末大作业的参考方案也可作为人脸识别入门的练习素材。随包附带的配置文件与说明文档能帮助使用者梳理OpenCV人脸识别的关键步骤与参数方便二次开发与扩展。1. 为什么人脸识别大作业要自己写一遍而不是直接调现成接口不少同学把OpenCV官方的人脸识别示例改个颜色就交了答辩时被问到“训练的模型文件是什么格式”“置信度多少算通过”就答不上来。其实机器视觉课的核心并不是让你调通一个云服务而是理解图像从像素到特征的完整链路。这个课程设计资源包含datasetCreator.py、trainer.py、detector.py、recognizer.py等几个脚本配合FaceBase.db和trainingData.yml构成了一个不依赖外网、离线可跑的人脸识别闭环适合期末大作业、课程设计也适合想真正搞懂Haar级联、LBPH和SQLite记录的新手。下面我按数据采集、模型训练、推理识别、数据库管理四层拆解每个文件怎么改、参数怎么调、答辩会问哪里都会标出来。2. 人脸数据采集与预处理datasetCreator.py 的关键细节2.1 初始化级联分类器与摄像头先看采集端代码import cv2 import os cascade_path haarcascade_frontalface_default.xml face_cascade cv2.CascadeClassifier(cascade_path) cap cv2.VideoCapture(0) if not cap.isOpened(): raise IOError(摄像头无法打开请检查设备索引或驱动)这里有两处容易踩坑。第一CascadeClassifier读取的是OpenCV自带的人脸检测模型文件路径最好是绝对路径或放在项目根目录否则在PyCharm里能跑、命令行一运行就报找不到文件。第二VideoCapture(0)表示系统默认摄像头在笔记本上通常是内置摄像头如果接外接USB摄像头或工业相机设备索引会变成1或2。机器视觉检测场景里有时候需要同时读取多个通道那就用VideoCapture(0)和VideoCapture(1)分别打开。2.2 灰度化与直方图均衡化的参数选择代码ret, frame cap.read() if not ret: return gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) gray_eq cv2.equalizeHist(gray)灰度化是为了把三通道彩图压缩成单通道亮度图Haar特征本身只依赖亮度差颜色信息反而会引入噪声。equalizeHist做的是全局直方图均衡化它把灰度分布拉伸到接近均匀解决背光或半边脸阴影导致的人脸对比度过低问题。这个函数输入必须是uint8单通道输出尺寸和输入一致没有额外参数。很多人把这一步省略结果在教室日光灯环境下检测率掉到一半以下所以不要跳。另一个细节是均衡化只用于检测实际保存人脸块时用原始gray而不是gray_eq这样可以避免训练数据和检测阶段的像素强度分布不一致影响LBPH直方图匹配。2.3 人脸框筛选与ROI截取faces face_cascade.detectMultiScale( gray_eq, scaleFactor1.1, minNeighbors5, minSize(64, 64) ) if len(faces) ! 1: continue for (x, y, w, h) in faces: roi gray[y:yh, x:xw] roi_resized cv2.resize(roi, (200, 200)) cv2.imwrite(fdata/user_{user_id}/{count}.jpg, roi_resized)detectMultiScale是Haar检测的核心函数四个参数直接影响误检率和漏检率scaleFactor表示每次缩放倍率1.1代表每次缩小10%值越小计算越慢但检测越全minNeighbors表示一个候选框周围必须存在多少个近邻框才保留太小会误检太大会漏检5是课程设计场景的折中值minSize(64, 64)过滤掉小于该尺寸的区域避免远处小脸或衣服纹理被当成脸。代码里限制len(faces) ! 1才采集能有效避免电视、海报等背景人脸混进样本导致标签混乱。保存前resize到200×200统一尺寸可以减小特征向量在空间上的漂移LBPH本身对缩放有一定容忍度但统一的输入在计算局部二值模式时更稳定。2.4 样本数量与目录结构规划采集前先创建目录结构常见做法是data/ user_0/1.jpg user_0/2.jpg user_1/1.jpg其中user_0对应数据库里一个人user_1对应另一个人。这里有个重要的索引约定标签ID必须是整数从0开始连续否则recognizer读取trainingData.yml时会报标签无法解析。样本数量和质量直接影响最终分数下面给出一组实际经验值每人样本数识别表现课程设计定位1 ~ 5 张同角度同光线能用稍一动就认错勉强及格10 ~ 20 张日常光照下稳定戴眼镜后偶发掉点中等偏上30 ~ 50 张换表情、换发型也有一定鲁棒性高分作业50 张以上接近工程可用但采集和训练时间变长有工程价值采集时让人脸做小幅度的左右旋转、抬头低头、靠近远离每个姿态保持约半秒再跳到下一个。不要连续疯狂截图相邻帧高度相似特征冗余大。我一般把摄像头帧率控制在15FPS左右每5帧保存一次一个用户采集40~50张只需要两三分钟。3. 训练自己的识别模型trainer.py 与 LBPH 参数3.1 从数据集读取图像和标签训练代码的核心是遍历刚才生成的data目录把图像路径和所属用户ID做成对照表import cv2 import os import numpy as np face_cascade cv2.CascadeClassifier(haarcascade_frontalface_default.xml) faces [] labels [] data_dir data for user_name in os.listdir(data_dir): user_path os.path.join(data_dir, user_name) if not os.path.isdir(user_path): continue label int(user_name.split(_)[1]) for img_name in os.listdir(user_path): img_path os.path.join(user_path, img_name) img cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) detected face_cascade.detectMultiScale(img, scaleFactor1.1, minNeighbors4) if len(detected) 1: (x, y, w, h) detected[0] faces.append(cv2.resize(img[y:yh, x:xw], (200, 200))) labels.append(label) faces np.array(faces) labels np.array(labels)这段代码遍历所有user_*文件夹从目录名中解出整数ID再对每张图执行一次人脸检测并裁出人脸区域。这里的detectMultiScale参数和采集时略有不同minNeighbors4放宽了一点因为训练样本可能是在不同光线下拍的检测框稳定性比实时视频差。这一步的作用是二次校验哪怕采集时保存了完整人脸训练前再裁一次可以去掉非人脸噪声。如果某张图检测不到人脸直接跳过不要让空数组进入训练器。3.2 LBPH的原理边界与参数选择训练器最常用的是OpenCV自带的LBPHrecognizer cv2.face.LBPHFaceRecognizer_create( radius1, neighbors8, grid_x8, grid_y8 )LBPH把人脸分成多个小网格在每个像素周围取半径为radius的邻域与中心像素比较大小按二进制编码统计直方图。radius1表示取3×3邻域radius2则覆盖5×5区域邻域半径越大能捕捉的纹理尺度越大但也更容易把表情皱纹和光照阴影混进特征。neighbors8是标准的8邻域采样点太小特征区分度差太大会对噪声敏感。grid_x8, grid_y8是把人脸划分成64个小块每个块独立统计直方图再首尾拼接这样能保留人脸的局部空间信息鼻子区域的特征不会跑到额头区域。对课程设计来说这组默认参数是经过大量样本验证的稳定组合不建议盲目增大grid因为小块越多维度越高小样本下直方图会稀疏识别率反而下降。3.3 训练并保存 trainingData.ymlrecognizer.train(faces, labels) model_path trainingData.yml recognizer.write(model_path) print(f训练完成共 {len(faces)} 个样本模型已保存到 {model_path})train接受两个参数样本矩阵和标签数组。样本矩阵形状是(N, 200, 200)标签是长度N的整数数组。write把训练结果序列化到YAML文件里面保存的是LBPH的直方图序列、标签映射和网格参数。答辩时如果问“模型文件里是什么”就答“每个标签对应一组从各网格块拼接的局部二值模式直方图以及训练时记录的用户ID列表。”不要答成神经网络权重。这里要注意一个常见错误直接用np.array(faces)时如果每张图尺寸不统一train会报data type must be 8-bit或维度不匹配所以采集端统一resize到200×200不能只在训练时临时resize。3.4 为什么课程设计用LBPH而不是深度模型机器视觉课程要求在有限时间、有限数据集里跑通闭环LBPH有三个不可替代的优势不需要GPUCPU上训练100张图只需几秒模型文件只有几十到几百KB部署简单代码量和原理都能在答辩时讲清楚。深度学习方案比如FaceNet、InsightFace在公开数据集上准确率更高但需要预训练权重、特征对齐和阈值调优一套流程下来复杂度翻倍而且期末项目里如果只有几十个样本微调效果通常还不如LBPH。我见过有人用face_recognition库两行代码出结果但那把特征提取和比对都封装了老师一问“距离阈值为什么是0.45”就答不上来。LBPH是可以用纸笔推导出计算过程的算法这也是大作业评分的隐藏加分项。4. 识别与数据库detector.py recognizer.py 配合 FaceBase.db4.1 加载模型与置信度阈值设定识别端代码recognizer cv2.face.LBPHFaceRecognizer_create() recognizer.read(trainingData.yml) label_map { 0: 张三, 1: 李四, 2: Unknown } threshold 80read从YAML文件恢复训练好的模型不需要重新训练。label_map把数字ID映射成真实姓名这里要注意这个映射最好和FaceBase.db中的记录保持一致。threshold是置信度阈值LBPH的predict返回的距离值越小表示越相似这个值的含义是“重建误差”或“直方图卡方距离”不是一个概率。常见取值在50到100之间具体要看训练集的个体差异。一个比较稳的做法是先收集10张已经训练过的人脸跑一遍把最低距离和最误判距离画一条分界线取两者中值作为阈值。4.2 识别流程与业务逻辑ret, frame cap.read() gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale(gray, scaleFactor1.1, minNeighbors5) for (x, y, w, h) in faces: roi gray[y:yh, x:xw] roi_resized cv2.resize(roi, (200, 200)) label, confidence recognizer.predict(roi_resized) if confidence threshold: name label_map.get(label, Unknown) color (0, 255, 0) else: name Unknown color (0, 0, 255) cv2.rectangle(frame, (x, y), (xw, yh), color, 2) cv2.putText(frame, f{name} ({confidence:.1f}), (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, color, 2)识别流程和训练时一致转灰度、检测人脸、resize、预测。唯一区别是predict返回两个值label是预测的标签confidence是距离值。很多同学只看label不设阈值导致任意一张陌生脸都被归到最近的已注册用户这在演示时非常尴尬。加了阈值判断后陌生脸会在“未注册”分支处理。这里的confidence大小和算法选择有关EigenFace返回的是欧氏距离FisherFace和LBPH返回的也是某种距离阈值不能跨算法通用。所以答辩时如果改了识别器阈值也要重新标定。4.3 SQLite 记录访问日志课程设计资源里的FaceBase.db就是SQLite数据库常见实现是记录姓名、时间、是否通过import sqlite3 from datetime import datetime conn sqlite3.connect(FaceBase.db) cursor conn.cursor() cursor.execute( CREATE TABLE IF NOT EXISTS access_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, pass TINYINT NOT NULL, time TEXT NOT NULL ) ) if name ! Unknown: cursor.execute( INSERT INTO access_log (name, pass, time) VALUES (?, ?, ?), (name, 1, datetime.now().strftime(%Y-%m-%d %H:%M:%S)) ) conn.commit()FaceBase.db用SQLite管理好处是不需要额外安装数据库服务交给老师时整个项目一个目录拷走即可。注意SQL语句使用参数化查询避免字符串拼接导致注入这也是代码规范的一个加分点。如果要导出签到表可以这样查SELECT name, time FROM access_log ORDER BY time DESC LIMIT 10;4.4 在界面上显示结果原资源中的界面逻辑通常在detector.py里通过OpenCV的imshow显示视频流配合键盘事件退出cv2.imshow(Face Recognition System, frame) key cv2.waitKey(1) if key ord(q): break这是最轻量的交互方式不依赖第三方GUI库。如果想做成更完整的课设界面可以引入tkinter或PyQt5把视频流嵌入Label控件同时把右侧Listbox显示数据库记录。但要注意OpenCV的imshow和waitKey必须在同一线程调用如果开启多线程读摄像头要保证cap.read()不被并发抢占。我在做过的一个版本里把摄像头读取放在子线程主线程用after()刷新UI结果因为GIL和缓冲区竞争出现画面撕裂。后来改成单线程轮询代码简单反而稳定。对于课程设计imshow方案已经够用界面美观主要靠窗口标题、字体颜色和识别框动画来体现。5. 把作业做出区分度的验证与调试技巧5.1 用留出法快速评估模型训练完不要直接放到识别端看效果先做一个离线的K折验证。常见做法是把每个人的样本随机打乱80%训练、20%测试统计准确率from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split( faces, labels, test_size0.2, stratifylabels, random_state42 ) r cv2.face.LBPHFaceRecognizer_create() r.train(X_train, y_train) correct 0 for img, true_label in zip(X_test, y_test): pred, conf r.predict(img) if pred true_label and conf threshold: correct 1 print(f准确率: {correct / len(X_test):.2f})stratifylabels保证每个人的测试样本比例一致防止某个人全是训练集、另一个人全是测试集的情况。这个脚本只要跑一次就能在写报告时给出一个量化的准确率数据比“感觉还不错”有说服力得多。5.2 现场演示最容易翻车的三个场景第一现场灯光和采集时差别太大。解决办法是识别的时候对每一帧也做equalizeHist并且尽量把采集和演示放在同一场所。第二误检非人脸。minNeighbors从5调到7牺牲一点帧率换稳定。第三阈值过高导致已注册用户被拒。可以在演示时打印confidence实时值看到通常稳定在40~60把阈值设为100左右留出余量。不要为了追求不误识把阈值压到40那样熟悉的人换个发型也会被拒。5.3 输出通道扩展从视频窗口到串口和数据库如果想把项目从“能跑”提升到“像产品”可以把识别结果同步写进FaceBase.db并推送一条串口指令。例如通过pyserial发送open_door给单片机import serial ser serial.Serial(/dev/ttyUSB0, 9600, timeout0.5) if name ! Unknown: ser.write(bopen_door\\n)这个动作在答辩时非常加分因为它把课堂上的OpenCV任务和实际门禁场景连起来了。需要注意的是串口号在Windows下是COM3这样的形式MAC下是/dev/cu.usbserial-*不同机器不一致可以写一个自动扫描端口的函数或者把串口配置单独放在config.py里。如果不想接硬件也可以用pygame.mixer播放声音或者用win32api弹窗提示核心思路是让输出多样化展现你对机器视觉应用边界的思考。真正决定大作业评分的不是算法本身而是边界情况处理得有多细。下来你可以把threshold、摄像头索引、样本尺寸这些参数集中到一个配置类里以后换数据集、换算法时只需要改配置文件不需要动识别主流程。本文还有配套的精品资源点击获取
返回列表