☰
基于OpenCV与Django的人脸识别系统课设源码拆解与实战
2026/10/1 11:00:58 网站建设 项目流程

简介:面向高校课程设计场景的完整人脸识别系统源码包,基于Python、OpenCV、Django与人脸识别库实现,适合计算机相关专业学生参考或二次开发,解决从模型构建、数据加载到Web端实时识别的全流程落地问题,覆盖数据采集、模型推理、页面交互等模块。资源压缩包中共包含130个文件,总大小约22.62MB,类型分布清晰:19个py源码与41个pyc编译文件构成项目核心逻辑,35个png与30个jpeg图片作为人脸样本数据,sqlite3数据库用于存储用户与识别记录,pb、data、index等文件为训练好的模型权重,便于直接调用。项目已获导师指导并通过,是97分高分课程设计大作业,代码完整、可直接运行。目前已有883人学习下载,适合需要快速搭建人脸识别课程项目、理解Django前后端交互、整理实验报告或进行二次开发的同学。

1. 人脸识别系统课设:这个包不是交差货,是真的能跑起来

如果你正在做“基于Python+OpenCV+Django的人脸识别系统”课程设计,大概率已经搜到过一堆号称“完整源码”的压缩包,下载下来不是缺依赖就是模型文件是空的。这个包不同,解压后能看到variables.data-00000-of-00002、variables.index这样一组TensorFlow格式的模型文件,还有四个哈希命名的jpeg样本图,说明它带的是训练好的深度模型权重,不是拿OpenCV的Haar级联凑数的Demo。系统走的是“OpenCV采集人脸 + 深度模型提取特征 + Django做Web端交互”的完整链路,适合做Java/Python课设里带Web界面的那类题目,也适合想快速跑通一个可演示人脸识别项目的从业者。下文按拆包、跑通、踩坑、改业务四个阶段把它过一遍。

2. 技术栈拆解:为什么是OpenCV + Django + 深度模型,而不是纯Haar级联

2.1 三个组件各干各的活,互不抢戏

先看这个项目选型的逻辑。OpenCV负责的不是“识别”本身,而是图像预处理和人脸检测框定位。很多课设项目只用OpenCV自带的Haar Cascade分类器去做人脸检测和识别,效果在小规模静态图片上勉强能看,光线一变、角度一歪就垮。这个包里带的是TensorFlow格式的模型文件,说明作者把识别环节交给了深度模型——常见做法是用dlib或face_recognition库做人脸编码,再训练或加载一个分类器;而更完整的课设版本会用FaceNet这类模型把一张人脸压成一个128维的特征向量,然后拿这个向量做距离比对。

Django在这里的角色是Web容器和业务逻辑层。人脸识别本身是离线计算,Django负责接收上传图片、调用识别模块、把结果渲染到页面上,以及管理用户上传记录。这三个组件的关系是:浏览器/摄像头 → Django视图函数 → OpenCV预处理 → 人脸编码/比对 → 结果回传。拆开看,每一层都是标准技术,组合起来就是一个完整可演示的课设架构。

2.2 模型文件先拆包验证,别急着配环境

项目正文里出现的variables.data-00000-of-00002、variables.data-00001-of-00002、variables.index,是TensorFlow SavedModel格式的模型分片文件。看到这组文件,先做一个判断:这个模型是用TensorFlow 1.x还是2.x保存的。变量分片文件+index的组合在两种版本里都存在,但加载方式不同。TF 1.x要用tf.saved_model.loader.load,TF 2.x用tf.saved_model.load。如果环境版本和模型保存版本不匹配,会直接抛ProtocolBuffer格式错误或OpKernel注册错误。

我一般会先写一段探针代码确认模型结构,再决定要不要重训练。探针代码的作用不是跑通Demo,而是确认输入张量的shape和dtype,避免后面接OpenCV预处理时尺寸对不上。

import tensorflow as tf # 尝试用当前环境加载模型,若失败说明版本不一致 try: model = tf.saved_model.load("models/facenet") print("模型加载成功") # 拿到模型的签名,确认输入输出 infer = model.signatures["serving_default"] print("输入结构:", infer.structured_input_signature) print("输出结构:", infer.structured_output_signature) except Exception as e: print("加载失败,信息如下:") print(repr(e)[:500])

