资讯动态

多人人脸识别课堂考勤系统:Python源码拆解与参数调优

发布时间:2026/10/1 5:36:29 来源:尧图企业网站定制
简介基于多人人脸识别的课堂考勤系统源码包面向想将计算机视觉落地到教学场景的学习者与开发者可用于自动记录到课情况并核对身份。项目覆盖人脸检测、特征提取、身份匹配、考勤记录与后台管理综合运用开源视觉库、关键点检测工具和深度学习模型配合数据库完成学生档案和出勤数据持久化结构清晰适合学习或二次开发。压缩包共三十六个文件包含十个Python程序文件、二十一个网页页面及数据库脚本、样式表、说明文档等程序文件承担业务逻辑与考勤流程网页文件用于教师管理查看数据库脚本负责初始化数据表整体仅约二十九千字节便于下载部署。已有九百二十三人学习。源码可帮助理解课堂考勤系统的模块划分、数据库设计以及如何将实时摄像头画面接入人脸识别流程对于实践计算机视觉与网络应用结合的项目来说是一份具有参考价值的可运行示例。1. 多人人脸识别课堂考勤系统源码拆解与落地思路课堂考勤是件看似简单、实际很烦的事四十人的班级点名要花五分钟代答到防不住学生低头或戴帽子人脸就漂移摄像头角度没调好后排一节课下来全是「未识别」。这套基于 Python 的多人人脸识别课堂考勤系统源码做的就是一件事——用摄像头连续抓取画面把画面里的每个面孔和已录入的学生人脸库做比对按课程、日期、学号写入考勤记录整个过程不按键、不扫码学生进教室坐下那几秒就被识别完了。它能直接解决传统点名耗时、漏签、代签三个痛点适合有 Python 基础的程序员、做课设的在校生以及想给工作室或社团做自动化考勤的从业者。源码的核心价值不在「人脸识别」四个字而在于「多人同时识别 自动去重 考勤落库」这条完整链路下面从选型开始讲。2. 技术选型与核心原理face_recognition 为什么够用2.1 从人脸检测到人脸比对这套管线分几步拿到这套源码先别急着跑得搞清楚它内部是怎么串联的。多人人脸识别考勤本质上是一条四段管线人脸检测、人脸对齐、特征提取、特征比对。课堂场景的难点在「多人」和「实时」两个词上教室里光线杂、有遮挡、人脸大小差异大检测器漏一个后排学生后面三步全白做。第一段是检测。源码用的检测方式是 HOG 特征 滑动窗口这是 face_recognition 库对 CPU 友好的核心设计不依赖 GPU普通笔记本能跑实时。检测完每个人脸后算法会找 68 个关键点包括眼睛、鼻子、嘴角位置这一步叫人脸对齐目的是把不同角度、不同远近的脸标准化到同一坐标系否则后面的特征比对会被角度干扰。第二段是特征提取。对齐后的人脸会送入预训练的 ResNet 模型把脸压缩成一个 128 维的浮点向量。这个向量的性质很关键同一个人的不同照片向量距离小不同人的向量距离大。源码目录里会有一个encodings.pkl或类似文件就是提前把学生照片库全部转成 128 维向量后序列化保存的结果运行时直接加载不用每帧重新走一遍模型。第三段是比对。拿摄像头里每一帧检测到的多个人脸向量和库里所有学生向量算欧氏距离距离小于阈值的就判定为命中。这套链路不需要训练自己的模型用的是通用预训练权重所以「换一批学生」只需要重新采集照片、重新生成编码文件不需要重训神经网络这也是它能落地到课堂场景的重要原因。2.2 为什么不用 YOLO 或深度模型全家桶常有读者问我现在 YOLOv8 都能检测 分类一条龙了为什么这套源码还要用两套工具组合我的看法是课堂考勤的识别对象是可控制的——学生座位固定、提前录了照片、人种和年龄段基本一致犯不着上目标检测大模型。face_recognition 加 OpenCV 的组合优势在于依赖少、调试直观、中间产物可视化一个帧能同时画出人脸框和识别结果出问题一眼能看见是检测漏了还是比对错了。YOLO 方案当然更强但代价是标注数据、训练时间、显存和部署复杂度对课设和中小型项目是性能过剩。源码的取舍逻辑是把昂贵的事特征模型用预训练权重一次搞定把便宜的事检测、追踪、逻辑控制留在 CPU 上跑。整套系统在 i5 处理器 8G 内存的机器上处理 640x480 画面、同时识别 10 人左右帧率能稳定在 1015 FPS够用。2.3 多人识别与「这帧到底谁是谁」的匹配逻辑多人场景有个隐藏问题检测框顺序每帧都在变——左边的人下一帧可能跑到右边如果只按「第一个人脸框」去查库记录会串位。源码的处理思路是给每个检测框加一个短时追踪 ID基于位置和大小做相邻帧匹配只有当某个 ID 连续 N 帧识别为同一个学生时才判定为有效识别。这个 N 值很讲究太小容易被单帧误识别干扰太大识别响应慢常见做法是在 35 帧之间。还有一个逻辑是「只打卡一次」。课堂上学生是持续坐在那里的如果每帧识别一次就写一次库一节课下来会产生几千条重复记录。源码在数据库层做了复合唯一约束按「课程编号 日期 学号」去重后到的记录直接丢弃保证每个学生每节课只有一条有效签到。3. 本地跑通流程环境、人脸库与启动参数3.1 环境搭建Python 版本是第一个坑不是 pip 能解决的先说我踩过最深的坑face_recognition 依赖 dlib而 dlib 是一个需要本地编译的 C 库Python 3.11 以上的版本装它非常容易编译失败报错千奇百怪。这套源码我建议直接用 Python 3.10 或 3.8省掉 90% 的编译问题。Windows 下还需要安装 Visual Studio Build Tools勾选「使用 C 的桌面开发」工作负载否则 CMake 会报找不到编译器。# 建议先建独立虚拟环境避免污染全局环境 python -m venv venv source venv/bin/activate # Windows 下执行 venv\Scripts\activate # 先装编译依赖再装 face_recognition顺序不能反 pip install cmake pip install dlib19.23.0 pip install face_recognition pip install opencv-python numpy Flaskdlib 的版本号看着不起眼实际是玄学常客19.23 是我用下来最稳的版本20.x 以后对编译器和 Python 版本的要求更苛刻。如果你装的是国内镜像源优先保证 face_recognition 和 dlib 从同一源装混装不同编译产物容易在运行时崩ImportError。装完后用python -c import face_recognition; print(face_recognition.__version__)验证能出版本号再往下走。3.2 人脸库目录结构与照片采集规范源码里通常会有dataset/目录它决定了识别结果的名字怎么来。我的约定是每个学生一个子目录目录名就是学号目录里放这个学生的照片照片源文件名叫什么都没关系但内容必须是这个人不能混入合照或背影。课堂教室这类场景每个学生 1020 张照片比较稳妥包含正面、左右侧脸 30 度、戴不戴眼镜、坐姿高度四个维度。采集照片的相机不一定要多好但有几条硬性规范人眼像素高度不小于 60 像素否则特征提取质量会骤降背景避免大面积反光尤其是白墙和窗户不要用学校证件照那种纯白底磨皮照片它和现场摄像头的光照差异太大同样一个人编码后的距离会比教室里拍的大很多。血的教训是我用证件照做库结果教室里识别成功率只有六成重新用摄像头拍了几张生活照后成功率立刻到九成。目录结构验证命令# 检查目录数量是否等于学生数学号里不要带空格和中文符号 find dataset -type d | wc -l ls dataset/3.3 生成编码文件一次生成运行期零计算编码文件是这套系统的「黑匣子」入口把原始照片变成可比对的向量。源码里一般会有encode_faces.py或build_encodings.py核心逻辑是遍历 dataset 目录对每张照片调face_recognition.face_encodings把返回的向量存进列表最后用 pickle 序列化保存。一次生成后用每次启动都复用运行期永远不重新提取。import os import pickle import face_recognition dataset_dir dataset known_encodings [] known_names [] for student_id in os.listdir(dataset_dir): student_dir os.path.join(dataset_dir, student_id) if not os.path.isdir(student_dir): continue # 每张照片提取一张人脸编码多人合照会被跳过避免污染 for img_name in os.listdir(student_dir): img_path os.path.join(student_dir, img_name) image face_recognition.load_image_file(img_path) encodings face_recognition.face_encodings(image) if len(encodings) 1: known_encodings.append(encodings[0]) known_names.append(student_id) else: print(f跳过 {img_path}: 检测到 {len(encodings)} 张人脸) with open(encodings.pkl, wb) as f: pickle.dump({encodings: known_encodings, names: known_names}, f)这段脚本做了两个关键处理只接受单人人脸的照片合照会被跳过并在控制台提示目录名直接作为学号使用不依赖照片内的任何元数据。生成后应检查known_names的数量理论上等于学生总数如果偏少大概率是某些照片角度太偏或分辨率太低需要回炉重新采集而不是直接往下跑识别。3.4 启动考勤主程序摄像头参数与运行观察点主程序跑起来后会打开摄像头预览窗口视频流上叠加每个人脸框和识别的学号。启动命令一般长这样python attendance.py --course CS101 --teacher 张三 --camera 0 --interval 3各参数含义--course是课程编号写进考勤记录作为课程维度--teacher是任课教师导报表时按教师过滤--camera是摄像头索引笔记本通常为 0外接 USB 摄像头可能为 1 或 2插拔顺序会影响索引号--interval是每隔多少帧做一次识别默认 3 表示每 3 帧识别一次调大省 CPU、但会漏掉快速短暂进入画面的人。运行后重点观察三个东西人脸框是否稳定框住每个学生、识别出的名字与实际座位是否一一对应、底部状态栏是否有「已签到」的计数变化。如果画面里人脸框乱跳、同一个学生一会儿 20190001 一会儿 20190002先别怀疑算法多半是摄像头分辨率太低或座位太挤物理上调整摄像头位置比调代码更有效。4. 命中判定与考勤记录阈值、去重和时间窗口怎么设4.1 tolerance 阈值0.4 和 0.6 之间差着一个代签识别比对里最重要的一个参数是欧氏距离阈值 tolerance。face_recognition 官方默认值是 0.6值越大越容易被识别为本人越小越严格。课堂考勤这个场景我的推荐值是 0.45 左右理由很现实太松会让长得像的同学互相误判太紧会让本来就模糊的摄像头捕捉到的脸识别不上。误判的代价在考勤场景里不对称——把 A 识别成 B等于 B 被代签了这是严重事故把 A 识别成「未识别」顶多让 A 课后手动补签。所以阈值宁紧勿松。我在实际操作中会在源码里找到比对函数把纯阈值判断改成「阈值 距离排序」双重判断# 假设比对函数里已拿到当前帧的人物特征 face_encoding distances face_recognition.face_distance(known_encodings, face_encoding) min_idx int(distances.argmin()) min_dist distances[min_idx] if min_dist 0.45 and min_dist (sorted(distances)[1] if len(distances) 1 else 1.0) * 0.75: matched_name known_names[min_idx] else: matched_name Unknown改动核心是一行不仅要求最小距离低于阈值还要求它显著小于第二小距离。这个二次条件能拦截「两个学生本身相似但都不是精准命中」的模糊匹配实战中能把误识别率降低一半以上。参数 0.75 是经验值第二近距离和最小距离差距不足 25%说明模型左右为难宁可判不认识。4.2 识别稳定帧避免「闪签」和「漏签」单帧识别结果不可信这件事上面提到过这里展开讲参数怎么设。源码里一般有个计数器逻辑记录某个追踪 ID 连续命中同一学号的帧数。课堂场景推荐连续 5 帧命中同一个人才判定签到一帧跳出就重置计数。TRACK_REQUIRED_FRAMES 5 # 连续命中帧数考场/严格场景可调到 8 track_count[track_id] track_count.get(track_id, 0) 1 if track_count[track_id] TRACK_REQUIRED_FRAMES: mark_attendance(student_id, course_id) track_count[track_id] -999 # 标记已签到防止重复触发TRACK_REQUIRED_FRAMES设 5 的理由摄像头 15 FPS 下学生从进入到坐稳大约 2 秒5 帧约 0.33 秒识别足够稳定如果是严谨的考试场景我建议调到 8代价是识别响应变慢但假阳性几乎为零。注意已签到后用-999不是删掉计数而是让计数器进入「不参与判断」的状态否则学生转头后再转回来又会触发签到逻辑。4.3 考勤落库与时间窗口SQLite 去重和迟到判定考勤数据最终落到 SQLite 数据库这是源码里最容易被新手忽略但实际影响报表准确性的部分。数据库表最少要有三个字段student_id、course_id、timestamp再加一个status字段记录「正常/迟到」。去重不能只靠单独一条 INSERT 语句而是在建表时就建好唯一索引CREATE TABLE IF NOT EXISTS attendance ( id INTEGER PRIMARY KEY AUTOINCREMENT, student_id TEXT NOT NULL, course_id TEXT NOT NULL, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, status TEXT DEFAULT normal, UNIQUE(student_id, course_id, date(timestamp)) );UNIQUE(student_id, course_id, date(timestamp))是去重的最后防线——即使上层逻辑有并发或重复触发数据库层也会拦截重复写入。源码里写入前会先查一次SELECT COUNT(*)判断是否已签到这是业务层判断唯一索引是兜底两层缺一不可。业务层判断是为了减少无谓的写入操作索引是为了防止并发或逻辑漏洞造成的重复。迟到判定建议在识别成功时做一次时间比较运行考勤时传入--start_time 08:00和--late_after 08:15源码内部会把当前时间和这两个时间点比较晚于 08:15 的写入statuslate。这个逻辑要放在识别稳定帧确认之后不要在检测到人脸时立刻判断否则学生刚进门还没坐定就被标了迟到。4.4 摄像头角度与座位覆盖物理参数比代码参数更影响结果这条放在中间讲是因为太多人把识别率低归咎于模型实际是摄像头位置错了。一个可容纳 50 人的教室摄像头架在讲台正前方 2.5 米高度、向下倾斜 15 到 20 度是覆盖范围和解剖结构最平衡的位置。角度太平后排人脸像素太小HOG 检测器直接漏检角度太陡拍到的是头顶68 个关键点里眼睛和鼻子定位失败。分辨率设置也有讲究cv2.VideoCapture(0).set(cv2.CAP_PROP_FRAME_WIDTH, 640)和set(cv2.CAP_PROP_FRAME_HEIGHT, 480)是性价比最高的组合。调到 1920x1080 不会让识别变准只会让处理每帧的时间翻倍帧率掉到 5 FPS 以下追踪 ID 频繁跳变反而误判率飙升。如果你的教室特别大、后排确实远优先拉焦距而不是上调分辨率物理放大比算法缩放更有效。5. 踩坑与排错课堂考勤我翻过的几个车5.1 dlib 编译失败pip 报错红海一片现象执行pip install dlib时终端滚动几百行编译信息后报error: command cl.exe failed或gcc: fatal error安装中断。原因dlib 需要本机编译 C 代码报错根源是缺编译器工具链Python 版本过新3.11、CMake 版本太老也会引发连锁失败。解决换 Python 3.10 建立虚拟环境先装 Visual Studio Build Tools勾选「使用 C 的桌面开发」再按顺序执行pip install cmake和pip install dlib19.23.0。如果还失败直接换 conda 环境conda install -c conda-forge dlibConda 有预编译包能绕开整个编译环节。5.2 识别结果张冠李戴A 变成了 B现象画面里学生 A 的脸框下赫然显示 B 的学号且连续多帧稳定错配。原因A 和 B 本就有些像加上摄像头分辨率低、光线偏暖两个人的 128 维向量距离被压缩到了阈值以内单阈值判断直接命中。更隐蔽的原因是 B 的照片库里有 A 的照片——采集照片时代拷了另一人的图。解决先检查dataset/下 B 目录里是否混入了 A 的脸有就删掉重新生成编码没有的话用 4.1 里的「距离排序二次判断」替换纯阈值判断最小距离和第二小距离拉不开差距就强制判 Unknown。实测这个双重判断能让错配率下降 70%。5.3 后排学生整节课未识别现象前排学生全部正常签到从第三排往后全部是 Unknown。原因摄像头分辨率 640x480 时后排人脸的人眼像素高度可能不足 20 像素HOG 检测器的检测置信度极低人脸框要么出不来要么框住的是远处窗外的路人。解决第一步物理调整摄像头尽量靠近教室中央且高度 2.5 米以上让后排人脸在画面中占比提到 40 像素宽。第二步降低画面缩小比例源码里一般有resolution 0.5的缩放参数改成0.25反而会让小人脸丢失改回 1.0牺牲帧率换检测覆盖率。第三步把后排学生的照片也加入侧脸角度增强库的容忍度。5.4 同一学生一节课签了三十次现象数据库里 20190001 在 09:00 到 09:45 之间出现了二十多条记录。原因业务层去重逻辑没生效常见两种忘记在 INSERT 前查库或查库用的比较条件是student_id course_id但没加日期于是今天和昨天互相比较永远查不到「已签到」。数据库层的唯一索引也没建或字段组合不含日期。解决把数据库表重建UNIQUE(student_id, course_id, date(timestamp))必须连同date()函数一起用。排查时先看已有的重复记录长什么样确认后执行DELETE FROM attendance WHERE id NOT IN (SELECT MIN(id) FROM attendance GROUP BY student_id, course_id, date(timestamp))清理脏数据然后跑一段测试视频验证去重生效。5.5 程序启动报摄像头被占用现象第一次运行正常第二次运行报V4L2: failed to start streaming或Camera index out of range程序闪退。原因上次运行没有正确释放摄像头资源OpenCV 的cap.release()没被调用或者有另一个 Python 进程还占着摄像头。解决关闭所有 Python 进程后重试更稳妥的是在主程序atexit注册释放函数保证异常退出时也释放摄像头。外接 USB 摄像头插拔后索引可能变化加上--camera 0参数前先用脚本枚举可用索引避免直接撞在非法的设备号上。5.6 室内光线变化导致识别率忽高忽低现象晴天上午识别正常下午拉上窗帘后同一批学生识别率骤降三成傍晚开灯后又恢复。原因人脸识别模型对光照变化高度敏感同一个人的脸在暗光下和人造光下提取出的向量距离差异能达到 0.2 以上刚好卡在阈值附近摇摆。解决两种思路叠加。第一是在采集学生照片时模仿真实教室光照——用教室灯光补光拍摄不要用自然光自拍第二是在识别端对帧做预处理cv2.cvtColor转灰度后做直方图均衡化再送给模型能小幅拉近光照差异。如果改代码嫌麻烦最简单的兜底是调高 tolerance 到 0.55但随之要接受误判风险上升只能二选一权衡。6. 验证识别质量与进阶我自己用的三遍抽查法和考勤报表导出源码跑通了、去重也生效了不代表系统就可以交付。我每次拿到一套新的考勤源码或换一个新教室都会强制自己走一遍三遍抽查流程这一步能省掉后面被老师或负责人质问「为什么缺勤名单错了」的尴尬。第一遍是静态验证取库里每个学生的照片重新跑一次编码提取确保每张照片都能比对自己的编码命中率必须 100%这一步排除照片库本身有废图。第二遍是动态验证录一段 2 分钟的真实教室视频包含 10 个学生依次入座、中途换座位、两个相似长相的人相邻坐三个场景回放视频看识别结果是否全程稳定。第三遍是交叉验证把 10 个学生的识别记录和人工签到表逐一比对迟到/正常标记是否正确、有没有漏签和多签。三遍都过了再做小范围试运行我一般试运行两周大约 4 次课第四周才敢正式使用。进阶方向里最常用的是考勤报表导出。源码里通常只提供 SQLite 查询但实际场景需要给老师一份 Excel。我习惯在源码基础上加一个export_report.py直接从 SQLite 读数据写 CSV再加一层建议如果每节课都导出文件名带课程号和日期方便归档。import sqlite3 import csv from datetime import date conn sqlite3.connect(attendance.db) cur conn.cursor() # 查某课程某日期的全部考勤记录缺席的学生不在表中联表补全 cur.execute( SELECT s.student_id, s.name, a.status, a.timestamp FROM students s LEFT JOIN attendance a ON s.student_id a.student_id AND a.course_id ? AND date(a.timestamp) ? ORDER BY s.student_id , (CS101, str(date.today()))) with open(fattendance_CS101_{date.today()}.csv, w, newline, encodingutf-8-sig) as f: writer csv.writer(f) writer.writerow([学号, 姓名, 考勤状态, 签到时间]) for row in cur.fetchall(): # 左连接中外键为空的即缺席状态写 absent status row[2] if row[2] else absent writer.writerow([row[0], row[1], status, row[3] or ])这里有个细节值得注意encodingutf-8-sig不是随便加的不加的话用 Excel 打开 CSV 中文会乱码utf-8-sig会让 Excel 正确识别 UTF-8 编码这是导出中文报表常见的坑。状态为空时补 absent是因为 LEFT JOIN 查不到考勤记录的学生并不是报错而是压根没签到要把这种情况显式标出。如果教室网络允许还可以加一个 Flask 小服务运行考勤的机器同时开python web_server.py老师手机浏览器访问同一局域网的 IP就能实时看到出勤进度。源码里如果没有 Web 端加一个只读查询接口大概 50 行代码就够完全没必要为一个课设上前后端分离的大架构。最后收个尾说说我自己的习惯从那以后每次换教室、换摄像头、换一批学生我都强制走一遍三遍抽查法静态 100%、动态三个场景、交叉对比人工表三关过了才开正式考勤。这套源码最大的价值不是省了那点点名时间而是把考勤数据的可信度提了上来——缺勤名单让老师复核时能理直气壮说「这是摄像头自动识别的不是谁随口报的」。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