基于Python+Django的人脸识别实验室门禁系统:从原理到工程实践
简介这是一套面向高校毕业设计或课程设计的实验室智能门禁系统完整项目基于PythonDjango框架与MySQL数据库实现。系统具备鲜明的岗位划分管理员可完成用户信息管理、实验室管理、实验室预约管理、考勤管理以及人脸识别门禁管理注册用户则能第一时间获取实验室动态、在线发起预约并实时查看预约信息与审批结果覆盖了门禁场景从后端管理到前端使用的完整业务链。压缩包共708个文件、约105.13MB包含287个Python源码文件构成Django项目主体另有HTML/CSS/JS等前端界面文件、图片/GIF示意资源、SQL脚本与SQLite3数据库文件以及venv环境配置和可执行脚本便于本地快速搭建运行环境。资源同时提供操作录像演示、说明文档与论文方便对照学习系统设计思路和功能流程尤其适合需要完成类似课题或想快速理解人脸识别门禁开发路径的开发者。目前该资源已有142人学习可直接用于毕业设计展示或功能二次扩展。1. 人脸识别实验室门禁系统为什么我推荐 PythonDjango 起步校园实验室的刷卡门禁有一个通病门卡容易被借用管理员换一次卡就要重新发一次。换成刷脸则麻烦在活体检测和后台管理。标题里的这套“基于人脸识别的实验室智能门禁系统”本质上是把两件事拼起来识别端独立做人脸检测、特征提取与比对Django 端做人员注册、通行记录、门禁策略后台。这样既能给毕设提交源码、录像演示、说明和论文也适合在企业内网快速搭一套门禁原型。适合三类人做毕设的本科生或研究生带项目的导师以及想用最少代码在实验室里跑通“照片登记—刷脸开门—日志追溯”闭环的工程师。下文按识别链路、Django 工程、实时演示与论文、调优四个部分展开。2. 识别链路与引擎选型从 OpenCV 到开源人脸识别模型的取舍2.1 先分清 Django 在门禁系统里该管什么Django 是人脸识别门禁系统的调度层。别指望它做特征提取也不要在视图里直接跑 while 循环读摄像头。Django 适合管理三个东西人员信息与人脸照片、门禁设备和开门策略、每次识别产生的通行日志。文件上传走 ImageField定时清理日志可以用 Celery对外提供 JSON 接口给识别端。识别端则用 OpenCV 取流用专门的人脸识别库做比对最后把结果 POST 回 Django。前后端分离的好处是识别脚本可以部署在树莓派或实验室边缘小主机上即使 Django 在一台服务器上维护门禁点位扩容时也不影响核心逻辑。选 Python 而不是 Java 或 C要清楚代价。Python 的优势不在性能而在库的完整度从打标签到训练再到推理都有现成库。Django 的 admin、migration、ORM 能在一周内把管理后台和 API 写完论文的数据表截图也能直接生成。代价是单进程并发能力弱高并发刷脸场景需要 uWSGI 加多进程或者把识别任务丢给独立的 Worker。对于实验室门禁这种每天几百次通行的负载Django 完全够用别为了追求高并发先把自己卡在工程瓶颈上。2.2 人脸识别引擎怎么选识别精度与部署难度的平衡不同引擎决定了后续代码量和演示效果这里先给出一张选型对比表引擎/方案能力LFW 精度参考部署环境适合场景OpenCV Haar Cascade只有检测框无身份识别不适合做身份比对CPU 即可做人脸 ROI 裁剪dlib HOG ResNet68 点关键点128 维特征99.3% 左右CPU 可跑毕设首选旧电脑也能跑face_recognition封装 dlibAPI 简单同 dlibCPU/GPU 均可快速原型InsightFace / ArcFace深度学习质量高99.8%推荐 GPU 或带向量指令的 CPU生产级识别PaddleFace PP-MobileFace轻量模型高支持 ARM 和国产化环境麒麟系统边缘部署选型要看你的交付物要求。标题里的毕业设计通常追求演示效果完整face_recognition 是最短路径几行代码就能出结果。但要注意face_recognition 底层依赖 dlib在 Windows 上编译容易失败建议直接使用预编译轮子或装 Visual Studio 的 C 工具链。如果项目要写到论文里的“创新点”可以在引擎不变的情况下把重心放到门禁策略上比如反尾随、二级联动、深度图防照片攻击。2.3 人脸识别链路拆解检测、对齐、特征提取、比对一个典型的识别链路分五步。第一步是摄像头采集原始帧第二步是人脸检测输出人脸框。第三步是人脸关键点对齐把眼睛、鼻子、嘴巴对准到模板位置消除侧脸和角度影响。第四步是特征提取模型把对齐后的图像映射成一个固定长度的向量一般是 128 维或 512 维。第五步是特征比对计算当前向量与人员库中每个向量的欧氏距离或余弦相似度再与阈值比较得到“是否是某人”的结论。这五步里最容易被忽略的是对齐。如果检测到的人脸没有对齐直接提特征会导致同一个人在不同角度下的特征距离远大于正脸之间的距离。face_recognition 库内部会自动检测 68 个关键点并做仿射变换所以外部代码通常只需要调用一个接口。如果换用 InsightFace则要自己拿 landmark 做对齐代码量会增加但换来的精度提升对遮挡和模糊更明显。2.4 最小可运行的识别代码import cv2 import face_recognition # 加载已知人员照片并提取特征 known_image face_recognition.load_image_file(alice.jpg) known_encoding face_recognition.face_encodings(known_image)[0] known_encodings [known_encoding] known_names [alice] # 读取摄像头一帧 video_capture cv2.VideoCapture(0) ret, frame video_capture.read() if not ret: exit(读取摄像头失败) # 缩小帧提高处理速度 small_frame cv2.resize(frame, (0, 0), fx0.5, fy0.5) # 定位人脸 face_locations face_recognition.face_locations(small_frame) face_encodings face_recognition.face_encodings(small_frame, face_locations) for face_encoding in face_encodings: # 通过阈值判断身份 matches face_recognition.compare_faces(known_encodings, face_encoding, tolerance0.5) distances face_recognition.face_distance(known_encodings, face_encoding) best_match_index distances.argmin() if distances.size 0 else -1 name known_names[best_match_index] if matches[best_match_index] else unknown print({name: name, distance: distances[best_match_index]})代码逻辑说明先加载已知人员照片并提取 128 维特征再从摄像头读一帧缩放帧是为了减少人脸检测的计算量。face_locations 返回人脸框坐标face_encodings 提取当前帧所有人脸特征。compare_faces 会拿当前特征与库中特征逐一比较返回布尔数组face_distance 返回欧氏距离数组。matches 为 True 且距离最小的人脸被认为匹配成功。参数 tolerance0.5 是欧氏距离阈值越小判定越严格这个值决定了门禁是偏严还是偏松后面调优章节会专门讲。要注意库中特征为空时best_match_index 为 -1代码里需要额外拦截。3. Django 工程搭建门禁系统的人员库、识别 API 与开门逻辑3.1 创建 Django 项目与 access app先建立虚拟环境并安装依赖再创建项目与应用python -m venv venv source venv/bin/activate pip install django djangorestframework opencv-python face-recognition django-admin startproject lab_door . python manage.py startapp access参数说明djangorestframework 用于后续写 REST API实际只用 Django 原生 JsonResponse 也可以但 DRF 自带的序列化和认证能让代码更干净。face-recognition 是识别引擎库。startproject 后面的点表示在当前目录生成 manage.py避免多套一层目录。接下来修改 settings.py把 access 和 rest_framework 加入 INSTALLED_APPS并配置媒体文件目录INSTALLED_APPS [ django.contrib.admin, django.contrib.auth, django.contrib.contenttypes, django.contrib.sessions, django.contrib.messages, django.contrib.staticfiles, rest_framework, access, ] MEDIA_URL /media/ MEDIA_ROOT BASE_DIR / mediaMEDIA_ROOT 用于存放上传的人脸照片和抓拍图。数据库默认使用 SQLite毕业设计阶段不用额外配置如果实验室服务器性能允许可以把 DATABASES 换成 MySQL注意修改连接串和以下参数DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: lab_door, USER: root, PASSWORD: 123456, HOST: 127.0.0.1, OPTIONS: {charset: utf8mb4}, } }3.2 数据模型人员、通行记录、门禁设备下面是 access/models.py 的核心模型from django.db import models class Person(models.Model): name models.CharField(姓名, max_length50) student_id models.CharField(学号, max_length20, uniqueTrue) face_image models.ImageField(人脸照片, upload_tofaces/) feature_vector models.BinaryField(特征向量, nullTrue, editableFalse) enabled models.BooleanField(允许通行, defaultTrue) created_at models.DateTimeField(auto_now_addTrue) def __str__(self): return self.name class AccessLog(models.Model): person models.ForeignKey(Person, nullTrue, on_deletemodels.SET_NULL, verbose_name人员) device models.CharField(门禁点, max_length50, defaultlab-gate-1) status models.CharField(结果, max_length10, choices[(allow, 允许), (deny, 拒绝)]) similarity models.FloatField(相似度, nullTrue) snap_image models.ImageField(抓拍图, upload_tosnaps/, nullTrue) created_at models.DateTimeField(auto_now_addTrue) class Device(models.Model): name models.CharField(设备名, max_length50) ip_address models.GenericIPAddressField(设备IP) room models.CharField(所在房间, max_length50)字段类型作用Person.feature_vectorBinaryField预提取的 128 维特征避免每次识别都重新跑模型Person.enabledBooleanField禁用后即使特征匹配也不放行AccessLog.similarityFloatField相似度数值后台排序和答辩展示用Device.ip_addressGenericIPAddressField识别端所在设备的固定 IP用于定位问题feature_vector 使用 BinaryField 需要自行管理序列化。我一般用 pickle.dumps 将 numpy 数组转成 bytes读取时 pickle.loads 还原。要注意 feature_vector 包含二进制数据Django admin 中不能直接编辑所以上方设置了 editableFalse。3.3 Admin 后台人员注册时自动提取特征用 Django admin 管理人员很方便但每次上传照片后还要手工生成特征就太累了。重写 PersonAdmin 的 save_model在上传照片时自动提取特征import pickle import numpy as np import face_recognition from django.contrib import admin from .models import Person, AccessLog, Device admin.register(Person) class PersonAdmin(admin.ModelAdmin): def save_model(self, request, obj, form, change): if obj.face_image and (not obj.feature_vector or face_image in form.changed_data): image face_recognition.load_image_file(obj.face_image.path) encodings face_recognition.face_encodings(image) if encodings: obj.feature_vector pickle.dumps(np.asarray(encodings[0], dtypenp.float32)) else: obj.enabled False super().save_model(request, obj, form, change)逻辑说明如果照片是新建的或者照片字段被替换过就重新提取特征。encodings 为空时说明照片里没有人脸自动把 enabled 置为 False避免一个无效人员占用门禁名额。dtypenp.float32 可以让 128 维向量节省一半内存也方便后续接入 faiss 索引。3.4 识别 API 与开门逻辑的 Django 实现识别端调用的是 Django 的一个 POST 接口接收图片返回是否放行。这里用一个简单的 csrf_exempt 视图import io import pickle import numpy as np import face_recognition from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from .models import Person, AccessLog csrf_exempt def face_auth(request): if request.method ! POST: return JsonResponse({error: POST only}, status405) file request.FILES.get(image) if not file: return JsonResponse({error: no image}, status400) image face_recognition.load_image_file(io.BytesIO(file.read())) encodings face_recognition.face_encodings(image) if not encodings: AccessLog.objects.create(personNone, statusdeny, similarity0) return JsonResponse({allow: False, reason: no_face}) query encodings[0] best_person, best_distance None, 0.6 for person in Person.objects.filter(enabledTrue): if not person.feature_vector: continue known pickle.loads(person.feature_vector) distance np.linalg.norm(query - known) if distance best_distance: best_distance distance best_person person allow best_person is not None and best_distance 0.5 AccessLog.objects.create( personbest_person, statusallow if allow else deny, similarityround(1 - best_distance, 4) ) return JsonResponse({ allow: allow, name: best_person.name if best_person else None, distance: round(best_distance, 4) })逻辑说明先从 request.FILES 读取图片字节用 face_recognition 检测人脸并提取特征然后遍历启用人员用 numpy.linalg.norm 计算欧氏距离。best_person 初始化为 None阈值上限设 0.6只有距离低于 0.5 才判定为同一人。AccessLog 无论成功还是拒绝都写入记录person 为 None 表示陌生人。csrf_exempt 只适合内部局域网设备调用外部访问时建议加 token 或签名认证。注意不要把 face_auth 这个阈值写死最好放进 Django settings 或 Database 配置中方便后期调优。3.5 Django 查询与删除对象的几个细节答辩时老师很喜欢问“删除人员时通行记录会不会丢”“如何批量查询记录”。常见做法是把 AccessLog 的外键设为 on_deletemodels.SET_NULL这样删除 Person 后日志还在。查询最近一小时记录时用from django.utils import timezone from datetime import timedelta recent_logs (AccessLog.objects .filter(created_at__gtetimezone.now() - timedelta(hours1)) .select_related(person) .values(person__name, status, similarity))这段查询会生成一条带 JOIN 的 SQLvalues() 返回字典列表适合直接渲染到模板或 JSON 输出。注意 select_related 只对 ForeignKey 有效能减少一次查询次数。批量删除时 QuerySet.delete() 会返回删除数量但每个实例的 delete() 方法不会被逐个调用所以如果有自定义清理逻辑需要先取出对象列表循环处理。4. 摄像头实时识别、录像演示与毕业论文产出4.1 摄像头实时采集并调用识别 API在门禁终端上写一个循环每隔固定时间抓取摄像头画面提交给 Django 识别接口import cv2 import requests import time cap cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) while True: ret, frame cap.read() if not ret: continue # 每 0.1 秒提交一次避免资源浪费 time.sleep(0.1) _, encoded cv2.imencode(.jpg, frame, [cv2.IMWRITE_JPEG_QUALITY, 85]) try: resp requests.post( http://127.0.0.1:8000/api/face_auth/, files{image: encoded.tobytes()}, timeout3 ) result resp.json() if result.get(allow): print(open door:, result.get(name)) except requests.exceptions.RequestException as exc: print(识别服务不可用, exc)参数说明分辨率 640x480 足够做人脸比对太高会拖慢特征提取JPEG 质量 85 能在清晰度和传输体积之间取平衡。time.sleep(0.1) 控制识别频率在 10 FPS 以内实验室门禁场景每秒钟识别一两次就够频繁请求只会增加日志量。requests 超时设为 3 秒避免摄像头线程卡死。4.2 GPIO 控制与无硬件模拟识别通过后需要开锁常见做法是用树莓派 GPIO 控制继电器接线时把继电器 IN 脚接到 GPIO18公共端串联电控锁电源。没有硬件时先把 print 当作开锁动作方便在电脑上跑通全流程import RPi.GPIO as GPIO import time RELAY_PIN 18 GPIO.setmode(GPIO.BCM) GPIO.setup(RELAY_PIN, GPIO.OUT) def open_door(duration2): GPIO.output(RELAY_PIN, GPIO.HIGH) time.sleep(duration) GPIO.output(RELAY_PIN, GPIO.LOW)参数说明duration2 表示继电器吸合 2 秒给电控锁足够的时间动作持续吸合会发热并缩短设备寿命所以必须及时恢复低电平。GPIO.setmode(GPIO.BCM) 指定引脚编号方式是博通编码需要和接线图对应。4.3 录像演示怎么排别把 30 分钟操作全录进去录像演示不是操作录像而是“功能证明”。总时长控制在 3 到 5 分钟屏幕布局建议左边是摄像头实时画面右边是 Django 后台的最近通行记录。录制顺序分四步先启动 Django 与识别脚本展示终端日志然后在 Admin 里新增测试人员上传一张登记照再让该人员走到摄像头前看到日志返回 allow最后换一个未注册的陌生人日志返回 deny门锁不动作。整个过程不要剪辑但可以分段录好后拼接。演示脚本里最好加一个“计划内失败”的操作比如让测试人员侧脸或者用手遮住一半脸再切回正脸通过。这样能直观展示识别阈值的作用也说明你不是只拍了一刀切的正脸匹配。最后切换到 AccessLog 列表按时间倒序展示刚才两条记录让评阅人看到相似度字段。4.4 说明文档和论文的结构映射说明文档要能让另一个人在陌生电脑上跑起来论文则要讲清楚“为什么这么设计”。下面是一张对应关系表交付物对应内容关键章节源码Django 工程 识别脚本第 2、3 章录像演示识别、开锁、日志完整过程4.3 节说明文档环境搭建、启动命令、配置项3.1、4.5 节论文需求分析、系统设计、数据库设计、系统实现、测试第 2 到第 5 章论文的常见大纲是绪论写背景和国内外现状需求分析写功能性和非功能性需求系统设计画总体架构图和人脸识别流程图数据库设计列出核心表结构系统实现放界面截图和关键代码系统测试给用例表和性能数据。注意论文的测试数据必须来自你自己运行的结果比如“注册 50 个人员识别 200 次通过率 97%”这比任何参考资料都可信。4.5 部署到服务器uWSGI、Nginx 与麒麟系统适配实验室服务器通常是 Linux部署时先建虚拟环境再迁移数据库python3 -m venv /opt/lab_door/venv source /opt/lab_door/venv/bin/activate pip install -r requirements.txt python manage.py collectstatic --noinput python manage.py migrate uwsgi --http :8001 --module lab_door.wsgi --static-map /static/opt/lab_door/static参数说明collectstatic 将 admin 静态文件汇总到指定目录migrate 按模型建表。uwsgi 的 --http 方便本地调试生产环境可以改为 --socket 连接 Nginx。在麒麟系统ARM64上编译 dlib 前先安装依赖库再用 --no-cache-dir 避免缓存导致安装中断sudo apt install build-essential cmake libopenblas-dev liblapack-dev libx11-dev pip install --no-cache-dir face-recognition dlibNginx 配置里把 80 端口请求转发到 uWSGIserver { listen 80; server_name lab.example.com; location /static/ { alias /opt/lab_door/static/; } location / { uwsgi_pass 127.0.0.1:8001; include uwsgi_params; } }uwsgi_pass 和 include 是 Nginx 的标准配置前者指定上游服务地址后者引入 uWSGI 请求头参数。用宝塔面板部署时可以在软件商店安装 Nginx然后在网站配置文件里粘贴这段内容再把 Python 项目设为常驻启动。5. 调优门禁识别的三个关键阈值、索引与异常场景5.1 用距离分布校准阈值tolerance 阈值不能只看默认值。常见做法是让 20 个注册人员和 20 个陌生人各识别 50 次把距离分布画出来取误识率和拒识率的交叉点。下面是一次实测数据阈值误识率拒识率0.450.0%2.3%0.500.3%1.2%0.601.8%0.4%这个场景可以选择 0.50误识率和拒识率都控制在 1.5% 以内。如果实验室要求严格就用 0.45代价是有人偶尔要刷两次。5.2 人员超过 500 时用 faiss 建索引线性遍历在几百人时还能扛超过 1000 人就会明显变慢。用 faiss 把所有人的特征建立成索引import faiss import numpy as np import pickle vectors np.vstack([pickle.loads(p.feature_vector) for p in persons if p.feature_vector]) index faiss.IndexFlatL2(128) index.add(vectors.astype(np.float32)) D, I index.search(query.reshape(1, -1), k1)逻辑说明IndexFlatL2 是精确的 L2 距离索引比 numpy 循环快很多D 是距离I 是人员下标。faiss 安装在服务器上可能涉及编译毕设环境可以直接用内存缓存替代。5.3 用 Django 中间件定位识别耗时识别接口慢不一定是模型问题也可能是网络传输或日志写入阻塞。加一个只统计接口耗时的中间件import time class AccessLogMiddleware: def __init__(self, get_response): self.get_response get_response def __call__(self, request): start time.time() response self.get_response(request) if request.path /api/face_auth/: print(elapsed:, time.time() - start) return response在 settings.py 的 MIDDLEWARE 里加入这个类后每次刷脸请求都会打印耗时。如果超过 300ms优先检查识别终端到 Django 的网络延迟再把特征比对从数据库移到 Redis 缓存。最后的调参建议先固定距离阈值再调帧率和图片质量最后才考虑换模型不要一开始就追求高精度引擎而拖慢反馈速度。本文还有配套的精品资源点击获取