ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python多人人脸识别课堂考勤系统:算法原理与工程实现

Python多人人脸识别课堂考勤系统:算法原理与工程实现 简介一份完整的Python多人人脸识别课堂考勤系统源码面向学习计算机视觉、人脸识别及Python Web开发的学生与开发者适用于课程设计、毕业设计或教师课堂考勤场景。资源共36个文件包含10个Python脚本、21个HTML页面模板、SQL数据库脚本、CSS样式以及说明文档与依赖清单压缩包仅29KB整体结构紧凑便于快速定位代码脚本覆盖应用入口、身份认证、数据库操作与核心识别逻辑配合页面模板可构成完整可运行项目。目前已有923人学习下载具有较好的参考热度。系统整合OpenCV人脸检测、Dlib关键点定位、深度学习识别模型与Haar级联分类器支持实时视频流捕捉、人脸特征提取、学生信息管理、考勤记录展示等流程通过阅读源码可掌握从摄像头采集、面部识别到数据库持久化的完整工程实现尤其适合初学者通过实际项目理解人脸识别技术落地。1. 课堂考勤为什么需要“多人同时识别”一个老师在阶梯教室点名底下六七十号人真正扣掉“到”“有”“嗯”之后名单上还剩三分之一没抬头。这是课堂考勤最痛的地方点名耗时长代答防不住随堂小测又没法把人与名字对上。Python 基于多人人脸识别的课堂考勤系统做的事就是把“老师念名字、学生应一声”换成“摄像头扫一遍教室现场把所有人脸与注册库里的名字对应上”一次性标出谁到场、谁迟到、谁缺勤。它不是门禁式的一对一核验而是面对一整片座位区的批处理识别。适合人群很明确做课程设计、毕业设计的学生开发者以及想给学院或培训班做一套离线考勤工具的人。门槛不高——电脑能跑 python 就能起步难点全在检测阈值、光线处理和多人去重这些细节上。这套方案真正值钱的地方也不是模型而是把识别结果落成一张干净考勤表的工程思路。2. 把教室场景拆进系统架构选型与两个关键算法2.1 先定架构离线本地处理还是调用云API先说结论课堂考勤适合做离线本地方案而不是调用云端人脸识别接口。原因有三条。第一成本。一场课如果连续开摄像头一节课 45 分钟按每秒一帧就是 2700 张画面云 API 按次计费一个学期下来费用远超这套源码本身。第二教室场景有多人、遮挡、侧脸、走动云 API 适合单张清晰照对动态视频流要额外做抽帧和比对逻辑省不了事。第三学生人脸数据属于敏感信息很多学校不允许把照片送到第三方服务。常见做法是OpenCV 负责读取视频帧并做人脸位置检测face_recognition 负责把检测到的人脸区域变成 128 维特征向量再和课前注册好的学生特征库做距离比对最后把结果写进 SQLite。整条链路全部在本地跑不需要公网也不产生按次费用。常见架构的数据流是这样的本地摄像头或视频文件 → 读取视频帧 → 检测画面中所有人脸框 → 对每张人脸提取特征向量 → 与注册特征库计算距离 → 距离小于阈值则判定为对应学生 → 滑窗去重后写入考勤表架构上有一个值得注意的点先抽帧再检测而不是用摄像头 SDK 的实时预览回调。原因是教室人数多、特征提取慢如果在每一帧都做全量识别笔记本 CPU 会直接跑满。常见做法是设置一个抽帧间隔比如每秒处理 12 帧识别间隔内完成比对就行漏掉中间帧对考勤结果几乎没有影响。这个细节会在第 3 章代码里体现。2.2 人脸检测与人脸特征提取用什么算法、阈值怎么设标题里写着“多人人脸识别”这里最核心的两个算法环节检测detection和识别recognition。“人脸识别算法”这个热搜词说的往往是这两件事的合并但在工程实现里它们必须分开看待。环节常见算法适用场景单人耗时CPU笔记本备注人脸检测dlib HOG人脸较正、光线好约 50100ms侧脸、低头时漏检明显人脸检测OpenCV DNN (res10_ssd)多人、侧脸、小目标更稳约 80150ms推荐作为课堂考勤首选人脸检测YOLO系列小脸密集场景约 200ms精度高但CPU吃力特征提取face_recognitiondlib resnet通用约 100ms/人输出128维浮点向量特征提取InsightFace / ArcFace高精度需GPU或更强CPU课设阶段没必要上检测负责回答“画面里有没有人脸、在哪”识别负责回答“这是谁”。常见误用是把两者混在一起在低算力设备上直接让检测模型去干识别的活结果就是换个人换个角度就认不出来。课堂场景里我一般这样设阈值人脸检测置信度 0.6特征比对欧氏距离阈值 0.5。置信度小于 0.6 的框直接丢弃能滤掉大部分虚影和远处的小脸欧氏距离小于 0.5 才认为匹配成功0.5 到 0.6 之间不做硬判而是看连续多帧投票结果这个细节后面单独讲。以上参数不是玄学实测下来教室 3 米以外的小脸在 0.6 阈值下依然能稳定检出但噪点框基本都被拦住了。2.3 比对策略1:N 的课堂版怎么做才不把全班认错课堂考勤本质上是一次 1:N 的人脸比对画面里出现一个学生要和全班注册库逐条比较找出最像的人是谁。这和门禁机那种 1:1 核验完全不同门禁机是“证明你就是你”课堂考勤是“在几十个人里找出你是谁”开放性强得多这也是为什么直接拿门禁那套参数过来经常会翻车。N 是全班人数通常 30100这个规模不需要建 Faiss 索引全量遍历 100 条特征向量在 CPU 上也就几毫秒。需要注意的问题在于“阈值”和“非本人”的区分。常见的翻车场景是画面里进来一位代课老师系统遍历之后发现他与某个学生的距离最近但距离其实有 1.1远大于阈值 0.5——可代码如果只写“取最小值”就会把这个老师误标成学生。正确做法是两步判断先取最小距离再判断这个距离是否小于阈值小于才算命中不满足就走“未注册”分类而不是强行归到最近的人名下。缺少这一步识别准确率会被陌生人漏洞拉低不少。比对策略上还有个容易被忽略的细节特征向量的“自比距离”。同一个学生在不同光线、不同角度下自比距离也会变化通常在 0.350.45 之间。如果注册照片是在室内灯光下拍的上课时靠窗座位是自然光两者叠加后距离可能达到 0.5 以上。所以注册环节建议每个学生录入两到三张不同角度的照片特征向量取平均或逐条保留比对时取“与注册集的最小距离”。这能直接解决“注册一张照现场认不出”的尴尬。3. 从零跑通多人识别考勤核心代码与参数3.1 注册入库把一张人脸照片变成特征向量先搭环境。python 版本建议 3.83.10face_recognition 依赖 dlibpython 3.11 以上编译 dlib 很容易翻车这是很多人拿到源码后第一个卡住的地方。建议先在 vscode 里把 python 环境配置好再执行安装命令pip install opencv-python face_recognition numpy openpyxl然后写注册脚本。注册阶段读入每个学生的照片文件夹每张照片跑一个人脸编码把 128 维特征连同个人信息一起存成 numpy 的 npz 文件后续识别阶段直接加载不用每节课重新算。import os import numpy as np import face_recognition # 目录结构students/学号_姓名/照片1.jpg 照片2.jpg base_dir students known_names [] # 存放姓名 known_ids [] # 存放学号 known_encodings [] # 存放128维特征 for stu_dir in os.listdir(base_dir): stu_path os.path.join(base_dir, stu_dir) if not os.path.isdir(stu_path): continue # 目录名约定为 2024001_张三 stu_id, stu_name stu_dir.split(_) enc_list [] for img_name in os.listdir(stu_path): if not img_name.lower().endswith((.jpg, .jpeg, .png)): continue img face_recognition.load_image_file(os.path.join(stu_path, img_name)) # 一张照片里可能检测到多张脸取最大的人脸框作为注册脸 locs face_recognition.face_locations(img, modelhog) if not locs: print(f警告: {stu_dir} 的 {img_name} 没检测到人脸) continue largest max(locs, keylambda r: (r[2]-r[0])*(r[1]-r[3])) enc face_recognition.face_encodings(img, known_face_locations[largest])[0] enc_list.append(enc) if not enc_list: print(f跳过: {stu_dir} 无有效人脸) continue known_ids.append(stu_id) known_names.append(stu_name) known_encodings.append(np.mean(enc_list, axis0)) # 多张照片取平均特征 np.savez(student_encodings.npz, idsknown_ids, namesknown_names, encodingsnp.array(known_encodings)) print(f注册完成: {len(known_ids)} 人)这段代码有一个关键决策多张注册照片取平均特征。逐条保留所有照片的特征会导致比对次数成倍上升而取平均后在 30100 人规模下依然稳定face_recognition 的 128 维特征向量在取平均后不是数学上的“平均脸”而是向量均值效果上相当于综合了多个角度。另一个决策是取最大人脸框注册防止注册照里出现同学入镜时把别人的脸当成学生本人。如果你所在环境里 dlib 安装顺利可以把 modelhog 换成 modelcnn检测更准但注册阶段没必要hog 已经够用。3.2 实时识别每一帧到底做了什么识别阶段是源码的核心。人脸识别系统里常有“黑匣子”的说法你看着它画了框但为什么认错、为什么漏检得把流程拆开看。每一帧的处理顺序是固定的缩小图像 → 检测人脸 → 提取特征 → 与注册库比对 → 画框与标注。import cv2 import numpy as np import face_recognition # 加载注册库 data np.load(student_encodings.npz, allow_pickleTrue) known_ids list(data[ids]) known_names list(data[names]) known_encodings data[encodings] match_threshold 0.5 # 欧氏距离阈值小于该值才认 def process_frame(frame): # 缩小到 640 宽加快检测与特征提取 h, w frame.shape[:2] if w 640: scale 640 / w small_frame cv2.resize(frame, (640, int(h * scale))) else: small_frame frame # 关键face_recognition 用的 RGBOpenCV 读出来是 BGR rgb_frame cv2.cvtColor(small_frame, cv2.COLOR_BGR2RGB) face_locs face_recognition.face_locations(rgb_frame, modelhog) face_encodings face_recognition.face_encodings(rgb_frame, face_locs) results [] for loc, enc in zip(face_locs, face_encodings): # 全量遍历注册库计算欧氏距离 distances np.linalg.norm(known_encodings - enc, axis1) min_idx int(np.argmin(distances)) min_dist distances[min_idx] if min_dist match_threshold: name known_names[min_idx] uid known_ids[min_idx] else: name unknown uid None results.append({name: name, uid: uid, dist: round(float(min_dist), 3), loc: loc}) return results这里的核心是 np.linalg.norm(known_encodings - enc, axis1) 这段向量运算。它一次性算出该人脸特征与全班注册特征的欧氏距离数组np.argmin 取最小距离的索引。需要注意face_recognition 的欧氏距离与余弦相似度不是一回事这个库内置的是欧氏距离阈值 0.5 也是针对欧氏距离的经验值。如果你改成 cosine 相似度阈值体系全部要重调。另外一个容易翻车的小坑OpenCV 读出来是 BGRface_recognition 要求 RGB漏了 cvtColor 这行识别率会明显下降。缩放到 640 宽的目的是压住检测耗时教室画面通常 1920×1080原始分辨率做 hog 检测会慢到 1 秒以上640 宽能把单帧耗时压到 150ms 左右。3.3 考勤落库与去重怎样才算“这个人到了”单帧识别结果还不能直接写库。原因很现实一个人被连续识别 10 帧就会落 10 条记录走过摄像头前的路人甲也有可能被当成某个学生低头 2 秒没被识别到考勤状态就会在“到”与“未到”之间反复横跳。常见解决办法是“连续帧滑窗 状态锁存”代码实现如下import sqlite3 import time DB_PATH attendance.db def init_db(): conn sqlite3.connect(DB_PATH) conn.execute( CREATE TABLE IF NOT EXISTS attendance ( id INTEGER PRIMARY KEY AUTOINCREMENT, course_id TEXT NOT NULL, student_id TEXT NOT NULL, student_name TEXT NOT NULL, status TEXT NOT NULL, first_seen TEXT NOT NULL, last_seen TEXT NOT NULL, UNIQUE(course_id, student_id) ) ) conn.commit() return conn class AttendanceTracker: def __init__(self, course_id, min_frames6, window_frames10): self.course_id course_id self.min_frames min_frames # 至少出现6帧才算到场 self.window window_frames # 统计最近10帧 self.history {} # student_id - buffer self.confirmed set() # 已确认到场的学号用于去重 def update(self, frame_results, conn): for r in frame_results: if r[uid] is None: continue buf self.history.setdefault(r[uid], []) buf.append({name: r[name], uid: r[uid], dist: r[dist]}) if len(buf) self.window: buf.pop(0) # 最近 window 帧里至少 min_frames 帧被识别到才算确认 if len(buf) self.min_frames and r[uid] not in self.confirmed: self.confirmed.add(r[uid]) conn.execute( INSERT OR IGNORE INTO attendance (course_id, student_id, student_name, status, first_seen, last_seen) VALUES (?, ?, ?, present, ?, ?), (self.course_id, r[uid], r[name], time.strftime(%Y-%m-%d %H:%M:%S), time.strftime(%Y-%m-%d %H:%M:%S)) ) conn.commit()参数 min_frames6、window_frames10 的含义在学生低头、转身、被前面同学遮挡的情况下10 帧窗口里有 6 帧识别到就可以确认到场这个冗余度能容忍约 40% 的漏检率。注意 INSERT OR IGNORE 配合 UNIQUE(course_id, student_id) 约束保证同一节课同一个学生只会落一条记录这是去重的最后一道保险。如果要做“迟到”状态在写入前对比当前时间与课程开始时间超过 15 分钟就把 status 写成 late这个逻辑放第 5 章再说。此刻你已经把源码的核心闭环跑通了注册 → 识别 → 去重 → 落库。4. 课堂考勤系统避坑五个最容易翻车的环节4.1 同画面多人时识别率骤降现象单个学生站在摄像头前识别正常一到教室全景画面里同时出现二三十个人系统只能认出五六个其余全标 unknown。原因人脸检测器对“小脸”特别敏感。教室全景里每个人的脸可能只有 40×40 像素dlib 的 HOG 检测器对这种小目标天生不稳另一个因素是密集人脸互相遮挡检测器只输出其中一个框。这不是特征比对的问题而是检测阶段就漏了。解决把检测模型从 hog 换成 OpenCV DNN即 res10_ssd 的 Caffe 模型它在小目标上明显更稳同时把小于 60×60 像素的人脸框直接过滤减少无效计算。如果坚持用 face_recognition 自带接口可以给 face_locations 传 modelcnn精度更高但速度会慢 35 倍适合离线对录像做考勤不适合实时课堂。我在实际项目里是先把画面裁成左右两半分别做检测再合并坐标精度足够且速度可接受。4.2 教室光线让肤色失真、特征提取失败现象靠窗座位逆光整张脸是黑的系统提示“检测到人脸但提取特征失败”或者教室日光灯频闪同一个人在相邻两帧的识别结果不同。原因人脸特征提取模型是在自然光、正脸样本上训练的极端侧光和强阴影会让人脸关键点定位偏掉特征向量漂移距离从 0.4 跳到 0.7超过阈值就认不出来。解决在预处理阶段加一个自适应 gamma 校正把暗部提亮再送入特征提取。cv2.createCLAHE(clipLimit2.0, tileGridSize(8,8)) 在实战里比普通直方图均衡化效果更好因为 CLAHE 限制对比度放大不会把高光区域过曝。另外一个血泪经验不要把摄像头正对窗户装机位侧一点让自然光从侧面进来识别率能稳定不少。这个调整成本为零效果比调任何代码参数都明显。4.3 一人多次签到、代答防不住现象同一个学生一节课在考勤表里出现 3 条记录或者某个同学帮忙“代答到”——人没来却用别人照片怼摄像头。原因前者是去重逻辑缺失后者是系统没有活体判断。课堂考勤是低风险场景多数源码不装备活体检测但重复计次是必须解决的。解决重复计次用第 3.3 节的 UNIQUE(course_id, student_id) 约束再加一个“确认后 5 分钟内不重复处理”的时间窗口。用照片怼摄像头这种问题入门方案是把“识别到同一个人脸持续 3 秒以上”作为到场条件照片怼镜头做不到持续多帧稳定因为照片会抖动、反光特征距离会跳动进阶方案是加 blink 检测但现阶段成本高对课堂考勤来说性价比不足。提醒一句如果你把这个源码用于正式的课程成绩考核建议保留现场录像作为复议凭证识别结果当辅助证据别把它当成唯一判定标准。4.4 打包 exe 后提示找不到模型文件现象源码在 IDE 里跑得好好的用 PyInstaller 打包成 exe 发给老师演示一运行就报错提示 dlib 或者模型文件找不到。原因PyInstaller 打包后资源文件的路径变了。代码里写死的 student_encodings.npz 是相对路径exe 启动时的当前目录根本不是源文件所在目录模型和特征库就找不到了。dlib 本身在 python 3.11 上编译失败也是这个问题的高发原因很多人因为装不上 dlib 直接弃坑。解决打包前把所有资源路径改成基于脚本所在目录的绝对路径os.path.join(os.path.dirname(os.path.abspath(file)), student_encodings.npz)。PyInstaller 打包时把模型文件作为 datas 资源加入运行时通过 sys._MEIPASS 读取这一步是 exe 能发布的关键。另外建议 python 环境固定用 3.8 或 3.10配 VS2019 以上的 C 构建工具装 dlib省掉最痛苦的编译环节。如果老师那边没有网络记得把 opencv-python 的 DLL 一起打进去常见做法是加 --add-data 参数打包exe 体积会到 200MB 左右这是正常的不是翻车。4.5 低配电脑卡到没法上课现象演示用的笔记本只有 8G 内存、没有独显摄像头一开CPU 直接飙到 100%画面变成幻灯片。原因实时模式对每一帧都做 hog 检测 128 维特征提取在 CPU 上单帧需要 300500ms加上教室多人每秒实际只能处理 23 帧视觉上就是卡。解决三条路按优先级来。第一把 3.2 节的抽帧间隔从逐帧改成每 3 帧处理 1 帧识别结果与当前画面差 0.5 秒对考勤完全够用第二把 process_frame 的宽从 640 降到 480检测面小了速度能提高约一倍代价是远处小脸更容易漏配合 4.1 的 DNN 模型刚好找回精度第三关闭 OpenCV 的窗口预览只保留数据采集界面更新交给低优先级的队列去做。教室里真正需要的是后台安静地记录而不是大屏实时炫技。5. 让系统能真正交给老师用考勤数据管理与界面5.1 考勤记录的数据结构课次、时间、状态怎么设计源码能跑通识别只是第一步交给老师之前数据表设计必须先想清楚。常见的课堂考勤需求有四个每节课谁到了、谁迟到了、谁一整个学期没来几次、某天临时调课能否重新考勤。基于这些三张表就够了表名字段说明studentsid, name, class_name, enroll_time学生档案coursesid, name, course_time, teacher课程与上课时间attendancecourse_id, student_id, date, status, first_seen, last_seen唯一键 (course_id, student_id, date)status 字段取值 present / late / leave不存 absent。absent 用“没记录”表达好处是缺勤名单可以从三位一体的查询里动态生成而不是写死状态坏处是“未到”需要在生成报表时额外补算这也是源码里最常见的两种实现分歧。我习惯用一张 absence 视图处理避免在主表里塞一堆默认行。迟到判定逻辑读取 courses.course_time上课时间后 15 分钟内识别到写 late超过 15 分钟写 leave这个 15 分钟是默认值不同学校可以调成 10 或 20。5.2 基于 tkinter 做一个可用的操作界面很多源码的界面用 tkinter 或 PyQt5。tkinter 是 python 自带的打包体积小适合课设演示PyQt5 外观现代但打包后体积增加几十 MB。这里给一个 tkinter 的最小可用界面覆盖“开课识别、查看记录、关闭系统”三个核心操作。import tkinter as tk from tkinter import ttk import threading import cv2 from attendance_tracker import AttendanceTracker # 复用 3.3 节类 class App: def __init__(self, root): self.root root self.root.title(课堂考勤系统) self.cap None self.running False self.conn init_db() # 打开 SQLite 连接全程复用 ttk.Button(root, text开始考勤, commandself.start).pack(pady10) ttk.Button(root, text停止考勤, commandself.stop).pack(pady10) self.status_label ttk.Label(root, text未开始) self.status_label.pack(pady5) def start(self): if self.running: return course_id CS101_2025S self.cap cv2.VideoCapture(0) self.running True self.tracker AttendanceTracker(course_id) threading.Thread(targetself.capture_loop, daemonTrue).start() self.status_label.config(text考勤中识别到的人会实时入库) def capture_loop(self): # 每 3 帧处理 1 帧减轻 CPU 压力 frame_idx 0 while self.running: ret, frame self.cap.read() if not ret: break frame_idx 1 if frame_idx % 3 ! 0: continue results process_frame(frame) # 复用 3.2 节函数 self.tracker.update(results, self.conn) self.cap.release()这个界面最大的作用是演示时不露怯按钮触发事件识别循环放在独立线程里tkinter 主线程不阻塞界面不会变成“未响应”。注意 conn 要在线程外初始化并通过参数传入不要在 capture_loop 里重复连接 SQLite。tkinter 界面本身不是难点难点是识别循环与 UI 的插桩方式——把识别回调丢进队列UI 定时 poll 更新人数要比直接在 tkinter 主线程里跑摄像头稳得多。用这个思路换成 PyQt5 也只需要把队列更新部分改成信号槽。5.3 导出 Excel 报表课时统计与导出考勤做完后最常用的是“某节课的缺勤名单”和“整个学期的出勤率统计”两张表。用 openpyxl 写 Excel比直接写 CSV 更接近老师的使用习惯。核心代码如下from openpyxl import Workbook import sqlite3 def export_report(conn, course_id, semester_dates): wb Workbook() ws wb.active ws.title 考勤明细 ws.append([学号, 姓名, 日期, 状态, 首次识别, 最后识别]) rows conn.execute( SELECT student_id, student_name, date, status, first_seen, last_seen FROM attendance WHERE course_id? ORDER BY date, student_id, (course_id,) ).fetchall() for r in rows: ws.append(list(r)) # 第二张表每个学生的出勤率 ws2 wb.create_sheet(出勤率) ws2.append([学号, 姓名, 应到次数, 实到次数, 出勤率]) for sid, sname in conn.execute( SELECT id, name FROM students ORDER BY id): expected len(semester_dates) actual conn.execute( SELECT COUNT(*) FROM attendance WHERE course_id? AND student_id? AND status IN (present,late), (course_id, sid)).fetchone()[0] ws2.append([sid, sname, expected, actual, f{actual/expected:.1%} if expected else -]) wb.save(f{course_id}_考勤报表.xlsx)导出报表的注意点应到次数不能直接查 attendance 表因为缺勤的人根本没有记录必须用课程日期列表去推迟到统计是否算“到”不同老师口径不同这里写成 present 和 late 都算实到如果你要按严格口径把 IN (present,late) 改成 present 即可。openpyxl 写大批量数据时逐行 append 够用班级超过 200 人再考虑批量写入普通课堂规模不需要上 pandas。6. 提升识别率的两个进阶手段时序平滑与 KNN 投票第 3 章的滑窗去重解决了“重复计次”但还没有解决“偶发误识别”。比如坐在后排的张同学因为脸小、光线差某一帧被识别成了邻座李同学——距离 0.48恰好低于阈值 0.5于是被记了一次错误考勤。单个错误帧如果直接落库后面就算识别正确也无法覆盖因为唯一约束已经把它锁死了。我的对策是不在单帧上做最终判决而是用“最近 10 帧的识别结果做多数表决”这是 KNN 投票思想的一个简化用法。from collections import Counter class VotingTracker(AttendanceTracker): def decide(self, uid): # uid 最近 window 帧的识别记录序列 buf self.history.get(uid, []) if len(buf) self.min_frames: return None # 统计这 10 帧里该 uid 被识别成各种身份的次数 votes Counter(rec[name] for rec in buf) name, count votes.most_common(1)[0] if count 6: return name return None # 票数不足继续等待不落库多数表决的关键参数是“10 帧窗口、6 票门槛”。6/10 意味着允许 4 帧误检但 5 帧以上错误不会通过对低头、转身这类短暂遮挡6 票积累需要约 3 秒连续识别刚好和“人站起来走动”的时间尺度区分开。这个方案对静止的课堂特别友好但对频繁走动的课堂会漏掉快速经过的人这时可以把门槛降到 5/10代价是偶发误识别多一些。阈值没有绝对最优我最后用的是一套自适应方案教室里人数少于 30 时用 6/10多于 30 时用 5/10因为人多的时候每个人被连续捕获的帧数本来就少。另一个个人体会是识别距离阈值的“双阈值”用法。单阈值 0.5 太硬教室光线变化很容易让同一个学生跨过阈值线双阈值则是距离小于 0.45 直接认0.450.60 之间交给投票机制大于 0.60 直接放弃。这套方案的原理是特征距离在 0.45 以内误匹配概率极低值得直接确认中间区间是光线和角度造成的噪声区域用多数表决消耗掉0.60 以上基本是陌生人或极端遮挡勉强认定会制造大量假阳性考勤。实际运行中我把这套“先卡粗阈值、再投票细判”的逻辑放进 AttendanceTracker误识别率比单阈值低了一个量级。我的教训是第一次在真实教室跑这套系统时为了演示效果把阈值调到 0.4结果靠窗的学生整节课都没被识别到考勤表上全是缺勤。后来才理解阈值高低不是玄学它决定的是一节课的“查全率”和“查准率”的取舍——阈值低了认不出真人阈值高了路人都会被当成学生。先拿一个固定机位、十来个学生的小教室试跑一节课记录识别距离分布再决定阈值这才是这套源码正确打开方式。有人把这样的问题归结为“源码有问题”但多数情况是环境、光线和参数没校准。课堂考勤系统的本质是识别、去重、报表三件事的工程整合识别算法只是其中一环把三件事都理顺这套源码从课设走向实际使用并不远希望帮到你。本文还有配套的精品资源点击获取
返回列表