简介:这是一款基于深度学习的人脸识别考勤系统完整毕业设计项目,适合计算机相关专业本科生作为毕业设计、课程设计或期末大作业参考,也适合希望掌握人脸识别实战开发的学习者。项目经导师指导并调试通过,可直接运行,覆盖数据预处理、模型训练、评估与预测等完整流程。压缩包内共48个文件,以Python源码为主(17个py脚本),并包含预训练h5模型权重、jpg/png图片数据、xml配置、txt标注文件、使用说明及毕业设计手册docx文档,整体约13.4MB,目录结构清晰,便于按模块学习。目前已有532人学习下载。通过该项目,读者可深入理解基于深度学习的人脸识别原理、Facenet与MobileNet等网络结构的应用,以及从数据集构建到考勤系统落地的完整工程实现。
1. 从毕业设计到能打卡:深度学习人脸识别考勤系统该怎么用?
人脸识别考勤听起来像个产品级项目,实际上这份 Python 深度学习源码更像一个“能跑出完整闭环的毕业设计”。核心链路很短:摄像头取帧、检测人脸、提取特征向量、与注册库比对、命中后写入考勤记录。真正让新手卡住的不在深度学习,而在特征库怎么建、打卡逻辑怎么防重复、遇到光线和中文路径怎么处理。
这套资源适合三类人:准备交毕业设计或课程设计的学生,想快速搭一个教室门口人脸打卡 demo 的开发者,以及想搞清楚“深度学习识别后面到底怎么串起来”的初学者。你不需要从头训练任何模型,重点是用好封装好的检测和识别管线,然后把数据换成你自己的,把参数调稳。
2. 系统拆解:从摄像头帧到考勤记录,四步别走偏
2.1 为什么这套系统更看重“特征编码”而不是训练一个完整模型
先给新手拨开一层误解:不是说“深度学习人脸识别”就要拿几十万张照片去训练一个模型。毕业设计这种场景,时间有限、数据有限,绝大多数源码采用的是“预训练模型 + 特征比对”的思路。人脸识别的完整链路其实是两段:第一段检测,框出图中有没有人脸;第二段识别,把框出来的人脸转成一个向量,再跟注册库里的人脸向量做距离比较。
这个思路和传统“训练分类器”差别很大。分类器只能回答“这人是张三还是李四”,如果班级里来了新人,必须重训模型;特征比对则把问题变成“这个向量像谁”,新增一个人只需要往库里加一张特征编码,不需要动网络权重。所以你会看到源码里多半有一个register.py或build_database.py之类脚本,它的作用就是把准备好的照片过一遍预训练模型,然后保存成特征文件,常见的是 pkl 或 npz 格式。
选型理由也很直接。如果源码用的是face_recognition库,内部就是 dlib 的 ResNet 模型,在 CPU 上也能跑,很适合毕业设计答辩现场演示;如果用的是 MTCNN 加 FaceNet,检测更稳,侧脸和遮挡稍微好一点,但模型加载慢,需要更大内存。你先看压缩包里有没有“weights”或者“models”目录,就能判断是哪一条路,后续调参重心也不同。这套系统的价值在于它把“预处理、特征提取、比对、记录”整个链条都串好了,你要做的不是重写算法,而是理解每个环节的输入输出边界。
2.2 注册与建库:把人脸变成一组可比较的数字
拿到资源之后,第一步要处理的不是识别,而是“建库”。常见做法是准备一个face_db目录,里面每个子目录以学生姓名命名,放若干张该学生在不同角度、不同光线下的照片。然后写一个建库脚本,遍历这些照片,用检测器找到人脸,编码成 128 维向量,最后存到一个干净的特征库里。这里“干净”指剔除掉检测失败、多人脸、模糊照片,否则后续识别会出现莫名其妙的误匹配。
以下是一个简化版建库脚本,思路和你拿到的源码基本一致:
import os import pickle import face_recognition face_db_path = "face_db" # 每个子文件夹名就是姓名 feature_file = "face_features.pkl" db = {} for name in os.listdir(face_db_path): person_dir = os.path.join(face_db_path, name) if not os.path.isdir(person_dir): continue encodings = [] for img_name in os.listdir(person_dir): img_path = os.path.join(person_dir, img_name) # load_image_file 内部已经处理好RGB顺序 image = face_recognition.load_image_file(img_path) # 用hog模型检测,若漏检可换成cnn boxes = face_recognition.face_locations(image, model="hog") if len(boxes) != 1: print(f"{img_path} 检测到 {len(boxes)} 张人脸,跳过") continue vec = face_recognition.face_encodings(image, known_face_locations=boxes)[0] encodings.append(vec) if encodings: db[name] = encodings print(f"{name} 入库 {len(encodings)} 张") with open(feature_file, "wb") as f: pickle.dump(db, f) print("特征库已保存到", feature_file)这段代码的关键点在于:每个学生可以不止一张照片,所以数据库里存的是“一个名字对应一组向量”,而不是单个向量。后续做比对时,可以取平均,也可以在比对时逐条计算取最小值,显然后者更能容忍表情和光照差异。参数说明里,face_db_path指向你的人脸数据目录,feature_file是输出路径。pickle 序列化后的文件很小,几十个人的特征库一般不到 1MB,拷到答辩机器上也能直接加载。如果你发现有些照片总被跳过,是因为model="hog"对侧脸和暗光不够敏感,可以改成model="cnn",但需要 TensorFlow 环境支持,同时每张图会慢不少。
2.3 考勤主流程:识别、打卡、落库,串成闭环
建库完成之后,考勤脚本要做的就是循环读摄像头帧,对每一帧做人脸检测和编码,再与特征库比对。比对标准很简单:计算当前人脸向量与库里每个向量的欧氏距离,最小距离低于阈值就认为匹配成功,否则提示“未识别”。真正生产化的逻辑还要处理重复打卡、时间窗口、批量导入,但先把主干跑通最重要。
下面这段是考勤主流程的核心片段:
import cv2 import pickle import datetime import sqlite3 import face_recognition with open("face_features.pkl", "rb") as f: known_db = pickle.load(f) conn = sqlite3.connect("attendance.db") cur = conn.cursor() cur.execute("CREATE TABLE IF NOT EXISTS attendance (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, check_time TEXT)") conn.commit() cap = cv2.VideoCapture(0) tolerance = 0.45 # 越小越严格,0.4~0.5 之间调 while True: ok, frame = cap.read() if not ok: break small_frame = cv2.resize(frame, (0, 0), fx=0.5, fy=0.5) rgb_frame = cv2.cvtColor(small_frame, cv2.COLOR_BGR2RGB) boxes = face_recognition.face_locations(rgb_frame, model="hog") encodings = face_recognition.face_encodings(rgb_frame, boxes) for encoding, box in zip(encodings, boxes): best_name = None best_dist = 1.0 for name, vec_list in known_db.items(): distances = face_recognition.face_distance(vec_list, encoding) min_dist = min(distances) if min_dist < best_dist: best_dist = min_dist best_name = name # 只有距离低于阈值才确认打卡 if best_name and best_dist < tolerance: now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") cur.execute("INSERT INTO attendance (name, check_time) VALUES (?, ?)", (best_name, now)) conn.commit() print(f"{best_name} 打卡,距离={best_dist:.3f},时间={now}") else: print(f"未识别,最近距离={best_dist:.3f}") cap.release() conn.close()这段代码演示了两个容易忽略的点。第一,摄像头的原始帧一般比较大,先用 0.5 倍缩小再人脸检测,速度能快不少,代价是远处小脸可能框不到,实际使用时根据摄像头分辨率调整系数。第二,打卡之后我没有立刻 break,因为循环本身可以在现场连续运行,但如果不加“一天只打一次”的约束,同一张脸会不停往表里插数据。真正要交作业的版本,你需要在这里补一个时间窗口判断:先查这个人今天有没有记录,有就直接跳过,或者只在规定时段内允许打卡。这个逻辑看着简单,却是答辩提问时最容易暴露短板的位置。
3. 参数与调优:容差、检测模型和打卡时间,缺一不可
3.1 容差/阈值:先理解“误识”与“拒识”的此消彼长
这个系统里最影响使用体验的数字是tolerance。face_recognition库的默认值是0.6,这个值表示允许的欧氏距离上限,物理含义是两张人脸特征向量在 128 维空间里的距离,越小表示越像。实际调参时不能只看“能不能认出自己”,要看两个极端。
tolerance调得太大,比如0.6,容易出现“谁都像谁”,张三刷脸可能把李四的考勤打了,这在答辩演示时非常尴尬。tolerance调得太小,比如0.3,又会把同一个人的不同表情、眼镜、光线变化全部拒之门外,导致站在摄像头前半天识别不了。
我一般会从0.5开始,然后刻意用班级里最像的两个人做测试。如果这两个人被互相识别,就把tolerance下调0.02再测;如果同一个人换角度就识别失败,再上调。通常0.45左右是一个不错的起点,但不要盲从,因为每台机器的照片质量和摄像头清晰度不同,最佳值可能差0.05。调参时要一边看控制台打印的距离值,一边调整,先观察同一个人在不同光照下的距离波动范围,再把阈值设在这个范围的下沿附近。
3.2 检测模型:HOG、CNN 与场景匹配
检测模型的选择直接影响识别率。face_recognition提供了两个预置模型:hog和cnn。hog在 CPU 上跑得很快,帧率能到几十,但对于低头、侧脸、戴帽子这些情况容易漏检;cnn使用的是深度学习检测器,抗遮挡能力强,但加载模型需占用几个 GB 内存,没有 GPU 时速度很慢,现场演示如果摄像头连续抽帧,会卡得不像样。很多基于深度学习的人脸识别考勤系统会默认用cnn,但这反而成了演示翻车的第一原因:帧率太低,人还没站定框就跳走了。
这里的建议是分场景选择:如果是教室门口,学生正常看镜头,用hog就够了,漏检率可以接受;如果是实验室门口,光线复杂,建议改用cnn,同时把摄像头帧率降到更低,或者让用户按一下“开始签到”再检测,避免持续抽帧。源码里通常会留一个参数切换,类似model="cnn",你把这一行改掉就能生效。注意如果源码用的是 MTCNN,那么检测器是独立封装的,切换模型就不是改一个字符串的事,而是改整个检测管线。
还要注意输入分辨率。有的源码会把摄像头帧缩到 1/4 来跑,速度上去了,但小脸彻底看不见。我的习惯是保持宽高比缩到 640,再往下缩到 480 就很容易漏检。这个值没有固定标准,跟你装摄像头的位置、学生离镜头距离直接相关,需要在现场试。
3.3 打卡逻辑与时间窗口:别只判断“是不是这个人”
很多毕业设计翻车不在人脸识别,而在考勤逻辑。最典型的问题是:学生在一节课里反复进出,记录表会被刷屏;或者晚上演示的时候,随便一张打印照片也能通过。所以一个能答辩的考勤系统至少要处理三件事。
- 一天只记一次:在插入打卡记录前,先查当天是否已有该学生记录,已有则跳过或更新为最后一次时间。
- 考勤时间窗口:只在规定的时间段内允许打卡,比如早 8:00 到 8:30,超出时间记录为迟到或缺勤,这个判断可以放在写入数据库之前,也可以在导出报表时处理。
- 活体检测:纯照片可能骗过系统,如果源码没有活体检测,答辩前至少要准备一个“眨眼”演示话术,更稳妥的做法是加一个简单的眨眼检测。
其中“一天只记一次”的代码很好加,在往attendance表插入前先执行一条查询:
today = datetime.date.today().isoformat() cur.execute( "SELECT COUNT(*) FROM attendance WHERE name=? AND date(check_time)=?", (best_name, today) ) already = cur.fetchone()[0] if already == 0 and start_time <= now_time <= end_time: cur.execute("INSERT INTO attendance (name, check_time) VALUES (?, ?)", (best_name, now)) conn.commit()这里的start_time和end_time建议设置成配置项,写在脚本头部,方便答辩时解释。date(check_time)依赖 SQLite 的日期函数,所以写入的check_time要保持YYYY-MM-DD HH:MM:SS格式。查询里的COUNT(*)只判断“有没有”,不考虑迟到和早退,需要更精细的状态判断时,可以再加一个status字段,把正常 / 迟到 / 缺勤三个状态落到数据库里,这样导出的报表才像正经系统。
4. 避坑手册:五个白天正常、晚上翻车的真实案例
4.1 摄像头、光线与多脸误检
先说一个我踩过的坑。白天在办公室测试,人脸识别距离基本稳定在0.35左右,识别率接近百分之百;到了傍晚开灯再测,同一张脸的距离直接飙到0.6以上,频繁提示未识别。现象就是“白天正常、晚上翻车”,原因是摄像头自动白平衡和曝光把脸照得偏色或过曝,特征向量整体偏移了。解决方法是固定摄像头曝光和色温参数,或者在建库时就把注册照片和现场识别放在同一光照条件下拍;不要拿白天拍的照片去匹配晚上的现场光,这属于自己给自己挖坑。
第二个坑是两个人离得近,检测框漂移。现象是两个人同框时,系统有时框到两张脸中间,把背景混进特征编码,结果两个人各打一次卡,或者完全识别不出。原因是hog检测器对近距离脸部的边界不够敏感,置信度低时框不完整。解决方法是调整考勤现场的通道位置,让队伍一个一个过;同时把tolerance适当调高一点,让“框偏了”的判定不至于直接变成“未识别”。如果你不改现场,只在代码层面调参,治标不治本。
第三个坑是照片骗过系统。现象是举着手机屏幕也能成功打卡,答辩时评委一眼看穿,印象分直接崩。原因是特征比对只看静态脸,不会判断你是不是活人。解决思路分两层:第一层,如果源码本身没有活体检测,演示前准备一个“真人动一动”的交互,比如眨眼一次再记录;第二层,时间充裕的话加一个基于人脸关键点的眨眼检测,眼睛纵横比连续几帧低于阈值才算活体。哪怕只是半成品,也表明你考虑过这个漏洞。
4.2 中文路径、编码与库文件版本坑
第四个坑和编码有关,尤其是 Windows 上跑这类 Python 考勤系统。现象是建库脚本遍历“张三”文件夹时突然报UnicodeDecodeError,或者明明能看到中文文件名,程序就是没法打开。原因是 Windows 默认使用 GBK 解码文件系统路径,而源码里写死了某种编码方式,两种编码一碰就崩。解决方法是不要硬碰编码,把face_db下所有子目录改成拼音或学号,比如zhangsan_2024001,识别时不展示中文就用拼音显示;如果在考勤报表里必须要中文,可以另外维护一个“学号到姓名”的映射表,这样既避开编码问题,也方便做数据库关联。
第五个坑特别容易被忽略:换电脑之后特征库失效。现象是在实验室机器上建好face_features.pkl,拷到答辩笔记本上跑,原来能识别的人全都不认识。原因是 pickle 版本兼容性,或者两台机器上装的 dlib / face_recognition 版本不一致,导致同一个网络提取出的特征分布有差异,这属于环境层面的玄学,和算法本身没关系。解决方法是在答辩前用目标机器重新跑一遍建库脚本;如果时间不够,至少保证两台机器的依赖版本完全一致,不要上了答辩台还顺手升级库版本。这个坑我见过好几个人踩,都是拿着旧特征文件直接换机器,临时重装环境又来不及,最后只能现场录照片重新建库。
5. 从“能跑”到“好用”:特征库重训、多角度采集与报表导出
5.1 换人重训与多角度底库:别把全班压在一张照片上
毕业设计交上去之前,通常要把示例数据换成自己班级的真实数据。操作分三步:清空face_db目录,放入新的人脸照片;删掉旧的face_features.pkl,重新运行建库脚本;清空attendance.db里的打卡表,或者直接删文件。这里有个容易忽略的点:如果学生照片是手机拍的,背景很杂,最好先用脚本批量裁剪出人脸区域,再入库。我一般会写一个预处理脚本,用face_recognition.face_locations检测人脸,把框出来的区域单独存成小图,再喂给建库脚本,这样背景干扰会小很多。
建库的底图数量也很关键。一个人只放一张精修图,虽然入库时快,但换发型、换眼镜就会翻车。我的做法是每个人至少三张:一张正脸光线均匀、一张手机前置摄像头自拍、一张稍微侧脸带点自然光。这三张分别做编码存进特征库,比对时取最小距离,容错空间会大很多。如果你手里的资源压缩包里已经带有批量采集脚本,用起来会更省事;没有的话,自己写 20 行 Python 也就够了。
5.2 报表导出与每日核对习惯
考勤数据落库后,报表导出是容易被轻视的一步。用 Python 写 CSV 时,Excel 直接打开中文列名经常乱码,原因是编码应该用utf-8-sig而不是utf-8。更省心的做法是用openpyxl直接写 xlsx,一步到位。导出后还要把每日签到记录和数据库里的条数核对一遍,能提前发现重复写入、漏写等问题。我自己有个习惯:每跑完一次考勤,不看屏幕上的“打卡成功”,去看attendance.db里实际插了几条;确认数量对了再关程序。
从那以后,我每次拿到这类考勤资源,都不会先急着开摄像头,而是单独拎出建库脚本,用三张自己的照片做冒烟测试,确认特征库没问题再跑主流程。这套方法让我换数据、换机器、临时演示都没再翻过大车。希望这些踩坑经验能帮你在答辩前少走弯路,把精力留在真正要展示的识别逻辑上。
本文还有配套的精品资源,点击获取