尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于Python的人脸识别门禁系统:从环境搭建到答辩演示

基于Python的人脸识别门禁系统:从环境搭建到答辩演示 简介基于Python的人脸识别智能门禁系统是一套面向计算机相关专业学生的完整毕业设计项目适合用作毕业设计、期末大作业或课程设计。代码注释较全前后端架构清晰关键模块包含人脸识别与门禁管理流程且已经过调试下载部署即可运行。资源包共262个文件压缩包约70.36MB主要包含前端页面、后端逻辑、数据库脚本、界面图片素材与操作演示视频还提供环境配置、代码片段等辅助文件其中前端以Vue与TypeScript技术栈为主并配备样式、图标与演示内容目录结构清楚便于按模块查漏补缺。当前已有146人学习/下载。借助整套项目读者可以看清从界面操作到数据存储的完整链路同时复用现成模块快速搭建属于自己的高分毕设系统整体完成度与实用性都较高。1. 基于 Python 的人脸识别门禁为什么适合做成高分毕设人脸识别门禁听起来很“系统”但从毕业设计的落地效果看它就是一套特别成熟的工程组合摄像头采图、人脸检测、特征比对、锁具控制、记录日志。多数人以为难点在“认人”实际上难点在把这几件事串成一个稳定闭环。用 Python 做这件事最大优势是生态完整注册、识别、树莓派 GPIO、Web 管理都能在一个语言环境里完成不用跨语言拼模块。这篇从一次门禁动作拆起给你一套能照抄的环境配置、注册与识别代码再把我踩过的阈值、光线、中文路径、特征库失效这几个坑逐个讲透最后落到 FAR/FRR 指标和答辩演示上让它真正成为能讲清楚、也能演示的高分项目。2. 门禁系统结构与算法选型人脸识别全链路拆解到选型2.1 一个门禁动作背后的五个环节门禁不是把摄像头拍到的脸拿去匹配那么简单。一次正常的开门在代码里至少要拆成五个环节人脸注册把允许进门的人提前录入生成“参考特征”。抓拍触发摄像头持续取流当画面中出现人脸时框出目标。特征提取把抓到的脸转换为一串可比较的数值向量。相似度比对用当前向量跟库里所有注册向量算距离找到最接近的人。执行与记录距离低于阈值就驱动继电器开门并把开门时间、识别对象、结果写进日志。很多同学一上来就盯着第 3 步选模型结果忽略了两件事。第一校园门禁这种人脸数不多的场景现成的预训练特征提取器已经足够第二评委关心的是“这个系统能不能出门、会不会误放人、记录是否完整”而不是你背了多少模型结构。所以先把五个环节做成闭环再谈模型改进。观念上还要分清“人脸检测”和“人脸识别”。检测只负责回答“画面里有没有脸脸在哪个位置”识别才负责回答“这张脸是谁”。门禁代码里两者经常连着用检测给出框识别在框里做比对。如果连位置都没有后面的步骤无从谈起。2.2 算法选型OpenCV、face_recognition 与深度学习模型毕设选型不能只盯着论文指标还要算你手里有多少时间、电脑能不能跑、答辩时现场是否稳定。下面这张表是我给学生的常见对比思路不是标准答案但能帮你快速排除“做不下去”的方案。方案特征形式运行依赖训练成本适用场景OpenCV LBPH局部纹理直方图只需 opencv-python低但要自己找正负样本适合展示传统识别方法精度一般face_recognition128 维预训练特征dlib opencv无需训练直接提取门禁项目主流起点CPU 可跑深度学习模型 MTCNN ArcFace512 维或更高维PyTorch / TensorFlow中高需要训练或引入预训练权重适合作为论文创新点但依赖重我的实际建议是把 face_recognition 作为主干。原因有三个它内置了从人脸检测到特征提取的完整流程不需要准备上千张训练图片做分类器训练在普通笔记本和树莓派 4B 上都能维持可用的帧率。如果你希望论文里有“算法对比”可以保留一条 OpenCV LBPH 的链路作为 baseline再用 face_recognition 的结果说明优势这比直接上大模型更能体现你对问题的理解。如果想突出创新点可以把检测模块替换成 OpenCV DNN 人脸检测器或者换用 MobileFaceNet 做特征提取改动集中在“取帧后得到特征”这一段门锁控制和日志模块完全不用动。这样写论文时就能设计成传统方法、预训练方法、改进方法三组实验对照逻辑完整且工作量可控。2.3 硬件与执行机构继电器、电磁锁和树莓派的接线门禁最终要控锁。毕设里使用最多的是树莓派 USB 摄像头 电磁锁 继电器。树莓派用 GPIO 输出高低电平驱动继电器再由继电器控制电磁锁上电或断电。注意电磁锁有两种常见规格通电开锁型和断电开锁型。宿舍或实验室用的门禁电插锁多数是“断电开锁”即平时锁体带电保持锁定断电瞬间释放。我习惯的接线方式是让树莓派 GPIO 接继电器信号端继电器的公共端和常闭端串在电磁锁电源回路上。GPIO 输出高电平时继电器动作常闭端断开电磁锁断电弹开。这样设计的好处是一旦系统崩溃或断电门锁自动释放不会把人困在屋里。硬件选型上要提醒一句树莓派 GPIO 只能提供毫安级驱动电流绝对不能直接接电磁锁电磁锁要单独配 12V 电源否则一接就烧板。确认继电器型号时注意看清输入控制电压是 3.3V 还是 5V。树莓派 GPIO 默认输出 3.3V如果买了 5V 触发的继电器会出现“信号给到位但继电器不动”的假故障。3. 最小实现跑通Python 环境搭建、人脸注册与开门代码3.1 环境准备安装命令与版本锁定先把虚拟环境建好不要图省事直接装到系统 Python 里。毕设周期长中间很可能要换 OpenCV 版本或重装环境虚拟环境能让你随时回到能跑的状态。python -m venv venv # Windows venv\Scripts\activate # Linux / macOS source venv/bin/activate pip install opencv-python pip install face_recognition pip install flask逻辑说明face_recognition会拉取dlib而dlib在 Windows 上可能需要本机 Visual Studio Build Tools 才能编译。怕麻烦的同学可以改用 Anaconda 环境conda 会直接提供编译好的 dlib。装完后用一条命令验证能输出版本说明环境没问题。python -c import cv2, face_recognition; print(cv2.__version__, face_recognition.__version__)参数说明我用的是 opencv-python 的常规发行版如果你需要 SIFT 这类专利相关算子要换成opencv-contrib-python。门禁项目用不到 SIFT普通版本足够。环境的坑集中在版本不统一。建议在项目根目录生成一份依赖清单防止换电脑后重装时版本漂移。做法很简单识别代码运行通过后执行pip freeze requirements.txt以后在新机器上只需要pip install -r requirements.txt。这一步对答辩前换设备特别有用属于最便宜的后悔药。3.2 人脸注册模块多帧采集与特征入库注册模块要解决两个问题只拍一张容易因角度或光线产生偏差照片里的脸不能偏离画面中心太多。下面这段代码会连续取 5 帧每帧都检测一次人脸成功检测到的时候就提取 128 维特征最后取平均当作这个人的参考特征。import os import cv2 import numpy as np import face_recognition FACE_DIR face_db def register_user(name): cap cv2.VideoCapture(0) encodings [] for i in range(5): ok, frame cap.read() if not ok: print(f第 {i 1} 帧读取失败) continue # 转换成 RGBdlib 的内部处理基于 RGB 顺序 rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) boxes face_recognition.face_locations(rgb, modelhog) if len(boxes) ! 1: print(f第 {i 1} 帧检测到 {len(boxes)} 张脸跳过) continue enc face_recognition.face_encodings(rgb, boxes)[0] encodings.append(enc) cap.release() if not encodings: return False, 连续 5 帧没有采集到有效人脸 # 平均多个特征再归一化让后续计算距离时不受到特征尺度影响 avg np.mean(encodings, axis0) avg avg / np.linalg.norm(avg) os.makedirs(FACE_DIR, exist_okTrue) np.save(os.path.join(FACE_DIR, f{name}.npy), avg) return True, f{name} 注册完成使用了 {len(encodings)} 帧有效图像逻辑说明face_locations返回的是人脸的边界框坐标modelhog表示用 HOG 检测器在 CPU 上速度快适合逐帧采集face_encodings再把框内人脸变成特征向量。np.linalg.norm归一化是为了让不同批次计算出的特征处于同一尺度避免某次光照偏亮导致整体向量偏大。参数说明modelhog的备选值是cnn准确率更高但需要显存树莓派上不建议用。连续取 5 帧里只要 1 帧成功也能注册这是“能跑”和“好用”的差别。注册别嫌麻烦给每个人录 5 个角度更可靠正面、左偏、右偏、抬头、低头。门禁场景用户会自然抬头看屏幕录入时加一帧抬头看镜头的角度能明显提升识别率。3.3 识别与开门阈值判断、GPIO 触发、日志落库识别模块的核心不是“找到最像的人”而是“像到一定程度才开门”。在只有一个摄像头、一个小数据库的场景下我直接用欧氏距离做判断阈值用tolerance统一收口。import time import sqlite3 import numpy as np import face_recognition import cv2 MATCH_TOLERANCE 0.45 DOOR_DELAY 3 try: import RPi.GPIO as GPIO GPIO.setmode(GPIO.BCM) GPIO.setup(18, GPIO.OUT) HAS_GPIO True except ImportError: HAS_GPIO False print(当前环境没有 RPi.GPIO会打印日志代替开锁) def load_known_faces(): import os names, encodings [], [] for file in os.listdir(face_db): if file.endswith(.npy): names.append(file[:-4]) encodings.append(np.load(os.path.join(face_db, file))) return names, np.array(encodings) if encodings else None def open_door(name, dist): # 常闭继电器GPIO 拉高时电磁锁断电门锁弹开 if HAS_GPIO: GPIO.output(18, GPIO.HIGH) time.sleep(DOOR_DELAY) GPIO.output(18, GPIO.LOW) print(f开门{name}距离 {dist:.3f}) def log_record(name, result, detail): conn sqlite3.connect(access.db) conn.execute( INSERT INTO access_log(person, result, detail, create_time) VALUES (?,?,?,datetime(now,localtime)), (name, result, detail), ) conn.commit() conn.close() def run(): names, known load_known_faces() cap cv2.VideoCapture(0) while True: ok, frame cap.read() if not ok: continue rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) boxes face_recognition.face_locations(rgb, modelhog) if len(boxes) ! 1: continue encoding face_recognition.face_encodings(rgb, boxes)[0] distances np.linalg.norm(known - encoding, axis1) idx int(np.argmin(distances)) dist float(distances[idx]) if dist MATCH_TOLERANCE: open_door(names[idx], dist) log_record(names[idx], 允许, fdist{dist:.3f}) if __name__ __main__: run()逻辑说明加载全部.npy文件后每次取到新的人脸特征就与已知特征做差求二范数得到与每个人之间的距离。argmin找出最小的那个距离对应的索引再用MATCH_TOLERANCE过滤。小于阈值才开门大于阈值说明画面里的人从来没注册过。参数说明MATCH_TOLERANCE是门禁最敏感的旋钮。face_recognition 官方默认 0.6这个值作为门禁太松陌生人被放行概率偏高我一般从 0.45 起调验收时如果发现本人在日常光线也经常被拦再退到 0.5。每次调整阈值后都要重跑一遍测试集不要凭感觉改。DOOR_DELAY是电磁锁保持释放的时间宿舍门一般 23 秒足够。time.sleep(DOOR_DELAY)在持续识别循环里会阻塞摄像头抓帧这个版本只能当教室演示用。想让系统更合理应把“开锁延时”放到独立线程里主循环继续识别这部分可以作为后期优化写进论文。日志表如果还没建第一次运行前在 sqlite 里手动初始化CREATE TABLE IF NOT EXISTS access_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, person TEXT, result TEXT, detail TEXT, create_time TEXT );3.4 加分项用 Flask 提供远程开门接口门禁系统加一个 Web 管理端演示效果和论文工作量都会提升。可以只提供 3 个接口查询日志、查看当前画面、远程开门。下面是远程开门接口的最小实现。from flask import Flask, jsonify, request app Flask(__name__) app.route(/api/open, methods[POST]) def api_open(): # 简单权限校验毕设够用正式环境应替换为 JWT 或更严格方案 token request.headers.get(X-Token) if token ! your-secret-token: return jsonify({success: False, msg: 未授权}), 401 ok, message call_door() return jsonify({success: ok, msg: message})逻辑说明远程开门不能绕过“人脸识别”本身否则就失去了门禁意义。常见做法是让远程接口先调一次当前摄像头的识别结果确认画面里确实有注册用户再执行开门。这样演示时既能说明远程能力又不会暴露安全问题。参数说明令牌校验只是最低限度的权限控制论文里建议补一句“该接口设计为内网调用场景”答辩时如果有老师追问你只要说清楚还没做外网映射就是在校实验环境的安全边界这样就站得住。4. 人脸识别门禁避坑4 个容易出现问题的环节与修正4.1 人脸识别成功了门锁却没动作现象控制台已经打印出“开门”物理锁纹丝不动。原因最常见的是继电器信号电压不匹配。树莓派 GPIO 输出 3.3V部分继电器模块要求 5V 触发结果就是电平看起来到位了但继电器根本没吸合。另一个常见原因是接线时把电磁锁的电源回路接在了继电器的常开端导致逻辑完全反了。还有一种情况是电磁锁电流超出了树莓派外接电源的供电能力锁的电源被拉垮。解决先在命令行里单独写一段测试代码直接给 GPIO 引脚输出高电平听继电器有没有“咔嗒”声。没有声音就换 5V 继电器或者用三极管驱动能吸合但门锁不动就把电源线从常开端换到常闭端。我把这个习惯固定成流程先测继电器再测锁最后才连摄像头确保故障边界清楚不互相甩锅。4.2 光线一变就拒识阈值和样本质量的平衡现象白天能正常开门傍晚或者室内顶灯偏暗时同一个人的匹配距离明显变大直接被拒。原因人脸特征的提取对光照比较敏感预训练模型并没有做过校园门禁专属的光照增强。回看匹配距离八成是目标距离稳定在 0.45 到 0.55 之间被阈值卡住。解决先补注册样本再调阈值顺序不能反。让库里每个注册用户保持 5 张不同角度和不同光线下的特征比单纯把MATCH_TOLERANCE从 0.45 放宽到 0.55 更有效。调阈值时要注意边界一次测试里随机 10 个陌生人如果出现距离小于 0.5 的就说明阈值过松。我的参数经验是在树莓派加 USB 摄像头的环境下0.45 到 0.5 之间通常能找到“本人能顺利通过、陌生人不误放”的点但每套摄像头参数不同必须实测试出来。最后给演示现场加一盏朝向人脸区域的 LED 补光。这不算作弊反而说明你考虑了真实工程环境。补光灯不用贵宿舍台灯就能当临时验证设备。4.3 中文路径下的图片读不出来现象cv2.imread(人脸库/张三.jpg)返回None程序不报错但人脸检测一直不执行。原因OpenCV 在部分系统上对 Unicode 路径支持不稳定尤其是中文文件名或中文目录这是老坑了不是代码逻辑问题属于黑匣子行为。解决不要直接用cv2.imread改用np.fromfile读字节再用cv2.imdecode转成图像。定义一个小函数所有读取图片的入口都走它。import numpy as np import cv2 def imread_unicode(path): data np.fromfile(path, dtypenp.uint8) return cv2.imdecode(data, cv2.IMREAD_COLOR)逻辑说明np.fromfile以字节流方式读取文件不经过 OpenCV 对路径的编码解析绕开了中文路径问题。cv2.imdecode负责把内存里的字节解码为图像矩阵。参数说明里cv2.IMREAD_COLOR表示转成 BGR 三通道如果后续要做灰度检测可以改成cv2.IMREAD_GRAYSCALE。这个坑属于“不知道的人浪费时间知道的人一条代码解决”。血泪经验是我在第一次做毕设时把采集照片命名为“张三正面.jpg”“张三侧面.jpg”结果调试了一晚上最后发现是中文路径。后来项目里所有临时文件一律用英文命名中文只存在数据库字段里少了无数麻烦。4.4 换台电脑后原库匹配不上现象在宿舍环境里一切正常拿到答辩教室的电脑上所有注册用户都识别失败。特征库文件都在代码也没报错。原因face_recognition把模型封装在 dlib 依赖里。如果你在新环境重新安装了 dlib或者从 Python 3.9 换到了 3.11预训练模型的内部参数版本可能不一致导致同一个人的 128 维特征变化明显。旧.npy文件里的特征和新环境提取的特征不匹配距离大幅上升。解决最稳的办法是换电脑后重新执行一遍人脸注册用答辩机器的摄像头重新生成特征库。换个角度说这也说明了注册模块要做得足够方便不能让每个用户花太多时间。另外把requirements.txt拿过来固定版本尽量让新环境与开发环境保持一致。形象一点说特征库不是普通数据它是跟环境绑定的一次性计算换环境后不要强行沿用直接跑重新注册脚本比排查差异更快。5. 人脸识别门禁毕设的最后一公里指标评估与答辩演示5.1 用自建小数据集算 FAR 与 FRR论文里不能只写“识别率 98%”要体现出你验证过系统的安全边界。门禁常用两个指标FAR 是误识率指未注册的人被放行的比例数值越低越安全FRR 是拒识率指注册用户被系统拒绝的比例数值越低越好用。这两个指标之间存在拉扯阈值调严 FRR 升高阈值放宽 FAR 升高。做一个小测试集就够了房间内 10 个注册用户每人 10 张新拍照片作为本人测试集另外收集 20 个未注册同学的照片作为冒名测试集。把识别结果整理成一个简单的脚本循环统计不同阈值下的 FAR/FRR。阈值本人通过率FRR未注册放行率FAR0.472%28%0%0%0.4591%9%1%1%0.596%4%8%8%上面这组数据是示意不适用于你的摄像头和光线但它代表的趋势是稳定的。答辩时拿出这张表说明你选 0.45 是“FRR 和 FAR 的平衡点”比空口说“系统准确”有说服力得多。如果论文想做得更专业可以用 ROC 曲线展示不同阈值下的表现让人看到 FAR 和 FRR 的关系这部分在 Python 里用 matplotlib 画并不复杂。5.2 答辩演示的 3 个细节第一演示顺序上先说结构图再跑现场避免老师还没理解系统的完整链路就被识别过程带走注意力。第二现场演示的图片最好在当前环境重新注册不要直接沿用开发机里的旧特征库这是我在 4.4 节里提醒过的坑也是答辩演示翻车的高频原因。第三给“前端页面”和“数据库日志”准备一个固定画面开门后立刻切到日志表让评委看到时间、人名、判定结果三条记录同时出现这个动作比任何截图都有说服力。答辩现场还要准备一条兜底路径如果摄像头在投影环境里帧率过低用一段预先录制的开门录像顶上同时口头说明“现场摄像头在当前投影布光下取流偏慢我用录制文件演示完整流程”。主动说明限制比被迫承认失败体面得多。5.3 让实验更扎实的一个技巧最后补充一个小技巧在代码里保留一个DEBUG_FLAG全局变量。识别时把当前帧、检测到的框、欧氏距离一起写到文件让你回看每一帧为什么被拒。毕设答辩时老师最怕问的就是“为什么这张脸没认出来”有调试日志就能当场指出闸值、光线或角度的问题。我做过一个门禁项目真正常久留存下来的不是模型而是那台调试摄像头时的取景角度。希望帮到你。本文还有配套的精品资源点击获取
返回列表