简介基于树莓派的人脸识别门禁系统的技术文献PDF适合学习智能安防、嵌入式开发与Python人脸识别应用的读者参考。内容完整呈现了系统从需求到模块实现的过程以Flask完成前后端交互、MySQL负责数据存储、Face_Recognition实现人脸识别并将系统划分为视频显示与运算、硬件控制、后台数据管理、数据存储四大模块。此外还介绍了Face_Recognition框架的识别原理与准确率、Flask工作流程以及树莓派在其中的扩展优势能够帮助读者快速理解人脸识别门禁的整体架构和实现思路。资源包为单个PDF文件大小约1020KB内容紧凑、便于查阅已有1202人学习下载。对于正在做毕业设计、课程项目或准备智能安防方向入门研究的读者这份PDF可作为方案设计、技术选型和论文写作的参考资料。1. 树莓派门禁的选型逻辑为什么用 Face_Recognition 而不是自训练模型一套门禁系统最麻烦的不是刷脸开门而是“录入新人”和“参数调节”这两件日常事。商用门禁机往往把人脸特征数据推到云端比对网络一断就尴尬而换到树莓派本地做识别只要一张 Ubuntu 或 Raspberry Pi OS 镜像、一个 USB 摄像头、两个 LED 和一个舵机就能把所有逻辑压在边缘端。这里选 Face_Recognition 的核心原因是零训练收录一个用户就是上传一张正脸照片、往 MySQL 插一行记录不需要像 LBPH 或 SVM 那样为每个人准备几十张样本重新训练。对树莓派 4B 这种 ARM 单板来说Face_Recognition 底层走 dlib 的预训练 ResNet 模型在 LFW 数据集上准确率约 99.38%比传统 OpenCV 内置识别方案高出一截而且能利用树莓派的多核并行推理。这个项目适合三类人做嵌入式课设或毕设的学生想低成本改造实验室门禁的运维以及不想把员工人脸数据交给第三方平台的团队。2. 视频流识别链路HOG 人脸检测与 128 维特征向量比对2.1 先理解“不训练”的识别原理传统 OpenCV 人脸识别方案如 EigenFaces、FisherFaces、LBPH都需要对每个目标人物采集足够多的样本提取特征后训练分类器。换一个人就得重新训练光照变了准确率掉得也厉害。Face_Recognition 的思路完全不同它加载的是 dlib 预训练好的深度残差网络输入一张对齐后的人脸输出固定 128 维的 float 特征向量比对阶段只需要计算两个向量之间的欧氏距离。这一设计对整个门禁系统影响很大。训练被前置到模型发布阶段运行期只剩“提取特征 算距离”两步。所以树莓派上不需要跑训练脚本只需要维护一个特征向量库。Face_Recognition 的人脸检测默认用 HOG 特征检测大致路径是灰度化、梯度计算、梯度方向直方图、重叠块归一化、得到 HOG 特征向量判断该帧是否存在人脸。HOG 对计算量要求比 CNN 低很多在树莓派 CPU 上能跑到接近实时的水平。检测方式适用场景树莓派 4B 上的经验耗时HOG默认视频流逐帧检测约 100~200 ms/帧CNNmodelcnn精度优先、离线图片数秒/帧不适合实时2.2 视频流主循环降采样、检测、编码、比对识别主循环的常见做法是OpenCV 读取摄像头帧先把帧缩小再做 RGB 转换然后交给 Face_Recognition 处理。缩小的目的是减少 HOG 和编码的计算量树莓派性能有限这一步不是可选项。import cv2 import face_recognition import numpy as np # 从数据库加载的已知人脸特征向量和姓名 KNOWN_ENCODINGS [] # 每个元素是 128 维 np.ndarray KNOWN_NAMES [] TOLERANCE 0.5 # 距离阈值越小越严格 cap cv2.VideoCapture(0, cv2.CAP_V4L2) 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 # 关键先把帧缩小一半HOG 检测耗时显著下降 frame cv2.resize(frame, (0, 0), fx0.5, fy0.5) rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) boxes face_recognition.face_locations(rgb, modelhog) encodings face_recognition.face_encodings(rgb, boxes) for box, enc in zip(boxes, encodings): distances face_recognition.face_distance(KNOWN_ENCODINGS, enc) min_dist float(np.min(distances)) if len(distances) else 1.0 name Unknown if min_dist TOLERANCE: name KNOWN_NAMES[int(np.argmin(distances))] top, right, bottom, left box cv2.rectangle(frame, (left, top), (right, bottom), (0, 255, 0), 2) cv2.putText(frame, name, (left, top - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 1) if name ! Unknown: trigger_door_open(name) # 硬件控制与写日志 cv2.imshow(raspi-access, frame) if cv2.waitKey(1) 0xFF 27: break cap.release() cv2.destroyAllWindows()这里为什么不用compare_faces直接拿布尔结果而是换成face_distance取最小距离因为compare_faces(known_face_encodings, face_encoding, tolerance)内部逻辑是把每个已知向量与当前向量算距离然后逐个和 tolerance 比较。如果底库里有两个人长得像且距离都小于阈值它会返回多个 True代码里若用matches.index(True)只会取到第一个可能开错门。用face_distance取 argmin再拿最小距离和阈值比较能保证每次只匹配一个人。face_recognition.face_encodings内部还包含人脸对齐步骤输出固定 128 维 float32 向量这也是KNOWN_ENCODINGS里每个元素的实际形状。2.3 识别成功后的联动动作识别成功不代表立刻开锁。门禁场景里通常还要做三件事查该用户是否有权限、写开门日志、触发硬件动作。权限判断可以直接落到 SQL 查询也可以在内存里维护一个名单。写日志建议用独立线程或队列避免日志写入阻塞视频流。触发硬件动作就是把 GPIO 拉低让舵机转到开门角度延迟指定秒数后再转回关门角度这部分在下一章展开。3. 舵机与 GPIO硬件开闭控制和 Web 参数下发3.1 反向接法的 LED 与舵机接线硬件模块由红色 LED、绿色 LED 和一个舵机组成。红 LED 亮、舵机 90° 表示关门绿 LED 亮、舵机 180° 表示开门。论文里没有指定具体 GPIO 编号我这里用 BCM 编号GPIO23 接红灯、GPIO24 接绿灯、GPIO18 接舵机信号线。接线有一个容易踩的坑LED 用的是“反向接法”。阳极接树莓派的 5V 输出引脚阴极通过限流电阻接到可控 GPIO 上。这样 GPIO 输出高电平时LED 两端压差小灯灭GPIO 输出低电平时LED 两端压差接近 5V灯亮。如果用正向接法GPIO 高电平 3.3V 驱动 LED 往往亮度不足。舵机供电建议单独从 5V 引脚取信号线接 GPIO舵机的地和树莓派共地。小舵机在堵转瞬间电流可能超过 500mA如果直接用树莓派 5V 供电建议在电源两端并联一个 470uF 电容防止瞬间压降导致树莓派重启。3.2 RPi.GPIO 输出 PWM 控制舵机角度舵机角度由 PWM 脉冲宽度控制RPi.GPIO 库在树莓派上生成 50Hz 的 PWM 信号对应周期 20ms。角度到占空比的换算关系是 duty angle / 18 2.5因此 90° 对应 7.5%180° 对应 12.5%。import RPi.GPIO as GPIO import threading import time RED_LED 23 GREEN_LED 24 SERVO_PIN 18 CLOSE_ANGLE_DUTY 7.5 # 90° OPEN_ANGLE_DUTY 12.5 # 180° GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) GPIO.setup(RED_LED, GPIO.OUT, initialGPIO.HIGH) GPIO.setup(GREEN_LED, GPIO.OUT, initialGPIO.HIGH) GPIO.setup(SERVO_PIN, GPIO.OUT) servo GPIO.PWM(SERVO_PIN, 50) # 50Hz servo.start(CLOSE_ANGLE_DUTY) def set_door(state: str): if state open: GPIO.output(GREEN_LED, GPIO.LOW) # 低电平点亮 GPIO.output(RED_LED, GPIO.HIGH) servo.ChangeDutyCycle(OPEN_ANGLE_DUTY) else: GPIO.output(RED_LED, GPIO.LOW) GPIO.output(GREEN_LED, GPIO.HIGH) servo.ChangeDutyCycle(CLOSE_ANGLE_DUTY) time.sleep(1) servo.ChangeDutyCycle(0) # 释放 PWM防止舵机抖动 def open_door(delay: int 15): set_door(open) threading.Timer(delay, set_door, args(close,)).start()servo.ChangeDutyCycle(0)这一步容易被忽略。舵机转到目标角度后如果继续输出固定占空比电机会持续受力表现为发热和抖动。把占空比归零后舵机靠机械结构保持角度功耗大幅下降。threading.Timer实现开门延迟默认 15 秒后自动切回关门状态参数来自后台管理页面而不是写死在代码里。GPIO 编号还有一种 BOARD 模式按物理引脚编号树莓派不同型号引脚布局一致时用 BOARD 更直观但代码里混用 BCM 和 BOARD 会导致引脚错乱前期要统一。3.3 Web 控制台修改门禁参数后台管理模块用 Flask 提供可视化配置页面默认监听 5000 端口。参数控制页可以修改门锁状态、开门延迟时间和人脸相似度。门锁状态可以直接触发强制开锁开门延迟时间决定舵机保持开门状态的秒数人脸相似度则映射到识别距离阈值。这套系统的核心参数换算逻辑在参数管理接口里from flask import Flask, request, jsonify import pymysql app Flask(__name__) db pymysql.connect(hostlocalhost, useraccess, passwordaccess_pass, databaseaccess_db) app.route(/api/param, methods[POST]) def update_param(): req request.get_json(forceTrue) # 页面展示的是人脸相似度 65~95库里存的是 tolerance 0.45~0.6 similarity float(req.get(similarity, 85)) tolerance round((185 - similarity) / 200, 3) # 这里落库识别进程会定期重新读取 with db.cursor() as cur: cur.execute(UPDATE system_param SET value%s WHERE nametolerance, (str(tolerance),)) db.commit() return jsonify({ok: True, tolerance: tolerance}) app.run(host0.0.0.0, port5000, debugFalse)debugFalse在树莓派上不是可选项。Werkzeug 的 reloader 会 fork 子进程两个进程同时抢摄像头和 GPIO轻则视频流卡死重则引脚被重复初始化。参数更新后识别进程不会立即感知常见做法是识别线程每 3~5 秒重读一次system_param表或者用 Redis 发布订阅但门禁系统并发量低轮询数据库已经足够。4. MySQL 四张表设计与 Flask 后台交互流程4.1 四张表的存储职责论文里明确了数据库中四张表的角色管理员表存登录账号用户表存门禁用户的人脸特征向量参数表存系统运行参数日志表存每次开门记录。实际建表时还可以扩展用户表的字段比如工号、部门、启用状态但核心结构保持四张表即可。CREATE TABLE admin ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password_hash VARCHAR(255) NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE user_info ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL, image_name VARCHAR(255), feature_vector BLOB NOT NULL, enabled TINYINT DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE system_param ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL UNIQUE, value VARCHAR(255), updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); CREATE TABLE open_log ( id INT AUTO_INCREMENT PRIMARY KEY, user_name VARCHAR(50), open_time DATETIME DEFAULT CURRENT_TIMESTAMP, open_status TINYINT DEFAULT 0, remark VARCHAR(255) );feature_vector用 BLOB 存 128 维 float32 的原始字节一个向量占 512 字节。如果转成 JSON 数组字符串存体积膨胀到 2~3KB读取效率差别不大但每次识别进程加载底库时反序列化开销明显。image_name只存文件名图片文件落在imgs/目录文件系统比数据库更适合存大文件备份时也只需同步目录和 SQL 两份数据。system_param表用name做唯一键这样后续再加参数不需要改表结构。4.2 添加用户页面的入库流程后台“添加用户”页面允许上传照片。上传接口要做两件事验证照片里是否恰好有一张人脸以及把人脸特征向量写入数据库。实践中经常遇到合影照片、卡通头像、模糊截图入库的情况如果不在接口层拦截识别时会产生大量误匹配。import os import numpy as np import face_recognition from flask import request, redirect, url_for UPLOAD_DIR /var/access/imgs app.route(/add_user, methods[POST]) def add_user(): name request.form.get(name, ).strip() photo request.files.get(photo) if not name or not photo: return 缺少姓名或照片, 400 # 文件名用时间戳重命名避免中文名和路径注入问题 ext os.path.splitext(photo.filename)[1].lower() if ext not in (.jpg, .jpeg, .png): return 仅支持 jpg/png, 400 file_path os.path.join(UPLOAD_DIR, f{name}_{int(time.time())}{ext}) photo.save(file_path) image face_recognition.load_image_file(file_path) encodings face_recognition.face_encodings(image) if len(encodings) ! 1: os.remove(file_path) # 入库失败就清理文件 return 照片中必须且只能包含一张人脸, 400 vector encodings[0].tobytes() with db.cursor() as cur: cur.execute( INSERT INTO user_info (name, image_name, feature_vector) VALUES (%s, %s, %s), (name, os.path.basename(file_path), vector) ) db.commit() return redirect(url_for(users))len(encodings) ! 1的判断是防止两人合影或无人脸照片进入底库。多张人脸照片入库后识别时会出现一个陌生人匹配到多个已知用户的情况日志会变得混乱。encodings[0].tobytes()把 numpy 数组序列化成 bytes写入 BLOB 字段。读取时用np.frombuffer(row[feature_vector], dtypenp.float32)还原注意 dtype 必须是 float32否则向量值会完全错乱。还有一个容易被忽视的问题dlib 模型版本升级后同一张照片生成的特征向量空间可能不再一致。如果更换了 Face_Recognition 或 dlib 版本旧底库向量需要重新生成否则会出现“换版本后全员识别失败”的现象。升级前先备份 MySQL 和图片目录再跑一遍批量重新编码脚本。4.3 识别进程与 Flask 进程如何协作识别主循环和 Flask 后台一般不在同一个进程里跑特别是用app.run()启动时Flask 自带服务器是单线程的如果把trigger_door_open里的 GPIO 操作和数据库写入直接放进去识别帧率会掉到不可用。推荐进程划分识别进程负责摄像头读取、人脸匹配、GPIO 控制并负责往open_log写数据Flask 进程只处理管理后台的增删改查。两个进程共享同一个 MySQL。识别进程启动时把所有底库加载进内存import numpy as np def load_known_faces(): with db.cursor() as cur: cur.execute(SELECT name, feature_vector FROM user_info WHERE enabled1) rows cur.fetchall() names [] encodings [] for name, vec in rows: encoding np.frombuffer(vec, dtypenp.float32) if encoding.shape (128,): names.append(name) encodings.append(encoding) return names, encodings底库加载后常驻内存识别循环里不再查询数据库只做向量比对。新增用户后识别进程不会立刻看到新数据。解决方式有两种简单一点每 5 分钟重新加载一次管理层可以接受这个延迟要即时生效可以在add_user接口里把识别进程加载的全局列表直接 append但这种写法在两个进程下不可用跨进程还是得靠共享存储。稳妥的做法是在add_user写库后touch一个版本文件识别进程每秒检查文件修改时间有变化就重新加载底库。5. tolerance 换算、性能边界与去重开门技巧5.1 相似度 65-95 的换算逻辑tolerance 参数官方推荐值是 0.6范围 0~1数值越低越严格。这个参数对普通管理员不友好所以系统用线性变换把它映射成人脸相似度公式为 f(x) 185 - 200x。当 tolerance0.6 时映射为 65tolerance0.45 时映射为 95界面展示范围正好是 65~95。反向换算写成代码就是tolerance (185 - similarity) / 200。相似度界面显示tolerance实际距离阈值实际表现650.6最宽松接近官方默认误识风险升高750.55适合光线浮动较大的通道门850.5推荐起点平衡准确率和通过率950.45严格适合机房等高安全区域这个公式的单调方向容易搞反相似度调得越高实际距离阈值越小越容易拒识而不是越容易通过。有团队把相似度调到 95 之后原本能通过的人开始频繁识别失败排查半天才发现是方向反了。部署初期建议从 85 起步观察一周日志再往下压。5.2 树莓派上的性能边界树莓派 4B 实测 640x480 输入、降采样到 320x240 后单帧流程大概在 200~400ms对应 3~5 FPS。这个帧率对门禁完全够用因为人走到闸机前会停顿不需要视频级流畅帧率。如果追求更高帧率把face_locations的number_of_times_to_upsample参数从默认 1 调成 0检测距离会变短但速度提升明显。摄像头后端建议显式指定cv2.CAP_V4L2。树莓派 bullseye 之后默认摄像头后端是 libcameraOpenCV 直接打开/dev/video0有时拿到黑帧加上 CAP_V4L2 能绕开不少驱动问题。系统镜像也建议用 64 位版同样的识别代码在 64 位系统上内存占用更小性能比 32 位略好。5.3 防止同一张脸重复触发的去重逻辑识别成功后如果人不离开摄像头视野下一帧又会匹配成功舵机会反复开关日志会被刷屏。常见做法是在内存里维护一个最近开门记录last_opened {} # name - timestamp def trigger_door_open(name: str, delay: int 15): now time.time() if name in last_opened and now - last_opened[name] delay: return last_opened[name] now open_door(delay) with db.cursor() as cur: cur.execute( INSERT INTO open_log (user_name, open_status) VALUES (%s, 1), (name,) ) db.commit()去重窗口直接复用开门延迟时间这样舵机动作结束后如果人还站在摄像头前也不会连续触发第二次。这个技巧对日志页面的意义很大否则一个人进门会生成十几条重复记录后台“当日开锁次数”的统计就失真了。最后把相似度调到 85 作为起点用正脸、平光、无遮挡照片建底库摄像头在识别区域补一盏常亮 LED这套系统在树莓派上的稳定性和可维护性就足够日常使用了。本文还有配套的精品资源点击获取