ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

OpenCV人脸识别实战:Haar级联与LBPH模型训练完整指南

OpenCV人脸识别实战:Haar级联与LBPH模型训练完整指南 简介面向研一图像处理课程与人脸识别初学者的完整期末项目基于OpenCV搭建从人脸检测、特征提取到模型训练与识别的一站式流程重点演示Haar级联分类器与LBPH等经典方法可作为课程设计参照或复现素材。资源共九个文件压缩包约一点零三兆核心为三个Python脚本、两个XML配置文件与一个YML训练模型另附说明文档和数据库文件分别承担数据采集、模型训练、实时识别与结果存储等任务目录结构清晰。目前已有1652人学习下载。通过该项目可完整掌握OpenCV人脸识别系统的工程组织方式理解人脸数据集制作、分类器训练、参数调优与效果评估等关键环节也能学习到从摄像头采集人脸、预处理图像到调用模型完成身份匹配的落地思路适合需要快速完成期末作业或入门计算机视觉的读者深入研读。1. 基于OpenCV的人脸识别一套期末大作业如何跑通完整识别链路期末周拿到「基于OpenCV的人脸识别」这套作业资源绝大多数人的第一反应不是看原理而是先打开里面那几个 Python 脚本看看能不能直接跑通。这个 zip 的核心价值恰好就在这它不是一个演示完就丢的 demo而是把数据采集、模型训练、实时识别三件事拆成 datasetCreator.py、trainner.py、detector.py 三个独立脚本配合 Haar 级联检测和 LBPH 特征构成一条完整的人脸识别链路。无论你是研一刚接触图像处理还是本科毕设临时接手 OpenCV这套结构都值得照着走一遍先把命令跑通再回来补原理最后改自己数据集时才知道参数该怎么动。2. 看懂项目骨架从文件清单反推系统设计2.1 解压之后先别急着跑文件清单本身就是系统设计拿到 zip 解压后第一件事不是找 README而是把文件列一遍。很多开源项目把设计意图藏在文件名里这套作业尤其典型。它的根目录结构如下文件/目录作用备注datasetCreator.py数据采集脚本调用摄像头拍人脸图运行后生成训练图像trainner.py训练脚本读取数据集训练 LBPH 模型输出 YML 模型文件detector.py识别脚本打开摄像头做实时识别读取 YML Haarrecognizer/训练模型目录里面存放 trainningData.ymltrainningData.ymlLBPH 训练产物项目里实际文件名是两个 n别改成单 nhaarcascade_frontalface_default.xmlHaar 级联人脸检测器OpenCV 官方自带FaceBase.dbSQLite 数据库常见做法是存 ID 到姓名的映射.idea/ vcs.xmlPyCharm 工程文件说明原项目用 PyCharm Git 管理.gitignore版本忽略文件通常屏蔽数据集和模型文件README.md项目说明一般会写清运行顺序从这个清单能看出两个重要信号。第一这个项目的功能边界非常清晰采集、训练、识别是三段独立流程不是一个大脚本从头跑到尾。这意味着你可以单独调试任何一个环节不用每次从头开摄像头。对交期末作业来说这比把所有逻辑揉在一起好改得多也方便在验收时只演示识别部分。第二它没有自己实现人脸检测算法而是直接使用 OpenCV 自带的 Haar 级联分类器。这也是绝大多数图像处理期末作业的做法——重点放在人脸识别与特征提取流程不想在检测部分重复造轮子。检测是黑匣子识别是重点思路很务实。运行顺序也很明确先跑 datasetCreator.py 给每个人采集一批人脸图再跑 trainner.py 生成 trainningData.yml最后跑 detector.py 打开摄像头看实时识别效果。这个顺序在 README 里通常也会写到但更可靠的判断方式是打开每个脚本看它 import 了什么、读写了什么路径。2.2 为什么选 Haar 级联 LBPH而不是 EigenFaces 或深度模型刚接触人脸识别的人很容易被名字吓到什么 EigenFaces、FisherFaces、LBPH以为每个都是玄学。实际上在 OpenCV 提供的三种传统人脸识别器里LBPH 是最适合这种小数据集工程场景的选择。方法原理小数据集表现光照敏感度实时性EigenFacesPCA 降维把脸投射到特征空间一般高好FisherFacesLDA 线性判别强调类间可分较弱高好LBPH局部二值模式直方图好低好在这个项目里训练集是摄像头采集的几十到几百张图数据量不大。EigenFaces 对光照极其敏感同一个人的照片换个灯光特征就可能对不上。LBPH 算子计算的是像素和邻域的局部关系对光照变化有明显鲁棒性。再加上它计算量小笔记本摄像头跑实时识别也能稳住帧率所以训练脚本里用 LBPH 是合理选择。需要强调一点OpenCV 核心库的 cv2.face 模块不在 opencv-python 主包里而是放在 opencv-contrib-python 里。这个问题会在环境准备章节重点讲很多人卡在 AttributeError 就是因为它。至于检测部分Haar 级联分类器尽管被深度模型甩开不少但它不需要额外下载权重文件一个 XML 就能跑而且在这套脚本里它只负责把脸部区域框出来不参与身份判断。检测和识别解耦意味着你可以把 Haar 换成任何检测器不影响后续训练流程。3. 环境准备与数据集采集让 datasetCreator.py 批量造训练图3.1 安装 OpenCV 系列包主包与 contrib 的差别大多数人翻车就在这里命令pip install opencv-python装完了import cv2 也不报错结果执行到cv2.face.LBPHFaceRecognizer_create()时直接 AttributeError。原因是 OpenCV 从 3.x 开始把很多扩展模块从主仓库拆了出去face 模块就是其中一个。常见做法是用以下方式一次装齐。建议先在一个独立的虚拟环境里操作避免和机器上其他 Python 项目互相踩python -m venv face_env # Windows 下激活 face_env\Scripts\activate # Linux / macOS 下激活 # source face_env/bin/activate pip install opencv-contrib-python pip install numpy pillow安装完成后验证一下import cv2 print(cv2.__version__) print(hasattr(cv2, face))如果第二行输出 True说明 face 模块可用了。这里有个细节opencv-python 和 opencv-contrib-python 不能同时存在于同一个环境里否则两个包的文件互相覆盖会出现 DLL 加载失败之类的诡异报错。如果你之前装过主包先卸载干净再装 contribpip uninstall opencv-python pip install opencv-contrib-python numpy pillow国内网络环境下大体积包下载容易超时加-i参数换国内 PyPI 镜像即可具体镜像地址按自己熟悉的来。numpy 和 pillow 是图像处理的基础依赖虽然 OpenCV 自带一部分数值运算能力但训练脚本里如果用了 PIL 读取图片就绕不开 pillow。3.2 datasetCreator.py 采集逻辑与参数调整先贴采集脚本的核心逻辑常见实现如下import cv2 import os # 人脸检测器放在项目根目录 detector cv2.CascadeClassifier(haarcascade_frontalface_default.xml) # 保存路径dataSet 目录 output_dir dataSet if not os.path.exists(output_dir): os.makedirs(output_dir) # 每个人分配一个数字ID0、1、2... face_id input(请输入人脸ID数字) count 0 # 打开笔记本摄像头 camera cv2.VideoCapture(0) while True: ret, frame camera.read() if not ret: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # scaleFactor1.3 表示检测窗口每次缩放1.3倍 faces detector.detectMultiScale( gray, scaleFactor1.3, minNeighbors5, minSize(100, 100) ) for (x, y, w, h) in faces: count 1 # 保存灰度图统一尺寸200x200减少特征计算噪声 face_roi cv2.resize(gray[y:yh, x:xw], (200, 200)) cv2.imwrite(f{output_dir}/User.{face_id}.{count}.jpg, face_roi) cv2.rectangle(frame, (x, y), (xw, yh), (0, 255, 0), 2) cv2.imshow(capture, frame) if cv2.waitKey(1) 0xFF ord(q): break if count 200: break camera.release() cv2.destroyAllWindows()注意保存图像的命名规则User.ID.序号.jpg。ID 就是刚才输入的数字trainner.py 会从文件名里解析标签。文件总数达到 200 张自动退出也可以按 q 手动结束。保存的人脸区域先 resize 到 200x200这样不同大小的脸部特征不会影响后续直方图计算。关键参数说明scaleFactor1.3每轮检测窗口缩放系数1.1 更慢但检出更全1.3 更快但偶尔漏检。对作业场景 1.2 到 1.3 都行。minNeighbors5每个候选区域至少被 5 个邻近窗口确认才算人脸调大能减少误检但也会漏掉侧面脸部。minSize(100, 100)小于这个尺寸的人脸直接忽略。这个值强烈建议保留。笔记本摄像头离人太远时脸部可能不足 100 像素训练进去全是糊的识别时也认不出。实际采集时人会下意识正对镜头不动。但训练集过于单一后面识别时稍微侧个头预测标签就容易出错。常见做法是采集时头部缓慢左右转动、上下微调把各个角度都截一些。同一人建议拍 100 到 200 张少于 20 张训练出的模型几乎必翻车。4. LBPH 模型训练trainner.py 的参数、输出与识别率调优4.1 训练脚本的执行逻辑数据采集完成之后dataSet 目录下已经有几百张 User.ID.序号.jpg。trainner.py 做的事情是遍历这些图片用 Haar 检测器再次定位人脸区域把尺寸归一后从文件名解析出标签 ID送入 LBPH 识别器训练最后把模型写入 recognizer/trainningData.yml。核心代码长这样import cv2 import os import numpy as np detector cv2.CascadeClassifier(haarcascade_frontalface_default.xml) # radius1对比半径neighbors8取周围8个邻居像素 recognizer cv2.face.LBPHFaceRecognizer_create( radius1, neighbors8, grid_x8, grid_y8 ) def load_dataset(path): files [f for f in os.listdir(path) if f.endswith(.jpg)] samples, labels [], [] for f in files: # 文件名 User.1.15.jpg label 1 label int(f.split(.)[1]) img cv2.imread(os.path.join(path, f), cv2.IMREAD_GRAYSCALE) # 训练前再做一次归一化防止尺寸不一致 img cv2.resize(img, (200, 200)) faces detector.detectMultiScale(img, scaleFactor1.2, minNeighbors5) for (x, y, w, h) in faces: samples.append(img[y:yh, x:xw]) labels.append(label) return samples, np.array(labels) samples, labels load_dataset(dataSet) print(f样本数量{len(samples)}标签分布{set(labels)}) recognizer.train(samples, labels) # 注意文件名是 trainningData.yml两个n保持与项目一致 recognizer.save(recognizer/trainningData.yml) print(模型已保存到 recognizer/trainningData.yml)训练前再次检测人脸而不是直接拿整张图训练是为了去掉背景和头发等区域。如果采集时已经把人脸裁得很干净这一步不是必须的但保留它能让模型更关注面部结构。训练过程中如果报错error: (-210:Unsupported format or combination of formats)通常是传入的 samples 尺寸不一致或者是空列表。前者用 resize 解决后者检查 dataSet 里是否有可用的 JPG 文件。另外标签必须是整数代码里 int() 解析不出来的话说明采集时命名规则写错了。label 数值不连续不会报错但会影响识别时名称映射的对应关系建议从 0 开始连续编号。4.2 LBPH 参数到底在调什么LBPH 的计算原理不是黑匣子。它的流程是这样的将灰度图划分成 grid_x 乘以 grid_y 个小格子每个小格子里对每个像素取半径 radius 范围内的 neighbors 个邻居像素把中心像素值与邻居逐个比较大于记 1 小于记 0组成一个二进制的局部二值模式转成十进制数后统计每个小格的直方图。最终整个图像的直方图拼接起来就是它的特征向量。识别时比较待识别图像的直方图与训练集中每个人的直方图距离距离越小越相似。这套算法主要参数参数默认值调高影响调低影响radius1对比范围更大对纹理尺度不敏感特征更细碎neighbors8更稳定计算更慢对噪声敏感grid_x / grid_y8 / 8空间信息更细容易过拟合特征更粗区分度下降对几十人以内的小数据集radius1、neighbors8、grid8x8 是最稳的组合。grid 调到 16x16 后虽然直方图能捕捉更多空间细节但训练样本不足时每个格子的统计都会变得稀疏反而容易把同一人脸在不同角度的差异放大识别率不升反降。4.3 训练后怎么检查模型质量模型训练完不是直接拿去跑就完了。我一般会先做一个离线自检写几行脚本把训练集以外的照片喂给 recognizer.predict打印返回的 label 和 confidence观察同一人的 confidence 是否稳定在 80 以下。import cv2 recognizer cv2.face.LBPHFaceRecognizer_create() recognizer.read(recognizer/trainningData.yml) test_img cv2.imread(test/selfie.jpg, cv2.IMREAD_GRAYSCALE) test_img cv2.resize(test_img, (200, 200)) label, confidence recognizer.predict(test_img) print(label, confidence)这里 confidence 不是「置信度百分比」而是 LBPH 的直方图距离。距离越小代表越相似。同一个人的照片通常在 50 到 80 之间超过 100 就该怀疑训练集质量或者人脸没对齐了。很多新手把这个值当成概率看到 70 想「才 70% 不敢认」方向搞反了。再补充一个细节训练集里人数大于等于 2 时才有「识别」意义。只训练了一个人模型会倾向于把所有输入都判成这个人这是正常现象不代表模型坏了。期末验收时至少采集两个人的数据才能看出区分能力。5. 避坑人脸识别期末作业最常见的五处翻车现场前面章节已经铺好了全流程但真正把代码跑起来翻车点往往集中在很少的几个位置。以下五条都是常见到可以写进教学文档的经验记录。5.1 编译报错cv2 没有 face 模块现象cv2.face.LBPHFaceRecognizer_create()报 AttributeError: module cv2 has no attribute face。原因安装的是 opencv-python 主包face 模块被拆到了 opencv-contrib-python。两个包面向不同场景主包只含稳定模块。解决卸载主包安装 contrib 包重开 Python 进程验证hasattr(cv2, face)。注意不能用pip install opencv-python opencv-contrib-python同时安装两个包存在同名文件冲突卸载干净再装。5.2 训练时 sample 列表为空现象训练脚本打印样本数量为 0训练直接报错。原因dataSet 目录里没有有效 JPG或者采集脚本保存路径和训练脚本读取路径不一致。项目里 datasetCreator.py 写的是 dataSettrainner.py 读的也是 dataSet但如果你是从压缩包直接解压后换了目录名两边就会对不上。解决打开两个脚本检查输出目录和读取目录是否完全一致。Windows 下不区分大小写Linux 下区分所以 DataSet 和 dataSet 在 Linux 会被当成两个目录这点很容易被忽略。5.3 识别结果谁都不认全显示 Unknown现象detector.py 跑起来人脸框能出来但名字永远是 Unknown无论框里是不是训练过的人。原因两个可能性。第一置信度阈值设置过严比如代码里if confidence 50稍远一点拍的脸部距离值跑到 80 多直接被判成 Unknown。第二训练集和实时画面差异太大比如训练时是室内灯光识别时背光LBPH 虽对光照鲁棒但极端光线变化还是会影响距离。解决把阈值放宽到 80 或 100先观察识别到熟人时 confidence 大致分布在什么区间再去找垂直切一刀的阈值。临时改法通常是直接改 detector.py 里那一行判断name names.get(label, Unknown) if confidence 80 else Unknown把 80 改成 100 或者 120 再跑看效果。如果调到 150 还是认不出问题就不在阈值而是训练集本身不够干净。5.4 距离值特别低陌生人也被认成自己人现象陌生人站到摄像头前confidence 显示 45系统直接认错。原因训练样本太少模型没有学到完整的类内差异把所有脸都映射到了同一个直方图附近。另一种可能是 grid 设置过大特征被过度细化导致模型过拟合训练集。解决增加同一个人不同角度的样本数量把 grid_x/grid_y 从 16 调回 8。如果还是压不下来检查采集过程中是否把大量背景图存了进来这类样本会成为训练集里的脏数据。5.5 笔记本摄像头打不开ret 一直是 False现象camera.read() 返回 (False, None)窗口黑屏或直接退出。原因摄像头被其他软件占用或者 VideoCapture(0) 默认使用 DirectShow 后端失败。Windows 10 以上系统里相机隐私权限也可能拦截 OpenCV 读取设备。解决先在任务管理器里关掉占用的程序在代码里指定后端cv2.VideoCapture(0, cv2.CAP_DSHOW)如果仍然失败把设备索引从 0 改成 1 试试外置摄像头。最后再去系统设置里检查相机访问权限是否开启。这五条循环往复出现的概率极高。第 5.3 和第 5.4 是一条线的两个方向阈值卡在 60 到 80 之间是很多作业里的常用起点实际以自己数据为准调整。6. 识别端到端验证从 FaceBase.db 到 detector.py 的最后一步6.1 detector.py 的识别链路与数据库映射detector.py 是整个项目最后一个脚本也是作业演示时最出效果的一个。它做的事可以拆成四步import cv2 import sqlite3 recognizer cv2.face.LBPHFaceRecognizer_create() recognizer.read(recognizer/trainningData.yml) detector cv2.CascadeClassifier(haarcascade_frontalface_default.xml) # 从 FaceBase.db 读取 ID 到姓名的映射 conn sqlite3.connect(FaceBase.db) cur conn.cursor() cur.execute(SELECT id, name FROM face_info) names {row[0]: row[1] for row in cur.fetchall()} conn.close() camera cv2.VideoCapture(0, cv2.CAP_DSHOW) while True: ret, frame camera.read() if not ret: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces detector.detectMultiScale(gray, scaleFactor1.3, minNeighbors5) for (x, y, w, h) in faces: label, confidence recognizer.predict(gray[y:yh, x:xw]) name names.get(label, Unknown) if confidence 80 else Unknown cv2.putText(frame, f{name} ({confidence:.1f}), (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) cv2.rectangle(frame, (x, y), (x w, y h), (0, 255, 0), 2) cv2.imshow(recognize, frame) if cv2.waitKey(1) 0xFF ord(q): break camera.release() cv2.destroyAllWindows()FaceBase.db 在这里的作用是避免把姓名硬编码在脚本里。训练时 label 只有数字数据库负责把 ID 映射成姓名。如果数据库里没建表或者没插入数据names 为空字典识别结果就是 Unknown这个坑和 5.3 的表现很像排查时先区分是模型没认出人还是数据库没查到名。6.2 让演示效果更稳的三个小技巧第一个技巧是固定采集距离和高度。摄像头画面中人脸尽量占据画面高度的三分之一以上并保证双眼位于画面中上部这个位置正好落在 Haar 检测最稳定的区域。第二个技巧是观察 confidence 数值的波动范围。实时运行时同一人在画面里移动confidence 在 50 到 90 之间浮动是正常的。如果上下跳动超过 40说明模型对灰度纹理的区分不够稳定回到 4.3 做离线自检检查训练样本是不是存在曝光差异过大的情况。第三个技巧是在日志里记录每个 label 对应的预测次数做简单统计来验证模型没有偏向某一个人。连续识别同一个人的 30 帧画面预测标签一致率应该在 90% 以上否则说明某类样本在训练集中占多数模型产生了偏置。我在交这份作业前就吃过偏置的亏为了凑数据集给某个人拍了 200 张另一个人只拍了 40 张演示时前者的识别率远高于后者现场非常尴尬。从那以后每次训练完我都要先打印 set(labels) 统计人数和每类样本量保证分布均衡再往下走。希望帮到你。本文还有配套的精品资源点击获取
返回列表