这段代码的关键在serving_default这个签名。SavedModel保存时可能带多个签名,加载后必须用具体的签名名调用模型。如果签名名不对,会报KeyError,报错信息里会列出所有可用签名,照着改就行。输入结构会显示期望的图片张量shape,比如TensorSpec(shape=(None, 160, 160, 3)),这代表支持batch维度、160x160的RGB图——后面OpenCV的预处理就得按这个尺寸来resize。

如果模型加载失败,备选方案是直接用face_recognition库替代。这个库封装了dlib的预训练模型,pip安装后一行代码就能生成128维人脸特征。课设评分不会因为你用的库是face_recognition还是TensorFlow就加分或扣分,关键是识别准确率和系统完整性。先想清楚:你的目标是学会人脸识别的完整流程,还是想在答辩时展示模型训练细节。前者用现成库就够了,后者才需要啃TF模型。

2.3 Django接入识别模块的两个组织方式

Django项目里放识别代码,有两条路。一条是把识别逻辑写进views.py,适合快速Demo;另一条是单独建一个recognition模块,封装成类或函数,视图层只负责调接口。这个包既然同时有模型文件和图片样本,大概率走了第二条路。问题在于:识别模型加载一次要几百毫秒到几秒不等,如果每次请求都重新加载模型,Web页面会卡到怀疑人生。

正确做法是在模块导入时就加载模型,利用Python模块缓存的特性让模型常驻内存。我见过不少课设代码把tf.saved_model.load写在视图函数内部,结果每刷新一次页面就重新加载一次模型,答辩现场翻车。实际操作时,模型的加载应该放在模块顶层,或者用一个懒加载单例包装起来。

# recognition/facenet_service.py import tensorflow as tf import cv2 import numpy as np from django.conf import settings _model = None def get_model(): global _model if _model is None: model_path = settings.MODEL_PATH # 在settings.py里配置 _model = tf.saved_model.load(model_path) return _model def extract_embedding(image_bgr): # 输入是OpenCV读到的BGR图,先转RGB再resize到模型要求的尺寸 rgb = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2RGB) resized = cv2.resize(rgb, (160, 160)) # 归一化到 [-1, 1],大多数FaceNet系模型需要这个区间 normalized = (resized.astype(np.float32) - 127.5) / 128.0 # 加batch维度 batched = np.expand_dims(normalized, axis=0) infer = get_model().signatures["serving_default"] embedding = infer(tf.constant(batched)) return embedding

参数说明:settings.MODEL_PATH在Django的settings.py里设置绝对路径或相对路径,注意Windows和Linux的路径分隔符差异。(resized.astype(np.float32) - 127.5) / 128.0是FaceNet系列模型通用的归一化公式,数值范围基本在-1到1之间。如果模型输出的向量距离分布异常,先检查这一步是不是漏了。

视图层调用时,只负责接收请求、调用extract_embedding、和数据库里的特征做比对、返回结果。这样职责就拆开了:Django管Web流程,识别模块管算法。

3. 本地复现全流程:从环境搭建到跑通第一个识别请求

3.1 环境版本搭配与安装

这个项目的依赖分三层:Python基础环境、OpenCV图像处理层、Django和识别库的Web+算法层。先说版本搭配,这是新手最容易卡住的地方。Python 3.8到3.10都兼容,不需要最新版本,反而Python 3.11以上某些库的wheel包不一定全。OpenCV用opencv-python发行版就够了,不用自己编译源码,除非你要用CUDA加速。

Django版本建议4.x,不要用5.x的最新特性,因为课设代码大多是按Django 3.x或4.x写的,用更高版本跑老代码偶尔会遇到django.contrib.auth相关API变动。人脸识别库优先看模型文件来源:如果模型是dlib训练的,用face_recognition库;如果是TensorFlow的,直接走tensorflow依赖。安装命令如下:

pip install opencv-python==4.8.1.78 pip install django==4.2.7 pip install tensorflow==2.10.0 pip install face_recognition # 可选,看模型是否走dlib路线

