
简介面向计算机相关专业毕业设计、课程设计及项目实战学习者这份基于Python与Django的人脸识别门禁管理系统提供了完整可运行的源码、数据库与必要配置说明可帮助快速搭建同类后台系统。压缩包共2001个文件其中1668个Python文件构成核心业务逻辑124个HTML模板与14个CSS负责页面结构及样式另有JavaScript脚本、配置文件、少量文档与依赖组件资源整包约415MB目录层级清晰便于按模块定位代码。当前已有125人学习下载。系统覆盖人脸识别、门禁权限管理、人员信息维护与通行记录查询等典型场景代码经过本地编译调试并获高分评价适合用于理解Django框架与图像识别技术的实际结合附带数据库文件与项目文档既能直接部署演示也可作为课程设计、期末大作业或二次扩展的参考样板。对需要参考完整项目结构、掌握从环境配置到后台管理开发流程的中等水平学习者是一份实用且完整的毕业设计资料。1. 人脸识别门禁比「能跑」更重要的是请求链路对得齐答辩现场最常见的翻车场景是演示人脸识别时只用一张提前存好的照片点一下按钮系统弹出「验证通过」。可一旦把摄像头对准真人要么画面黑屏要么连续报「未识别」。这个基于 Python Django 的人脸识别门禁系统之所以能在答辩里拿 98 分核心不是识别算法多先进而是把「摄像头帧 → HTTP 请求 → Django 视图 → 特征比对 → 门锁控制」这条链路完整打通了并且每个环节都有日志和数据库记录兜底。很多人拿到毕设源码跑不起来往往不是模型没装好而是没搞清楚 Django 的请求应答模型和摄像头视频流的推流模式根本不是一回事。这篇博文按我拆项目时的顺序来写先看它怎么组织 Django 工程和数据表再讲人脸识别模块怎么封装才能被视图直接调用然后把门禁闭环从采集到落库走一遍最后把最容易卡人的 dlib 安装、光照干扰、摄像头打开失败这些坑连根拔出来。无论是做毕业设计、课程设计还是想把人脸识别模块嵌进自己的 Web 项目这套拆解都够用。2. 项目结构设计App 划分、特征入库与数据库读写节奏2.1 为什么把人员表单独放一个 Django App拿到源码建议先看目录而不是先装依赖。这个项目里除了 Django 默认的settings.py和urls.py实际的业务代码集中在face_access这个 App 里。模型本身只有两张核心表一张存人员档案一张存进出记录。把人员表单独建 App 的好处是后续加考勤、加访客预约时不用重构而且人脸特征向量可以直接作为人员表的一个字段避免了每次比对时重复去目录里读图。我见过不少毕设把人脸图片存到 media 目录然后在视图里用os.listdir再循环检测这种写法演示没问题但真到了门禁场景人员上千时每次比对都要读一遍磁盘延迟高到没法用。这个项目的做法是注册时把特征向量算好持久化进数据库比对时只做一次数学距离计算速度能快两个数量级。2.2 数据表字段设计与 sqlite 的取舍Django 默认配置里用的是sqlite3实际生产门禁并发写入频繁时会遇到锁冲突但作为毕设和中小型内部系统sqlite 足够而且迁移和备份都方便。关键建模代码如下from django.db import models class Person(models.Model): name models.CharField(max_length50, verbose_name姓名) employee_no models.CharField(max_length20, uniqueTrue, verbose_name工号) face_image models.ImageField(upload_tofaces/, verbose_name人脸照片) face_encoding models.BinaryField(nullTrue, blankTrue, verbose_name人脸特征向量) created_at models.DateTimeField(auto_now_addTrue) class Meta: db_table person verbose_name 人员档案 class AccessLog(models.Model): person models.ForeignKey(Person, on_deletemodels.SET_NULL, nullTrue) recognized models.BooleanField(verbose_name是否识别成功) confidence models.FloatField(nullTrue, blankTrue, verbose_name比对距离) image models.ImageField(upload_tocaptured/, verbose_name抓拍图) created_at models.DateTimeField(auto_now_addTrue) class Meta: db_table access_log verbose_name 进出记录face_encoding字段用的是BinaryField直接把 numpy 数组转成二进制存进去。很多人会问为什么不用 JSON 或数组字段因为face_recognition.face_encodings()返回的是 numpy 的 ndarray转成bytes存二进制读取时再还原序列化最干净。AccessLog是门禁的审计基础每次识别不管成功失败都落库。created_at带auto_now_add不需要手动写当前时间这和后面展示的视图代码是配套的。2.3 初始化数据注册时计算特征而不是使用时计算在 Django shell 里注册一个新人员需要先算好特征再入库python manage.py shellimport face_recognition from face_access.models import Person person Person(name张三, employee_noA001) person.face_image.save(zhangsan.jpg, open(zhangsan.jpg, rb)) image face_recognition.load_image_file(zhangsan.jpg) encodings face_recognition.face_encodings(image) if encodings: person.face_encoding encodings[0].tobytes() person.save() print(特征提取成功) else: print(未检测到人脸)这段代码里最关键的手法是tobytes()和保存前判断encodings是否为空。如果照片里没有对齐的人脸face_encodings返回空列表直接取下标会抛IndexError这也是很多新手最常见的报错之一。参数说明load_image_file内部调用 PIL支持 jpg/png/webp它会自动把图片转为 RGB 模式。这里不用关心图片原本是 BGR 还是 RGB因为该函数处理好了。如果自己用 OpenCV 读图后面章节会专门讲转换问题。3. 人脸特征提取与比对封装成可复用的识别模块3.1 为什么选 face_recognition 而不是 OpenCV 的 LBPHOpenCV 自带的 LBPH 人脸识别器训练快、模型小但它的核心是局部二值模式直方图对光照变化和表情变化非常敏感秒级的比对性能虽然不错实际识别率在门禁这种复杂光线的环境下往往只有 70% 左右。这个项目选用face_recognition库它基于 dlib 的深度学习模型实测干净环境下识别率能到 95% 以上而且封装得简洁几行代码就能拿到 128 维特征向量。另一个理由是 Dlib 的模型做特征提取不依赖 GPUCPU 上跑一帧 1080p 图大约 0.4 秒完全能满足毕设演示。如果换更好的算法像 ArcFace精度更高但安装和依赖复杂度会劝退多数人不适合毕设周期。3.2 门禁识别类的封装与参数明细为了避免视图代码变得又臭又长我把识别逻辑单独抽成一个类放在utils/face_recognizer.py里import face_recognition import numpy as np class FaceRecognizer: TOLERANCE 0.5 def __init__(self, modelhog): self.model model self.known_encodings [] self.known_names [] def load_known_faces(self, persons): persons: QuerySet of Person from Django ORM for p in persons: if not p.face_encoding: continue enc np.frombuffer(bytes(p.face_encoding), dtypenp.float64) self.known_encodings.append(enc) self.known_names.append(p.name) def recognize_frame(self, frame_rgb, toleranceNone): tol tolerance or self.TOLERANCE box_locations face_recognition.face_locations(frame_rgb, modelself.model) encodings face_recognition.face_encodings(frame_rgb, box_locations) results [] for (top, right, bottom, left), enc in zip(box_locations, encodings): distances face_recognition.face_distance(self.known_encodings, enc) min_idx np.argmin(distances) name unknown if distances[min_idx] tol: name self.known_names[min_idx] results.append({ name: name, distance: round(float(distances[min_idx]), 4), box: [top, right, bottom, left] }) return resultsTOLERANCE默认 0.5这个值需要根据你的实际用户量调。越小越严格0.4 以下容易出现合法的员工被拦在门外0.6 以上则可能出现误放行。从我的经验看室内统一光照下用 0.45 到 0.55 之间比较合适。modelhog表示用 HOG 方向梯度金字塔检测人脸CPU 上每帧约 80 毫秒速度较快想更准可以把 model 改成cnn但这需要 GPU 或者接受很慢的速度。np.frombuffer把数据库里的二进制还原成 float64 数组这里要注意数据类型必须是 float64因为 face_recognition 的特征是 128 个 float64 组成的向量。face_distance返回的是欧氏距离值越小代表越相似。3.3 视图里如何集成封装好的识别类Django 视图只负责接收请求和返回 JSON识别逻辑全部交给上面的类。一个典型的识别接口实现如下import base64 import cv2 import numpy as np from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from face_access.models import Person, AccessLog from utils.face_recognizer import FaceRecognizer recognizer FaceRecognizer() def refresh_recognizer(): recognizer.load_known_faces(Person.objects.exclude(face_encodingNone)) csrf_exempt def recognize_api(request): if request.method ! POST: return JsonResponse({error: must be POST}, status405) image_b64 request.POST.get(image) if not image_b64: return JsonResponse({error: no image}) img_bytes base64.b64decode(image_b64.split(,)[-1]) img_array np.frombuffer(img_bytes, dtypenp.uint8) bgr_frame cv2.imdecode(img_array, cv2.IMREAD_COLOR) rgb_frame cv2.cvtColor(bgr_frame, cv2.COLOR_BGR2RGB) refresh_recognizer() results recognizer.recognize_frame(rgb_frame) if results and results[0][name] ! unknown: person Person.objects.get(nameresults[0][name]) AccessLog.objects.create( personperson, recognizedTrue, confidenceresults[0][distance] ) return JsonResponse({status: success, data: results[0]}) else: AccessLog.objects.create(recognizedFalse, confidenceNone) return JsonResponse({status: denied, data: results})refresh_recognizer每次请求都会重新读数据库这个在真实使用中会有性能瓶颈但毕设演示时能保证新增人员后立刻生效。更优雅的做法是放在 Django 的signals.post_save里增量更新这在第 5 章会提到。为什么要用cv2.imdecode而不是直接给 face_recognition 传 base64因为前端可能传的是带data:image/jpeg;base64,前缀的字符串必须先去掉前缀再解码。split(,)[-1]就是为了处理这种常见格式。另外一定不要忘了cv2.COLOR_BGR2RGB转换OpenCV 默认读出来是 BGR 顺序直接传给 face_recognition 会导致识别率骤降。4. 门禁闭环从摄像头抓拍、图片上传到数据库记录4.1 统一使用 POST base64 图像绕开浏览器摄像头权限问题前端如果直接调用摄像头需要 HTTPS 或者 localhost否则浏览器会拦截。这个项目在演示时采用「抓拍后上传」的方式摄像头由前端页面借用 MediaDevices 相机在视频帧中截取一帧再转成 base64 字符串 POST 给 Django 接口。这样做的好处是后端部署时不用关心摄像头驱动和权限任何能跑 Django 的机器都能接收图像。如果你要在本机直接用 Python 打开摄像头那么这个脚本更合适import cv2 import base64 import requests cap cv2.VideoCapture(0) if not cap.isOpened(): print(无法打开摄像头检查索引或权限) exit(1) ret, frame cap.read() if ret: _, buffer cv2.imencode(.jpg, frame) b64_img base64.b64encode(buffer).decode(utf-8) resp requests.post( http://127.0.0.1:8000/recognize/, data{image: b64_img} ) print(resp.json()) cap.release()参数说明cv2.VideoCapture(0)的 0 是摄像头索引笔记本内置通常为 0外接 USB 摄像头可能变成 1 或 2。Linux 下可以用ls /dev/video*查看可用节点。直接把摄像头帧原样编码成 jpg质量参数默认是 95压缩率足够省得在网速差的演示现场耗时过长。4.2 延时与并发控制不要同步阻塞整个线程Django 默认的同步执行机制下如果在recognize_api里执行识别那个请求会一直占着 worker 进程识别一次最长可能要 0.3 秒再加上refresh_recognizer读取所有人员时间极端情况下一个请求超过 1 秒。对门禁系统来说1 秒已经会让排队的人感到明显卡顿。我通常在视图中用线程池限制同一时间只处理一个识别请求额外的请求直接排队或返回「识别中请稍后」from concurrent.futures import ThreadPoolExecutor recognition_executor ThreadPoolExecutor(max_workers1)max_workers1可以防止多个请求同时导致数据库混乱也能够保护摄像头帧的串行处理。当然这个方案只适合毕设和低并发场景真正商用要上消息队列。4.3 记录日志不放过每一次失败识别很多毕设只记录成功的记录导致答辩时被问到「如果有人冒充怎么办」答不上来。这个项目的AccessLog表不管成功失败都会写而且把抓拍到的原图存到captured/目录这样事后排查非常方便。我在演示时特意准备了一个陌生人照片连续刷几次失败记录界面上的时间轴和图片一目了然这种细节给答辩老师留下的印象很深。实际运行中如果AccessLog里大量出现recognizedFalse你需要检查的事按优先级排序摄像头画面中的人脸是不是太小小于 120x120 像素时误识率剧增注册照片和现场照片光照差异是否过大是否忘了把最新人员的特征加载进recognizer。4.4 门锁控制的模拟实现到门锁这一步不需要接真实硬件用 Django 的 URL 接口模拟即可。比如识别成功后请求/door/open/返回一个 json 表示继电器闭合 5 秒import time from django.http import JsonResponse def open_door(request): # 这里替换成调用 GPIO 或串口指令 return JsonResponse({door: open, duration: 5})如果你后续要接树莓派只需要在这个视图里用RPi.GPIO或serial发一个高电平信号。这样设计可以把 Web 层和硬件层解耦测试时随时用 mock 数据。5. 部署与验证dlib 安装、光照干扰和测试用例设计5.1 安装 dlib 的高效路径pip install face_recognition会自动装 dlib但 Windows 上经常因为缺少 C 编译环境报错。常见的做法是先装一个预编译的 dlib wheel:pip install dlib19.22.0如果这条命令失败则用 conda:conda install -c conda-forge dlibLinux 系统下如果遇到g: error: unrecognized command line option -marchnative说明编译选项和 CPU 不匹配可以编辑dlib的 CMake 配置关掉这个选项但更省事的是直接安装系统包:sudo apt install libdlib-dev pip install face_recognition --no-binary dlib安装完验证一下能不能导入:import face_recognition print(face_recognition.__version__)face_recognition没有公开版本号一般输出模型路径表示正常。只要不报错就说明 dlib 和 numpy 版本兼容。5.2 光照干扰的应对手段直方图均衡化与角度归一化人脸识别最大的干扰是逆光。当摄像头背靠窗户时人脸区域过暗特征提取困难。我在这个项目的识别接口里加了一个预处理分支如果图片整体亮度均值小于 800-255 范围就做一次 CLAHE 自适应直方图均衡化lab cv2.cvtColor(bgr_frame, cv2.COLOR_BGR2LAB) l, a, b cv2.split(lab) clahe cv2.createCLAHE(clipLimit3.0, tileGridSize(8, 8)) l2 clahe.apply(l) lab2 cv2.merge((l2, a, b)) bgr_frame cv2.cvtColor(lab2, cv2.COLOR_LAB2BGR)clipLimit3.0是限制对比度放大的阈值太大容易引入噪点太小就没有增强效果。tileGridSize(8,8)表示在 8x8 的局部区域分别做均衡化。这个增强不是每次都要做只针对暗光场景否则图片整体亮度高时反而会失真。角度方面face_recognition默认只检测正脸如果门禁机装在侧面需要将face_locations换成face_recognition.face_locations的number_of_times_to_upsample1它可以检测更远更小的人脸但会增加耗时。5.3 测试用例一张照片打天下的验证方法直接用真实相机反复测速度慢、难复现更好的方式是准备一个测试脚本给定一组注册照片和一张待识别的照片批量跑识别率python test_recognition.py --known_dir faces_known --test_dir faces_test --tolerance 0.5import argparse import face_recognition import os ap argparse.ArgumentParser() ap.add_argument(--known_dir, requiredTrue) ap.add_argument(--test_dir, requiredTrue) args ap.parse_args() known_encs [] known_names [] for fname in os.listdir(args.known_dir): img face_recognition.load_image_file(os.path.join(args.known_dir, fname)) encs face_recognition.face_encodings(img) if encs: known_encs.append(encs[0]) known_names.append(fname.split(_)[0]) correct 0 total 0 for fname in os.listdir(args.test_dir): img face_recognition.load_image_file(os.path.join(args.test_dir, fname)) encs face_recognition.face_encodings(img) if not encs: print(f{fname}: 未检测到人脸) continue total 1 dist face_recognition.face_distance(known_encs, encs[0]) predict known_names[dist.argmin()] if dist.min() 0.5 else unknown actual fname.split(_)[0] if predict actual: correct 1 else: print(f{fname}: 预测 {predict} 实际 {actual} 距离 {dist.min():.3f}) print(f识别率: {correct}/{total} {correct/total:.1%})这个脚本最有用的地方是能帮你快速确定tolerance的合理值。把所有测试结果的距离值分布画出来通常同一人距离在 0.35-0.5 之间不同人距离在 0.6 以上中间的空隙就是你的容错区间。如果发现重叠就要收集更多人的照片重新调整。5.4 数据库迁移时容易踩的两个坑每次改了模型后必须执行python manage.py makemigrations face_access和python manage.py migrate否则数据库字段对不上。另外一旦表里已经有数据给Person增加face_encoding字段时要提供默认值否则迁移会报错。另一个坑是ImageField的upload_to目录不存在时Django 会自动创建但如果你手动把项目拷贝到新机器media/目录缺失上传头像时会报找不到路径。我的做法是启动前用 Django 的信号自动创建目录import os from django.db.models.signals import post_migrate from django.conf import settings def ensure_media_dir(sender, **kwargs): for d in [faces, captured]: os.makedirs(os.path.join(settings.MEDIA_ROOT, d), exist_okTrue) post_migrate.connect(ensure_media_dir)这样一来每次migrate后目录必然存在减少了部署时的意外。最后落一个具体的验证技巧用上面那个test_recognition.py对门禁系统进行压力测试每次调低tolerance0.05看误识率和拒识率如何变化。找到一个让两组指标交叉最低的平衡点后再把这个值写进FaceRecognizer.TOLERANCE。我用这个方法在实验室环境把误识率控制在了 1% 以下答辩时直接展示这个调参过程比空口说「系统很准」有说服力得多。本文还有配套的精品资源点击获取