
简介基于卷积神经网络的学生人脸识别考勤系统是一份完整的Python毕设源码与配套文档适用于计算机、人工智能、自动化等专业的学生、教师或从业者可用于期末课程设计、课程大作业或毕业设计参考。压缩包共55个文件体积2.22MB包含23个Python脚本、12个Qt界面ui文件、7个pyc编译文件、3个numpy数据文件、2个SQLite数据库以及1份docx说明文档涵盖人脸检测、人脸特征提取、考勤管理、界面交互等完整模块目录结构清晰。代码已经过调试测试确保可运行且作者称答辩评审分达98分具备较高的学习借鉴价值。目前已有213人学习下载。适合基础薄弱者按步骤学习也可由能力较强的读者在此基础上修改扩展以实现不同考勤场景功能整体是深度学习入门与毕设参考的实用资料。1. 人脸识别考勤系统这个毕设题到底要你做出什么东西一个班的课一学期下来点名就要耗掉好几十分钟代签、漏签更是常事。把人脸识别考勤系统当毕业设计的人多半是被这个场景触动的摄像头往讲台上一架学生进门看一眼屏幕考勤就自动完成。Python 加卷积神经网络CNN加人脸识别三个词凑起来既有“人工智能”的门面又有“能跑通、能演示”的落地空间。这套方案解决的核心问题是把人工点名变成无感考勤适合需要毕设代码的专业学生也适合不想买昂贵门禁机的实验室。拿到源码和文档说明你要做的不是跑通就交差而是把它拆成数据采集、模型训练、实时识别、考勤落库四条链路每条都要能在答辩时讲清楚。2. 技术选型CNN 模型、人脸检测库与数据集的三方取舍2.1 为什么毕设选 CNN 而不是传统人脸识别算法在动代码之前很多同学会先问一句人脸识别一定要用 CNN 吗答案是看场景。传统人脸识别算法里的 LBPH局部二值模式直方图和 Eigenfaces特征脸在光照均匀、姿势端正的实验室条件下做得不错但教室里的真实情况是窗边逆光、后排人脸小、有人低头玩手机还有人戴口罩。这些情况下手工设计的纹理特征和线性子空间方法变得非常脆弱识别率经常掉到七成以下。CNN 的立足点是端到端特征学习。卷积层从原始像素出发逐层提取边缘、纹理、五官结构等特征不再依赖人工设计特征算子面对光照和姿态变化有更强的鲁棒性。具体到这份毕设里CNN 走的是“人脸检测 → 区域缩放 → 卷积分类 → 输出学号”这条路属于轻量级方案但足够应付几十人班级的考勤。相比之下工业级门禁机里的方案通常是 FaceNet、ArcFace 这类用海量数据预训练的大模型识别精度高但参数量动辄几十兆还得配套专用硬件。毕设用不上那么大的模型也不建议直接用因为老师会追问“模型太黑盒”以及“训练复杂度太高”——自己搭一个小型 CNN 反而每一步都能解释清楚。选 CNN 还有一个实际考量答辩好讲。卷积层、池化层、全连接层、softmax 分类都是《深度学习》课程里的基础概念。老师不要求你刷到 SOTA只要能说清楚“为什么卷积结构能做人脸识别”以及“过拟合是怎么压下去的”就比套一个大黑匣子划算得多。2.2 人脸检测库选型Haar、OpenCV DNN、MTCNN 三选一人脸识别考勤的第一环不是认人而是先“找到人在哪”。这一步叫人脸检测常见方案有三个优缺点差别很大检测方案原理速度CPU精度适用场景OpenCV Haar CascadeHaar-like 特征 Adaboost极快30fps低误检多光线稳定、距离近的正面照OpenCV DNNResNet-10 SSD轻量 SSD 目标检测网络快20fps 左右中对角度有一定容忍教室光线变化不大的实时场景MTCNN三个 CNN 级联P-Net/R-Net/O-Net较慢10-15fps高对遮挡和侧脸更稳追求精度、可接受部分帧丢失毕设我一般推荐 MTCNN 或 OpenCV DNN。Haar Cascade 虽然加载快、调用简单但误检率实在太高拍个墙面、衣服纹理都能框出“脸”而且对侧脸和低头姿势几乎没有抵抗力考勤现场会频繁把不相关区域送进分类器。MTCNN 本身就是由三个卷积神经网络组成的级联结构P-Net 快速生成候选框R-Net 精筛O-Net 输出最终人脸框和关键点坐标。用 MTCNN 做检测整条链路从检测到识别都是卷积神经网络和标题的“基于卷积神经网络”完全一致论文里也能多写一节。代价是 CPU 下帧率偏低后面会讲怎么用跳帧来弥补。OpenCV DNN 是务实之选。它内置的 ResNet-10 SSD 模型权重很小加载后 CPU 实时性很好精度介于 Haar 和 MTCNN 之间。如果你的开发机没有独立显卡又想演示流畅就选它。另外要注意 Dlib 的 HOG 人脸检测器虽然也常用但本质是传统特征不是 CNN和这个题目不太搭不推荐。2.3 数据集怎么准备公开数据集、班级自采与数据增强的配比CNN 是数据喂出来的但“基于卷积神经网络的学生人脸识别考勤系统”这个题目有个特殊约束你要识别的是本班学生不是明星脸。公开数据集LFW、CASIA-WebFace里的人脸和你的用户对不上直接拿公开数据训出来模型不认识张三李四。所以自采数据是绕不开的一步。常见做法是让每个学生坐在摄像头前转动头部、变化表情、摘戴眼镜采集 30 到 50 张图片。一个 30 人的班级总原始样本约 1000 到 1500 张一个下午能采完。之后用 Keras 的 ImageDataGenerator 做数据增强随机旋转、平移、缩放、水平翻转、亮度扰动能把训练样本扩到 3 到 5 倍模拟教室不同座位角度和光线变化。需要注意增强只作用于训练集验证集用原始图片否则验证结果会虚高。还有一类同学想让省事直接从网上爬明星脸当训练集。这种做法的最大问题是现场识别效果不可控——你训出来的模型比的是“谁更像某张网络图片”而不是“谁更像坐在教室里的这个人”到了答辩演示阶段很容易当场翻车。自采数据虽然累但它是整个源码里最不可替代的部分也是答辩时可以拿出来讲的真实工作量。3. 从 0 到 1 复现训练一个能认人的 CNN 并把它接进考勤系统3.1 制作标签数据集目录结构、划分与归一化采完原始图第一件事是整理成 Keras 直接可读的数据集结构。规范是一个文件夹一个学生文件夹名就是学号或姓名因为flow_from_directory会自动按目录名生成标签。../dataset/processed/ ├── train/ │ ├── zhangsan/ # 每类 40~60 张增强图 │ ├── lisi/ │ └── wangwu/ └── val/ ├── zhangsan/ # 每类 10~15 张原始图 ├── lisi/ └── wangwu/训练验证划分有一个经常踩的坑不要随机切分。视频里连续采集的帧高度相似随机划分会导致同一个动作的相近帧同时出现在训练和验证里验证集被“剧透”准确率虚高。我习惯按采集顺序切一个人从头到尾的帧前 80% 进训练后 20% 进验证。这样验证集里全是模型没见过的姿势测出来才是真实水平。图片尺寸选 128×128 是权衡过的。224×224 的输入在 CPU 上单帧预测要 200 到 400ms考勤画面会明显卡顿128×128 对小型 CNN 来说单次预测约 30 到 80ms摄像头采集能做到 5 到 10fps 的刷新率体感完全不同。下面这段代码完成划分和统计顺手检查类别均衡import os import shutil raw_root ../dataset/raw train_root ../dataset/processed/train val_root ../dataset/processed/val for student in os.listdir(raw_root): raw_dir os.path.join(raw_root, student) if not os.path.isdir(raw_dir): continue files sorted(os.listdir(raw_dir)) split_idx int(len(files) * 0.8) for f in files[:split_idx]: os.makedirs(os.path.join(train_root, student), exist_okTrue) shutil.copy(os.path.join(raw_dir, f), os.path.join(train_root, student, f)) for f in files[split_idx:]: os.makedirs(os.path.join(val_root, student), exist_okTrue) shutil.copy(os.path.join(raw_dir, f), os.path.join(val_root, student, f)) # 打印每个类别的数量排查类别不均衡 for root in [train_root, val_root]: for student in sorted(os.listdir(root)): n len(os.listdir(os.path.join(root, student))) print(f{root}/{student}: {n})这段代码的逻辑是按文件名排序后做 8:2 切分。split_idx决定前多少张进训练剩下的进验证。打印统计的目的是排查“张三采了 100 张、李四只有 20 张”这类问题——类别不均衡会让李四的召回率崩到惨不忍睹。如果确实有人缺席没拍到就只对这个类别额外加增强轮次而不是整体加数据。3.2 搭建并训练 CNN 分类器模型结构、关键参数与训练回调模型不用复杂。这个考勤场景的类别数是固定的几十个学生不需要像人脸识别门禁机那样做百万级参数量的大模型。我用的是三组“卷积 批归一化 池化”堆叠再接全连接层和 softmax 输出的结构输入是单通道灰度图。import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import (Conv2D, MaxPooling2D, Flatten, Dense, Dropout, BatchNormalization) from tensorflow.keras.preprocessing.image import ImageDataGenerator from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau IMG_SIZE (128, 128) BATCH_SIZE 32 NUM_CLASSES 3 # 按实际学生人数修改 # 训练集增强验证集只归一化 train_datagen ImageDataGenerator( rescale1.0 / 255, rotation_range15, # 随机旋转 ±15 度 width_shift_range0.1, # 水平随机偏移 10% height_shift_range0.1, # 垂直随机偏移 10% brightness_range(0.8, 1.2), # 亮度扰动模拟不同座位光照 horizontal_flipTrue, ) val_datagen ImageDataGenerator(rescale1.0 / 255) train_gen train_datagen.flow_from_directory( ../dataset/processed/train, target_sizeIMG_SIZE, batch_sizeBATCH_SIZE, class_modecategorical, color_modegrayscale) val_gen val_datagen.flow_from_directory( ../dataset/processed/val, target_sizeIMG_SIZE, batch_sizeBATCH_SIZE, class_modecategorical, color_modegrayscale) model Sequential([ Conv2D(32, (3, 3), activationrelu, input_shape(128, 128, 1)), BatchNormalization(), MaxPooling2D((2, 2)), Conv2D(64, (3, 3), activationrelu), BatchNormalization(), MaxPooling2D((2, 2)), Conv2D(128, (3, 3), activationrelu), BatchNormalization(), MaxPooling2D((2, 2)), Flatten(), Dense(256, activationrelu), Dropout(0.5), # 随机丢弃一半神经元防止过拟合 Dense(NUM_CLASSES, activationsoftmax), ]) model.compile(optimizeradam, losscategorical_crossentropy, metrics[accuracy]) callbacks [ EarlyStopping(monitorval_loss, patience5, restore_best_weightsTrue), ReduceLROnPlateau(monitorval_loss, factor0.5, patience2), ] history model.fit(train_gen, validation_dataval_gen, epochs50, callbackscallbacks) model.save(../models/cnn_face_model.h5)这段代码有四个关键参数值得说透。第一BatchNormalization放在每个Conv2D之后能让每层输出分布稳定在光照变化明显的验证集上更稳这是很多初版源码缺失的细节。第二Dropout(0.5)放在全连接前对几十人的小样本场景是刚需否则模型会把训练图“背”下来验证集却一塌糊涂。第三EarlyStopping(patience5)连续 5 轮验证损失不降就停restore_best_weightsTrue保证回滚到最优权重不用手动保存。第四ReduceLROnPlateau(factor0.5, patience2)是两轮不降时学习率减半让 loss 后期能钻过小坑。epochs 设置 50 并不代表跑满 50实际在 15 到 25 轮左右就会触发早停收敛。3.3 实时识别与考勤落库摄像头读帧、置信度过滤与 SQLite 去重模型训练完接下来是把识别链路接进考勤系统。最简实现是 OpenCV 读帧MTCNN 检测人脸裁剪送入 CNNsoftmax 概率过滤后写入 SQLite。import cv2 import numpy as np import sqlite3 import datetime import tensorflow as tf from mtcnn.mtcnn import MTCNN model tf.keras.models.load_model(../models/cnn_face_model.h5) class_names list(train_gen.class_indices.keys()) detector MTCNN() conn sqlite3.connect(../attendance.db) conn.execute(CREATE TABLE IF NOT EXISTS attendance ( student_name TEXT NOT NULL, date TEXT NOT NULL, checkin_time TEXT NOT NULL, UNIQUE(student_name, date) )) CONF_THRESHOLD 0.85 # 置信度低于此值判为未知 cap cv2.VideoCapture(0) def predict_and_checkin(frame): faces detector.detect_faces(frame) for face in faces: x, y, w, h face[box] # 越界保护防止检测框超出画面导致裁剪崩溃 x, y max(0, x), max(0, y) w, h min(w, frame.shape[1] - x), min(h, frame.shape[0] - y) crop cv2.cvtColor(frame[y:yh, x:xw], cv2.COLOR_BGR2GRAY) crop cv2.resize(crop, (128, 128)).astype(float32) / 255.0 crop np.expand_dims(crop, axis(0, -1)) probs model.predict(crop, verbose0)[0] idx int(np.argmax(probs)) if probs[idx] CONF_THRESHOLD: name class_names[idx] today datetime.date.today().isoformat() now datetime.datetime.now().strftime(%H:%M:%S) conn.execute( INSERT OR IGNORE INTO attendance (student_name, date, checkin_time) VALUES (?, ?, ?), (name, today, now), ) conn.commit() cv2.rectangle(frame, (x, y), (xw, yh), (0, 255, 0), 2) cv2.putText(frame, name, (x, y-8), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2) while True: ret, frame cap.read() if not ret: break predict_and_checkin(frame) cv2.imshow(attendance system, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows() conn.close()这段代码里有三个容易翻车的地方。第一MTCNN 返回的box坐标可能越界或为负裁剪前必须 clamp否则frame[y:yh, x:xw]会直接抛异常程序在演示现场崩溃很难看。第二model.predict逐帧调用在 CPU 上是性能瓶颈检测加识别一帧约 130ms画面会卡。想顺滑就在while循环里加一个帧计数器每 3 帧才执行检测识别中间帧只显示画面考勤记录的实时性并没有实质损失。第三考勤去重依赖UNIQUE(student_name, date)约束配合INSERT OR IGNORE同一个学生同一天只记一条这条逻辑在答辩时讲成“数据库层防重复”是很清晰的设计点。4. 踩坑排查人脸识别考勤系统最容易翻车的五个真实场景4.1 逆光与侧脸导致检测框反复跳动、识别率骤降现象靠窗的学生被阳光直射时人脸框在脸上跳来跳去识别结果在“张三”和“未知”之间反复横跳考勤记录一直写不进去。原因人脸检测对光照已经比识别环节更敏感但逆光时人脸区域过曝或过暗灰度图变成一片白或一片黑CNN 提取不到纹理特征softmax 输出概率被拉平跨不过置信度阈值。解决预处理环节把灰度图做 CLAHE对比度受限自适应直方图均衡化替代普通直方图均衡化能明显改善过暗区域的纹理表达。若仍不行先用小样本测试把阈值从 0.85 降到 0.75确认是不是阈值设太严导致漏判如果两者都无效最现实的调整是移动摄像头安装位置让光源来自人脸前方而不是背后。这个结论可以直接写进论文的“系统测试与分析”一节。4.2 训练验证划分不当导致“虚高准确率”现象训练时准确率 98%验证集也 95%一到现场实测只有 60%换个戴眼镜的就认不出。原因划分数据集时用了随机洗牌同一个学生的连续采样帧被同时分进训练和验证模型相当于提前看过了验证题的答案。训练损失和验证损失都好看真实场景却打回原形。解决严格按时间序切分前 70% 到 80% 的帧做训练后 20% 到 30% 做验证。更严格的做法是按采集批次切分今天采的做训练明天采的做验证模拟真实场景下不同时间、不同光线的分布漂移。这条经验放在任何数据驱动的毕设里都适用提前避开能少走很多弯路。4.3 小样本下的严重过拟合训练准确率到 100%验证集却忽高忽低现象训练 10 轮后训练集准确率到 100%验证集只有 82%而且每个 epoch 之间差三四个百分点模型明显在“背题”。原因总样本可能只有几百张CNN 的参数量却达到几十万级别模型直接把训练图片的收入记了下来没有学到泛化的面部特征。Dropout 太小、数据增强太弱、全连接层太宽三个问题往往同时存在。解决按顺序调整三处。第一Dropout从 0.3 提到 0.5。第二ImageDataGenerator的rotation_range加到 20brightness_range放宽到 (0.7, 1.3)。第三全连接层从 256 降到 128同时把卷积核数量 64 改成 32降低模型容量。小样本场景下模型“傻一点”反而更稳这是一个反直觉但非常有效的原则。4.4 多人同时入镜时识别互相干扰、考勤记录张冠李戴现象两个学生并肩进门系统有时把两人识别成同一个名字或者第一帧认出甲第二帧把甲的名字安到乙头上。原因MTCNN 能检测出多个框但每个框送入 CNN 时可能裁剪到旁边同学的脸部边缘或重合区域。另外当前代码是每帧对每个检测框独立预测没有跨帧跟踪身份标签在人脸位置抖动时容易跳变。解决做两层加固。第一对检测框做过滤去掉面积过小的框并检查检测框之间的 IoU重叠度过高的保留置信度更高的那个。第二加入简单跨帧记忆记录上一次识别成功的人脸位置新帧优先对与旧位置 IoU 最大的框做预测连续 3 到 5 帧都是同一个人才写入考勤。这样既避免单帧误判也让多人场景下的记录更稳定。4.5 模型在另一台电脑上加载失败或 CUDA 环境不生效现象源码和 h5 模型拷贝到室友电脑上load_model报兼容性错误或者明明装了 TensorFlow GPU 版跑起来还是 CPU 的速度。原因Keras 模型跨版本加载时如果训练和加载的 TensorFlow 版本相差太大比如 2.6 训的模型拿到 2.15 加载可能产生SavedModel兼容警告甚至报错。GPU 不生效的原因基本是 CUDA 与 cuDNN 版本和 TensorFlow 自带的不匹配框架检测失败后会静默回退到 CPU。解决在源码 README 里锁定精确版本写清楚pip install tensorflow2.10.*这类命令。训练环境与运行环境不一致是毕设演示时最普遍的事故源头。GPU 跑不起来不用死磕这套小模型用 CPU 推理一帧 80ms 完全能接受追求更好的跨环境兼容性就在训练完后额外导出一次 SavedModel 格式比单独 h5 文件在版本迁移时更稳。5. 验收与进阶让考勤系统从“能跑”到“能答辩”5.1 用混淆矩阵代替准确率评估模型只看准确率在这个场景里不够可信因为如果有人总是被认成另一个人整体准确率可能还有 95%但被误认的同学天天考勤失败。打印混淆矩阵能直观看到每个学生被识别成谁、误判集中在哪个方向import os import numpy as np import tensorflow as tf from sklearn.metrics import confusion_matrix, classification_report model tf.keras.models.load_model(../models/cnn_face_model.h5) val_root ../dataset/processed/val class_names sorted(os.listdir(val_root)) class_to_idx {name: i for i, name in enumerate(class_names)} y_true, y_pred [], [] for class_name in class_names: class_dir os.path.join(val_root, class_name) for img_file in os.listdir(class_dir): img tf.keras.preprocessing.image.load_img( os.path.join(class_dir, img_file), color_modegrayscale, target_size(128, 128)) img tf.keras.preprocessing.image.img_to_array(img) / 255.0 img np.expand_dims(img, axis0) prob model.predict(img, verbose0)[0] y_true.append(class_to_idx[class_name]) y_pred.append(int(np.argmax(prob))) print(classification_report(y_true, y_pred, target_namesclass_names)) print(confusion_matrix(y_true, y_pred))如果混淆矩阵显示张三和李四主要是在侧脸角度互认那就针对侧脸补样本重训这种“由数据驱动决策”的过程正是答辩时老师最想看到的工程思路。5.2 界面化与考勤导出的低成本收尾命令行跑通和毕设演示之间还差一个界面。常见做法是用 Tkinter 包一个简单考勤面板左边放摄像头画面实时框出人脸和姓名置信度右边放当天的考勤表格。Tkinter 是 Python 自带库不需要额外安装赶时间时最省事。核心实现就是把 3.3 节的识别函数挂到 UI 的定时器里每 100 毫秒刷新一次画面并同步更新表格。导出考勤 Excel 用 pandas 读 SQLite 再to_excel十行代码就能闭环。5.3 答辩高频问题与验收自查清单答辩时最常被追问的三个问题为什么选 CNN样本不够怎么办实时性和准确率是多少。这三个问题的答案分别在前面的 2.1、4.3 和 5.1 里。验收时建议过一遍下面的自查清单光照变化下同一人能连续识别成功 10 次以上两个人同时入镜能分别打上正确标签置信度过滤能把陌生人挡在门外同一个人重复出现在摄像头前不会重复记录考勤界面版识别刷新率不低于 5fps。我自己的习惯是拿到任何毕设源码先做四件事锁定 Python 版本到 3.8 到 3.10锁定 TensorFlow 版本把项目里所有路径改成相对路径最后用绝对路径校验一遍数据目录能读通。做到这四条换电脑演示时基本不会翻车。人脸识别考勤这个方向技术栈成熟、演示效果直观、提分点也清晰只要把第 2 到第 4 章这套链路完整跑通答辩的底气就有了。希望帮到你。本文还有配套的精品资源点击获取