新闻详情

新闻详情

首页 / 资讯中心 / 详情

基于OpenCV与Python的LBPH人脸识别门禁系统实战详解

发布时间:2026/9/12 14:39:23来源:尧图网络
基于OpenCV与Python的LBPH人脸识别门禁系统实战详解
简介这套基于Python与OpenCV的人脸识别门禁系统面向计算机视觉入门者及小型安防项目开发者借助LBPH算法实现人脸检测、训练与识别当相似度超过70%时判定识别成功可应用于门禁、考勤等场景。资源包共49个文件大小约987KB包含4个Python脚本界面、预测、数据采集与测试、30个PGM灰度人脸样本、用于人脸/眼睛检测的Haar级联XML配置以及PNG/JPG/BMP图片素材、README说明和编译生成的pyc文件等目录结构清晰。目前已有199人学习下载适合作为人脸识别项目的入门参考。通过阅读代码和运行脚本读者可以了解OpenCV的LBPH人脸识别流程、Haar级联检测器的用法及数据采集方式同时内置的GUI界面与训练好的模型文件也便于直接体验或在此基础上扩展。整个项目代码量适中既有数据预处理和模型保存也有实时预测与界面控制是一份可直接运行的完整示例。1. 从LBPH到门禁一个不需要GPU的人脸识别方案人脸识别门禁这些年被互联网大厂包装得很玄但很多实际落地的小型门禁系统里跑的还是传统方法。这个基于OpenCV和Python的人脸识别门禁系统核心就是一个LBPH分类器外加两个Haar级联XML训练一次在PC上不到一分钟识别时实时性也够用。项目定了条规则相似度达到70%以上才放行这个阈值不是随便拍的它直接对应LBPH预测时返回的置信度换算。对于实验楼门禁、课堂考勤、毕设改造这些场景这套方案的性价比比深度学习高得多几十张样本就能训练代码量很少参数都能直接看到。下面我会把这个项目的文件结构、数据流和关键参数完全拆开顺带把安装OpenCV时会踩的face模块缺失的坑交代清楚。2. LBPH算法原理与OpenCV人脸检测环境2.1 LBPH怎么把一张脸变成可以比较的向量LBPH全称Local Binary Patterns Histograms也就是局部二值模式直方图。它的思路比较直观对图像里每个像素以它为中心取一个半径为radius的圆在这个圆上均匀采样neighbors个点把每个采样点和中心像素比大小大于中心记1否则记0按顺序排成一串二进制数。这样每个像素就得到了一个LBP编码值。对人脸区域做这样的处理后把整个图像切分成grid_x * grid_y个格子在每个格子里统计LBP值的直方图再把这些直方图拼接起来就是一张脸的“特征向量”。这也就是为什么它和EigenFace、FisherFace不一样。EigenFace用的是PCA降维本质是找训练样本方差最大的方向FisherFace用的是LDA要利用类别信息做类间类内判别。这两种方法都很容易受光照和表情变化影响LBPH则是对局部纹理编码对光照变化有一定的天然鲁棒性而且在只有少量训练样本时表现比PCA和LDA更稳。项目里同时用到两个Haar级联分类器haarcascade_frontalface_alt2.xml用来定位人脸haarcascade_eye.xml用来做眼睛检测眼睛检测的目的很简单确认检测到的是真实人脸降低手机照片假脸误判的风险。加载级联分类器是第一个关键步骤打开项目里的face_predict.py最常见的就是这两行face_cascade cv2.CascadeClassifier(haarcascade_frontalface_alt2.xml) eye_cascade cv2.CascadeClassifier(haarcascade_eye.xml)这里用的是相对路径所以分类器xml文件必须跟脚本放在同一级目录。如果从别处下载xml文件要注意OpenCV默认自带的Haar分类器在opencv/data/haarcascades目录下把对应文件拷到项目根目录就能用。alt2版本比默认版本误检稍少但速度慢一点点对门禁这种固定摄像头场景是合适的。2.2 安装OpenCV时最容易踩的face模块缺失很多人在这个项目上卡住不是因为代码而是因为ModuleNotFoundError: No module named cv2或者更隐蔽的AttributeError: module cv2 has no attribute face。后者几乎都是因为只装了opencv-python这个包不包含人脸识别模块LBPH的创建函数cv2.face.LBPHFaceRecognizer_create在opencv-contrib-python里。所以安装命令要同时包含两个包pip install opencv-python opencv-contrib-python装完之后验证一下face模块是否存在python -c import cv2; print(cv2.__version__); print(cv2.face.LBPHFaceRecognizer_create)如果能打印出版本号和built-in function LBPHFaceRecognizer_create说明环境没问题。需要注意的是如果之前已经装过opencv-python再装opencv-contrib-python时不要卸载旧的两者可以共存pip会自动覆盖成contrib版本。另外建议用Python 3.6以上环境虽然这个项目源码里出现了.pyc后缀是cpython-35的编译文件说明作者当时用的是Python 3.5但在现代Python上运行逻辑完全一样不必刻意降版本。2.3 LBPH关键参数与置信度含义cv2.face.LBPHFaceRecognizer_create可以接受一组参数默认值通常够用但要想把门禁系统调到“正好能识别又不能乱开门”必须理解这几个参数的物理意义。我整理了常用参数表参数默认值作用经验取值范围radius1LBP采样圆的半径半径越大覆盖范围越广1 或 2太大会丢失局部细节neighbors8采样点数量直接影响特征维度和计算量8 是常用值增加到12会变慢grid_x8水平分块数8 或 6分块越多空间信息越强grid_y8垂直分块数8 或 6thresholdDBL_MAX预测时的最大距离阈值手动设置后超过阈值返回label-1创建识别器并读取训练好的模型文件项目里叫trainer.yml核心代码是这样的import cv2 recognizer cv2.face.LBPHFaceRecognizer_create( radius1, neighbors8, grid_x8, grid_y8 ) recognizer.read(trainer.yml)read会把训练阶段写出的模型加载进来。注意threshold这个变量在训练时没有暴露一般在创建识别器对象后通过recognizer.setThreshold(50)设置。也就是说如果你想依赖OpenCV内置的“不认识就返回-1”机制一定要显式调setThreshold。如果你不想提前设死阈值就在预测后自己拿距离值判断这也是这个门禁项目里“70%相似度”做法的前提直接分析返回的距离而不是盲信label。大概说明一下predict的返回值。调用recognizer.predict(gray_face)时返回一个二元组(label, distance)label是训练时传入的标签distance是当前人脸特征向量与训练集中最接近样本之间的距离。这个距离越大代表越不像。如果你设置了threshold距离超过它时label会被OpenCV置为-1表示未知人如果没有设置label永远是某个训练标签所以你必须在业务层手动判断距离是否可信。后文会给出具体的相似度换算方式。3. 人脸样本采集与LBPH模型训练3.1 数据集目录与master.txt的设计训练LBPH模型不需要单独的CSV或数据库文件系统本身就可以当数据表。这个项目里face_data目录承担了这个角色通常组织成下面这种结构face_data/ user_1/ 0.jpg 1.jpg ... user_2/ 0.jpg ...目录名里的数字就是训练标签也就是模型预测返回的label。这么做的好处是通过os.listdir或glob按目录读取时标签可以通过路径解析出来不会错位。项目里的master.txt是一份“标签和姓名”的映射表常见格式是每行一个用户编号和姓名用空格或Tab分隔1 zhang_san 2 li_si训练阶段只需要用到编号但推理阶段识别到label后必须查这份表才能知道具体是谁。所以读取master.txt的逻辑要写在实时识别模块里作为门禁系统展示名称和记录日志的来源。3.2 用摄像头采集人脸样本没有现成数据集时最直接的做法就是用本地摄像头自己拍。采集脚本的核心工作打开摄像头检测人脸把灰度图中的人脸区域裁剪出来resize到统一尺寸按标签保存到对应目录。下面这段代码基本是这个项目face_date_save.py或face_test.py里的逻辑import cv2 import os face_cascade cv2.CascadeClassifier(haarcascade_frontalface_alt2.xml) cap cv2.VideoCapture(0) user_id input(输入用户编号: ) out_dir fface_data/user_{user_id} os.makedirs(out_dir, exist_okTrue) count 0 while count 50: ret, frame cap.read() if not ret: continue 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 gray[y:yh, x:xw] face cv2.resize(face, (200, 200)) cv2.imwrite(f{out_dir}/{count}.jpg, face) cv2.rectangle(frame, (x, y), (xw, yh), (0, 255, 0), 2) count 1 cv2.imshow(capture, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这段代码里有几个参数值得说明。scaleFactor1.1表示每次检测窗口按10%比例缩小数值越接近1检测越细但速度越慢minNeighbors5表示每个候选区域必须至少被5次检测命中才认为是人脸数值调大能减少误检但设成8或10以后人脸略侧一点就会漏检。minSize(100, 100)用来过滤掉太小的人脸在门禁摄像头距离固定的场景下非常有意义可以避免远处路人进入检测范围造成干扰。注意我把保存的人脸统一resize到了200×200。LBPH对输入尺寸不敏感因为提取的是分块直方图但统一尺寸可以保证训练和预测的特征维度一致。这里我一般会多绕一道不要直接把detectMultiScale裁剪出的区域存起来而是再检查一下这个区域里是否检测到眼睛比如用项目里的haarcascade_eye.xml再跑一次这样保存下来的样本基本都是正脸。你可以把眼睛检测作为兜底不用强制否则带眼镜时容易丢掉样本。3.3 训练LBPH分类器并保存trainer.yml样本采集完成后训练代码在face_predict.py或单独的训练脚本里。思路就是遍历所有user_*目录解析标签读图重新做人脸检测把裁剪后的人脸收集到列表里最后调用recognizer.train。我对项目里这类代码做了一点整理import cv2 import glob import os import numpy as np recognizer cv2.face.LBPHFaceRecognizer_create( radius1, neighbors8, grid_x8, grid_y8 ) face_cascade cv2.CascadeClassifier(haarcascade_frontalface_alt2.xml) faces [] labels [] for path in glob.glob(face_data/user_*/*.jpg): label int(path.split(os.sep)[-2].split(_)[1]) img cv2.imread(path, cv2.IMREAD_GRAYSCALE) dets face_cascade.detectMultiScale( img, scaleFactor1.1, minNeighbors5, minSize(80, 80) ) if len(dets) ! 1: continue x, y, w, h dets[0] face img[y:yh, x:xw] face cv2.resize(face, (200, 200)) faces.append(face) labels.append(label) labels np.array(labels, dtypenp.int32) recognizer.train(faces, labels) recognizer.write(trainer.yml) print(ftrain done, {len(faces)} samples)训练前再检测一次人脸的原因很简单手动截取的图片里可能混入模糊帧、半张脸甚至背景。如果直接把噪声喂给模型相当于在特征空间里导入了脏数据测试时反而更不准。这里labels必须转成np.int32否则OpenCV某些版本会报类型错误。训练完成后生成的trainer.yml就是可部署的模型文件后续识别程序直接recognizer.read(trainer.yml)读取不用每次启动都训练一遍。如果训练样本特别少比如每个用户只有10张图可以在采集时让用户左右转动头部各一次让样本里包含一些非正脸角度。LBPH对小幅度的姿态变化还可以容忍但夸张的侧脸即使能检测到眼睛识别率也会掉得很快。常见的做法是把采集数量提到50张以上其中正脸占七成左右偏转各占15%这样训出来的模型在门禁场景下才比较稳定。4. 实时人脸识别与70%相似度门禁判定4.1 从摄像头帧到预测结果实时识别的流程和采集非常像区别在于每一帧都要跑一次检测和预测。打开摄像头对每一帧灰度化、人脸检测把人脸resize成训练时一致的200×200然后调用recognizer.predict。这个循环在face_ui.py里运行核心逻辑如下import cv2 recognizer cv2.face.LBPHFaceRecognizer_create() recognizer.read(trainer.yml) def load_name_map(): mapping {} with open(master.txt, r, encodingutf-8) as f: for line in f: line line.strip() if not line: continue uid, name line.split() mapping[int(uid)] name return mapping name_map load_name_map() cap cv2.VideoCapture(0) while True: ret, frame cap.read() if not ret: continue 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 cv2.resize(gray[y:yh, x:xw], (200, 200)) label, distance recognizer.predict(face) print(flabel{label}, distance{distance}) cv2.imshow(attention, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这里predict接收的是一个二维灰度图不要传彩色图否则OpenCV会报维度错误或者识别结果极不稳定。label是训练标签distance在前面讲过是距离值距离越小越像。在实际门禁逻辑中光判断“是不是某个人”还不够还需要决定“是不是足够像”这就引出了70%相似度阈值的换算。4.2 相似度换算与阈值比较OpenCV的LBPH返回的是距离而不是相似度所以项目里说的“达到百分之七十以上的相似度”需要做一层映射。常见做法是把距离限制在某个区间然后定义为similarity max(0, 100 - distance)。这个换算并不严谨但足够直观而且对LBPH的距离分布通常是适用的因为训练和预测用的是相同尺寸、相同光照条件时距离大多落在0100这个区间。判定代码可以写成这样SIMILARITY_THRESHOLD 70 MIN_DISTANCE 50 # 可选距离过大直接当陌生人 similarity max(0, 100 - distance) if label 0 and similarity SIMILARITY_THRESHOLD: name name_map.get(label, unknown) print(f识别成功: {name}, 相似度: {similarity:.2f}) # 在这里触发开门动作 else: print(f拒绝通行, 相似度: {similarity:.2f})注意一个容易混淆的点如果创建识别器时没有调用setThresholdpredict返回的label一定是训练标签中的某个值即使输入的是完全陌生的人脸它也会返回一个“最像”的标签。所以label 0这个判断在默认情况下永远为真真正决定放不放行的是similarity是否达到阈值。另一种写法是用recognizer.setThreshold(30)这样如果距离超过30predict会直接返回label-1然后在外面判断label -1即可。两种方式效果等价但手动算相似度更直观也方便做多阈值控制。4.3 门禁事件记录与人机界面项目中的face_date_save.py并不是保存人脸数据而是负责把每次识别成功的时间、人员信息写入日志实现解锁记录。真正接入门禁硬件时通常是树莓派或者工控机通过GPIO控制继电器把电锁的电源短暂接通。在纯PC测试环境里可以先用日志代替电磁锁动作比如from datetime import datetime def record_and_unlock(name): with open(door.log, a, encodingutf-8) as f: f.write(f{datetime.now().strftime(%Y-%m-%d %H:%M:%S)} {name} 通过\n) # 以下为硬件控制占位 # gpio.set_value(gpio_pin, 0) # 开锁face_ui.py里一般会做一个简单的桌面界面显示摄像头画面、鉴权结果、通行记录。Tkinter方案比较省事可以在after回调里不断刷新画面帧同时把识别状态渲染到Label上。这里不多贴完整UI代码只说一个关键点不要在UI线程的主循环里做predict否则摄像头帧率会降到每秒一两帧。把检测和预测放到后台线程把识别结果通过队列传给UI线程去更新这样界面才能保持流畅。5. 相似度阈值调优与多帧投票技巧这套LBPH系统百分之八十的问题都出在阈值配置上。70%相似度只是一个起点不同摄像头、不同光照强度下同一批人的距离分布会整体漂移。我一般的做法是采集测试者正脸连续视频100帧记录每帧的similarity再采集几个陌生人同样记录然后看两组分布。如果陌生人最大相似度是60注册用户最小相似度是75把阈值设在70就很稳如果两组重叠严重说明光照或样本量有问题调阈值救不回来。场景建议相似度阈值备注室内固定光源6070用户配合度低取60室外或窗户边7585光照变化大距离波动大高安全等级通道85以上需要用户主动面向镜头调试时可以把distance连同label一起输出到控制台观察同一个人多次识别的波动范围。不要每次都打印所有帧打印连续识别中的波动值即可。LBPH有一个明显弱点偶尔某帧因为遮挡或运动模糊距离会突然跳高单帧判断就会误拒。项目里没有做状态冗余实际应用时需要加一个多帧投票机制。处理办法是连续取5帧每帧都预测一次只统计相似度达到阈值的标签超过3帧命中同一标签才开门import time def get_stable_prediction(recognizer, face_cascade, cap, frames5): votes {} for _ in range(frames): _, frame cap.read() gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale(gray, 1.1, 5, minSize(100, 100)) if len(faces) ! 1: continue x, y, w, h faces[0] face cv2.resize(gray[y:yh, x:xw], (200, 200)) label, distance recognizer.predict(face) similarity max(0, 100 - distance) if similarity SIMILARITY_THRESHOLD: votes[label] votes.get(label, 0) 1 time.sleep(0.03) if not votes: return -1, 0 final_label max(votes, keyvotes.get) return final_label, votes[final_label]把这段逻辑直接替换原来的单帧判断识别成功率会有肉眼可见的提升。多帧投票的本质是用时间换稳定性对门禁场景很划算因为用户一般会站在摄像头前1秒以上。最后留一个建议如果这套系统要长时间挂着跑建议在每个用户样本集里加入几张横向翻转的图片再用cv2.equalizeHist对灰度图做直方图均衡化这能把LBPH在不同亮度下的距离波动压下来。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Spring AI工具配置详解:全局与动态调用实践 2026/9/12 15:15:27

Spring AI工具配置详解:全局与动态调用实践

1. Spring AI工具配置概述Spring AI 1.x版本的工具调用机制提供了灵活的方式来扩展AI模型的能力。工具配置主要分为两种模式:全局默认配置和运行时动态配置。全局默认工具适用于整个应用生命周期中需要频繁使用的功能,而运行时工具则针对特定请求临时生效…

阅读更多 →
ESLint 配置调试实战指南:使用 --debug、--print-config 与 Config Inspector 定位配置问题 2026/9/12 15:15:27

ESLint 配置调试实战指南:使用 --debug、--print-config 与 Config Inspector 定位配置问题

ESLint 配置调试实战指南:使用 --debug、--print-config 与 Config Inspector 定位配置问题 【免费下载链接】eslint Find and fix problems in your JavaScript code. 项目地址: https://gitcode.com/GitHub_Trending/es/eslint ESLint 会为每个被检查的文件…

阅读更多 →
WezTerm 文本闪烁缓动配置指南:深入理解 `text_blink_ease_in` 与淡入淡出动画 2026/9/12 15:15:27

WezTerm 文本闪烁缓动配置指南:深入理解 `text_blink_ease_in` 与淡入淡出动画

WezTerm 文本闪烁缓动配置指南:深入理解 text_blink_ease_in 与淡入淡出动画 【免费下载链接】wezterm A GPU-accelerated cross-platform terminal emulator and multiplexer written by wez and implemented in Rust 项目地址: https://gitcode.com/GitHub_Tren…

阅读更多 →
Cilium `cilium-dbg bpf policy add` 命令详解:直接写入 BPF Policy 映射的策略注入指南 2026/9/12 15:15:27

Cilium `cilium-dbg bpf policy add` 命令详解:直接写入 BPF Policy 映射的策略注入指南

Cilium cilium-dbg bpf policy add 命令详解:直接写入 BPF Policy 映射的策略注入指南 【免费下载链接】cilium eBPF-based Networking, Security, and Observability 项目地址: https://gitcode.com/GitHub_Trending/ci/cilium 导读 cilium-dbg bpf policy…

阅读更多 →
Turso 的 SQLite 兼容性全解析:版本基线、支持矩阵与迁移路径 2026/9/12 15:15:27

Turso 的 SQLite 兼容性全解析:版本基线、支持矩阵与迁移路径

Turso 的 SQLite 兼容性全解析:版本基线、支持矩阵与迁移路径 【免费下载链接】turso A SQL database in Rust: SQLite-compatible, now also speaking Postgres (experimental). The LLVM of databases. 项目地址: https://gitcode.com/GitHub_Trending/tu/turso…

阅读更多 →
SSM+Vue家具商城毕业设计实战:从数据库到部署答辩全流程 2026/9/12 15:12:27

SSM+Vue家具商城毕业设计实战:从数据库到部署答辩全流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