Python在线课堂考勤系统:人脸识别签到从零搭建实战
发布时间:2026/9/28 22:17:53来源:尧图网络
简介这份资源是一套基于Python的在线课堂考勤系统完整项目源码面向具备一定Python基础、希望入门计算机视觉与深度学习的学生和开发者用于解决传统课堂点名效率低、易出错的问题。项目以卷积神经网络为核心结合OpenCV与深度学习框架完成人脸特征提取与身份识别并配套Web界面实现考勤记录的查看与管理覆盖数据收集、预处理、模型构建、训练优化到测试部署的完整流程。压缩包共98个文件约409.43MB包含22个py源码、28张jpg与6张png图像样本、15个xml配置、3个pt模型权重及dat数据文件等目录按功能模块划分便于对照学习。目前已有1461人学习下载。读者可从中获得可运行的人脸考勤实现方案、CNN模型训练与调参思路、图像预处理与数据集组织方式以及前后端与数据库对接的工程结构参考适合作为课程设计或毕业设计的实践素材。1. 在线课堂考勤系统从摄像头到考勤表的完整落地路径线上课上了三年最头疼的不是课件质量而是点名。一个六十人的班挨个喊名字要花八分钟中途还有人挂机去干别的。后来我干脆用 Python 写了一套在线课堂考勤系统核心思路很简单用摄像头抓拍 人脸识别自动签到老师端只负责看结果。整套东西跑在一台普通笔记本上不需要 GPU不需要云服务Python 安装好就能跑。这篇文章面向的是想自己搭一套考勤工具的开发者或者被线上考勤折磨到想动手的技术老师。我会从技术选型讲到代码实现再到实际部署时踩过的坑每一步都有可复现的命令和参数说明。如果你正在搜「python 在线课堂考勤系统」想找个能跑的方案这篇就是我从零搭完之后的完整记录。2. 技术选型为什么用 face_recognition 而不是 dlib 裸调2.1 人脸识别方案的三个候选与取舍做在线课堂考勤人脸识别是核心环节。市面上 Python 能用的方案大致三类OpenCV 自带的 Haar 级联、dlib 的深度学习模型、以及 face_recognition 这个封装库。我最初用的是 OpenCV Haar优点是轻量pip install opencv-python就能跑但实际测试下来误检率太高——侧脸、戴眼镜、光线稍暗就识别不出来课堂场景下学生不可能正襟危坐对着摄像头所以很快放弃了。dlib 的 CNN 人脸检测模型精度够但编译安装是个噩梦。在 Windows 上要装 CMake、Boost、Visual Studio Build Tools一套下来半小时起步而且不同 Python 版本对应的 dlib wheel 还不一样。我试过在 Python 3.11 上装 dlib源码编译直接报错最后降级到 3.9 才成功。对于只是想快速搭一套考勤系统的场景这个门槛太高了。最终我选了 face_recognition。它底层就是 dlib但作者把编译好的 wheel 传到了 PyPIpip install face_recognition直接装不需要额外配置。精度方面用 HOG 模型做检测128 维编码做人脸比对在课堂场景下识别率能到 90% 以上。代价是它依赖 dlib安装时还是会拉 dlib 的 wheel但至少不用自己编译。注意face_recognition 在 Python 3.12 上目前没有预编译 wheel建议用 Python 3.9 到 3.11。我用的是 3.10.11实测没问题。2.2 环境搭建从 python 安装到依赖就绪如果你还没装 Python去 python 官网下载 3.10 版本的安装包安装时勾选「Add Python to PATH」。装完之后在命令行验证python --version # 应输出 Python 3.10.x pip --version # 应输出 pip 23.x 或更高接下来装依赖。face_recognition 依赖 dlib、numpy、Pillow 这几个库一条命令搞定pip install face_recognition opencv-python numpy Pillow如果你用的是 vscode 配置 python 环境在项目根目录建一个.vscode/settings.json指定解释器路径{ python.defaultInterpreterPath: C:\\Python310\\python.exe }Linux 系统安装 python 的话Ubuntu 下用sudo apt install python3.10 python3-pip即可。装完依赖后验证一下import face_recognition import cv2 import numpy as np print(face_recognition version:, face_recognition.__version__) print(OpenCV version:, cv2.__version__)如果这两行都能打印出版本号环境就通了。我见过有人卡在 dlib 编译报错上大概率是没装 CMakepip install cmake之后再重试即可。2.3 考勤系统的模块划分整套系统我拆成四个模块人脸注册、实时检测、签到记录、数据导出。人脸注册负责把学生的照片转成 128 维编码存起来实时检测从摄像头读帧比对编码判断是谁签到记录把识别结果和时间戳写进 SQLite数据导出生成 Excel 考勤表。模块之间用函数调用串联不引入 Web 框架保持轻量。这样拆的好处是每个模块可以单独测试。比如人脸注册跑通了可以先拿几张照片验证编码是否正常再去调摄像头。如果一上来就写完整流程出了问题很难定位是注册环节还是检测环节。3. 核心实现人脸注册、实时检测与签到落库3.1 人脸注册把学生照片转成 128 维编码注册环节的输入是一组学生照片命名格式建议用「学号_姓名.jpg」比如20230101_张三.jpg。代码遍历照片目录提取人脸编码存成 pickle 文件import face_recognition import os import pickle def register_faces(photo_dir, output_fileencodings.pkl): known_encodings [] known_names [] for filename in os.listdir(photo_dir): if not filename.lower().endswith((.jpg, .jpeg, .png)): continue # 文件名格式学号_姓名.jpg name_part os.path.splitext(filename)[0] student_id, student_name name_part.split(_, 1) filepath os.path.join(photo_dir, filename) image face_recognition.load_image_file(filepath) # 用 HOG 模型检测人脸位置num_jitters1 表示只采样一次 locations face_recognition.face_locations(image, modelhog) if len(locations) 0: print(f[跳过] {filename} 未检测到人脸) continue if len(locations) 1: print(f[警告] {filename} 检测到多张人脸取第一张) # 提取 128 维编码num_jitters1 保证速度 encoding face_recognition.face_encodings( image, known_face_locations[locations[0]], num_jitters1 )[0] known_encodings.append(encoding) known_names.append(f{student_id}_{student_name}) print(f[注册] {student_name} ({student_id})) with open(output_file, wb) as f: pickle.dump({encodings: known_encodings, names: known_names}, f) print(f共注册 {len(known_names)} 人编码已保存至 {output_file}) if __name__ __main__: register_faces(./photos)这段代码的关键参数有两个modelhog和num_jitters1。HOG 模型比 CNN 模型快 10 倍以上在 CPU 上就能跑代价是精度略低但课堂场景够用。num_jitters控制人脸编码的采样次数值越大越精确但越慢注册阶段用 1 就行检测阶段可以适当调高。跑完注册脚本后目录下会生成encodings.pkl里面存了所有人的编码和对应的学号姓名。这个文件是后续检测的基础如果学生名单有变动重新跑一遍注册脚本即可。3.2 实时检测摄像头读帧与人脸比对检测环节从摄像头读帧对每一帧做人脸检测和编码比对。为了降低 CPU 占用我用了「跳帧检测」策略——每 3 帧检测一次中间帧复用上一次的结果import face_recognition import cv2 import pickle import numpy as np from datetime import datetime def load_encodings(pkl_fileencodings.pkl): with open(pkl_file, rb) as f: data pickle.load(f) return data[encodings], data[names] def run_attendance(encodings, names, tolerance0.45, frame_skip3): cap cv2.VideoCapture(0) # 0 表示默认摄像头 if not cap.isOpened(): print(摄像头打开失败) return # 记录已签到的人避免重复写入 checked_in set() frame_count 0 last_results [] while True: ret, frame cap.read() if not ret: break frame_count 1 # 跳帧每 frame_skip 帧才做一次检测 if frame_count % frame_skip 0: # 缩小到 1/4 尺寸加速检测 small_frame cv2.resize(frame, (0, 0), fx0.25, fy0.25) rgb_small cv2.cvtColor(small_frame, cv2.COLOR_BGR2RGB) face_locations face_recognition.face_locations(rgb_small, modelhog) face_encodings face_recognition.face_encodings(rgb_small, face_locations) last_results [] for encoding, location in zip(face_encodings, face_locations): # 计算与已知编码的欧氏距离 distances face_recognition.face_distance(encodings, encoding) best_idx np.argmin(distances) best_distance distances[best_idx] if best_distance tolerance: name names[best_idx] last_results.append((name, location, best_distance)) if name not in checked_in: checked_in.add(name) record_attendance(name) print(f[签到] {name} 距离{best_distance:.3f}) else: last_results.append((未知, location, best_distance)) # 在原始帧上画框坐标要放大 4 倍 for name, location, dist in last_results: top, right, bottom, left [v * 4 for v in location] color (0, 255, 0) if name ! 未知 else (0, 0, 255) cv2.rectangle(frame, (left, top), (right, bottom), color, 2) cv2.putText(frame, f{name} {dist:.2f}, (left, top - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) cv2.imshow(Attendance, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这段代码有三个关键设计。第一tolerance0.45是比对阈值face_recognition 官方推荐 0.6但课堂场景下我调到 0.45宁可漏识也不误识——把别人认成你比认不出来更麻烦。第二frame_skip3跳帧检测每 3 帧做一次识别中间帧复用结果CPU 占用从 80% 降到 30% 左右。第三缩小到 1/4 尺寸再检测速度提升明显画框时坐标乘 4 还原。record_attendance函数负责写库下一节展开。3.3 签到落库SQLite 表结构与写入逻辑签到数据我用 SQLite 存不需要额外装数据库服务一个文件搞定。表结构如下CREATE TABLE IF NOT EXISTS attendance ( id INTEGER PRIMARY KEY AUTOINCREMENT, student_id TEXT NOT NULL, student_name TEXT NOT NULL, check_time TEXT NOT NULL, date TEXT NOT NULL );对应的 Python 写入函数import sqlite3 from datetime import datetime DB_FILE attendance.db def init_db(): conn sqlite3.connect(DB_FILE) cursor conn.cursor() cursor.execute( CREATE TABLE IF NOT EXISTS attendance ( id INTEGER PRIMARY KEY AUTOINCREMENT, student_id TEXT NOT NULL, student_name TEXT NOT NULL, check_time TEXT NOT NULL, date TEXT NOT NULL ) ) conn.commit() conn.close() def record_attendance(full_name): # full_name 格式学号_姓名 student_id, student_name full_name.split(_, 1) now datetime.now() check_time now.strftime(%H:%M:%S) date now.strftime(%Y-%m-%d) conn sqlite3.connect(DB_FILE) cursor conn.cursor() # 同一天同一人只记一次 cursor.execute( SELECT COUNT(*) FROM attendance WHERE student_id? AND date?, (student_id, date) ) if cursor.fetchone()[0] 0: cursor.execute( INSERT INTO attendance (student_id, student_name, check_time, date) VALUES (?, ?, ?, ?), (student_id, student_name, check_time, date) ) conn.commit() conn.close()这里有个细节record_attendance里做了去重判断同一天同一学号只写一条记录。虽然检测环节的checked_in集合已经做了内存去重但程序重启后集合会清空所以数据库层面再加一层保险。初始化数据库在程序启动时调用一次init_db()即可。如果要做多节课考勤可以在表里加一个course_id字段区分。3.4 考勤导出用 pandas 生成 Excel 报表考勤数据最终要交给教务导出成 Excel 最方便。用 pandas 读 SQLite 直接写 xlsximport sqlite3 import pandas as pd def export_attendance(date, output_fileNone): if output_file is None: output_file fattendance_{date}.xlsx conn sqlite3.connect(attendance.db) query SELECT student_id AS 学号, student_name AS 姓名, check_time AS 签到时间, date AS 日期 FROM attendance WHERE date ? ORDER BY check_time df pd.read_sql_query(query, conn, params(date,)) conn.close() if df.empty: print(f{date} 无签到记录) return df.to_excel(output_file, indexFalse) print(f已导出 {len(df)} 条记录至 {output_file})pandas 写 Excel 依赖 openpyxl如果报ModuleNotFoundErrorpip install openpyxl即可。导出的表格可以直接发给教务也可以再做透视表统计出勤率。提示如果学生人数多建议在attendance表的student_id和date上建联合索引查询速度会快很多。4. 避坑与排查那些让我加班到凌晨的问题4.1 摄像头读帧卡顿甚至假死现象程序跑了几分钟后画面卡住cap.read()返回 False但摄像头指示灯还亮着。原因OpenCV 的VideoCapture在某些 USB 摄像头上缓冲区会堆积尤其是跳帧检测时读帧速度和处理速度不匹配缓冲区满了之后read()就阻塞。解决在cv2.VideoCapture(0)之后设置缓冲区大小为 1cap cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)另外如果用的是笔记本内置摄像头尽量别同时开其他调用摄像头的程序比如腾讯会议否则会抢占设备导致读帧失败。4.2 人脸编码比对误识率偏高现象明明不是张三系统却识别成张三距离值在 0.4 到 0.5 之间。原因tolerance设得太宽松或者注册照片质量差导致编码不具代表性。解决先把tolerance从 0.6 降到 0.45观察误识是否减少。如果还有误识检查注册照片——模糊的、侧脸的、戴墨镜的照片全部换掉。我一般要求注册照片是正面、光线均匀、不戴帽子的证件照风格。另外注册时可以多拍几张取平均编码但 face_recognition 不直接支持需要自己算均值。4.3 dlib 安装报 CMake 错误现象pip install face_recognition时卡在 dlib 编译报CMake is not installed或Visual Studio not found。原因dlib 没有对应 Python 版本的预编译 wheelpip 回退到源码编译而源码编译需要 CMake 和 C 编译器。解决先确认 Python 版本3.9 到 3.11 一般有 wheel。如果确实需要编译Windows 下装 Visual Studio Build Tools 并勾选「C 生成工具」然后pip install cmake再重试。Linux 下sudo apt install cmake build-essential即可。实在搞不定就降 Python 版本这是最快的路。4.4 多人同时入镜时识别混乱现象两个人一起走到摄像头前系统只识别出一个人或者把两个人的名字搞混。原因face_recognition 对每张人脸独立编码比对但如果两张脸距离太近HOG 检测可能只框出一张脸。解决调整摄像头角度让学生逐个入镜。如果必须多人同时签到把model从hog换成cnn检测精度更高但需要 GPU 或者耐心等——CPU 上 CNN 模型每帧要 1 到 2 秒。我的做法是在签到页面写一句提示「请逐个面向摄像头保持 1 秒」。4.5 签到记录重复写入现象同一个学生同一天在数据库里有好几条记录。原因checked_in集合只在内存中有效程序重启后清空或者多线程环境下集合读写有竞争。解决数据库层面加唯一约束把student_id和date设为联合唯一索引CREATE UNIQUE INDEX IF NOT EXISTS idx_student_date ON attendance (student_id, date);这样即使内存去重失效数据库也会拒绝重复插入。插入时用INSERT OR IGNORE替代INSERT避免报错中断程序。5. 进阶技巧把考勤系统做成可执行文件并加一层活体检测5.1 用 PyInstaller 打包成 exe老师双击就能用代码写完了但让老师装 Python、配环境、跑命令行不现实。用 PyInstaller 打包成单个 exe 文件双击就能运行pip install pyinstaller pyinstaller --onefile --add-data encodings.pkl;. attendance.py--onefile表示打包成单个文件--add-data把编码文件一起打进去。注意 Windows 下分隔符是分号Linux 下是冒号。打包完成后在dist/目录下找到 exe复制到老师电脑上即可。face_recognition 打包时容易漏掉 dlib 的模型文件如果运行报错找不到模型加--collect-all face_recognition_models参数。5.2 加一个简单的活体检测眨眼判断照片翻拍是考勤系统的经典攻击方式。学生拿手机拍一张同学的照片对着摄像头系统照样识别。加一个眨眼检测能挡住大部分翻拍import cv2 import dlib detector dlib.get_frontal_face_detector() predictor dlib.shape_predictor(shape_predictor_68_face_landmarks.dat) def eye_aspect_ratio(eye_points): # 计算眼睛纵横比低于阈值说明闭眼 vertical abs(eye_points[1].y - eye_points[5].y) abs(eye_points[2].y - eye_points[4].y) horizontal abs(eye_points[0].x - eye_points[3].x) return vertical / (2.0 * horizontal) def check_blink(frame, face_location): gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) rect dlib.rectangle(*face_location) landmarks predictor(gray, rect) left_eye [landmarks.part(i) for i in range(36, 42)] right_eye [landmarks.part(i) for i in range(42, 48)] ear (eye_aspect_ratio(left_eye) eye_aspect_ratio(right_eye)) / 2.0 return ear 0.2 # 低于 0.2 认为闭眼shape_predictor_68_face_landmarks.dat需要从 dlib 官网下载约 100MB。逻辑是连续检测到眨眼动作才认为活体否则只识别不签到。这个方案不完美但挡住静态照片足够了。5.3 参数调优速查表调参是这套系统最耗时的环节我把常用参数和推荐值整理成表方便对照调整参数位置推荐值说明tolerance人脸比对0.45越低越严格误识少但漏识多frame_skip检测循环3越大 CPU 占用越低但响应变慢model人脸检测hog有 GPU 可换 cnn精度更高num_jitters编码提取1注册时用 1检测时可调到 5resize 比例检测前缩放0.25越小越快但太小会漏检CAP_PROP_BUFFERSIZE摄像头1防止缓冲区堆积导致卡顿这张表是我反复试出来的但不同硬件和场景可能需要微调。比如教室光线暗tolerance可以放宽到 0.5如果误识严重降到 0.4。5.4 我踩过的最大的坑最后说一个血泪教训。我最初把注册照片和检测放在同一个脚本里每次启动都重新注册一遍结果摄像头刚打开时学生还没走到镜头前系统就把空背景里的人脸检测成了「未知」然后疯狂写签到记录。后来我把注册和检测彻底分开注册只在开学时跑一次检测脚本启动时直接加载encodings.pkl问题才解决。另一个习惯是每次改完参数先用一段录好的课堂视频跑一遍确认识别率和误识率都正常再去接实时摄像头。直接拿真人测试效率太低而且学生会有意见。这套系统现在跑了一个学期六十人的班签到时间从八分钟压缩到两分钟老师只需要在课后导出 Excel 核对一下异常记录。代码不复杂关键是参数要调对坑要提前避开。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网