关于tensorflow==2.10.0要说明一点:这是CPU版和GPU版分道扬镳前的最后一个版本,之后GPU支持需要额外装tensorflow-gpu,配置复杂度直接起飞。课设没有大量训练需求,CPU版完全够用,识别一张图几百毫秒在演示场景下可以接受。如果你是Apple Silicon芯片,TensorFlow 2.10对MPS的支持还不成熟,建议直接用2.13以上版本,或者在macOS下转用face_recognition路线。

安装完后验证环境,一句命令搞定:

python -c "import cv2, django, tensorflow; print(cv2.__version__, django.get_version(), tensorflow.__version__)"

这句命令把三个核心依赖一次性验证了。如果报ModuleNotFoundError,先看报错的是哪个库,再回到pip安装那一步。

3.2 项目目录结构与Django工程对接

解压源码包后,先对着目录结构走一遍。典型的课程设计工程分这么几个部分:manage.py是Django入口,app/下面是Web应用,recognition/或face_engine/封装算法逻辑,models/或weights/存模型文件,static/和templates/管前端资源。图片文件(c2884ccfef69573.jpeg这类的哈希命名)是采集的人脸样本,通常放在media/或以数据集形式组织。

Django工程和算法模块的对接要先注册app、配置路由和模板路径。

# settings.py 关键配置片段 INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'faceapp', # 你的应用名 ] # 模型路径配置,集中管理,不要散落在视图函数里 MODEL_PATH = os.path.join(BASE_DIR, 'models', 'facenet_saved_model') MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

注意MODEL_PATH是我建议自行添加的配置项,因为Django原生没有这个设置。好处是换模型不用改代码,只改配置。MEDIA_ROOT用于存放用户上传的图片,和STATIC_ROOT不是一个东西,上传文件的读写都在这个目录下。

3.3 跑通摄像头采集与静态图片识别两条路径

识别系统的演示通常有两条路径:上传图片识别和摄像头实时识别。摄像头路径在课设答辩里最抓眼球,但最不稳定——笔记本摄像头索引号、光线条件、OpenCV弹窗阻塞都有可能翻车。静态图片路径是最稳的保底方案,先把这条调通,再上摄像头。

先写一个不依赖Django的裸脚本验证识别链路:

import cv2 import numpy as np from recognition.facenet_service import extract_embedding # 读取一张测试图 img = cv2.imread("media/c2884ccfef69573.jpeg") if img is None: print("图片读取失败,检查路径和中文字符") exit(1) # 提取特征向量 embedding = extract_embedding(img) print("特征向量shape:", embedding.shape) print("向量范数:", np.linalg.norm(embedding))

特征向量的shape和范数是两个重要的健康指标。FaceNet输出的维度一般是512或128,范数接近1说明归一化步骤正确,如果范数明显偏离1,后续做距离比对时阈值会失真。cv2.imread读不到图时返回None而不是抛异常,所以判空逻辑必须写在前面。如果图片路径含中文,OpenCV会直接返回None,这是OpenCV的历史遗留问题,解决方式是用np.fromfile配合cv2.imdecode。

摄像头路径的Django实现通常用VideoCapture(0),注意索引号0是默认摄像头,1是外接摄像头,笔记本自带的摄像头一般是0。坑在于:在Django视图里直接调用摄像头,会把摄像头设备绑定在服务端进程上,多人访问时互相抢资源。课设场景单人访问没问题,但要记住摄像头弹窗只能在本地跑通,部署到服务器上是不可能的。

# faceapp/views.py 摄像头识别视图 import cv2 import numpy as np from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from recognition.facenet_service import extract_embedding @csrf_exempt def webcam_recognition(request): if request.method != 'POST': return JsonResponse({"error": "仅支持POST请求"}, status=405) # 打开默认摄像头 cap = cv2.VideoCapture(0) if not cap.isOpened(): return JsonResponse({"error": "无法打开摄像头"}, status=500) ret, frame = cap.read() cap.release() # 用完立刻释放,不然下次打不开 if not ret: return JsonResponse({"error": "读取摄像头帧失败"}, status=500) embedding = extract_embedding(frame) # 这里省略与数据库特征比对的具体实现,见第5章 return JsonResponse({"status": "ok", "embedding_shape": list(embedding.shape)})

