
简介这份课程设计源码实现了一套基于Python、OpenCV、Django与人脸识别库的人脸识别系统面向计算机相关专业学生完成人脸识别类课题或期末大作业可用于快速复现核心功能与扩展二次开发。压缩包共130个文件约22.62MB主体为19个Python源码文件与41个编译后的pyc文件包含项目主逻辑、视图函数与配置脚本35张PNG图片与30张JPEG图片用于人脸样本、测试图像及界面展示另含SQLite数据库文件和TensorFlow模型参数文件pb、data、index支持模型的加载与持久化存储。该项目曾获导师指导并通过得到97分高分代码结构完整可直接运行。目前已有883人学习下载适合需要借鉴完整方案或学习DjangoOpenCV集成实践的读者。资源内预置了训练权重与示例图片能减少环境配置和数据集准备成本帮助使用者聚焦于识别流程与界面联调。1. 人脸识别系统课程设计为什么这套技术栈值得你抄作业一套能跑通的人脸识别系统放在课程设计里是加分项放在真实项目里就是人脸门禁、考勤机、课堂签到这类应用的雏形。标题里的 Python OpenCV Django 人脸识别库恰好覆盖了从图像处理、人脸特征提取到 Web 服务落地的完整链路。很多人栽在第一步装了 OpenCV 却 import 报错或者把 Django 当普通脚本写最后前端调不通接口。这套源码的价值不是给你一堆文件而是把「摄像头取帧 → 人脸检测 → 特征比对 → Web 展示」这条主线串起来新手能照着改熟手能直接抽走里面的识别模块。如果你正打算做类似课题或者需要快速交付一个带界面的人脸识别 demo跟着这篇文章的拆解走能少踩一半的坑。2. 人脸识别系统的核心链路检测、对齐、特征提取与比对2.1 检测与对齐OpenCV 的 Haar Cascade 与 Dlib 的 68 点模型人脸识别第一步不是「认人」而是「找到脸在哪」。常见做法是用 OpenCV 自带的 Haar Cascade 分类器做快速检测或者用 Dlib 的 HOG 线性分类器。Haar Cascade 的优势是模型文件小、CPU 上跑得快对正脸和轻度侧脸效果稳定但遇到大角度侧脸、遮挡、暗光就容易漏检。Dlib 的检测器更稳但依赖 dlib 库的安装Windows 上经常需要编译很多人在这里翻车。检测到人脸后下一步是对齐。Dlib 的 68 点人脸 landmarks 模型可以定位眼睛、鼻子、嘴巴等关键点然后通过仿射变换把人脸旋转到标准位置。这一步很多人会跳掉直接拿原图去提取特征结果同一张脸在不同角度下的特征距离比不同人的还大。对齐后的脸再送入特征提取模型识别率会有明显提升。import cv2 import dlib detector dlib.get_frontal_face_detector() predictor dlib.shape_predictor(shape_predictor_68_face_landmarks.dat) img cv2.imread(face.jpg) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces detector(gray, 1) # 1 表示上采样一次提高小脸检测率 for face in faces: landmarks predictor(gray, face) # 通过 landmarks 计算旋转矩阵做仿射变换对齐这里detector(gray, 1)的第二个参数是上采样次数值越大越容易检出小脸但速度会变慢。对齐操作可以用 OpenCV 的cv2.estimateAffinePartial2D配合左右眼坐标计算旋转矩阵。如果你只是做课程设计不做大角度姿态变化的人脸这一步可以简化成「只检测不旋转」但至少要把人脸区域裁出来。2.2 特征提取与比对从 face_recognition 库到欧氏距离阈值标题里说的「人脸识别库」在 Python 生态里最常用的就是 face_recognition它封装了 Dlib 的深度学习模型用起来几乎零门槛。face_recognition.face_encodings会返回一个 128 维的特征向量比对就是算两个向量之间的欧氏距离。阈值一般定在 0.45 到 0.55 之间低于阈值认为是同一个人高于则不是。这个阈值是个玄学。定得太小容易把同一个人不同光线下的照片误判为陌生人定得太大又容易把长得像的两个人混为一谈。我一般会先收集同一个人的多张照片算出特征向量两两之间的距离分布再取一个中间值。比如某个人自己的照片距离在 0.3 到 0.45 之间和其他人的距离在 0.7 以上那 0.55 就是安全线。import face_recognition known_img face_recognition.load_image_file(known.jpg) known_encoding face_recognition.face_encodings(known_img)[0] unknown_img face_recognition.load_image_file(unknown.jpg) unknown_encoding face_recognition.face_encodings(unknown_img)[0] distance face_recognition.face_distance([known_encoding], unknown_encoding)[0] print(fdistance: {distance:.3f}) if distance 0.5: print(匹配成功) else: print(未知人员)注意face_encodings返回的是数组如果一张图里有多张脸要通过face_locations把每张脸的位置传进去否则它会默认取最大的人脸。很多新手在测试时发现识别不准就是因为照片里有多个人脸特征向量取错了对象。另外face_recognition 底层是 Dlib 的 ResNet 模型第一次加载会花一点时间但识别速度在 CPU 上也能达到每张几十毫秒课程设计完全够用。2.3 注册库的设计用 SQLite 存特征还是存图片路径一个可用的系统一定有「注册」和「识别」两个流程。注册时把新人的照片和姓名入库识别时拿摄像头帧的特征去库里面比对。这里的存储设计直接决定后续的扩展性。最简单的做法是在 SQLite 里存图片的 Base64 字符串或者文件路径每次识别时重新计算库中所有图片的特征。如果库里有几十个人每次识别都要算几十次特征提取CPU 基本跑不动。更合理的方案是注册时就把 128 维特征向量作为 BLOB 存进数据库识别时只算一次摄像头帧的特征然后遍历库里的向量做距离计算速度快一个量级。import sqlite3 import numpy as np def save_face(name, encoding): conn sqlite3.connect(faces.db) blob encoding.tobytes() # 把 numpy 数组转成 bytes conn.execute(INSERT INTO face (name, encoding) VALUES (?, ?), (name, blob)) conn.commit() conn.close() def load_all_faces(): conn sqlite3.connect(faces.db) rows conn.execute(SELECT name, encoding FROM face).fetchall() faces [(name, np.frombuffer(enc, dtypenp.float64)) for name, enc in rows] conn.close() return faces特征向量用np.float64的字节序列存储不同的 face_recognition 版本可能返回不同 dtype读取时一定要保持一致否则比对结果全是乱的。这个细节看起来小实际排查时非常头疼。3. 用 Django 把识别能力包成 Web 服务从模型加载到 API 设计3.1 为什么选 DjangoSession、ORM 和静态文件管理都是现成的人脸识别本身和 Django 没什么关系但一旦要做「用户上传照片注册」、「管理员查看识别记录」这类功能Django 的 Session 机制和 ORM 能省下大量代码。课程设计通常要求有登录、注册、历史记录这些 Django 内置的 auth 模块直接能改比 Flask 裸写要稳妥。另外一个关键点是摄像头接入。网页端调摄像头一般用 WebRTC 或者简单的navigator.mediaDevices.getUserMedia然后把视频帧 POST 到 Django 后端识别。后端不能每次启动都重新加载一次 face_recognition 模型那样响应会慢到没法用。常见做法是写一个单例类在 Django 启动时把模型和已知人脸的特征库加载到内存。# recognition/service.py import face_recognition import numpy as np from django.conf import settings class FaceService: _instance None def __new__(cls): if cls._instance is None: cls._instance super().__new__(cls) cls._instance.known_faces [] cls._instance.known_names [] return cls._instance def load_from_db(self): from .models import FaceProfile profiles FaceProfile.objects.all() self.known_faces [np.frombuffer(p.encoding, dtypenp.float64) for p in profiles] self.known_names [p.name for p in profiles]这里用了单例模式Django 的 view 里每次调用FaceService()拿到的都是同一个实例特征库只要加载一次。注意load_from_db要在 Django 启动时或者首次请求前调用否则会出现「库里有人但识别不到」的诡异问题。3.2 上传图片注册的 API处理 multipart 数据和头像裁剪注册接口是 Web 端最基础的功能。前端用表单提交一张图片和一个姓名后端接收后先做一次人脸检测确认图片里确实有脸再提取特征入库。这里有个很多人忽略的问题用户上传的照片可能很大直接送进 face_recognition 会慢。应该在检测前先压缩到宽不超过 800 像素。# recognition/views.py from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from .service import FaceService from .models import FaceProfile import face_recognition import cv2 import numpy as np csrf_exempt def register(request): if request.method ! POST: return JsonResponse({error: method not allowed}, status405) name request.POST.get(name) image_file request.FILES.get(image) if not name or not image_file: return JsonResponse({error: missing name or image}, status400) # 读取并压缩图像 img cv2.imdecode(np.frombuffer(image_file.read(), np.uint8), cv2.IMREAD_COLOR) h, w img.shape[:2] if w 800: ratio 800 / w img cv2.resize(img, (800, int(h * ratio))) rgb_img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) locations face_recognition.face_locations(rgb_img) if not locations: return JsonResponse({error: no face detected}, status400) encoding face_recognition.face_encodings(rgb_img, locations)[0] FaceProfile.objects.create(namename, encodingencoding.tobytes()) FaceService().load_from_db() # 更新内存缓存 return JsonResponse({success: True})csrf_exempt在课程设计里可以用但真实项目里一定要处理 CSRF token。face_locations用的是 HOG 模型比 Haar 稳但速度稍慢压缩到 800 宽后一般能接受。如果图片里有多张脸face_encodings会按 locations 顺序返回多个向量注册时默认取第一张如果你想做多人注册得改成前端传 face_id 指定第几张。3.3 实时识别接口把视频帧送到后端还是前端跑推理实时识别有两种做法一种是前端把每一帧图片 POST 到后端另一种是前端用 TensorFlow.js 之类的方案在浏览器里跑。既然标题锁定了 Python OpenCV Django后端识别是主线。前端截帧的间隔不能太密否则后端口舌会被刷爆。我一般会控制在每秒 2 帧也就是setInterval里做 500ms 间隔的采集。后端接收帧后先做缩小和灰度处理再送去检测和编码。这里有个性能矛盾检测用 OpenCV 的 Haar 快但特征编码必须用 face_recognition。其实可以用 Haar 先快速检测出人脸区域再把人脸区域裁出来做编码比全图直接送 face_recognition 快不少。# recognition/views.py csrf_exempt def recognize_frame(request): image_file request.FILES.get(frame) if not image_file: return JsonResponse({error: no frame}, status400) img cv2.imdecode(np.frombuffer(image_file.read(), np.uint8), cv2.IMREAD_COLOR) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 级联分类器检测 cascade cv2.CascadeClassifier(cv2.data.haarcascades haarcascade_frontalface_default.xml) faces cascade.detectMultiScale(gray, scaleFactor1.1, minNeighbors5, minSize(80, 80)) if len(faces) 0: return JsonResponse({result: unknown, faces: []}) best_name unknown best_distance 1.0 for (x, y, w, h) in faces: face_crop img[y:yh, x:xw] rgb_crop cv2.cvtColor(face_crop, cv2.COLOR_BGR2RGB) encodings face_recognition.face_encodings(rgb_crop) if not encodings: continue distances face_recognition.face_distance(FaceService().known_faces, encodings[0]) min_idx np.argmin(distances) if distances[min_idx] 0.5 and distances[min_idx] best_distance: best_distance distances[min_idx] best_name FaceService().known_names[min_idx] return JsonResponse({result: best_name, distance: round(best_distance, 3)})detectMultiScale的minNeighbors5能过滤掉很多误检但如果你发现漏检率高可以降到 3。minSize(80, 80)是经验值摄像头离人太远时把脸裁得很小特征提取质量会下降宁可检测不到也别提一个糊的特征。每次识别的耗时大头在face_encodings上如果人脸框比较大耗时可能到 100ms 级别所以前端截帧频率一定不能太高。4. 从源码到可运行项目环境配置、数据库迁移与启动排错4.1 Python 虚拟环境与依赖版本为什么不要直接用 pip install 全家桶拿到源码后最忌讳的是直接pip install -r requirements.txt然后跑python manage.py runserver。因为 face_recognition 依赖 dlib而 dlib 在 Windows 上需要 CMake 和 Visual Studio 编译工具很多人卡在这一步半小时。如果你用的是 Windows最省事的方式是用 pip 安装预编译的 dlib wheelpip install dlib19.24.0 pip install face_recognition pip install django opencv-python numpy如果pip install dlib报错说明你的 Python 版本太高或者没有装 Visual C Build Tools。建议直接用 Python 3.9 或 3.10这两个版本对应的 dlib wheel 相对好找。OpenCV 的包名要注意开发用opencv-python如果你还需要用到cv2.VideoCapture读取视频文件没问题但如果涉及视频编解码的扩展功能可以再装opencv-contrib-python不过课程设计一般用不到。python -m venv venv venv\Scripts\activate # Windows source venv/bin/activate # Linux/macOS pip install django4.2 opencv-python face_recognition numpyDjango 4.2 和 Python 3.10 组合比较成熟。如果源码里用的是 Django 3.x跑在 4.2 上大概率会出现django.core.exceptions.ImproperlyConfigured错误主要原因是urls.py里的url()函数被移除了要改成re_path()。这个排错思路很重要——先看报错是来自哪一层再去对应改。4.2 数据库迁移与初始数据Django ORM 和 SQLite 的坑源码里如果给了models.py那么第一次运行时需要做makemigrations和migrate。很多人直接运行python manage.py migrate发现没有建出人脸表因为 Django 只会迁移当前项目里注册过的 app。先确认settings.py的INSTALLED_APPS里有你的 app 名字。python manage.py makemigrations recognition python manage.py migrate如果migrate报错说表已存在可以用python manage.py migrate --fake recognition跳过已有表。SQLite 文件默认在项目根目录删掉重新迁移也是常见的后悔药。注意 face_recognition 的特征向量是一个 128 维 float64 数组存进 SQLite 时用 BLOB 字段Django 的BinaryField正好对应。读取时转 numpy 数组时一定要指定dtypenp.float64不然距离计算的结果全是错的。4.3 启动后常见报错ModuleNotFoundError 和 OpenCV 路径问题我在搜索引擎的热搜词里看到太多人碰到ModuleNotFoundError: No module named cv2但已经装过 opencv。这种情况八成是因为虚拟环境和系统环境混用了。比如你在系统的 pip 下装了 opencv又在 venv 里跑 Pythonimport 自然失败。用pip list看看你当前环境的包列表别只看pip show输出。另一个高频坑是 OpenCV 的cv2.error: OpenCV(4.4.0)在调用cvtColor或imdecode时报错。比如传 RGB 转 BGR 时遇到空的 numpy 数组或者图片路径含中文导致imread返回 None。判断方法是在读图后先判断img is None如果是 None多半是路径或文件流问题。Django 里接收UploadedFile时不能直接传给cv2.imread必须用np.frombuffer先转字节流这个和本地读文件完全是两回事。不过相比之下Django 执行查询的时候也有经典坑FaceProfile.objects.filter(name张三).delete()在执行后如果同时有外键关联可能报ProtectedError。简单的做法是改成逐条obj.delete()。这里的索引设计也很关键识别时用 Python 遍历所有特征向量的距离是 O(n) 的n 到几千时还行上万后就要考虑向量索引了。课程设计一般几十个人不用焦虑性能。5. 课程设计答辩前必查的五个雷区从 OpenCV 摄像头打到人脸阈值调优5.1 摄像头打开失败的三种伪装很多源码里会写cv2.VideoCapture(0)取摄像头但在 Django 的 view 里VideoCapture往往打开失败返回的read()是(False, None)。原因有三种一是笔记本的摄像头被其他程序占用比如 Zoom 或微信二是索引不对多摄像头电脑上0不一定是你想要的那个三是在 Web 容器里没有访问摄像头的权限。应对思路很简单前端用getUserMedia拿摄像头帧后端完全不知道摄像头存在这样避开了所有权限和索引问题。如果你确实要在后端直接开摄像头做演示可以写一个camera_test.py独立脚本验证不要在 Django 里调试。import cv2 cap cv2.VideoCapture(0) if not cap.isOpened(): print(camera open failed) exit(1) ret, frame cap.read() if ret: cv2.imwrite(test.jpg, frame) cap.release()5.2 人脸特征库为空导致识别结果永远显示「unknown」这是课程设计 demo 里最丢人的场景——注册了十几个人但识别时说谁都不认识。原因绝大多数是FaceService单例里的known_faces是空的因为你启动 Django 时还没加载数据库。我一般会在apps.py的ready()方法里调用一次FaceService().load_from_db()这样只要 Django 一启动特征库就在内存里。# recognition/apps.py from django.apps import AppConfig class RecognitionConfig(AppConfig): default_auto_field django.db.models.BigAutoField name recognition def ready(self): from .service import FaceService FaceService().load_from_db()注意ready()会在migrate时也执行如果此时数据库表还没建好load_from_db可能报错。稳妥一点是在ready()里包一层try-except等系统跑起来后再手动触发加载。很多老手会鄙视这种写法但课程设计阶段能用、稳定就行。5.3 阈值 0.5 不是万能的光线和分辨率的影响人脸特征提取对光照非常敏感。同一个学生在教室白炽灯下注册走廊昏暗灯光下识别距离可能从 0.3 跳到 0.7直接变成「陌生人」。我踩过的坑是阈值定死 0.5结果下午四点阳光照在脸上识别失败换成 0.6 又好用了。但 0.6 又会带来误判风险。我的建议是不要用固定阈值而是保存这个人最近几次识别成功的平均距离动态调整这个人自己的阈值。课程设计如果嫌麻烦至少准备两张注册照片一张正常光、一张暗光。代码里可以这样处理# 注册时支持同一人多张照片 encodings [] for path in [zhangsan_1.jpg, zhangsan_2.jpg]: img face_recognition.load_image_file(path) enc face_recognition.face_encodings(img) if enc: encodings.append(enc[0]) # 存成 dist 列表比对的用时取最小距离5.4 前端视频帧 POST 被 Django CSRF 拦截Django 默认所有 POST 请求都要带 CSRF token如果前端只用fetch不带 token会收到 403。课程设计里最简单的解法是在后端 view 上加csrf_exempt但如果你不想用装饰器可以改前端代码去读 cookie 里的csrftoken。注意如果你用csrf_exempt需要在请求的Content-Type上用application/json或multipart/form-data别让 Django 的request.FILES拿不到文件。5.5 模型加载慢的「第一次请求卡死」错觉face_recognition 底层加载 Dlib 的模型文件大概需要 23 秒第一次请求时你会觉得像是死锁了。实际上后端正在加载模型第二次请求就快了。我一般在ready()里强制调用一次face_recognition.face_encodings(np.zeros((100, 100, 3), dtypenp.uint8))来热身这样第一个用户进来时就无感。这个技巧说不上多高级但演示时很管用。6. 把课程设计升级成真正的「系统」加一张 Redis 缓存表和前端体验优化如果你只想拿个学分前面五章够了。但如果答辩老师问你「并发来了几百个人怎么办」「识别记录存在哪」你得有一个可持续的答案。我通常会做两件事一是把特征向量缓存从 Django 进程内存挪到 Redis二是增加识别流水表记录每一次识别的距离和结果。Redis 缓存的好处是 Django 多进程部署时每个 worker 共享同一份特征库否则用 gunicorn 跑 4 个 worker每个都要加载一次模型和特征内存翻四倍。缓存 key 用face:known_facesvalue 存 pickle 序列化后的(names, encodings)列表读取时反序列化。这个改动很小但也体现你理解 Web 应用的状态管理。import redis import pickle r redis.Redis(hostlocalhost, port6379, db0) def load_known_faces_from_redis(): raw r.get(face:known_faces) if raw: names, encodings pickle.loads(raw) return names, encodings # 从数据库加载并写回缓存 profiles FaceProfile.objects.all() names [p.name for p in profiles] encodings [np.frombuffer(p.encoding, dtypenp.float64) for p in profiles] r.set(face:known_faces, pickle.dumps((names, encodings)), ex3600) return names, encodings前端方面建议把截帧做防抖检测到人脸后再执行识别请求人脸没了就停止。用canvas把video帧缩小成 320 宽再转 blobPOST 出去的字节数从几 MB 降到几十 KB后端响应时间直接减半。识别结果用「绿色框 姓名」叠加在视频上比单纯显示文字更像真实产品。另一个值得加的功能是识别记录。每次识别都往recognition_log表里写时间、照片路径、识别结果和距离分数。下面这张表是你答辩时最好的素材能证明系统有数据沉淀能力而不是一次性 demo。字段名类型说明idint主键自增namevarchar识别出的姓名unknown 也算confidencefloat特征距离越小越可信image_pathvarchar保存当前帧的截图路径created_atdatetime识别时间这个表的查询要加上created_at索引否则数据量一大Django 的order_by(-created_at)会全表扫描。做课程设计时展示「查某个人今天的识别记录」用 Django 的 ORM 一条filter(namexxx)就能完成但你是否知道如何优化它是拉开分数的地方。最后说一个我自己的习惯每次拿到这类人脸识别源码包第一件事不是跑起来而是抽出它的识别核心单独写个脚本用自己手机里的照片测试曲线。真正的技术消化不是能在 Django 页面上看到一个名字而是你能说出「为什么这个距离 0.48 算认识0.52 算不认识」。当你亲手调过阈值、看过日志里的 fake 记录答辩时老师问什么你都不慌。希望这篇实战笔记帮到你让你少花时间在环境依赖上把精力放在真正体现能力的模块设计上。本文还有配套的精品资源点击获取