简介这份毕业设计围绕PythonFaceNet人脸检测与识别技术构建了一整套课堂学生签到系统适用于高校毕业设计、课程实践以及入门人脸识别项目开发的读者。系统实现了人脸检测、特征提取、身份识别与签到记录等完整流程代码经本地编译可运行评审分达95分以上难度适中且教学模块清晰。压缩包共21个文件大小约40MB其中以15个Python脚本为核心覆盖GUI界面、摄像头调用、签到逻辑等模块另含4个pyc编译文件、1个字体文件和1个gif演示动画便于直接启动与效果预览。同时附带配套数据集和详细设计文档帮助理解FaceNet原理、数据集组织形式以及系统部署细节。该资源已有152人学习下载内容经过助教审定适合需要完整项目参考或二次开发的学生使用。1. FaceNet 课堂签到系统从点名到刷脸的落地路径很多人拿到这类课堂签到项目第一反应是“把模型训练好就万事大吉”实际动手后才发现真正让项目翻车的从来不是模型训练而是检测器选型、阈值设定和数据集分布这些看似不起眼的细节。这个基于 Python FaceNet 的课堂学生签到系统走的是“先检测人脸位置再提取人脸特征做身份比对”的标准两段式路线配套源码、数据集和详细文档适合正在做毕业设计的学生也适合想快速搭一套小范围人脸考勤 Demo 的开发者参考。我见过某开发者把精力全花在调 FaceNet 训练脚本上结果注册照和课堂抓拍的光照差异太大阈值怎么调都不对最后签到准确率不到六成。这个标题背后真正值钱的部分不是 FaceNet 本身而是“怎么把 FaceNet 接到一套能用的签到流程里”。这套流程里需要解决的关键问题包括用什么检测器框出人脸、怎么把照片转成 128 维特征向量、阈值怎么定、多人同框怎么去重、以及课堂这种半受控场景下有哪些坑。本文就按这个顺序,把整条链路讲透。2. 方案选型为什么是 FaceNet检测与识别怎么分工2.1 FaceNet 的核心思路128 维嵌入与三元组损失FaceNet 不是一个分类模型而是一个度量学习模型。传统的人脸识别做法是把人脸照片当成一个类别去训练 softmax 分类器这种方案的问题在于班级里新增一个学生就需要重新训练或者至少更新分类层。FaceNet 的做法完全不同它把一张人脸照片映射成一个 128 维的浮点向量训练目标让同一个人的不同照片在向量空间里距离很近不同人的照片距离很远。训练时使用的三元组损失是理解 FaceNet 的关键。每组训练数据包含三张图锚点样本某人的一张照片、正样本同一个人的另一张照片、负样本另一个人的照片。损失函数要求锚点与正样本的距离加上一个间隔仍然小于锚点与负样本的距离这个间隔在原始实现里通常取 0.2。训练完成后这个 128 维向量就成为一个“人脸指纹”。课堂签到场景用 FaceNet 有一个很现实的好处注册新学生只需要给他拍一张照片提取一次特征向量存进库里不需要重新训练任何模型。班级规模通常只有几十人特征比对用余弦相似度或者欧氏距离扫一遍计算量完全可以忽略。这也是为什么这类毕业设计普遍选择 FaceNet 而不是重新训练分类网络的原因。2.2 检测器选型MTCNN、RetinaFace 与 OpenCV 的取舍人脸识别链路的第一步是检测也就是从图像里找到“哪里有人脸”。FaceNet 本身不负责检测它只接收已经被裁剪和缩放好的人脸图片。所以检测器的好坏直接决定后续识别效果。课堂场景常用三个选择OpenCV 自带的 Haar Cascade、MTCNN、RetinaFace。Haar Cascade 速度最快CPU 上也能跑得很流畅但对侧脸、低头、遮挡非常敏感而且经常把衣服纹理、书本上的图案误检成人脸。ResNet 误检一多后面识别就会拿大量垃圾框去比对签到记录里出现一堆 unknown。MTCNN 是 FaceNet 生态里最常见的搭配它分三个阶段级联检测除了人脸框还能输出双眼、鼻尖、嘴角五个关键点利用这些关键点可以做仿射对齐正好满足 FaceNet 对输入人脸“对齐”的要求。RetinaFace 精度更高对模糊和小尺寸人脸更友好但模型体积和计算量成倍增长CPU 上单帧推理就要几百毫秒课堂场景除非有 GPU否则没必要上。我一般建议课堂签到项目直接用 MTCNN理由有三个一是它自带关键点对齐后喂给 FaceNet 的识别精度明显高于 Haar二是 CPU 可以跑单帧检测 30 到 80 毫秒配合跳帧策略完全够用三是开源实现成熟不管是 TensorFlow 版还是 PyTorch 版都有现成封装不需要自己写 NMS 和后处理。检测器关键点支持CPU 单帧耗时侧脸/遮挡表现误检率推荐场景Haar Cascade无约 10ms差容易漏检高快速原型不推荐用于签到MTCNN5 点约 30-80ms中等低课堂签到首选RetinaFace5/106 点数百 ms 起好低GPU 环境或硬场景2.3 课堂签到场景下识别阈值的设定逻辑检测完人脸后FaceNet 会输出 128 维向量接下来要回答“这个人是谁”。常见做法是计算待识别向量与注册库中所有向量的相似度取最大值对应的身份然后拿这个相似度和阈值比较超过阈值才确认身份否则判为 unknown。阈值设定是这个项目里最玄学也最关键的一步。如果只看 FaceNet 官方给出的参考间距欧氏距离 0.7 到 1.1、余弦相似度 0.6 到 0.8 是常见范围但直接套用会翻车。原因是课堂上每个学生的学习场景变化很大早上第一节课和下午最后一节课的光照不同学生低头写字、趴在桌上、用手托腮这些都会让同一个人的特征向量发生偏移。注册时用的是正面清晰照课堂上检测到的是各种姿态特征分布根本不对齐。我在做这类项目时阈值从来不是拍脑袋定的而是基于“注册库 课堂实拍验证集”标定出来的。简单做法是准备几十张课堂抓拍手工标注每个人是谁然后计算“本人对”的相似度分布和“他人对”的相似度分布取两个分布交叉点再往严里收一点作为阈值。后面第 6 章会给出具体校准方法这里先记住一个结论课堂场景的阈值应该比通用人脸识别的参考值更严格否则同班同学之间脸型相近误识别率会明显上升。3. 环境与数据准备把一张照片跑通「检测—嵌入—比对」3.1 Python 版本与依赖搭配这类项目的环境搭建有个常见误区直接装最新版 Python 和最新版深度学习框架结果某些预训练权重和旧接口对不上报错报得莫名其妙。我一般推荐 Python 3.8 到 3.10深度学习框架选 PyTorch 1.10 到 2.x 的稳定版本不要追最新。人脸识别链路依赖的库不多核心就这几个mtcnn 或 facenet-pytorch 负责检测InceptionResnetV1 或 TensorFlow 版 FaceNet 模型负责特征提取OpenCV 负责图像读取和摄像头采集NumPy 负责向量运算。这里强烈建议直接用 facenet-pytorch 这个封装库它把 MTCNN 检测器和 InceptionResnetV1 识别模型打包在一起预训练权重也一并处理好了。用库而不是自己手写检测和特征提取能省掉大量版本兼容的精力。安装时如果遇到 torchvision 版本不匹配多半是 PyTorch 和 torchvision 的版本号对应关系没对上去官网查一下对应表保持两者一致就能解决。安装命令的核心部分如下注意先装 PyTorch 再装 facenet-pytorch顺序反了可能出现依赖覆盖的问题pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install facenet-pytorch opencv-python numpy pandas参数说明cu118 表示 CUDA 11.8 版 PyTorch如果你的机器只有 CPU把 index-url 去掉直接装 CPU 版即可。facenet-pytorch 会自动带上 MTCNN 和 InceptionResnetV1 的实现不需要额外装 mtcnn 库两个库的接口不兼容混用反而会出问题。3.2 课堂人脸数据集的三层结构注册照、验证照与公开数据补充标题里的“数据集”在课堂签到项目里通常不是拿来训练 FaceNet 的而是用来验证系统效果的。我一般会把数据集分成三层来组织第一层是注册照每个学生一张正面清晰照命名格式用“学号_姓名.jpg”放在 students 目录下这张照片负责生成注册特征库。第二层是课堂验证照模拟真实签到场景包含学生坐在座位上抬头、低头、侧脸、光照变化等不同状态的照片命名规范一致用于测试识别准确率和标定阈值。第三层才是公开人脸数据集只在你想微调 FaceNet 模型时才需要。很多人拿到一个模型先想着重新训练这个思路在课堂签到场景里往往没必要。FaceNet 预训练权重在公开人脸数据集上已经见过足够多的人脸变化课堂场景的人脸种类并不特殊微调带来的收益很有限反而容易过拟合到某个班级的几十张脸上失去泛化能力。我见过某高校的项目组花一周时间微调 FaceNet最后准确率只提升了两个百分点却在更换教室光线后准确率暴跌。把时间花在组织验证集和校准阈值上性价比高得多。数据集的目录结构建议按下面的方式组织每一层用单独的目录隔离避免代码误读data/ ├── registered/ │ ├── 2024001_张三.jpg │ ├── 2024002_李四.jpg │ └── ... ├── test/ │ ├── class_session_morning/ │ ├── class_session_afternoon/ │ └── ... └── public/ └── (微调时才需要)目录说明registered 存放注册照test 下按时间段分子目录存放课堂验证照public 目录在需要微调模型时再放入公开数据。命名用学号而不是姓名拼音能避开 Windows 中文路径的坑这个在第 5 章会详细展开。3.3 用一张照片跑通「检测—嵌入—比对」的最小代码环境准备好之后先不要着急搭完整签到系统用一张照片把最核心的链路跑通。这段最小代码包含三个动作检测人脸、提取 128 维特征、计算两个特征的相似度。from facenet_pytorch import MTCNN, InceptionResnetV1 from PIL import Image import torch import numpy as np device torch.device(cuda if torch.cuda.is_available() else cpu) # 初始化检测器输入图片尺寸 160x160最小人脸 20px mtcnn MTCNN(image_size160, margin0, min_face_size20, thresholds[0.6, 0.7, 0.7], devicedevice) # 加载预训练识别模型vggface2 权重 resnet InceptionResnetV1(pretrainedvggface2).eval().to(device) def get_embedding(img_path): img Image.open(img_path) # 检测并裁剪人脸返回 3x160x160 的张量 face mtcnn(img) if face is None: return None # 提取 128 维特征并做 L2 归一化 emb resnet(face.unsqueeze(0).to(device)) emb torch.nn.functional.normalize(emb, p2, dim1) return emb.detach().cpu().numpy()[0] emb1 get_embedding(data/registered/2024001_张三.jpg) emb2 get_embedding(data/test/session/morning_001.jpg) if emb1 is not None and emb2 is not None: # 余弦相似度 归一化向量的点积 sim float(np.dot(emb1, emb2)) print(f相似度: {sim:.4f})逻辑说明MTCNN 的 image_size 参数决定输出人脸的统一尺寸FaceNet 的预训练模型要求 160×160 输入这个值不要随意改。margin 表示在检测框基础上向外扩多少像素0 表示不扩如果发现识别时刘海或下巴被切掉可以把它调到 10 到 20。thresholds 是 MTCNN 三个阶段的置信度阈值默认 [0.6, 0.7, 0.7] 是可用的误检多就调高漏检多就调低。代码运行成功后你会看到两个不同来源的张三照片输出一个相似度数值。如果这个数值在 0.6 以上说明链路是通的如果在 0.3 以下先检查注册照和验证照是不是同一个人、有没有被检测器裁错区域而不是急着改模型。4. 签到核心链路注册库、单帧识别与去重逻辑4.1 注册库构建批量把学生照片转成特征向量最小链路跑通后第一步工程化就是把整个班级的注册照批量转成特征库。特征库的本质是一个字典键是学号值是 128 维向量数组。一个学生可以放多张注册照特征向量存成列表识别时取这些向量的平均相似度能有效提升稳定性。import os import numpy as np def build_register_db(reg_dir): db {} for filename in os.listdir(reg_dir): if not filename.endswith(.jpg): continue sid filename.split(_)[0] # 学号 path os.path.join(reg_dir, filename) emb get_embedding(path) if emb is None: print(f警告: {filename} 未检测到人脸) continue db.setdefault(sid, []).append(emb) # 每个学号的多张特征取平均归一化后入库 for sid, embs in db.items(): mean_emb np.mean(embs, axis0) db[sid] mean_emb / np.linalg.norm(mean_emb) return db参数说明文件名用下划线分割学号和姓名避免解析时遇到姓名中的空格。检测不到人脸的照片直接跳过并打印警告不要静默丢弃。多张注册照取平均能抵消单张照片的姿态偏差但如果某张照片本身模糊或光照异常平均反而会拉低整体质量所以要加一个人工检查步骤把明显的坏照片删掉再入库。特征库生成后建议存成 npy 文件下次启动直接加载不用每次重新跑一遍检测和特征提取。如果班级人员有变动只需要重新对变动的学生照片执行一次特征提取合并进原文件即可。4.2 单帧识别坐标、置信度与身份判定课堂签到最核心的是对摄像头每一帧做检测和识别。这里有个关键设计检测和识别是两个独立的步骤先跑 MTCNN 得到所有人脸框再把每个人脸框对应的裁剪图批量送进 FaceNet。批量推理比逐个人脸循环快很多因为 GPU 或 CPU 的矩阵运算可以并行处理多张图。def recognize_frame(frame, db, threshold0.55): # frame 是 BGR 格式的 numpy 数组先转成 RGB rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # 检测一帧里的所有人脸返回边界框和关键点 boxes, probs mtcnn.detect(rgb) results [] if boxes is None: return results for box, prob in zip(boxes, probs): if prob 0.95: # 置信度过滤 continue x1, y1, x2, y2 [int(v) for v in box] face_crop rgb[y1:y2, x1:x2] face_tensor mtcnn(Image.fromarray(face_crop)) if face_tensor is None: continue emb resnet(face_tensor.unsqueeze(0).to(device)) emb torch.nn.functional.normalize(emb, p2, dim1) emb emb.detach().cpu().numpy()[0] # 遍历注册库找最大相似度 best_sid, best_sim None, -1 for sid, db_emb in db.items(): sim float(np.dot(emb, db_emb)) if sim best_sim: best_sim, best_sid sim, sid if best_sim threshold: results.append((best_sid, best_sim, (x1, y1, x2, y2))) else: results.append((None, best_sim, (x1, y1, x2, y2))) return results逻辑说明这里先做了一次完整检测得到坐标框再对裁剪出来的人脸区域做一次 MTCNN 对齐虽然多了一次检测开销但能保证喂给 FaceNet 的人脸是经过关键点对齐的识别精度比直接裁剪高不少。如果追求速度可以改用 mtcnn(img, save_pathNone) 直接返回对齐后的人脸张量但那样就拿不到坐标框用于画框标注了。置信度过滤阈值 0.95 是一个偏严的值课堂场景下正脸识别率不会受影响却能过滤掉大量误检。4.3 签到记录与去重逻辑防止一帧多签、一人多签签到系统最容易被忽略的就是去重逻辑。如果不做去重同一个学生在视频里出现 10 秒就可能被写入 20 条签到记录考试时一个人替另一个人签到系统也识别不出来。我一般会设计两层去重一是同一个学号在短时间内只允许签到一次二是同一个学号连续出现在多帧时只在状态变化时触发一次签到写库。from datetime import datetime import pandas as pd class AttendanceRecorder: def __init__(self, cooldown_minutes10): self.cooldown cooldown_minutes * 60 self.last_sign {} # sid - 上次签到时间戳 self.records [] def sign(self, sid, frame_time): now frame_time last self.last_sign.get(sid, 0) if now - last self.cooldown: return False # 冷却期内不重复签到 self.last_sign[sid] now self.records.append({ sid: sid, time: datetime.fromtimestamp(now).strftime(%Y-%m-%d %H:%M:%S) }) return True def export(self, pathattendance.csv): df pd.DataFrame(self.records) df.to_csv(path, indexFalse, encodingutf-8-sig)参数说明cooldown 是签到冷却时间默认 10 分钟意思是同一个学生 10 分钟内只记一次签到。这个值要结合课程时长设置45 分钟一节课设 10 分钟没问题如果是两小时的大课可以考虑放宽到 20 分钟避免学生中途去洗手间回来被重复记录。导出用 utf-8-sig 编码是因为 Excel 直接打开 utf-8 编码的 CSV 会乱码加 BOM 头才能正常显示中文表头。替人签到的场景单靠人脸识别解决不了需要配合活体检测或人工抽验这部分在第 6 章里展开。去重逻辑是签到系统的底线宁可漏记一次也不能重复记否则后台统计数据完全不可信。5. 课堂人脸签到避坑指南五个高频翻车现场5.1 现象侧脸和低头时识别率骤降课堂实拍里最常见的问题就是学生侧脸跟邻座说话、低头看手机或写字这些姿态下 MTCNN 往往能检测到人脸框但关键点定位偏差变大仿射对齐后的人脸区域发生扭曲FaceNet 提取的特征和注册照的正面特征距离明显拉大相似度掉到阈值以下。原因有两个层面一是检测器对极端姿态下的关键点回归本身就不准二是 FaceNet 预训练权重虽然包含一定姿态变化但课堂角度范围比公开数据集更集中侧脸达到 60 度以上时特征漂移是必然的。解决思路不是让模型变得更鲁棒而是调整签到策略。实际操作中我会把姿态异常的人脸单独标记出来不直接判为 unknown而是记为一个“待确认”状态。更简单有效的做法是降低对单帧的依赖采用多帧投票同一张脸在连续 5 帧里被识别成同一个学号至少 3 次才最终确认身份。这个策略对低头抬头的动态过程特别有效因为学生总会有抬头看黑板或看讲台的瞬间。5.2 现象不同光照下同一人脸的比对分数波动大我做过一个模拟项目 X上午第一节课测试准确率有 92%下午换到阳光直射的教室后掉到 71%同一个人同一套注册照只是课堂光线从日光灯变成了自然光加阴影。这说明光照变化对 MTCNN 裁剪区域和 FaceNet 特征的影响非常大尤其是侧窗光会在脸上形成半明半暗的分布导致特征向量偏移。原因不光是人脸像素值变了更隐蔽的是 MTCNN 检测框的位置和大小会随光照微调裁剪出来的人脸在眼睛、嘴巴的相对位置上出现像素级差异这种差异经过网络前向传播后被放大。解决分两步第一步在注册阶段就采集多光照条件的照片比如每个学生交一张正面照加一张稍暗环境下的照片两张都入库取平均特征。第二步在课堂视频流里对输入帧做亮度归一化常见做法是直方图均衡化或者 CLAHE 自适应均衡让暗光下的人脸细节更清晰。这两步做完下午场景的准确率通常能拉回 85% 以上。5.3 现象同班同学脸型相近导致误识别课堂场景有一个通用人脸识别很少遇到的问题班级里几十个学生的年龄、发型、脸型高度相似有些双胞胎或者长相接近的同学他们的 FaceNet 特征向量距离本身就比普通人的“他人对”距离更近。如果阈值设得宽松这两个人之间就会出现交叉误识别。原因不是模型不行而是度量学习的区分度在面对“难负样本对”时天然有限。FaceNet 训练时见过的负样本大部分是不同人种、不同年龄的差异同卵双胞胎这种极端相似的情况并不常见。解决方向是收紧阈值同时给长相相近的学生增加专属判定规则。收紧阈值会让一些真正的本人签到被拒所以需要配合 4.1 节里的多张注册照取平均每张注册照的相似度稍微拉高一点。如果班级里确实存在高相似度的同学可以在后台给这两个学号加一个互斥标记同一节课里A 签到后 B 再签到如果两人特征相似度极高弹出人工确认提示。这种半自动处理比单纯调阈值实用得多。5.4 现象CPU 上跑检测加识别卡成 PPT很多毕业设计的主机没有独立显卡用 CPU 跑完整流程MTCNN 加 FaceNet 每帧推理加起来超过 300 毫秒视频直接卡成幻灯片。有人以为是代码效率问题其实是没理解检测和识别的耗时分布MTCNN 检测 30 到 80 毫秒FaceNet 对每个人脸的特征提取 50 到 150 毫秒一帧里如果有 5 个人脸总耗时直接翻倍。解决的常用做法有三板斧。第一是跳帧视频流不是每一帧都需要处理一般每 3 到 5 帧取一帧做识别因为学生坐在座位上不会快速移动5 帧里抽 1 帧完全不会漏掉签到。第二是把检测帧率和识别帧率解耦检测每帧跑识别只在检测到新的人脸框出现或旧人脸框消失时才触发。第三是缩放输入帧把 1920×1080 的帧缩到 640×480 再送进检测器检测速度能提升好几倍人脸尺寸仍然满足 MTCNN 的最小人脸要求。我见过把这三板斧全部用上的某开发者CPU 上整体帧率从 3 帧提升到 12 帧左右签到准确率几乎没有下降。关键原因是课堂场景里的人脸数量少且位置相对固定不需要每帧都做全量识别。5.5 现象中文路径和编码导致的诡异报错Windows 环境下做这个项目会碰到一堆跟人脸识别无关的报错cv2.imread 读不到中文路径的图片返回 NoneTensorFlow 或 PyTorch 加载含中文的路径直接报 UnicodeDecodeErrorCSV 导出后 Excel 打开乱码。这些问题的共同根源是 Windows 的文件系统编码和 Python 默认编码不一致。常见做法是彻底避开中文字符。注册照文件名用“学号_姓名拼音”而不是“学号_姓名”中文项目根目录放在纯英文路径下比如 D:\face_attendance 而不是 D:\项目\签到系统。照片里的中文姓名信息通过学号映射表来关联映射表用 CSV 存储时注意编码统一成 utf-8-sig。如果确实无法避免中文路径OpenCV 的读取可以绕过 imread改用 np.fromfile 配合 cv2.imdecodeimport cv2 import numpy as np def imread_unicode(path): data np.fromfile(path, dtypenp.uint8) return cv2.imdecode(data, cv2.IMREAD_COLOR)这段代码先用 numpy 以二进制方式读取文件再交给 cv2.imdecode 解码成图像绕开了 OpenCV 在 Windows 上对中文路径的支持缺陷。同理写入图片时用 cv2.imencode 后配合 tofile能解决 imwrite 保存到中文路径失败的问题。这个坑看起来小但在毕业设计答辩演示时如果当场报错非常影响印象分。6. 进阶一项阈值校准、活体检测与更稳的工程习惯6.1 用课堂实拍数据校准阈值而不是拍脑袋定 0.6前面第 2 章提到阈值要标定这里给出具体做法。收集大约 50 张课堂验证照手工标注每张照片里的学号然后对每个学号计算两张分布本人对的相似度注册照和课堂照是同一个人的所有组合和他人对的相似度不同人的所有组合。把分布画出来你会看到两条曲线本人对的分布集中在 0.6 到 0.9他人对的分布集中在 0.1 到 0.5中间会有一个交叉区。阈值就取交叉区中间偏严一点的位置比如两条曲线交汇在 0.55就把阈值设为 0.58 或 0.6。这样做的好处是每个班级的阈值都是专属的不会出现 A 班调好的参数搬到 B 班就失效的情况。如果画完图发现两条分布几乎重叠说明注册照质量和课堂照差异太大这时候调阈值没用要回去补注册照。6.2 活体检测的轻量做法眨眼检测与头部姿态课堂签到防作弊最基本的是防止学生用手机拍一张同学的照片、或者用平板展示照片来代签。完整的活体检测需要深度摄像头或专用算法课堂场景用 OpenCV 就能做轻量版本。眨眼检测是最实用的方案真实的活人会在几秒内眨眼而照片不会。计算眼睛纵横比 EAR 是经典做法眼睛闭合时 EAR 值会明显下降。在识别成功后的 3 秒内连续采样 EAR如果出现一次从高到低再回升的过程就认为是活体。头部姿态检测也可以作为辅助真实学生面对摄像头时头部会有微小晃动用 MTCNN 返回的关键点估算偏航角持续 2 秒内角度变化超过 5 度即视为活体。这两种方法实现成本都很低却能把照片代签的作弊方式直接挡掉。6.3 更稳一档的工程习惯日志、缓存与结果导出项目交付时功能完整只是一方面真正让人加分的是工程习惯。我一般会给签到系统加三样东西第一是运行日志每帧识别结果、每次签到写入、每次阈值判定都追加到日志文件里这样出了问题能回查是哪一帧、哪个人、哪个相似度出的问题而不是对着黑匣子猜。第二是特征库缓存注册库构建一次后序列化保存启动时直接加载省掉每次运行几分钟的预处理时间。第三是签到结果导出除了 CSV再加一份按课程汇总的统计表包含应到人数、实到人数、缺勤名单和签到时间分布后台人员直接看表就能完成考勤统计。我自己早期做过一套签到 Demo因为没有日志线上签到率突然下降时完全不知道是检测器漏检还是阈值漂移只能整个重跑一遍。后来把所有中间结果落盘十分钟就定位到是教室换了新摄像头画面比例从 16:9 变成了 4:3导致 MTCNN 的最小人脸参数失效。这个教训让我养成一个习惯任何识别类系统先把日志和缓存做好再谈准确率优化。希望帮到你。本文还有配套的精品资源点击获取