cap.release()是很多人会漏的一行。摄像头是独占设备,进程结束后如果没有释放,下一次VideoCapture(0)会一直返回isOpened()==False,只能重启Python进程才能恢复。@csrf_exempt这个装饰器在演示时方便,但答辩之后如果真的要部署,要换成Django的CSRF认证机制,否则所有POST请求都跳过CSRF校验,这是安全隐患。

3.4 静态图片上传识别链路的完整代码

摄像头路线适合演示,上传图片路线适合作为系统的核心功能。Django的request.FILES接收上传文件,存到MEDIA_ROOT,再调用识别模块,返回比对结果。完整链路代码:

# faceapp/views.py 上传图片识别视图 import os import numpy as np from django.conf import settings from django.shortcuts import render from django.http import JsonResponse from .models import FaceRecord # 假设已定义数据库模型 def upload_recognition(request): if request.method == 'POST': uploaded_file = request.FILES.get('image') if not uploaded_file: return JsonResponse({"error": "未选择图片"}, status=400) # 保存上传文件到media目录 save_path = os.path.join(settings.MEDIA_ROOT, uploaded_file.name) with open(save_path, 'wb+') as f: for chunk in uploaded_file.chunks(): f.write(chunk) # 调用识别模块 img = cv2.imread(save_path) if img is None: # 处理中文路径问题 img = cv2.imdecode(np.fromfile(save_path, dtype=np.uint8), cv2.IMREAD_COLOR) embedding = extract_embedding(img) # 与数据库中的所有人脸特征做距离比对 best_match = None best_distance = float('inf') for record in FaceRecord.objects.all(): known_embedding = np.frombuffer(record.embedding_bytes, dtype=np.float32) distance = np.linalg.norm(embedding - known_embedding) if distance < best_distance: best_distance = distance best_match = record threshold = 0.9 # 距离阈值,小于该值判定为同一人 if best_distance <= threshold: return JsonResponse({ "recognized": True, "name": best_match.name, "distance": round(float(best_distance), 4) }) return JsonResponse({ "recognized": False, "distance": round(float(best_distance), 4) }) return render(request, 'faceapp/upload.html')

chunks()方法按块读取上传文件,大文件不会一次性占用太多内存,这是Django官方推荐写法。距离阈值0.9不是拍脑袋定的:FaceNet生成的128维向量在欧氏距离下,同一人通常小于0.8,不同人大约在1.0到1.5之间,0.9是个相对安全的初始值。record.embedding_bytes这个字段的存储方式值得注意——用numpy.frombuffer把二进制还原成向量,比把向量存成JSON字符串再解析要快两个数量级。数据库模型定义里这个字段的类型应该是BinaryField,这是存储特征向量最合适的方式。

4. 避坑指南:人脸识别课设最常见的五个翻车现场

4.1 dlib编译失败:CMake报错与Visual Studio缺失

现象:pip安装face_recognition时,在building wheel for dlib阶段报错,信息里出现CMake must be installed to build the following extensions: dlib,有时候还会跟着一堆红色C++编译错误。

原因:dlib没有预编译的wheel包,pip需要现场编译。Windows机器缺少C++编译器和CMake,Linux机器缺少g++和cmake,都会触发这个错误。

解决:Windows下先装Visual Studio Build Tools,勾选“使用C++的桌面开发”工作负载,再装CMake并加入系统PATH,最后重试pip install。Linux下执行sudo apt-get install build-essential cmake。如果不想折腾编译环境,直接下载dlib的预编译wheel包手动安装,或者改用纯OpenCV + TensorFlow路线。

4.2 TensorFlow SavedModel版本不匹配

现象:tf.saved_model.load报错,提示Op type not registered 'BlockLSTM'或者Unsuccessful TensorSliceReader constructor。

原因:模型是TF 1.x的GraphDef格式,当前环境是TF 2.x,或者反向兼容层没生效。模型分片文件variables.data-00000-of-00002对应旧格式保存,加载器要读取完整的变量索引文件。

解决:先查模型是用哪个版本保存的。如果是TF 1.x,可以用tf.compat.v1.saved_model.loader.load兼容加载;如果是TF 2.x保存的模型在1.x环境加载,基本无解,必须升级环境。判断方式是看模型目录下有没有saved_model.pb,有的话用Python读一下文件开头的字节,TF 2.x的proto包含明确的版本信息。

