ARTICLE DETAIL

资讯详情

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

基于OpenCV的人脸识别实战:从Haar检测到LBPH模型训练

基于OpenCV的人脸识别实战:从Haar检测到LBPH模型训练 简介研一图像处理课程的人脸识别期末大作业代码包基于OpenCV完成从人脸检测、特征提取到身份识别的完整流程适合计算机视觉、人工智能方向的学生作为课程设计模板或入门实践。资源包共9个文件压缩包约1.03MB包含Python脚本、XML级联文件、YML训练数据、SQLite数据库和README说明文档覆盖数据集创建、模型训练、实时识别与结果存储等环节结构精简。目前已有1652人学习。作为OpenCV人脸识别入门项目运行工程可快速跑通识别流程直观理解Haar级联分类器的人脸定位原理以及LBPH等传统特征在小型识别任务中的应用代码模块边界清晰便于修改数据集、调整模型参数并复用到其他图像处理实验是兼顾学习与实践的轻量级参考。1. 研一图像处理期末大作业基于 openCV 的人脸识别到底在考什么如果你正在为这门课熬夜十有八九是被“图像处理”四个字骗进来的——以为写写滤波、算算直方图就能交差结果老师扔来一个“基于 openCV 的人脸识别”题目要求你从摄像头或者图片集里把一张脸找出来再告诉你是谁。这作业说难不难说简单也绝对不简单难点不在数学而在你要把一整条识别流水线从头到尾跑通图像预处理、人脸检测、特征提取、模型训练、识别比对最后还要写报告回答案辩问题。这门课的核心目标不是让你发明新算法而是让你理解“传统视觉方案如何解决一个真实问题”。在深度学习霸屏的今天openCV 里的 Haar 级联检测器和 LBPH 识别器依然是最适合课程作业的技术栈——它不依赖 GPU、不需要海量数据集、代码量可控、原理能讲清楚。用对工具两天能交差用错方案两周都在调环境。这篇笔记就是按照我当年做完这个作业的路线把环境搭建、检测、识别、调参和踩坑整个给你过一遍保证每一步都能照着抄。2. 环境准备与 openCV 安装三个版本陷阱先说清2.1 为什么 openCV 的安装是个“高危操作”每次有人问“我 openCV 装好了为什么 import 报错”我第一反应都是问他装的是哪个版本、用什么命令装的。openCV 的安装坑在于它有 lib、contrib、headless 三种不同的 pip 包还有 2.x、3.x、4.x 三个大的版本分支加上 Windows 和 Ubuntu 的安装方式完全不一样网上教程互相抄最后抄出来一堆没头没尾的命令。2.2 Windows 环境创建独立虚拟环境minimal requirements常见做法是先为这个作业创建独立的虚拟环境防止和系统 Python 里的包冲突。Windows 下我建议用 Anaconda 或者 Python 3.8-3.10 搭配 venv命令如下# 创建虚拟环境Windows 示例 python -m venv face_env # 激活虚拟环境 face_env\Scripts\activate.bat # 使用清华源安装核心库和 contrib 扩展库 pip install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple pip install opencv-contrib-python -i https://pypi.tuna.tsinghua.edu.cn/simple # 依赖库 pip install numpy pillow -i https://pypi.tuna.tsinghua.edu.cn/simple这里有两个要素值得解释第一opencv-contrib-python必须装因为人脸识别模块face_recognition 相关算法在 OpenCV 4.x 里被挪到了 contrib 包中只装opencv-python会报AttributeError: module cv2.face has no attribute LBPHFaceRecognizer_create第二如果你在实验室电脑上已经装了 Anaconda尽量不要裸pip install到base环境里否则后面调试时你永远不确定是环境问题还是代码问题。2.3 Ubuntu 环境apt 安装与源码编译的选择Ubuntu 上装 openCV 也有两条路。一条是sudo apt install python3-opencv几分钟搞定但版本通常较老而且不带 contrib 模块另一条是从源码编译时间成本和出错的概率都高得多。对期末大作业而言我的建议是直接走 pip 路线和 Windows 下的命令几乎一致# Ubuntu 下激活虚拟环境 source face_env/bin/activate pip install opencv-python opencv-contrib-python numpy pillow只要你的 Ubuntu 版本不是太老这条命令基本不需要碰系统依赖。如果确实要用摄像头Linux 下还要装 v4l-utils 来测试摄像头是否能被系统识别这个和 openCV 本身关系不大但经常被忽略。安装完成后用下面这段极简代码验证安装状态避免做到一半才发现版本不对import cv2 import numpy as np print(cv2.__version__) # 例如 4.8.0 # 关键验证LBPH 识别器是否可用 try: recognizer cv2.face.LBPHFaceRecognizer_create() print(LBPH ready) except AttributeError as e: print(LBPH missing, need opencv-contrib-python, e)运行这段代码后如果版本号能打印出来而 LBPH 报错那基本不是代码错误而是没装 contrib 包。另外如果项目里还需要处理视频流最终跑的时候大概率要接触 cv2.VideoCapture它依赖 FFmpegWindows 下如果提示找不到编码器重新装一次 opencv-python 或换用 opencv-python-headless 再配合 ffmpeg 来解决。环境通了之后再往前推进不然代码写再好也是白搭。3. 人脸检测Haar 级联的原理与最小实现3.1 Haar 特征为什么能搞定“找脸”这件事检测是一切的起点识别的前提是先知道框在哪里。openCV 自带的人脸检测器是基于 Haar 特征和 AdaBoost 训练的级联分类器它的核心思想不是直接看像素而是用若干个矩形模板计算图像的灰度差异。比如眼睛区域通常比其下方脸颊区域暗这个明暗关系就是一个特征鼻子中间区域比鼻梁两侧亮这又是一个特征。单个特征当然不能判断是不是人脸所以 AdaBoost 将大量弱分类器组合成强分类器再通过级联的结构逐级判断——前面的几层用很少的特征快速排除明显不是脸的区域只有通过前面若干层的区域才进入更精细的判断。这样做的好处是检测速度快在 CPU 上也能跑到实时。当然它也有局限对侧脸、低头、暗光下的脸容易漏检这个是 Haar 本身的弱点不是你的代码问题理解了这个才能在写报告的时候不心虚。3.2 用 cv2.CascadeClassifier 完成第一版检测openCV 官方已经在仓库里预训练好了几个级联分类器 XML 文件下面是一段能直接对图片文件运行的最小示例import cv2 # 读取图片转灰度 img cv2.imread(test.jpg) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 加载 openCV 自带的人脸检测模型 face_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) # 关键参数scaleFactor 每次缩放比例minNeighbors 最小邻域数 faces face_cascade.detectMultiScale( gray, scaleFactor1.1, minNeighbors5, minSize(30, 30) ) for (x, y, w, h) in faces: cv2.rectangle(img, (x, y), (x w, y h), (0, 255, 0), 2) cv2.imwrite(result.jpg, img) print(f检测到 {len(faces)} 张人脸)参数说明scaleFactor1.1表示图像每次缩小 10%数值越小检测越精细、运算越慢minNeighbors5表示一个候选区域至少被周围 5 个检测框命中才保留数值越大人脸漏检越多但误检越少。两者配合调整是后面避坑章节的重点。3.3 把检测从单张图片扩展到摄像头视频流课程作业如果要求实时识别那你还需要把死去活来的静态图像代码改成视频循环。视频流的逻辑和图片完全不同核心是cv2.VideoCapture循环读取帧、逐帧调用检测器的过程。常见做法是把检测封装为一个函数这样既方便在视频上复用也方便在之后的识别阶段把框坐标直接传给识别器。import cv2 face_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) # 读取视频 cap cv2.VideoCapture(0) if not cap.isOpened(): cap cv2.VideoCapture(input_video.mp4) while True: ret, frame cap.read() if not ret: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale(gray, scaleFactor1.2, minNeighbors5) for (x, y, w, h) in faces: cv2.rectangle(frame, (x, y), (x w, y h), (0, 0, 255), 2) cv2.imshow(Face Detection, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()从参数来说视频检测时scaleFactor1.2比图片的 1.1 稍大可以减少每帧的计算开销因为视频连续的帧之间人脸位置变化不大略微粗糙的采样不会太明显minSize(30, 30)可以过滤掉那些远小于预期人脸的区域防止把背景纹理当成人脸。这里还有一个不同电脑都会踩的坑在笔记本摄像头分辨率较高时直接对全分辨率做检测会很慢把帧先 resize 到宽度 640 再检测往往能提速一倍以上。4. 人脸识别落地LBPH 模型训练与识别流程4.1 为什么期末作业选 LBPH 而不是深度学习模型人脸检测只是画个框要“说出这是谁”才叫人脸识别。现在工业界都在用深度学习做人脸识别像 ArcFace、FaceNet 这些模型确实准确率极高但放到期末大作业的场景里它们存在一个致命问题需要大量样本来训练或者微调需要 GPU 或者云端算力很难在课程答辩时现场演示“我用自己的数据训练了一个模型”。LBPHLocal Binary Pattern Histogram局部二值模式直方图是 openCV 里唯一完整保存、训练和预测接口的传统识别器原理清晰、几十张图片就能训出一个可用的模型、单张识别跑在 CPU 上毫秒级完成这三点决定了它就是课程作业的“标准答案”。LBP 的原理简单说就是把每个像素与其周围像素比较生成一组二进制模式再统计为直方图。人的不同面孔在相同区域的纹理分布不一样因此直方图有区分度。观察邻居半径 r 和采样点数 p 对识别效果影响很大下面第 4.2 节的代码中两个参数要重点理解。4.2 构建自己的训练数据集文件夹与标签管理在训练模型之前要把人脸图片准备好并对齐裁剪。常见做法是建立dataset/目录其中每人一个子文件夹每个子文件夹内放置若干张不同角度、光照下的正脸照片。然后写一个脚本统一把检测到的人脸区域裁剪为灰度图并缩放到统一尺寸。import os import cv2 face_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) # 数据集根目录子目录名即人员 ID data_root dataset os.makedirs(data_root, exist_okTrue) for person_id, name in enumerate([Alice, Bob]): person_dir os.path.join(data_root, str(person_id)) os.makedirs(person_dir, exist_okTrue) # 每个 person 的原始照片放在 raw 目录下按 0.jpg, 1.jpg 命名 raw_dir fraw/{name} for i, img_name in enumerate(os.listdir(raw_dir)): img cv2.imread(os.path.join(raw_dir, img_name)) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale(gray, scaleFactor1.1, minNeighbors5) for (x, y, w, h) in faces: # 只保留人脸区域并固定尺寸不做归一化 face_roi gray[y:yh, x:xw] face_resized cv2.resize(face_roi, (200, 200)) cv2.imwrite(os.path.join(person_dir, f{i}.jpg), face_resized)这段脚本的逻辑非常直白每个子目录的编号 0、1 就是标签读训练图片、用上一章的检测器定位人脸、裁剪出来统一尺寸。标签管理是这里最容易翻车的地方——训练时读文件用的 ID 必须和识别时返回的 ID 一一对应一旦中间有目录顺序变动模型就会把张三认成李四。所以在保存训练图片时建议顺手维护一个名字映射表如 JSON 文件而不是依赖 enumerate 的顺序。4.3 训练 LBPH 识别器并验证准确率数据准备好之后训练代码短得让人怀疑。以下是一个完整的训练加验证样例import cv2 import os import numpy as np # 初始化 LBPH 识别器 recognizer cv2.face.LBPHFaceRecognizer_create() # 设置 LBP 半径与邻域点数 recognizer.setRadius(1) recognizer.setNeighbors(8) recognizer.setGridX(8) recognizer.setGridY(8) faces [] labels [] for person_id in os.listdir(dataset): person_path os.path.join(dataset, person_id) for img_name in os.listdir(person_path): img_path os.path.join(person_path, img_name) gray_img cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) faces.append(gray_img) labels.append(int(person_id)) recognizer.train(faces, np.array(labels)) recognizer.save(face_model.yml) print(f训练完成共 {len(faces)} 张样本)参数说明setRadius(1)表示比较中心像素与半径 1 的邻域像素setNeighbors(8)表示取周围 8 个点采样。这两个值组合其实就是最经典的 LBP 算子识别效果稳定且计算量小。setGridX(8)和setGridY(8)表示把图像划分成 8×8 的网格每个网格单独统计直方图再拼接——网格越细空间信息保留越多但对对齐要求更高网格太粗则容易丢失局部特征。用 200×200 的图像配合 8×8 网格是朴素的 gold setting。验证阶段用另一组没有参与训练的照片作为测试集调用predict()得到 ID 和置信度。置信度越低代表匹配程度越高这个反直觉逻辑很多人第一次都会弄错写报告时一定要标注清楚。test_img cv2.imread(test_alice.jpg, cv2.IMREAD_GRAYSCALE) test_img cv2.resize(test_img, (200, 200)) label, confidence recognizer.predict(test_img) print(f识别结果label{label}, confidence{confidence}) # LBPH 的置信度阈值 —— 越低越可信 if confidence 80: print(这是已注册的人) else: print(未识别或置信度过低)4.4 级联把检测框送入识别器的完整流程训练和验证没问题后最后一步就是把整个流程拼成一个逻辑闭环。先检测人脸位置然后把人脸区域裁剪缩放再丢给识别器 predict。注意每个关键帧只需对检测框区域内做预测而不是对整个画面做否则会把背景也当成脸来分析。import cv2 face_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) recognizer cv2.face.LBPHFaceRecognizer_create() recognizer.read(face_model.yml) cap cv2.VideoCapture(0) while True: ret, frame cap.read() if not ret: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale(gray, scaleFactor1.2, minNeighbors6) for (x, y, w, h) in faces: roi cv2.resize(gray[y:yh, x:xw], (200, 200)) label, confidence recognizer.predict(roi) if confidence 70: cv2.putText(frame, fUser {label}, (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) else: cv2.putText(frame, Unknown, (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2) cv2.rectangle(frame, (x, y), (xw, yh), (0, 255, 0), 2) cv2.imshow(Face Recognition, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()代码逻辑非常简单但要注意一个高频翻车点识别器的read()方法和load()方法并不完全等价。read()需要传入一个已经创建的 LBPH 实例而load()会重建实例。很多人把load()和 CV2 其他 API 混用导致模型读入失败却不报错最后识别结果全是乱码。5. 常见问题避坑环境、检测识别、答辩现场三大类5.1 环境类modulenotfounderror 与版本错乱现象一pip install opencv-python后import cv2报ModuleNotFoundError: No module named cv2。原因最常见的是 pip 安装到了系统的 Python 而不是当前激活的虚拟环境中。比如 Windows 上 Aanaconda 的 base 环境已经有一个 openCV 4.5.5你又用sudo pip装到系统 Python 3.8一旦在 Jupyter Notebook 里跑import cv2内核用的可能是 conda Python找不到包。解决统一在项目虚拟环境内操作终端先确认which python或python --version的路径与你使用的解释器一致如果你只用命令行跑脚本那就把pip换成python -m pip如下python -m pip install opencv-python opencv-contrib-python现象二cv2.error: OpenCV(4.4.0) ...这类编译错误通常出现在安装时用pip install opencv-python又混用 conda 的 opencv导致cv2库文件版本冲突。原因包管理器的二进制冲突conda 的 opencv 和 pip 的 opencv 链接到不同的 DLL/so。解决把两个源的 opencv 都卸载干净只保留 pip 源或只保留 conda 源pip uninstall opencv-python opencv-contrib-python conda remove opencv -y # 然后重新安装你选定的一路 pip install opencv-python opencv-contrib-python5.2 检测类检测不到脸或框得不准现象三detectMultiScale返回空列表明明照片里有一张很清楚的人脸。原因灰度化之后对比度太低或者人脸小于minSize另一个常见原因是输入的图片尺寸太大检测窗口扫描时跳过了边缘。解决先检查灰度图质量用 CLAHE 增强对比度把minSize调整为(50, 50)甚至更小把图片 resize 到宽度 800 左右再检测。务必打印调试信息——把检测到的人脸区域保存成 JPEG 看一次你就知道问题是预处理还是检测参数。5.3 识别类置信度阈值与跨平台模型路径现象四模型训练正常predict()返回的置信度超过 100几乎无法匹配任何人哪怕本人出现在镜头里也报 Unknown。原因LBPH 的置信度是一个距离值它对光照、对齐极度敏感。如果训练样本是在较好光照下用 200×200 的人脸区域而出现在镜头里时人脸是侧脸或者戴了眼镜直方图差异会被放大distance 随之飙升。解决最直接的改善方向是让训练阶段的数据涵盖戴眼镜、不同光照、微侧脸的情况。如果做不到就把模型判别的confidence阈值放宽到 120。不要等答辩那天才临时调阈值要在真实环境下拿视频流跑一遍记录正常匹配的典型置信度范围再确定阈值这是最可靠的玄学破除方法。现象五模型文件放在本地没问题提交给老师的.zip包解压后运行时提示找不到face_model.yml。原因代码里用了相对路径而别人解压后当前工作目录变了。解决代码中用脚本文件所在目录来定位模型路径而不是简单的纯相对的路径import os import cv2 base_dir os.path.dirname(os.path.abspath(__file__)) model_path os.path.join(base_dir, face_model.yml) recognizer.read(model_path)5.4 答辩级避坑演示时摄像头人脸框抖动现象六实时识别时人脸框在视频流里上下跳动识别结果时而 A 时而 Unknown。原因视频帧之间检测框本身就有较大波动直接拿每一帧的检测结果预测不稳定是必然的。解决不要每帧都调用predict而是每 5 帧预测一次在做识别前先把当前帧的人脸区域与上一帧的检测区域做位置平滑这里说的平滑就是用上一次的检测框限定了当前搜索范围。这是最符合期末答辩形态的优化手段也是可以写进报告“系统优化”一节的内容之一。6. 把作业升级保存模型、连续帧识别与答辩演示技巧识别器训练好之后你会发现它保存的是一个.yml文件。这里有一个很值得做的小优化把训练和识别拆成两个脚本训练脚本只用跑一次识别脚本负责加载模型、读摄像头流。这样做有两个好处一是答辩现场如果摄像头断流或环境变了不需要重跑训练二是在验收时可以直接打开识别脚本展示实时的识别效果代码路径清晰不会在老师面前手忙脚乱。另一个实战技巧是要学会在识别脚本里加入“最近 N 帧结果投票”的逻辑而不是单单信单帧的识别标签。常见做法是维护一个长度为 5 的队列里面存最近 5 帧的识别标签和置信度每帧把新的识别结果加进来然后统计队列中哪个标签出现次数最多就输出哪个标签。这个策略能显著抑制因为光线闪烁导致的识别抖动而且队列的代码只有约 10 行from collections import deque # 每帧识别后执行 label_queue.append((label, confidence)) if len(label_queue) 5: label_queue.popleft() labels [item[0] for item in label_queue] # 选出出现次数最多的标签作为最终结果 final_label max(set(labels), keylabels.count)选好置信度阈值同样影响最后呈现效果。如果置信度比较阈值过长比如允许 200 以下都通过那脸部和背景不够接近的人会频繁误判如果太严格比如小于 50那么真实用户也会被拒因为光照稍微变暗就会使 LBPH 的 distance 暴涨。我个人的经验是训练完成后拍一段 10 秒视频统计正常识别时置信度的平均值然后把这个平均值乘 1.2 作为阈值比自己拍脑袋设 70 靠谱得多。最后很多人在答辩前才想起要“界面效果好”其实完全不必做图形界面。一个直接展示摄像头窗口的程序配上实时可用的 “Authenticated” 或 “Unknown” 标签已经足够体现核心能力。如果这是有加分的课程展示可以在识别窗口上叠加一个遮罩层把自己想显示的名字和置信度信息用cv2.putText打在画面右上角。我觉得做这个作业最有价值的环节不是把模型训练到 99% 精度而是完整地意识到“看脸”和“认人”是两个解法检测负责缩小范围识别负责决策身份边界清晰代码独立。写完这份作业你以后再看到门禁机上的人脸识别至少不会认为它是把两张图直接比较一下像素那么简单了。那次交作业的前一天我把模型文件不小心覆盖了只能重新收集数据、重新训练。从那次以后我养成了一个习惯——训练完立刻备份模型到项目根目录之外总觉得这是这学期最值的一次血泪教训。希望帮到你。本文还有配套的精品资源点击获取
返回列表