简介:这份毕业设计资源面向计算机相关专业学生与深度学习入门者,围绕人脸识别签到系统展开,提供一套可运行的完整项目源码,帮助解决从算法调用到Web端落地、数据库建模与后台管理的全流程实现问题。压缩包共27个文件,约101.47MB,以Python脚本与HTML模板为主,辅以SQLite数据库、Alembic迁移配置、CSS样式、字体文件及说明文档,覆盖人脸注册、识别签到、用户增删改查等核心模块,目录结构清晰,便于按功能定位代码。资源中已内置初始管理员账户与数据库初始化脚本,读者可据此快速还原运行环境,理解Flask后端与深度学习模型如何协同工作。目前已有404人学习下载,适合作为毕业设计参考、课程项目模板或深度学习应用练手素材,也能为后续功能扩展与二次开发提供基础框架。
1. 从一张“认错人”的签到截图说起:这套深度学习人脸识别系统到底能干什么
去年帮学弟调毕业设计,他发来一张签到后台截图:同一个人站在摄像头前,系统先识别成张三,两秒后又跳成李四,签到记录里同一个人刷出三条不同学号。这不是玄学,是典型的人脸识别签到系统在“检测—对齐—特征—比对”链路上某一环没调好。这套「基于深度学习的人脸识别签到系统」,本质是把摄像头采集的人脸图像,经过检测、对齐、特征提取后,与已注册的人脸库做比对,命中则写入签到记录。它解决的是传统签到(纸质、刷卡、扫码)代签、效率低、无法追溯的问题,适合计算机、软件工程、电子信息类毕业设计选题,也适合想跑通一个完整深度学习落地项目的同学。整套系统通常包含人脸注册、实时识别签到、签到记录管理三块,技术栈以 Python + OpenCV + 深度学习框架为主,是能写进论文、也能当场演示的那类项目。
2. 系统技术选型:为什么是 MTCNN + FaceNet 而不是直接上分类网络
2.1 人脸识别签到和普通图像分类的本质区别
很多人第一反应是“用 CNN 训个分类器,几班人分几类”。这条路在毕业设计里几乎必翻车。分类网络要求类别固定、每类样本充足,而签到系统的人脸库是动态的——今天注册 30 人,明天可能加 5 人,每人类别只有 1~3 张底图。分类模型每加一个人就得重新训练,这在演示现场是灾难。
人脸识别签到真正要的是“度量学习”:把每张人脸映射成一个特征向量,让同一个人不同照片的向量距离近,不同人的距离远。这样新增人员只需注册底图、提取特征入库,不用动模型。这就是 FaceNet 这类模型的核心价值,也是选型时第一个要讲清楚的理由。
常见做法是两段式流水线:先用 MTCNN 或 RetinaFace 做人脸检测和对齐,裁出标准人脸;再用 FaceNet(或 ArcFace、MobileFaceNet)提取 128/512 维特征;最后用余弦相似度或欧氏距离比对。这套组合在毕业设计里资料多、预训练权重好找、CPU 也能跑,性价比最高。
2.2 环境搭建与依赖安装
先把环境跑通,再谈算法。我一般用 conda 建独立环境,避免和系统 Python 打架。深度学习环境配置是这类项目第一道坎,版本对不上后面全是血泪。
# 创建独立环境,python 版本建议 3.8~3.10,太新很多包没轮子 conda create -n face_sign python=3.9 -y conda activate face_sign # 核心依赖:opencv 负责采集与图像处理,numpy 做向量运算 pip install opencv-python==4.8.1.78 pip install numpy==1.24.3 pip install scikit-learn==1.3.0 # 深度学习框架二选一,CPU 演示选 onnxruntime 最省事 pip install onnxruntime==1.16.3 # 若用 PyTorch 版 FaceNet pip install torch==2.0.1 torchvision==0.15.2参数说明:opencv-python版本不要盲目追新,4.8 系列和多数人脸库兼容性好;onnxruntime用于加载导出的 ONNX 模型,CPU 推理速度足够签到场景(单帧 100~300ms)。如果显卡可用,把onnxruntime换成onnxruntime-gpu并装对应 CUDA 版本,帧率能翻几倍。
提示:装完先跑
python -c "import cv2, numpy; print(cv2.__version__)"验证,别等写完代码才发现 import 失败。
2.3 人脸检测与对齐:MTCNN 的三个阶段
MTCNN 分 P-Net、R-Net、O-Net 三级网络,逐级过滤候选框并回归关键点。它的输出不只是人脸框,还有 5 个关键点(双眼、鼻尖、双嘴角),这 5 个点正是对齐的依据。对齐的意义在于:把歪头、侧脸统一旋转缩放到标准姿态,让后续特征提取稳定。不做对齐,同一个人稍微偏头,特征距离就会飙高,识别直接失败。
import cv2 import numpy as np from mtcnn import MTCNN # pip install mtcnn detector = MTCNN() def detect_and_align(img_bgr, target_size=(160, 160)): """检测人脸并对齐到标准尺寸,返回对齐后的人脸图""" img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB) results = detector.detect_faces(img_rgb) if not results: return None # 取置信度最高的一张脸 r = max(results, key=lambda x: x['confidence']) x, y, w, h = r['box'] # box 可能越界,做裁剪保护 x, y = max(0, x), max(0, y) face = img_bgr[y:y+h, x:x+w] if face.size == 0: return None # 关键点对齐:以双眼连线为水平基准做仿射变换 kps = r['keypoints'] left_eye, right_eye = np.array(kps['left_eye']), np.array(kps['right_eye']) angle = np.degrees(np.arctan2(right_eye[1]-left_eye[1], right_eye[0]-left_eye[0])) M = cv2.getRotationMatrix2D(tuple((left_eye+right_eye)/2), angle, 1.0) aligned = cv2.warpAffine(img_bgr, M, (img_bgr.shape[1], img_bgr.shape[0])) face = aligned[y:y+h, x:x+w] return cv2.resize(face, target_size)逻辑说明:detect_faces返回框和关键点;取confidence最高的脸避免误检干扰;用双眼连线角度算旋转矩阵,把脸摆正;最后统一缩放到 160×160,这是 FaceNet 的标准输入。参数target_size必须和后面特征模型输入一致,不一致会直接报维度错误。
2.4 特征提取与比对:FaceNet 的 128 维向量怎么用
FaceNet 把对齐后的人脸映射成 128 维向量(也有 512 维版本),训练目标是让同类距离小于异类距离。使用时不需要它的分类头,只要那个 embedding 输出。比对用余弦相似度:值越接近 1 越像,一般阈值取 0.6~0.75,具体要按你的数据实测调。
import numpy as np def cosine_similarity(a, b): """余弦相似度,返回 -1~1,越接近 1 越相似""" a, b = a.flatten(), b.flatten() return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))) def match_face(query_feat, face_db, threshold=0.65): """在已注册人脸库中找最相似的人 face_db: {学号: 特征向量} 返回 (学号, 相似度) 或 (None, 最高相似度) """ best_id, best_score = None, -1.0 for sid, feat in face_db.items(): score = cosine_similarity(query_feat, feat) if score > best_score: best_id, best_score = sid, score if best_score >= threshold: return best_id, best_score return None, best_score参数说明:threshold是识别通过的门槛,调高更严(漏识增多),调低更松(误识增多)。毕业设计演示建议 0.65 起步,用真实数据画一次相似度分布再定。face_db用字典存学号到特征,注册时一人可存多张底图的平均特征,能明显提升稳定性。
3. 从注册到签到:把整条链路跑起来的实操步骤
3.1 人脸注册模块:底图质量决定上限
注册就是采集底图、检测对齐、提特征、入库。这一步偷懒,后面识别全是坑。我一般要求每人采 3~5 张,覆盖正脸、轻微左右偏、戴不戴眼镜两种状态。底图分辨率不低于 640×480,光照均匀,别背光。
import os import pickle def register_face(student_id, img_paths, extract_fn, save_path='face_db.pkl'): """注册:多张底图取平均特征,降低单张噪声""" feats = [] for p in img_paths: img = cv2.imread(p) if img is None: print(f'[跳过] 读不到图: {p}') continue face = detect_and_align(img) if face is None: print(f'[跳过] 未检测到人脸: {p}') continue feats.append(extract_fn(face)) # extract_fn 为特征模型推理 if not feats: print(f'[失败] {student_id} 无有效底图') return avg_feat = np.mean(feats, axis=0) # 加载已有库,追加或覆盖 db = {} if os.path.exists(save_path): with open(save_path, 'rb') as f: db = pickle.load(f) db[student_id] = avg_feat with open(save_path, 'wb') as f: pickle.dump(db, f) print(f'[成功] {student_id} 注册完成,有效底图 {len(feats)} 张')逻辑说明:多张底图特征取平均,能抵消单张的姿态和光照噪声;pickle存字典方便演示,正式点可换 SQLite 或 MySQL。参数上,底图少于 2 张时平均意义不大,建议至少 3 张。
3.2 实时签到主循环:摄像头采集与去重
签到主循环要解决两件事:一是持续读帧识别,二是防止同一个人连续多帧重复签到。常见做法是加一个“冷却时间”,同一学号在 N 秒内只记一次。
import time def run_signin(extract_fn, face_db, threshold=0.65, cooldown=5): """实时签到主循环""" cap = cv2.VideoCapture(0) # 0 为默认摄像头 if not cap.isOpened(): print('摄像头打开失败,检查设备号或被占用') return last_sign = {} # 学号 -> 上次签到时间戳 while True: ok, frame = cap.read() if not ok: break face = detect_and_align(frame) if face is not None: feat = extract_fn(face) sid, score = match_face(feat, face_db, threshold) now = time.time() if sid and now - last_sign.get(sid, 0) > cooldown: last_sign[sid] = now print(f'签到成功: {sid}, 相似度 {score:.3f}') # 此处写数据库或文件 elif sid: pass # 冷却期内,忽略 else: print(f'未识别, 最高相似度 {score:.3f}') cv2.imshow('signin', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()参数说明:cooldown单位秒,太小会重复签到,太大会漏掉快速连续签到,5 秒是演示常用值。cv2.waitKey(1)里的 1 是毫秒,别写 0,否则画面卡死。摄像头打不开时优先查设备号(换VideoCapture(1))和是否被其他程序占用。
3.3 签到记录落库:SQLite 够用且免配置
毕业设计演示用 SQLite 最省心,单文件、免安装。表结构建议包含学号、姓名、签到时间、相似度,方便论文里做数据分析。
import sqlite3 def init_db(db_path='signin.db'): conn = sqlite3.connect(db_path) conn.execute('''CREATE TABLE IF NOT EXISTS records ( id INTEGER PRIMARY KEY AUTOINCREMENT, student_id TEXT NOT NULL, name TEXT, sign_time TEXT NOT NULL, similarity REAL )''') conn.commit() return conn def add_record(conn, student_id, name, similarity): from datetime import datetime conn.execute('INSERT INTO records (student_id, name, sign_time, similarity) VALUES (?,?,?,?)', (student_id, name, datetime.now().strftime('%Y-%m-%d %H:%M:%S'), similarity)) conn.commit()逻辑说明:AUTOINCREMENT保证记录唯一递增;时间用字符串存便于直接展示和排序。相似度字段别省,论文里画阈值-准确率曲线就靠它。查询时按student_id + 日期去重即可统计出勤。
4. 避坑与排查:五个让演示当场翻车的真实问题
4.1 现象:同一个人识别成别人,相似度还很高
原因:底图质量差或只有一张,特征不具代表性;或者阈值设太低。解决:每人补到 3 张以上底图取平均,把阈值从 0.6 提到 0.7 实测;同时检查是否把未注册的人硬匹配成了库里最近的人——match_face里低于阈值必须返回 None,不能强行给结果。
4.2 现象:摄像头画面卡顿,识别延迟好几秒
原因:每帧都跑完整 MTCNN + 特征提取,CPU 扛不住。解决:加帧间隔,比如每 3 帧检测一次;或先把 MTCNN 换成更轻的检测器做人脸框初筛,再对框内做对齐。演示机没独显时,把输入分辨率从 1080p 降到 640×480,帧率立刻上来。
4.3 现象:换台电脑就报模型加载失败或维度不匹配
原因:模型文件路径写死、输入尺寸和训练时不一致。解决:路径用相对路径或配置文件管理;把target_size、特征维度写成常量集中定义,别散落在各处。ONNX 模型要确认输入名和 shape,用onnxruntime的get_inputs()打印核对。
4.4 现象:注册时提示未检测到人脸,但肉眼明明有脸
原因:光照过暗、侧脸角度过大,或 MTCNN 置信度阈值默认偏高。解决:注册环境补光,要求正脸;调低检测置信度阈值(如 0.9 降到 0.85)并打印每张图的检测结果定位问题。别用美颜过度的照片,特征会被扭曲。
4.5 现象:签到记录重复写入同一个人
原因:冷却逻辑没生效,或每帧都新建了last_sign字典。解决:last_sign必须在循环外初始化;确认cooldown判断用的是同一份字典。另外多线程写 SQLite 会锁库,演示用单线程即可,别过早引入并发。
5. 把准确率再抬一截:阈值标定与活体检测的轻量做法
系统能跑通只是及格线,答辩时老师最爱问“误识率多少、怎么证明”。这里给两个能直接加分的进阶点。
第一是阈值标定。别拍脑袋定 0.65,用你自己的数据画分布:把同一人的特征对相似度记为正样本,不同人的记为负样本,各统计一批,找让误识率和漏识率平衡的点。下面这段能直接跑。
import numpy as np def calibrate_threshold(genuine_scores, impostor_scores): """genuine: 同一人相似度列表; impostor: 不同人相似度列表 返回使 (误拒+误识) 最小的阈值""" candidates = np.linspace(0.3, 0.95, 66) best_t, best_err = 0.65, 1e9 for t in candidates: frr = np.mean(np.array(genuine_scores) < t) # 误拒率 far = np.mean(np.array(impostor_scores) >= t) # 误识率 err = frr + far if err < best_err: best_t, best_err = t, err return best_t, best_err参数说明:genuine_scores至少准备 50 对同人相似度,impostor_scores准备 200 对以上异人相似度,样本越多阈值越可信。论文里把这条曲线画出来,比空谈“准确率 99%”有说服力得多。
第二是活体检测。照片翻拍能骗过纯识别系统,答辩现场被问到很尴尬。轻量做法是用眨眼检测:连续帧里用关键点算眼睛纵横比(EAR),低于阈值再恢复即判定眨眼。不需要额外模型,OpenCV + 关键点就能做,代价是要求用户配合眨眼,演示时提前说明即可。我一般把活体作为可选开关,默认关闭保证流畅,答辩演示时打开。
从那以后我每次交付这类系统,都强制先跑一遍阈值标定、再录一段翻拍视频测活体,两个都过了才敢让人站到摄像头前。希望帮到你。
本文还有配套的精品资源,点击获取