4.3 OpenCV读图返回None,尤其是路径带中文

现象:cv2.imread("media/人脸样本/001.jpg")返回None,print(img)输出None,后续代码直接崩。

原因:OpenCV的imread底层用C函数读文件,对中文路径编码支持不完善,Windows下中文路径必现。

解决:换用np.fromfile读取文件字节流,再用cv2.imdecode解码成图像。代码写法上一节已经给了,这里再单独强调一次:这个坑在课设答辩现场出现频率极高,因为很多同学习惯把样本图片放在中文命名的文件夹里。

4.4 摄像头打开后黑屏或权限报错

现象:cv2.VideoCapture(0)返回True,但cap.read()拿到的是全黑图像,或者在macOS/Windows下弹出权限请求框但回帧失败。

原因:摄像头索引不对,或者前置/后置摄像头编号不是0。黑屏通常是因为摄像头被其他程序占用,比如微信视频聊天、浏览器视频会议没退出。权限报错是操作系统隐私设置拦截了Python进程。

解决:先用cap = cv2.VideoCapture(1)试外接摄像头;关闭所有占用摄像头的程序;macOS在系统设置-隐私-摄像头里放行Python;Windows在设置-隐私-相机里打开允许桌面应用访问相机。调好之后用一段循环代码读10帧确认稳定,再接入Django视图。

4.5 Django静态文件404导致页面无样式

现象:页面能打开但纯文字排版,Chrome开发者工具里静态文件请求全部404,控制台报GET /static/xxx.css 404。

原因:Django开发环境的静态文件服务没有开启,或者STATICFILES_DIRS配置缺失,模板里引用的CSS/JS文件找不到。

解决:先在settings.py里确认STATICFILES_DIRS指向实际静态文件目录;在urls.py的开发环境配置中加static()路由,让Django在DEBUG模式下托管静态文件;模板里用{% load static %}标签引入资源,不要写死路径。这个坑跟人脸识别算法无关,但占了课设页面展示的一多半精力,尽早确认静态文件能正常加载。

5. 把识别系统改造成自己的课设:阈值调优和业务扩展

5.1 距离阈值的标定方法

裸跑通识别不等于能答辩。同一个系统,阈值定高了会把不同人识别成同一个人,阈值定低了会频繁拒绝已注册用户。最靠谱的标定方式是做一次简单的实验:

收集你自己的10张人脸图片,分别提取特征向量,计算两两之间的欧氏距离;再收集另外5个不同人的图片,计算跨人距离。同一人的距离一般落在0.4到0.8之间,不同人的距离一般落在1.2到2.0之间。取两类距离的中间值作为阈值,通常0.9到1.0之间。这个实验写成脚本跑一遍,把距离分布图打印出来,答辩时展示这个标定过程,比空口说“阈值设置0.9”有说服力得多。

5.2 把比对结果接进Django管理界面

识别系统只返回“是/否”还不够,课设评分看重的是完整闭环。可以在Django admin里注册人脸记录模型,录入姓名、学号、特征向量,这样在上传识别页面里就能直接显示出“识别成功:张三,距离0.65”。admin后台的接入代码:

# faceapp/admin.py from django.contrib import admin from .models import FaceRecord @admin.register(FaceRecord) class FaceRecordAdmin(admin.ModelAdmin): list_display = ('name', 'student_id', 'created_at') search_fields = ('name', 'student_id')

这段代码的重点是search_fields和list_display,前者让管理员在后台能按姓名或学号搜索注册记录,后者直观展示关键字段。人脸特征向量embedding_bytes不要在列表页展示,二进制数据显示在页面上就是乱码。

从那以后,我每次做带模型识别的Web课设,都会在动手前先花十分钟做三件事:确认模型文件和当前库版本兼容、写一个裸脚本验证单张图片识别链路、检查上传文件的路径编码问题。这三件事做完,后面90%的坑都能提前消掉。希望这份拆包笔记帮到你,按这个顺序复现,你的答辩演示大概率能稳稳跑完。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询