ARTICLE DETAIL

资讯详情

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

Python+MySQL人脸识别门禁系统源码详解:从数据库设计到阈值校准

Python+MySQL人脸识别门禁系统源码详解:从数据库设计到阈值校准 简介一套基于Python的人脸识别智能化小区门禁管理系统源码面向物联网、人工智能及安防系统方向的开发者与学生适用于小区、园区等场景的出入身份验证与门禁自动化管理。资源共101个文件、压缩包约12.17MB主要包含py源码、pyc编译文件、qt编写的ui界面、sql数据库脚本、onnx人脸模型、jpg/png示例图片、txt及md说明文档等目录划分清晰兼顾前端采集、后端识别与数据存储。系统涵盖数据采集、人脸识别、MySQL数据管理、用户界面和访问控制五大模块代码结构与业务逻辑完整便于二次开发或作为毕业设计参考。目前已有36人学习资料开放仅用于学习交流使用时应遵守开源协议与隐私保护要求不得用于商业用途。整体方案将OpenCV/TensorFlow人脸特征比对与MySQL记录管理结合能够直观呈现智能化门禁的实现路径。1. 门禁系统翻车点不在算法Python人脸识别项目的价值怎么判断小区门口装了人脸识别门禁业主群反而更闹腾了张三刷脸屏幕却弹出李四的名字住户换个发型就被拒之门外。拉开差距的往往不是“认不认得出人”这一步而是从人像采集、特征比对到MySQL写入这一整条数据流水线。这套基于Python和MySQL的人脸识别智能化小区门禁管理系统源码正好把这条流水线完整摆在你面前覆盖人像采集、人脸检测与特征比对、用户信息与出入记录管理、门禁放行控制。适合三类人做毕业设计或课程设计、需要快速跑通一套可演示门禁系统的学生想搞清楚人脸识别和数据库怎么协作的Python初学者以及准备做方案选型验证、想先复现一套原型判断可行性的工程师。2. 先看懂数据流再动手OpenCV的活和MySQL的活怎么分门禁系统的架构从逻辑上说是前后端分离的前端摄像头抓人脸后端负责解码、比对、落库。无论界面是PyQt还是控制台简化版这条链路都不会变。我一般把识别链路拆成四步人脸检测、人脸对齐、特征编码、距离比对。每一步都有对应的Python库选型时别贪多够用就行。2.1 人脸识别主干检测、对齐、编码、比对的职责拆解人脸检测解决的是“脸在哪里”的问题通常用OpenCV的Haar级联或者dlib的HOG检测器检测到人脸之后下一步是对齐也就是把歪头、侧脸、高低角度统一到同一坐标系这一步直接影响后面的特征编码质量编码做的事情是把人脸压缩成一个定长向量最后拿这个向量和库里的所有注册向量算距离距离小于阈值就放行。这套源码里最核心的比对逻辑跑不出下面这段代码的逻辑import face_recognition # 读取注册照片要求是单人正面照 image face_recognition.load_image_file(register/zhangsan.jpg) faces face_recognition.face_encodings(image) if len(faces) 1: face_encoding faces[0] print(特征维度:, len(face_encoding)) # 128 维 else: print(检测到人脸数量:, len(faces))逻辑说明load_image_file返回的是RGB格式的图像数组face_encodings内部已经串联了检测、对齐和编码三步不需要自己再调模型。faces[0]取出的是第一张人脸的128维特征向量这一步就是后面MySQL里要存的关键数据。参数说明len(faces)一定要校验。注册照片如果是一张多人合照faces里会有多个特征向量直接取[0]会把别人的脸注册到你名下这个坑在后面的避坑章里会专门展开。比对阶段的核心逻辑类似这样import numpy as np import face_recognition # known_encodings 是从数据库读出的所有注册用户特征向量 # frame_encodings 是当前摄像头抓拍到的人脸特征 distances face_recognition.face_distance(known_encodings, frame_encodings[0]) print(与最近注册脸的欧氏距离:, round(float(np.min(distances)), 3))逻辑说明face_distance计算的是欧氏距离不是相似度。数值越小表示越像默认阈值0.6小于0.6判定为同一人。这个反直觉点特别容易踩坑——很多人把阈值调低以为“更安全”实际上是把通过标准卡死了。2.2 MySQL数据模型用户表与出入记录表怎么设计人脸特征往哪里存是这类系统第一个要做的设计决策。常见的做法是两种一种是不存特征每次启动把所有用户照片重新跑一遍编码注册人数少的时候无所谓人一多启动时间成倍上涨另一种是把128维向量序列化后存进MySQL的BLOB字段启动时一次性读出。门禁系统追求快速启动我见过的大多数可运行实现都走第二种方案。核心表结构通常围绕两类数据注册用户信息和出入记录。下面是一个可以直接落库的建表方案CREATE DATABASE door_system DEFAULT CHARACTER SET utf8mb4; USE door_system; CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(64) NOT NULL, photo_path VARCHAR(255) NOT NULL, face_feature BLOB NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE access_log ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NULL, result TINYINT NOT NULL COMMENT 1放行 0拒绝, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_time (created_at), CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;逻辑说明face_feature字段存储的是float32的128维向量序列化后的字节串128个float32约512字节一千个注册用户也只有半MB左右内存占用完全可接受。access_log里的user_id用外键关联到users表保证日志记录不产生“孤儿数据”。参数说明两个关键点。第一DEFAULT CHARACTER SET utf8mb4必须写在建库语句里否则遇到中文姓名就是乱码第二access_log的idx_time索引是给“查询某个时间段出入记录”准备的门禁管理后台最常见的操作就是看“今天谁几点进的”这个索引能省不少查询时间。2.3 目录结构与样本文件对照哪些是代码哪些是“坑源”拿到压缩包之后先别急着运行对着文件清单过一遍能省掉后面一大半排查时间。这套源码包里的文件可以分成几类文件名称特征在系统中的可能角色README.md项目说明环境依赖、运行步骤、作者说明需求(5).docx需求文档系统功能与模块设计说明002.jpg / 005.jpg / 007.jpg / 008.jpg数字编号历史测试图片或采集样本zs123.jpg / lisi1234.jpg账号拼音编号用户注册照片寮犱笁.jpg乱码文件名GBK编码的“张三.jpg”被错误解码后的产物这里最值得注意的就是“寮犱笁.jpg”这个文件。它的本质是“张三.jpg”在GBK编码下写入又被UTF-8编码的环境读取产生了乱码文件名。这说明原作者的工作环境是GBK编码的Windows系统而大多数现代Python环境和MySQL默认是UTF-8。拿到源码后如果不先处理这个文件名的编码问题后续导入数据库时中文姓名会一路乱到底。文件功能看清之后复现顺序就清晰了先建库建表再整理注册照片目录并规范文件名然后跑特征编码脚本把BLOB字段填上最后启动主循环接摄像头。下面一章按这个顺序逐段过。3. 从空MySQL到门禁跑通环境、建库与主循环逐段复现复现这套源码最怕的不是逻辑看不懂而是环境装到一半卡住、数据库连接串写错、摄像头调不起来。这一章按我实际复现的顺序来每一步都给出能直接跑的命令和代码同时说清楚每条参数背后的理由。3.1 环境安装Python版本、依赖包与两个容易失败的包拿到源码先别急着执行pip install -r requirements.txt因为这个包不一定带了完整的依赖清单。更稳妥的做法是先建虚拟环境再按顺序装依赖。人脸识别相关的库对版本非常敏感特别是face_recognition会连带安装dlibdlib需要本地有完整的C编译工具链没有工具链时源码编译几乎必失败。python -m venv venv source venv/bin/activate # Windows 下用 venv\Scripts\activate pip install --upgrade pip pip install opencv-python numpy pymysql pip install face-recognition逻辑说明先装OpenCV、numpy和pymysql最后再装face-recognition这样如果dlib编译失败前面的基础依赖已经就位排查范围更小。pymysql是纯Python实现的MySQL客户端比mysql-connector-python轻量对这种单机演示系统足够。参数说明Python版本建议选3.8到3.10之间不要一上来就追最新的3.12或3.13。dlib这类带C扩展的库对Python版本很敏感新版本刚发布时通常还没有对应的预编译wheel。如果编译dlib实在折腾不动常见的替代做法是conda install -c conda-forge dlibconda会直接提供编译好的二进制包省掉本地工具链的问题。3.2 初始化数据库建库建表、插入用户与特征更新数据库建好后第一步是插入用户基本记录。这里有一个习惯要养成照片文件名和用户姓名的对应关系从一开始就要定好。最简单的约定是“文件名即姓名”注册照片放进register/目录文件名就叫张三.jpg、lisi1234.jpg。USE door_system; INSERT INTO users (name, photo_path) VALUES (张三, register/张三.jpg), (李四, register/lisi1234.jpg);逻辑说明这条SQL只是建立了用户基本信息和照片路径的映射此时face_feature字段还是空的。下一步要做的是读取每张照片计算特征向量并写回BLOB字段。import os import pymysql import face_recognition conn pymysql.connect( host127.0.0.1, userroot, password你的密码, databasedoor_system, charsetutf8mb4 ) cursor conn.cursor() cursor.execute(SELECT id, photo_path FROM users) for user_id, photo_path in cursor.fetchall(): if not os.path.exists(photo_path): print(f文件不存在: {photo_path}) continue image face_recognition.load_image_file(photo_path) faces face_recognition.face_encodings(image) if len(faces) ! 1: print(f用户 {user_id} 的照片检测到 {len(faces)} 张脸跳过) continue cursor.execute( UPDATE users SET face_feature%s WHERE id%s, (faces[0].tobytes(), user_id) ) conn.commit()逻辑说明这段脚本是“注册阶段”的核心。faces[0].tobytes()把128维的float32向量序列化成字节串方便存进BLOB字段。len(faces) ! 1的检查是必要的防线——如果照片里有路人甲的脸程序会直接跳过并提示而不是把错误的特征写入。参数说明pymysql连接串里的charsetutf8mb4不是可选项少了它中文姓名在写入时可能直接乱码。执行完脚本后可以用下面这条SQL验证特征是否写入成功SELECT id, name, LENGTH(face_feature) FROM users;如果LENGTH(face_feature)不是512或显示为NULL说明那行记录没有成功写入特征回头检查照片路径和人脸数量。3.3 主循环视频流读取、识别比对与放行记录门禁系统的主体是一个while True循环读摄像头一帧转成RGB找人脸算特征和已载入的注册特征算距离距离小于阈值就写一条放行记录。下面是去掉界面封装后的核心骨架import cv2 import numpy as np import face_recognition THRESHOLD 0.6 # known_encodings 和 known_names # 启动时从 MySQL 读出所有 face_feature 并还原 # known_encodings.append(np.frombuffer(row[0], dtypenp.float32)) cap cv2.VideoCapture(0, cv2.CAP_DSHOW) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) while True: ok, frame cap.read() if not ok: continue rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) face_locations face_recognition.face_locations(rgb, modelhog) face_encodings face_recognition.face_encodings(rgb, face_locations) for face_encoding in face_encodings: distances face_recognition.face_distance(known_encodings, face_encoding) idx np.argmin(distances) if distances[idx] THRESHOLD: # 放行写入 access_logresult1 print(放行:, known_names[idx]) else: # 拒绝写入 access_logresult0 print(拒绝: 未注册人员) cv2.imshow(door, frame) if cv2.waitKey(1) 0xFF ord(q): break逻辑说明cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)这行不能省。OpenCV默认读出来的是BGR顺序而face_recognition内部按RGB顺序处理颜色通道顺序搞反了检测率会明显下降。np.argmin(distances)找出与当前人脸距离最近的注册用户再用THRESHOLD判断是否放行。参数说明face_locations的modelhog是CPU友好型检测模式在640x480分辨率下帧率足够modelcnn检测更准但CPU下会明显变慢入门演示用hog合适。cv2.VideoCapture(0, cv2.CAP_DSHOW)中的CAP_DSHOW是Windows下使用DirectShow后端可以避免部分USB摄像头打不开或延迟的问题。3.4 验证识别结果有没有真正写进MySQL门禁系统最容易忽略的是“数据落地”环节。识别成功并不代表系统完整你还要去access_log表里确认记录真的写进去了。打开一个新终端连上MySQL查最近十条出入记录SELECT u.name, l.result, l.created_at FROM access_log l LEFT JOIN users u ON l.user_id u.id ORDER BY l.created_at DESC LIMIT 10;逻辑说明LEFT JOIN保证即使user_id为空日志记录也会显示出来方便排查“拒识”事件。如果这个查询查不到任何记录优先检查代码里有没有conn.commit()。pymysql默认autocommitFalse执行完INSERT或UPDATE后必须手动commit()否则数据只停留在事务里程序一退出就丢了。这是Python操作MySQL时频率最高的翻车点没有之一。4. 门禁源码踩坑实录四条最容易翻车的细节这套源码我在本地完整跑过也在类似的门禁项目里踩过同样的坑。下面四条不是理论推断是实际会发生的现象、原因和解法按“现象→原因→解决”的顺序写。4.1 注册阶段人脸张冠李戴照片和姓名错位配对现象张三站在摄像头前刷脸屏幕显示的却是“李四欢迎回家”后台日志里的名字和实际刷脸人对不上。原因常见写法是用os.listdir()扫描注册目录然后把扫描结果和姓名列表按位置索引直接配对。但文件系统的目录排序在不同操作系统、不同磁盘格式下并不一致Windows下还会区分字母大小写结果就是“第0张照片配第0个名字”的顺序假设不成立一旦有一张照片不在预期位置后面全部错位。解决不要依赖目录扫描顺序直接用文件名作为姓名标识import os for item in os.listdir(register): if not item.lower().endswith((.jpg, .jpeg, .png)): continue name os.path.splitext(item)[0] # 文件名即姓名 cursor.execute( INSERT INTO users (name, photo_path) VALUES (%s, %s), (name, os.path.join(register, item)) )逻辑说明os.path.splitext(item)[0]取出去扩展名后的文件名张三.jpg就直接得到张三。如果文件命名是lisi1234.jpg这种账号格式就单独建一张姓名映射表但配对的基准永远是文件名或ID不能是列表位置。4.2 中文名字进MySQL变成乱码一乱到底的三级链条现象控制台打印姓名正常但MySQL表里“张三”变成一串“???”或者“寮犱笁”这样的乱码。顺带提一句源码包里那个“寮犱笁.jpg”文件就是GBK编码的“张三.jpg”被按UTF-8解码后的产物属于同一个坑的不同表现。原因字符链路涉及四个环节文件系统文件名编码、Python源码文件编码、pymysql连接字符集、MySQL库表默认字符集。文件系统是GBKPython源码是UTF-8数据库表是latin1任何一级不一致最终落库的字符串就是乱的。解决把连接、库、表三级全部统一成utf8mb4conn pymysql.connect( host127.0.0.1, userroot, password你的密码, databasedoor_system, charsetutf8mb4 )ALTER DATABASE door_system CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4;注意如果数据已经乱成“寮犱笁”这种程度说明写入时字节就已经被破坏靠ALTER TABLE是救不回来的。唯一的解法是把文件名重新规范为UTF-8清空users表重新导入。这也是我在前面章节反复强调“先规范文件名再入库”的原因。4.3 把阈值从0.6调到0.4门禁直接“全拒”现象有人觉得阈值0.6太宽松改成0.4更安全。结果测试时同一人大晴天正面刷脸也被拒绝换上0.6又恢复正常。原因face_recognition.face_distance()返回的是128维向量之间的欧氏距离数值越小代表越像。0.4意味着要求两张人脸几乎一模一样只要换个发型、戴个眼镜、角度偏一点距离就会冲到0.5以上。门禁场景下阈值越低不等于越安全而是误拒率急剧上升安全是靠特征本身的区分度和合理阈值一起配合出来的不是靠把阈值调到极端。解决把阈值恢复到0.6起步然后打印真实距离分布再微调。调试时加一行import numpy as np import face_recognition distances face_recognition.face_distance(known_encodings, face_encoding) min_idx np.argmin(distances) print(最近匹配:, known_names[min_idx], 距离:, round(float(distances[min_idx]), 3))逻辑说明不管是本人还是陌生人先看距离到底落在什么区间。本人正面正常光照下通常在0.35到0.55之间不同人通常在0.7以上。如果本人的距离最大值和不同人的距离最小值之间还有空隙阈值就取在这个空隙里。这个值是用数据跑出来的不是拍脑袋定的。4.4 摄像头画面卡顿、识别延迟缓冲与抽帧没处理现象摄像头画面一卡一卡人站在门口两三秒才出结果有时候识别到的还是上一帧的人脸。原因cv2.VideoCapture在Windows下默认的帧缓冲较大循环里read()读到的经常是队列里积压的旧帧再加上每一帧都跑全图人脸检测CPU被占满取流和解码互相拖累。解决限制缓冲区长度同时做抽帧识别import cv2 cap cv2.VideoCapture(0, cv2.CAP_DSHOW) cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) frame_id 0 while True: ok, frame cap.read() if not ok: continue frame_id 1 if frame_id % 3 ! 0: continue # 每3帧取1帧做识别 # 识别逻辑放在这里逻辑说明CAP_PROP_BUFFERSIZE设为1是告诉底层尽量只保留最新帧减少读到旧帧的概率。抽帧不是偷懒门禁是长时间值守任务640x480分辨率下每3帧处理1帧延迟和CPU占用都能控制在合理范围。如果一台机器要带多路摄像头识别逻辑还应该丢到独立线程里主线程只负责取流和显示。5. 进阶验证用视频回放把阈值校准到能用的数人脸识别门禁调试最麻烦的地方在于现场工况不可复现。今天改个阈值明天换个人戴帽子测试同一个参数在不同时段的表现都不一样。我的做法是用视频回放替代摄像头把测试变成可重复的离线过程。5.1 把摄像头源换成视频文件让测试可重复常见做法是先录三段视频正常光照通行、逆光或夜间通行、戴帽或侧脸通行。然后把主循环里摄像头编号0换成视频文件路径让同一段视频反复跑对比每次改动前后效果import cv2 # 原来是 VideoCapture(0)调试时换成视频文件 cap cv2.VideoCapture(door_test.mp4) cap.set(cv2.CAP_PROP_POS_FRAMES, 0) frame_id 0 while True: ok, frame cap.read() if not ok: break # 视频读到结尾自然结束 frame_id 1 if frame_id % 3 ! 0: continue # 这里复用主循环的识别逻辑逻辑说明VideoCapture对视频文件和摄像头的接口完全一致主循环代码几乎不用改。区别在于视频读到结尾时read()返回False循环会正常退出不会像摄像头那样无限阻塞。CAP_PROP_POS_FRAMES设为0是让回放从头开始方便重复跑同一段素材。参数说明测试视频建议保证至少包含三个人的脸部画面并且覆盖正脸、斜脸、光照变化三种情况。素材太单一校准出来的阈值没有参考价值。5.2 记录每个身份的比对距离区间再定阈值跑完回放之后把每次匹配的距离打出来统计同一人多次进出的距离区间以及不同人被错误匹配时最近的距离区间。下面是我在自己测试里得到的一组示意数据测试场景与本人最近注册脸的距离与最近不同人的距离正面正常光照0.42 ~ 0.510.78 ~ 0.91戴帽或侧脸0.55 ~ 0.680.88 ~ 0.94逆光或夜间0.61 ~ 0.760.80 ~ 0.99真实数据要跑你自己的视频才能拿到但这张表能说明一个规律本人距离分布和不同人距离分布之间的空隙就是阈值该待的地方。我的测试里正面和侧脸场景本人最大距离约0.68不同人最小距离约0.78阈值取0.6到0.7之间都可以0.6是face_recognition的默认值正好落在安全区间里。如果两个分布之间没有空隙、甚至发生了交叠说明单靠一个阈值解决不了正确做法是给用户多注册几个角度和光照条件下的照片而不是继续调阈值。从那以后我每次拿到新的门禁或考勤源码第一件事不是打开IDE改功能而是先录一段30秒的视频把识别链路在离线状态完整跑一遍统计距离分布把阈值、跳帧、检测模型这些参数一次定好再让程序接摄像头。识别阈值这种参数跑出来的数才敢用临时改的到现场就是玄学。希望帮到你。本文还有配套的精品资源点击获取
返回列表