ARTICLE DETAIL

资讯详情

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

Python课堂人脸签到系统设计与实现:检测、阈值与并发写库

Python课堂人脸签到系统设计与实现:检测、阈值与并发写库 简介这是一份面向计算机相关专业毕业生与课程设计学习者的毕业论文文档围绕基于Python的课堂人脸签到系统展开针对传统点名效率低、代课替课难监管等问题给出从需求分析到系统测试的完整实现路线。文档以单个docx文件交付约1.58MB按论文规范组织依次涵盖概述、相关技术介绍、系统调研与可行性分析、系统设计、系统实现、系统测试及结论等章节可直接用作选题参考与写作模板。技术部分介绍了OpenCV与Dlib在人脸检测识别中的应用、Flask框架配合jinja2模板引擎搭建动态页面以及RESTful API完成前后端的数据交互功能模块覆盖选课、人脸识别签到、考勤查询、开放与编辑课程、拍照权限设置、修改学生考勤等。目前已有458人学习下载适合需要完整毕设思路、模块划分方式与论文框架的读者借鉴。1. 课堂人脸签到系统到底解决什么问题课堂点名从纸质签到表换成二维码代签只是多走两步换成刷脸问题会立刻变得具体后排光线太暗、学生低头看手机、教室只装了一个 720P 摄像头、帧率卡在 15fps以及“到底把相似度阈值卡在多少才不漏签也不误签”。基于 Python 设计的课堂人脸签到系统要做的是把摄像头取流、人脸检测、对齐、特征比对、签到状态落库、导出考勤表这几步串成一条能连续跑一整节课的链路而不是调一次cv2就结束的演示脚本。它适合两类人正在做课程设计或本科毕设、需要把“系统设计与实现”写出代码量和技术细节的同学以及已经在用 Python 做视觉项目、需要认真处理误识率、并发写库和部署边界的工程师。接下来按模型选型、最小可跑代码、业务表结构、实测排错和论文材料逐层拆开。2. 人脸签到链路的选型检测、对齐与特征比对的参数账2.1 检测器与识别模型怎么配课堂场景的第一个约束是人数一间教室 40 到 120 人摄像头可能只拍到前几排。检测器要在 1080P 画面上稳定找到 20 到 50 张脸且单帧耗时不能超过 60ms否则 15fps 的摄像头都喂不满。常见做法是把“检测”和“识别”拆成两个模型检测负责框出人脸并给出 5 个关键点识别负责把对齐后的人脸映射成特征向量最后用余弦相似度或欧氏距离做 1:N 比对。Haar 级联在小场景够快但侧脸和遮挡下误检明显不建议作为毕设主方案MTCNN 精度好CPU 上单帧约 80 到 150ms适合人数少的签到终端RetinaFace 或 SCRFD 在 640×640 输入下能到 30fps 以上配合 ONNX Runtime 在 CPU 上也可用YOLOv8-face 则是另一种思路把人脸框当目标检测任务适合需要同时检测人形和脸的场景。识别模型上face_recognition用的是 dlib 的 ResNet-29输出 128 维特征优点是 API 极简、论文里引用方便缺点是 dlib 编译麻烦、1:N 规模大时精度下降。InsightFace 的buffalo_l包输出 512 维 ArcFace 特征1:N 场景下同阈值下的误识率明显更低代价是依赖onnxruntime和多一个 5 点对齐步骤。下面这张表是我在选型时会对比的几组搭配。方案检测耗时CPU1080P特征维度1:N 表现主要坑Haar LBPH20ms直方图差光照一变就失效MTCNN FaceNet100ms128/512中小脸漏检dlib HOG dlib ResNet60ms128中侧脸阈值难调SCRFD ArcFace35ms512好需要 onnxruntimeYOLOv8-face ArcFace25ms需 GPU512好训练/导出成本高选型结论很直接毕设演示阶段用face_recognition能把代码量压到最低但论文里最好写清楚它属于 dlib 的 HOG 检测加 ResNet 特征如果要在真实教室跑换 SCRFD ArcFace特征维度从 128 提到 512阈值也要重新标定不能把tolerance0.6直接搬过去。2.2 环境搭建与依赖锁定Python、OpenCV、dlib 的安装坑Python 安装教程和 VSCode 配置 Python 环境的文章很多但人脸签到系统真正的坑在 dlib 和 OpenCV 的版本配合。我一般先建虚拟环境再锁版本避免pip install face_recognition时自动拉一个需要现场编译的 dlib。# 1. 建虚拟环境Python 建议 3.9 到 3.11 python -m venv .venv source .venv/bin/activate # Windows 是 .venv\Scripts\activate # 2. 先装 numpy 和 opencv避免依赖顺序导致的 ABI 冲突 pip install numpy1.26.4 opencv-python4.9.0.80 # 3. dlib 在 Windows 下没有官方 wheel优先用 conda 或预编译包 # Linux/macOS 可尝试直接编译需要 cmake 和 g pip install cmake pip install dlib19.24.2 pip install face_recognition1.3.0 # 4. 想避开 dlib 编译用 InsightFace onnxruntime pip install insightface onnxruntime参数说明numpy1.26.4是很多 OpenCV 4.9 轮子编译时的 ABI 基准升到 2.x 后部分旧版opencv-python会报_ARRAY_API not foundopencv-python不带 GUI 功能如果要用cv2.imshow需要装opencv-python而不是opencv-python-headlessdlib 编译失败时看第一行报错如果是CMake not found就补 cmake如果是Visual Studio not found就在 Windows 上装 Build Tools 并勾选 C 桌面开发。VSCode 里按CtrlShiftP选Python: Select Interpreter指到.venv/bin/python或.venv\Scripts\python.exe再在.vscode/settings.json里写python.analysis.extraPaths: [./src]否则跨目录导入会标红但实际能跑。提示dlib 编译一次可能超过 10 分钟建议在自己的机器上编译好后把 wheel 缓存下来换机器时直接pip install dlib-19.24.2-*.whl不要在答辩现场重装。2.3 最小可跑的人脸注册与比对代码注册阶段每个学生录 3 到 5 张正脸分别提取特征后取均值比单张模板稳。比对时不要把注册特征全部展开成矩阵反复算直接算欧氏距离取最小即可。import face_recognition import numpy as np import pickle import os def enroll(student_id, img_paths): 注册返回该学生的平均特征向量 encodings [] for p in img_paths: img face_recognition.load_image_file(p) # modelhog 快但小脸漏检modelcnn 准但需要 GPU locations face_recognition.face_locations(img, modelhog) if len(locations) ! 1: print(f{p} 检测到 {len(locations)} 张脸跳过) continue encodings.append(face_recognition.face_encodings(img, locations)[0]) if not encodings: return None return np.mean(encodings, axis0) # 多张取均值降低单张噪声 def load_db(pathtemplates.pkl): with open(path, rb) as f: return pickle.load(f) # {student_id: np.ndarray(128,)} def match(probe_enc, db, tolerance0.45): 1:N 比对返回 (student_id, distance) ids list(db.keys()) matrix np.array([db[i] for i in ids]) dists np.linalg.norm(matrix - probe_enc, axis1) idx int(np.argmin(dists)) best_id, best_dist ids[idx], float(dists[idx]) return (best_id, best_dist) if best_dist tolerance else (None, best_dist) if __name__ __main__: db load_db() probe face_recognition.face_encodings( face_recognition.load_image_file(probe.jpg) )[0] sid, dist match(probe, db, tolerance0.45) print(sid, dist)逻辑说明face_locations返回的是(top, right, bottom, left)元组列表如果一张图里检测到多张脸注册阶段直接跳过避免把别人的脸算进模板。face_encodings返回的是 128 维向量np.mean按列求均值后仍是一个 128 维向量可以直接参与距离计算。np.linalg.norm(matrix - probe_enc, axis1)是逐行算欧氏距离矩阵大小是N×128N 是学生数几百人以内毫秒级完成。参数说明tolerance是判定阈值face_recognition默认 0.6那是 1:1 验证场景的值课堂签到是 1:N阈值要收到 0.42 到 0.48具体看下面那张表。modelhog与modelcnn的区别在检测器HOG 在 CPU 上快CNN 需要 CUDA 才划算。如果换 InsightFacematch里的欧氏距离要改成余弦相似度阈值方向相反通常是相似度大于 0.35 到 0.45 判为同一人。2.4 阈值、误识率与课堂场景的容错设计阈值不是拍脑袋的。准备一组注册库和一组探测图让同一个人不同角度、不同光照的图做正样本让库外的人做负样本扫一遍阈值画 FAR/FRR 曲线。下面是我在一间 60 人教室、1080P 摄像头、MTCNN FaceNet 组合下实测的粗略口径供调参时参考。阈值误识率 FAR拒识率 FRR课堂体验0.60明显偏高很低陌生人可能被认成某学生0.50中等低侧脸容易签不上0.45低中等多数毕设演示的平衡点0.40很低偏高需要学生正对摄像头0.35接近零高后排基本签不上课堂签到和门禁不一样门禁误识一次是安全问题课堂签到误识一次是考勤纠纷拒识一次是学生举手补签。所以工程上更常见的做法是把阈值设在 0.45 附近同时对拒识样本走一次“二次确认”——让学生靠近摄像头再拍一帧或者弹出一个挑战动作而不是把阈值一路降到 0.55。注意同一份代码换摄像头、换镜头焦距后特征分布会变阈值必须重新标定。别把实验室调好的 0.45 直接搬到另一间教室。3. 签到业务层实现数据库、状态机与课堂日期课程表3.1 SQLite 表结构与签到状态字段设计人脸比对只是链路的一半另一半是“谁、在哪节课、什么时间、以什么状态签到”。表设计如果只留一张attendance后面统计迟到、请假、补签时会很难受。我一般拆成学生、模板、课程、课次、签到流水五张表SQLite 足够撑住一个院系的课程设计并发写库用 WAL 模式。PRAGMA journal_mode WAL; -- 允许读的同时有一个写者 PRAGMA busy_timeout 5000; -- 写锁等待 5 秒避免直接报 database is locked CREATE TABLE students ( student_id TEXT PRIMARY KEY, name TEXT NOT NULL, class_name TEXT NOT NULL ); CREATE TABLE face_templates ( student_id TEXT NOT NULL REFERENCES students(student_id), model_name TEXT NOT NULL, -- 例如 dlib_resnet_128 / arcface_512 vector BLOB NOT NULL, -- numpy 数组 tobytes() updated_at TEXT NOT NULL, PRIMARY KEY (student_id, model_name) ); CREATE TABLE courses ( course_id TEXT PRIMARY KEY, course_name TEXT NOT NULL, teacher TEXT NOT NULL, room TEXT ); CREATE TABLE sessions ( -- 一次课 session_id INTEGER PRIMARY KEY AUTOINCREMENT, course_id TEXT NOT NULL REFERENCES courses(course_id), start_time TEXT NOT NULL, -- ISO8601 end_time TEXT NOT NULL, late_after TEXT NOT NULL -- 超过这个时间算迟到 ); CREATE TABLE attendance_logs ( -- 签到结果一人一课次一条 session_id INTEGER NOT NULL REFERENCES sessions(session_id), student_id TEXT NOT NULL REFERENCES students(student_id), status TEXT NOT NULL, -- present / late / absent / leave confidence REAL, -- 最小距离或余弦相似度 checked_at TEXT NOT NULL, source TEXT NOT NULL, -- face / manual PRIMARY KEY (session_id, student_id) );参数说明PRAGMA journal_mode WAL是 SQLite 在“一个写者 多个读者”场景下的关键设置不开 WAL 时摄像头线程一边写日志、导出线程一边读表会直接锁死busy_timeout设成 5000 表示遇到写锁时等 5 秒再报错实测比默认的 0 好用很多。face_templates里存BLOB而不是把 JSON 字符串写进 TEXT是因为numpy.tobytes()和np.frombuffer()的往返没有精度损失512 维 float32 只要 2KB。attendance_logs用(session_id, student_id)做联合主键天然防止同一次课重复插入。3.2 用 FastAPI 暴露签到接口和活体挑战如果系统只有一台摄像头接在讲台上可以直接在桌面程序里调match。但如果要做成前后端分离、让助教用手机看签到结果就把比对封装成一个 HTTP 接口。FastAPI 的好处是上传文件、返回 JSON、自动生成文档都在一个文件里完成。from fastapi import FastAPI, UploadFile, File, HTTPException from pydantic import BaseModel import numpy as np, face_recognition, pickle, io from PIL import Image app FastAPI() db pickle.load(open(templates.pkl, rb)) class CheckinResp(BaseModel): student_id: str | None distance: float status: str app.post(/api/checkin, response_modelCheckinResp) async def checkin(session_id: int, file: UploadFile File(...)): raw await file.read() img np.array(Image.open(io.BytesIO(raw)).convert(RGB)) locs face_recognition.face_locations(img, modelhog) if len(locs) ! 1: raise HTTPException(400, fexpect 1 face, got {len(locs)}) enc face_recognition.face_encodings(img, locs)[0] ids list(db.keys()) mat np.array([db[i] for i in ids]) dists np.linalg.norm(mat - enc, axis1) idx int(np.argmin(dists)) best_id, best_dist ids[idx], float(dists[idx]) if best_dist 0.45: return CheckinResp(student_idNone, distancebest_dist, statusunknown) return CheckinResp(student_idbest_id, distancebest_dist, statuspresent)逻辑说明UploadFile接收前端FormData传来的图片Image.open读成 RGB 数组后交给face_locations。如果检测到的人脸数不是 1直接返回 400因为签到接口一次只处理一个人。距离小于阈值就返回学号大于阈值返回unknown让前端提示重拍。真正的写库动作不在这个函数里做而是交给一个单线程写入队列避免多个请求同时打 SQLite。提示接口层不要直接传原始特征向量给前端特征是可以被重放攻击的。如果需要更强的防代签加一步随机动作挑战例如“请眨眼”或“请向左转头”由前端录 1 秒视频后端抽 3 帧比对。3.3 批量签到与并发写入队列与事务边界教室里的摄像头是连续取流的每 500ms 抽一帧检测到脸就比对。如果每比对成功一次就INSERTSQLite 在高频写入下会频繁触发 checkpoint导出 Excel 时容易卡。更稳的做法是推理线程只把(session_id, student_id, distance, ts)推进queue.Queue写库线程每积累 10 条或每 1 秒批量executemany一次。import queue, threading, sqlite3, time write_q queue.Queue(maxsize1000) def writer_loop(db_path: str): conn sqlite3.connect(db_path, isolation_levelNone) conn.execute(PRAGMA busy_timeout5000) batch [] while True: try: item write_q.get(timeout1.0) batch.append(item) except queue.Empty: if batch: flush(conn, batch); batch.clear() continue if len(batch) 10: flush(conn, batch); batch.clear() def flush(conn, batch): conn.execute(BEGIN IMMEDIATE) # 立刻拿写锁避免和其他写者交错 conn.executemany( INSERT OR IGNORE INTO attendance_logs (session_id, student_id, status, confidence, checked_at, source) VALUES (?,?,?,?,?,?), batch, ) conn.execute(COMMIT) threading.Thread(targetwriter_loop, args(checkin.db,), daemonTrue).start()逻辑说明q.get(timeout1.0)在队列空时最多等 1 秒超时后 flush 残留批次保证下课铃响时数据不丢。BEGIN IMMEDIATE比默认的延迟事务更可控它在事务一开始就请求写锁避免两个写线程同时进入后又互相等。INSERT OR IGNORE配合联合主键同一学生同一次课重复签到不会报错也不会覆盖第一次的checked_at。参数说明maxsize1000是防止推理线程比写库线程快太多时内存膨胀满了就丢弃最旧数据或者阻塞推理。如果一台机器上同时跑多个摄像头、多个班级不要用多进程各自开 SQLite 写把写入收敛到一个进程推理可以用multiprocessing开多个 workerworker 通过Queue把结果发回主进程写库。3.4 导出签到表到 Excel 与论文里可用的统计口径导出不是简单SELECT *。考勤表要能直接交给教务列一般是学号、姓名、班级、课程、课次时间、签到状态、签到方式、相似度。统计口径要先定义清楚present是checked_at late_after的签到late是超过late_after但仍在end_time前absent是整节课没有记录leave是人工标记的请假。下面这段用 pandas 把三张表拼起来注意 SQL 里已经把时间比较下推给了数据库避免把全表拉到内存里再算。import pandas as pd, sqlite3 conn sqlite3.connect(checkin.db) sql SELECT s.student_id, s.name, s.class_name, c.course_name, se.start_time, se.late_after, COALESCE(a.status, absent) AS status, COALESCE(a.source, -) AS source, a.confidence, a.checked_at FROM students s CROSS JOIN sessions se JOIN courses c ON c.course_id se.course_id LEFT JOIN attendance_logs a ON a.student_id s.student_id AND a.session_id se.session_id WHERE se.session_id ? df pd.read_sql(sql, conn, params(1,)) df[status] df.apply( lambda r: late if r[status] present and r[checked_at] and r[checked_at] r[late_after] else r[status], axis1) df.to_excel(attendance_session_1.xlsx, indexFalse) print(df.groupby(status).size())逻辑说明CROSS JOIN sessions的作用是让每个学生都出现在每节课里再用LEFT JOIN补签到记录没记录的统一显示为absent。COALESCE把 NULL 替换成可读值避免 Excel 里出现空单元格。时间比较放在 Python 里做是因为late_after的格式可能带时区实际项目里建议在 SQL 里用datetime()函数比较。参数说明params(1,)绑定session_id不要用 f-string 拼 SQL。导出前先print(df.groupby(status).size())看一眼比例如果某节课absent占 80%先检查是不是阈值太高或者摄像头没开而不是直接交给老师。论文里如果要放统计图用 matplotlib 画应到、实到、迟到的柱状图横轴课次纵轴人数图注里写清楚统计口径。4. 课堂实测里的误报、代签与光照问题怎么排4.1 阈值调参从 FAR/FRR 到实际教室阈值在实验室里调好搬到教室往往会偏高或偏低。原因有三个教室顶部灯光造成的高光、投影仪打在脸上的色偏、以及学生坐姿导致的俯视角度。调参时不要只看一张图按下面的步骤做一轮标定通常能把误识率压下来同时保持可接受的拒识率。第一步准备数据集。每个学生录 5 张注册图再在教室不同位置、不同时间拍 3 张探测图库外再找 5 个不注册的人拍 10 张负样本。第二步写一个扫描脚本把阈值从 0.30 到 0.60 按 0.01 步长跑一遍统计每个阈值下的正样本通过率和负样本误通过率。第三步画曲线选 FAR 接近 0 且 FRR 还能接受的拐点。第四步把选定的阈值写进配置文件不要在代码里写死。import numpy as np def sweep(probe_pos, probe_neg, db, lows0.30, highs0.60, step0.01): probe_pos/probe_neg 是已提取的特征列表db 是注册库 ids list(db.keys()) mat np.array([db[i] for i in ids]) rows [] for t in np.arange(lows, highs 1e-9, step): def accept(enc): d np.linalg.norm(mat - enc, axis1).min() return d t # 正样本比对结果必须是本人负样本任何匹配都算误识 pass_rate sum(accept(e) for e in probe_pos) / len(probe_pos) far sum(accept(e) for e in probe_neg) / len(probe_neg) rows.append((round(float(t), 2), pass_rate, far)) return rows逻辑说明probe_pos是同一个人的不同角度图提取出的特征probe_neg是库外人员的特征。pass_rate相当于 1 减 FRRfar是误识率。实际跑的时候要把probe_pos和对应学号绑定accept里还要判断“最近邻是不是本人”上面为了简洁只展示了距离判断。输出的rows可以直接写进论文的实验表。参数说明步长 0.01 足够再细没有意义因为特征分布本身有噪声。扫描范围不要只扫 0.40 到 0.50先放宽到 0.30 到 0.60 看全貌。如果某组数据在 0.40 到 0.50 之间曲线很陡说明特征区分度不够换 ArcFace 或增加注册图数量比继续调阈值更有效。4.2 常见报错与排查课堂现场最怕临场报错下面这几个是我遇到频率最高的按现象、原因、处理列出来答辩前逐条过一遍。现象可能原因处理dlib安装到一半报 CMake 错误缺 cmake 或 C 编译器装 cmake 和 Build Tools或改用 conda 装 dlibcv2.imshow报not implemented装的是 headless 版pip uninstall opencv-python-headless后重装opencv-pythoncv2.VideoCapture(0)读到全是黑帧摄像头被其他程序占用或索引不对换1、2试关掉会议软件Linux 下ls /dev/video*database is locked多个连接同时写 SQLite开 WAL设busy_timeout写入收敛到单线程识别结果全是unknown注册特征用了 RGB探测用了 BGR统一用face_recognition.load_image_file或统一cv2.cvtColor同一张脸距离忽大忽小没有做对齐或检测框抖动用 5 点对齐检测框外扩 10 像素再裁剪CPU 占用 400% 但帧率只有 5每个进程加载了独立模型推理放一个进程多摄像头队列分发numpy报_ARRAY_API not foundnumpy 2.x 与旧版 opencv 不兼容降到 1.26.x最后一条特别值得说很多“人脸识别突然变慢”的问题不是模型问题而是cv2.VideoCapture的缓冲。默认情况下 OpenCV 会缓存几帧处理慢的时候读到的是旧画面看起来像延迟。可以在读帧循环里每轮grab()两次再retrieve()或者把分辨率从 1080P 降到 720P帧率立刻上来。注意答辩现场的插座、USB 延长线、投影仪 HDMI 都可能干扰摄像头。提前用v4l2-ctl --list-devicesLinux或设备管理器确认摄像头工作正常不要等到演示才开始调。4.3 防代签的几种工程手段课堂签到的作弊成本很低举手机照片、戴别人的口罩、找长得像的同学。纯 1:N 比对挡不住照片攻击因为打印照片也能提取出人脸特征。下面几种手段可以叠加使用按实现成本从低到高排列。手段实现方式挡住什么代价双帧比对连续两帧特征距离小于阈值静态照片几乎为零随机动作挑战弹“眨眼/转头”抽 3 帧看关键点变化照片、视频重放需要关键点模型深度摄像头用 RGB-D 相机的深度通道判断立体照片硬件成本签到时间窗开始后 5 分钟内才有效过期要补签帮别人远程签业务规则位置校验手机端签到时校验蓝牙或二维码远程代签需要移动端人工复核对confidence接近阈值的记录标黄边界误识助教工作量实际落地的组合通常是双帧比对 随机动作挑战 签到时间窗。双帧比对负责挡静态照片动作挑战负责挡视频重放时间窗负责把“课后帮签”变成需要人工介入的补签。代码上双帧比对就是在match之前加一个 3 帧滑动窗口要求连续两帧的student_id相同且距离差小于 0.05动作挑战则需要dlib的 68 点或 MediaPipe FaceMesh计算眼睛纵横比或鼻尖偏移。有一个容易被忽略的点注册模板的更新。学生换发型、戴眼镜后半年前的特征可能匹配不上。可以在每次成功签到且distance 0.35时把当前特征以 0.1 的权重混入模板让模板缓慢适应。但混入前必须确认这次签到不是误识所以只在高置信度下做避免模板被污染。5. 论文与代码同步收尾把系统设计与实现写成可答辩的材料5.1 论文结构与人脸签到模块的对应关系“基于 Python 的课堂人脸签到系统设计与实现”这个题目论文里最容易写空的是“设计与实现”那一章。一个可操作的做法是让论文的每一节都能指到具体的文件和函数需求分析对应签到流程和角色用例总体设计对应模块划分图至少画清楚采集、检测、识别、业务、存储五层详细设计对应enroll、match、writer_loop、export四个核心函数每个函数放一段伪代码或真实代码配参数说明数据库设计对应第 3 章那几张表把 ER 图和建表 SQL 放一起测试章对应阈值扫描表和 FAR/FRR 曲线不要只写“测试通过”。5.2 实验数据表与截图规范实验部分建议放三张表加两组图。表一是不同模型的检测耗时和内存占用用同一段视频跑 ten times 取平均表二是阈值从 0.30 到 0.60 的 FAR/FRR 对照表三是某次真实课堂的考勤结果应到、实到、迟到、缺勤的人数。两组图分别是 ROC 曲线和签到界面截图。截图要打码或虚构学号不要用真实学生信息。所有时间戳统一 ISO8601分辨率统一写清楚否则复现不了。5.3 答辩现场一次跑通的打包命令演示前不要手忙脚乱地装依赖。把虚拟环境、模板文件、数据库、模型权重放在同一个目录写一个setup_check.py做自检读一次摄像头、加载一次模板、往临时数据库插一条再删掉、调用一次比对。演示当天先跑自检再跑主程序。# 打包前确认版本 pip freeze requirements.txt # 自检摄像头、模板、数据库、比对链路 python setup_check.py --camera 0 --db ./data/checkin.db --templates ./data/templates.pkl # 启动签到主程序示例一分钟一节课的演示模式 python main.py --course CS101 --session-demo --duration 60 --threshold 0.45参数说明--camera 0是默认摄像头索引如果用了外接 USB 摄像头改成 1--session-demo让程序在 60 秒后自动结束并导出适合答辩时控制节奏--threshold 0.45从命令行传入避免现场改代码。演示前把摄像头索引、数据库路径、阈值写进config.yaml跑一次setup_check.py确认全绿比临场调参数稳得多。本文还有配套的精品资源点击获取
返回列表