基于MTCNN和FaceNet的人脸考勤系统Python源码实战解析
发布时间:2026/9/28 1:37:22来源:尧图网络
简介面向计算机相关专业完成毕业设计、课程设计及人脸识别项目实战的学习者这份基于深度学习的人脸识别考勤系统源码提供了从人脸信息录入、实时检测识别到考勤签到与记录管理的完整实现方案包含人脸库管理、签到状态展示、历史记录查询等常用模块。项目经导师指导并获评审98分代码结构清晰前端界面与后端逻辑分层明确可直接运行调试适合有Python基础并希望深入理解人脸识别应用开发的学习者。压缩包共25个文件整体大小约239.41MB除Python主程序与核心模块脚本外还包含界面可视化所需的多张png图片、图标与ttf字体文件、config配置文件、API接口文档以及docx格式的使用手册各类型文件分工明确便于对照学习环境搭建、模型调用与界面定制。已有191人学习下载。借助这份源码可以快速掌握深度学习人脸识别考勤系统的工程化落地方法理解特征提取、人脸比对、签到状态写入等关键编码思路同时参考其模块划分、API设计与UI布局为独立完成同类项目提供可靠模板与排错经验。1. 人脸识别考勤系统一包能跑通的Python毕业设计源码含金量在哪网上一搜“人脸识别考勤系统”十个里八个是OpenCV的Haar级联加直方图匹配跟“深度学习”基本不沾边。而这套Python毕业设计源码走的是真深度路线MTCNN做人脸检测FaceNet提取128维特征向量接分类器或阈值判定完成身份识别最后落到数据库里做考勤统计。它最大的价值不是“能识别脸”而是把算法链路和业务闭环一次性给全了——从模型加载、特征注册、实时打卡到统计导出每一环都能看到代码答辩时能讲清楚的东西非常多。适合正在做Python方向毕业设计的学生、想给作品集加一个完整业务场景项目的求职者也需要现场演示的课设党。下面按拆包思路过一遍选型、实现和坑。2. 核心技术选型与整体架构为什么是“检测嵌入分类”三段式2.1 为什么用深度学习而不是传统算法考勤场景有三个硬伤传统的人脸识别方案里LBPH、EigenFaces是出现频率最高的两个。它们的原理是提取局部二进制纹理模式或者对整张脸做PCA降维再和注册库里的特征做距离匹配。问题在于这类方法对光照、角度、遮挡极其敏感人脸稍微转个角度像素层面的分布就完全变了。在考勤这种实际环境里早上逆光、下午侧光、戴口罩漏半张脸传统方法很容易把同一个人的识别结果跳来跳去。深度学习方案不一样。卷积网络会逐层学习从边缘、纹理到五官结构的抽象特征最终得到一个对姿态和光照相对鲁棒的Embedding向量。严格说深度学习模型也做不到百分之百不变但它的泛化能力比手工特征强一个量级。另一个关键差别是“开集识别”和“闭集识别”传统方法会把任何一张脸硬归到距离最近的注册人身上哪怕这张脸根本不在库里而深度学习方案因为这个128维向量的存在可以设定阈值把陌生人拒之门外。对考勤系统来说代打考勤的人能不能被拦下来恰恰是答辩评委最喜欢追问的场景。对比点传统方法LBPH/EigenFaces深度学习方案MTCNNFaceNet特征来源手工设计的像素统计卷积神经网络自动学习光照变化高敏感鲁棒性较好实时性检测快但误判多推理稍慢CPU可接受训练数据量每人几张即可每人建议5-20张并做增强陌生人拦截基本做不到用阈值或分类器可以做到这就是为什么我每次给想做毕设的人推荐时都建议优先选带深度学习的源码。LBPH版本虽然能跑但答辩时老师问一句“你的特征是怎么来的”回答就很难展开。深度学习版本至少能讲清楚预训练权重、Embedding、阈值三条线索。2.2 整体架构一条从摄像头到考勤统计的六层流水线这套源码如果拆开看本质上是一条流水线摄像头帧采集 - 人脸检测 - 人脸对齐 - 特征提取 - 身份判定 - 考勤写入与统计。前四个环节属于算法层后两个环节属于业务层。算法层的输出是一个128维的特征向量业务层只跟这个向量打交道不直接碰图像。这种分层设计最大的好处是换数据库、换界面、换统计规则都不需要动模型代码。系统里同时存在两条流程。离线注册流程是给每个人员采集若干张样本照片 - 检测并对齐人脸 - 生成特征向量 - 写入人员表。在线考勤流程是实时抓帧 - 检测人脸 - 提取向量 - 与注册库里的特征做距离计算 - 最小距离低于阈值则判定为对应人员 - 写入考勤记录表。两条流程共用同一个人脸检测和特征提取模块代码不用写两遍。再往细看还有一条隐藏的训练流程。FaceNet通常直接加载预训练权重不一定需要从头训练但如果要训练一个分类器来做身份判定则需要准备标注好的人脸数据集把每个人一个文件夹的图片切成“注册集”和“验证集”。训练流程跑完后保存的是分类器模型而不是重新训练FaceNet。这样做的好处是大幅降低毕设的算力要求普通笔记本CPU也能跑通。这套架构里的“注册”和“训练”是两回事。注册是给一个新人录特征只需要一两秒训练是调整模型参数需要几分钟到几小时。很多第一次做毕设的人容易把这两个概念混在一起导致新增一个人员就要重新跑一遍训练效率非常低。源码把register_face.py单独拆出来就是为了避免这个问题。2.3 环境与目录结构在Windows上把它跑起来的依赖组合先把依赖说清楚。项目基于Python 3.8及以上核心依赖是MTCNN做人脸检测facenet-pytorch提供预训练模型和特征提取OpenCV负责摄像头读帧和图像处理pandas用来做考勤统计PyQt5或Tkinter做桌面界面。安装命令如下pip install opencv-python4.8.0.74 mtcnn0.1.1 facenet-pytorch2.5.3 pip install torch2.0.0 torchvision0.15.0 pip install numpy1.24.3 pandas2.0.1 scikit-learn1.2.2 pymysql1.0.2 pyqt55.15.9逻辑说明MTCNN负责把“脸在哪”找出来facenet-pytorch负责把人脸转成向量OpenCV只做读帧和画框这些外围工作pandas和PyQt5负责业务展示。torch版本要注意和facenet-pytorch兼容目前2.x版本都能跑如果你的电脑没有NVIDIA显卡torch会自动用CPU推理识别一张脸大约需要几十毫秒考勤场景完全够用。依赖作用版本注意mtcnn人脸检测与关键点输出0.1.x稳定facenet-pytorch预训练权重与Embedding提取2.5.x权重自动下载torch推理后端2.xCPU可跑opencv-python摄像头采集与图像操作4.8.x目录结构一般是这样的face_attendance_system/ |-- train_model.py # 可选训练分类器 |-- register_face.py # 注册人员人脸特征 |-- attendance.py # 实时考勤打卡主程序 |-- face_utils.py # 检测/对齐/特征提取封装 |-- db_helper.py # 数据库操作 |-- requirements.txt |-- models/ # 预训练权重存放目录 |-- data/ # 原始照片与注册照片 -- output/ # 考勤统计导出文件train_model.py和register_face.py分开摆放是有意的。第一次接触这个项目的人先看register_face.py理解对齐和Embedding流程再去看attendance.py里的实时循环思路会顺畅很多。facenet-pytorch首次运行时会尝试联网下载预训练权重如果网络受限需要手动把权重文件放到models目录里再在代码里指定权重的本地路径。3. 检测、对齐与特征提取人脸识别考勤系统的核心实现3.1 人脸检测MTCNN把边界框和五个关键点一起给你整个系统的第一步是确定图像里人脸在哪。MTCNN是一个三级级联的卷积网络先在图像金字塔里生成大量候选框再用更高层网络逐步筛选和精修。它和传统Haar级联的最大区别是Haar只能给你一个矩形框MTCNN还能同时给出左眼、右眼、鼻尖、左嘴角、右嘴角五个关键点坐标。这五个点后续做对齐非常关键所以优先选择MTCNN而不是OpenCV自带的检测器。# face_utils.py 中的检测部分 from mtcnn import MTCNN import cv2 detector MTCNN(min_confidence0.9, # 低于该置信度的检测结果直接丢弃 scale_factor0.7, # 图像金字塔缩放系数 min_face_size40) # 最小人脸尺寸阈值 def detect_face(image): result detector.detect_faces(image) if not result: return None, None, None box result[0][box] # [x, y, width, height] keypoints result[0][keypoints] return box, keypoints, result[0][confidence]逻辑说明detect_faces一次能返回多张人脸但考勤场景摄像头前通常只有一个人所以只取置信度最高的第一个结果。box是一个四项列表分别是左上角x坐标、y坐标、框宽、框高后面画矩形提示框和裁剪都要用到。keypoints是包含五个关键点坐标的字典下一节的对齐全靠它。参数建议值说明min_confidence0.9到0.95太高漏检太低把背景噪声当脸scale_factor0.7越小检测越慢但精度越高min_face_size40低于40像素的人脸不检测这里有一个细节我刚开始做的时候没注意min_face_size如果设成20背景里的海报脸、远处路过的人都会被框出来。考勤场景里远处人脸根本提不出可用特征检测出来反而干扰识别。设成40以后只有走近摄像头的人才能触发识别误报少很多。3.2 人脸对齐用双眼连线把歪着的脸掰正很多源码包上来就对人脸框做裁剪然后直接送进特征提取模型后面识别率上不去就怪模型不好。实际上面部识别模型非常吃对齐歪了20度的脸和正脸特征向量距离可能直接超过判定阈值。MTCNN给出的五个关键点在这里派上用场以双眼连线为基准做旋转再以两眼中心为原点裁出正方形区域。# face_utils.py 中的对齐部分 import numpy as np def align_face(image, keypoints, output_size(160, 160)): left_eye np.array(keypoints[left_eye], dtypefloat) right_eye np.array(keypoints[right_eye], dtypefloat) dx right_eye[0] - left_eye[0] dy right_eye[1] - left_eye[1] angle np.degrees(np.arctan2(dy, dx)) center ((left_eye[0] right_eye[0]) / 2, (left_eye[1] right_eye[1]) / 2) rot_mat cv2.getRotationMatrix2D(center, angle, scale1.0) aligned cv2.warpAffine(image, rot_mat, (image.shape[1], image.shape[0])) half output_size[0] // 2 x1 int(max(0, center[0] - half)) y1 int(max(0, center[1] - half)) x2 int(min(image.shape[1], center[0] half)) y2 int(min(image.shape[0], center[1] half)) aligned aligned[y1:y2, x1:x2] aligned cv2.resize(aligned, output_size) return aligned逻辑说明getRotationMatrix2D生成一个2×3仿射变换矩阵warpAffine按双眼连线角度把整张图旋转到水平位置。之后以两眼中心为裁剪中心裁出160×160的正方形再resize到FaceNet标准输入尺寸。整个过程把姿态差异造成的特征偏移尽量压缩。这一步看起来不起眼但对识别率的影响非常直接。我见过一个翻车案例去掉对齐后系统把同一个人的侧脸照和正脸照判成了两个人加上对齐后距离从1.4直接降到0.3。这属于典型的“预处理决定上限”的问题后续再怎么调阈值都补不回来。3.3 特征提取与身份判定128维向量怎么用才靠谱FaceNet的核心思想是让同类人脸的距离尽可能近不同类人脸尽可能远。facenet-pytorch封装了Inception-ResNet v1模型输入160×160的RGB图输出一个128维的向量。这个向量就是人脸在特征空间中的坐标后续所有比较都基于它展开。# face_utils.py 中的特征提取部分 from facenet_pytorch import InceptionResnetV1 import torch # 使用vggface2预训练权重classifyFalse表示只取Embedding model InceptionResnetV1(pretrainedvggface2, classifyFalse, devicecpu).eval() def get_embedding(aligned_face): # 输入是BGR格式转RGB并按 [-1, 1] 归一化 rgb cv2.cvtColor(aligned_face, cv2.COLOR_BGR2RGB) rgb (rgb.astype(np.float32) - 127.5) / 128.0 tensor torch.from_numpy(rgb).permute(2, 0, 1).unsqueeze(0) with torch.no_grad(): emb model(tensor) return emb[0].cpu().numpy() # 形状为 (128,)逻辑说明pretrained参数选择vggface2权重这个权重集是在大规模名人数据集上训练的泛化能力适合考勤场景。classifyFalse表示不要最后的分类层要的是倒数第二层的Embedding向量。归一化方式必须和预训练时的输入分布一致所以把像素值从0到255映射到-1到1。permute把HWC维度变成CHWunsqueeze增加batch维度因为模型要求四维输入。有了向量之后身份判定常见有两种做法一种是直接算欧氏距离或余弦距离设定阈值另一种是把它喂给SVM分类器。考勤系统里使用两者的混合判定通常更稳。基础版先用距离判定# 识别遍历注册库找最小距离 def recognize_by_distance(embedding, db_features, threshold0.8): min_dist float(inf) matched_name None for name, feat in db_features.items(): dist np.linalg.norm(embedding - feat) if dist min_dist: min_dist dist matched_name name return matched_name if min_dist threshold else None逻辑说明这个函数核心是一个线性扫描对注册库里的每个特征向量算欧氏距离。阈值是关键使用facenet-pytorch默认权重时同一个人不同照片的欧氏距离通常在0.5到0.9之间不同人的距离通常在1.0以上。把阈值设成0.8陌生人就容易被拒设成2.0几乎所有人都会被识别成某一员。实际项目中我还会给每个注册人保存多张照片的多个特征向量这样识别时不是算一个最小距离而是把这张脸对某人的平均距离也算出来。这种“多特征注册”方式能明显减少漏检。4. 考勤业务闭环从摄像头抓帧到考勤统计导出4.1 数据模型设计三张表把人员、打卡记录和识别日志理顺算法层做好以后业务层的数据结构决定了系统好不好扩展。这套源码通常用三张核心表来组织数据person存人员信息和特征向量attendance_record存每次打卡记录recognition_log存每次人脸识别的原始结果。三张表各司其职复盘问题的时候不用去翻日志文件。CREATE TABLE person ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, employee_no TEXT UNIQUE NOT NULL, embedding BLOB, register_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE attendance_record ( id INTEGER PRIMARY KEY AUTOINCREMENT, person_id INTEGER NOT NULL, check_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, status TEXT DEFAULT normal, source TEXT DEFAULT camera, FOREIGN KEY (person_id) REFERENCES person(id) ); CREATE TABLE recognition_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, person_id INTEGER, distance REAL, confidence REAL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );逻辑说明person表的embedding字段用BLOB存储把128维float数组打成二进制塞进数据库读取时用np.frombuffer还原。这样做比直接存图片快得多也省空间。attendance_record是业务核心每次识别命中后写一条status字段预留了迟到、早退等扩展状态。recognition_log记录的是每一帧推理的原始结果包括命中的距离和置信度这个表是调阈值的依据。如果换成MySQL要注意三处差异AUTOINCREMENT变成AUTO_INCREMENTBLOB变成LONGBLOBTIMESTAMP的默认值写法要调整。这个细节答辩经常被问到属于“一旦答不上来就很尴尬答上来了就加分”的那种问题。表名核心字段作用personembedding, employee_no人员注册信息与特征attendance_recordperson_id, check_time, status考勤打卡业务记录recognition_logperson_id, distance, confidence识别过程留痕与调参复盘4.2 实时打卡主流程一帧帧识别命中后只记一次实时主程序的难点不在识别本身而在“怎么控制节奏”。摄像头每秒输出15到30帧如果每帧都做检测和识别CPU会持续满载而且同一个人站在摄像头前两秒钟就会刷出十几条重复打卡记录。所以代码里必须加防重复机制。# attendance.py 的关键循环 cap cv2.VideoCapture(0) interval 60 # 60秒内同一人不允许重复打卡 last_record_time {} # 姓名 - 最近一次有效打卡时间 while True: ret, frame cap.read() if not ret: continue box, keypoints, conf detect_face(frame) if box is not None: aligned align_face(frame, keypoints) emb get_embedding(aligned) name recognize_by_distance(emb, db_features, threshold0.8) if name: now time.time() last last_record_time.get(name, 0) # 超过时间间隔才允许再次打卡并写库 if now - last interval: write_attendance(name) last_record_time[name] now cv2.putText(frame, name, (box[0], box[1]-10), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.imshow(Attendance, frame) if cv2.waitKey(1) 0xFF ord(q): break逻辑说明整个循环的节奏是“读一帧、检测一次、有脸就识别”。last_record_time用内存字典保存每个员工的最近打卡时间而不是每次都查数据库因为查库一次要几毫秒在高频循环里会堆积延迟。interval设成60秒意味着一个人在一分钟内只算一次有效打卡既防止重复写入也允许员工短时间离开再回来。参数建议值效果interval60到300秒防连续重复打卡threshold0.8越小越严格陌生人更难混入摄像头索引0或1笔记本默认0外接USB经常是1这个循环还有一个容易被忽略的点putText画的名字框用的是检测到的box坐标而不是对齐后的坐标。很多人把对齐后的图片显示了在160×160的小窗口里画框体验很怪。实时显示要基于原图坐标系识别才基于对齐后的区域。4.3 考勤统计与导出从原始记录到日报月报打卡数据写入数据库后还需要把“谁在几点打卡”变成“谁迟到谁没来”。统计逻辑的核心是一条按人员分组的SQL查询再用pandas转成表格。# 统计某天的考勤 def report_daily(date_str): sql SELECT p.name, MIN(a.check_time) AS first_check, MAX(a.check_time) AS last_check, COUNT(*) AS count FROM attendance_record a JOIN person p ON a.person_id p.id WHERE DATE(a.check_time) ? GROUP BY p.id rows query(sql, (date_str,)) return rows逻辑说明MIN(check_time)是当天第一次打卡MAX(check_time)是最后一次。通过这两个时间推导迟到和早退比单独存状态字段更可靠。COUNT(*)表示当天打卡次数如果等于0说明这个人根本没来。GROUP BY p.id保证了每个员工只输出一行汇总数据。导出Excel时需要把rows转成DataFrame再补充状态列import pandas as pd def export_daily_report(date_str, output_path): rows report_daily(date_str) if not rows: return None df pd.DataFrame(rows, columns[姓名, 首次打卡, 最后打卡, 打卡次数]) # 首次打卡晚于09:00视为迟到 df[状态] df[首次打卡].apply( lambda t: 正常 if str(t)[11:16] 09:00 else 迟到 ) df.to_excel(output_path, indexFalse) return df逻辑说明apply函数对每行做一个字符串截取从datetime里取出“HH:MM”格式的时间再和09:00做字符串比较。“HH:MM”是定长格式字典序和实际时间顺序一致所以可以这样比较。如果你更喜欢严谨的做法可以先把字符串解析成datetime对象再比较。这里用字符串截取是为减少代码行数工程够用。到这一步一个完整的考勤闭环就通了注册特征、实时识别、防重复打卡、按日汇总、导出Excel。剩下的是最容易翻车的那些边缘情况。5. 避坑指南人脸识别考勤系统五个高频翻车点5.1 照片集上准确率高摄像头实拍认不出现象离线测试注册照片识别准确率超过95%一接摄像头就频繁识别失败。原因注册照片和摄像头实拍存在三个差异光照、角度、分辨率。注册时用的是手机正面照打卡时是顶光或侧脸模型看到的分布完全不一样。另一个原因是注册时偷懒直接resize整张图没有对齐。解决注册样本就按考勤场景采集每人至少在不同角度和亮度下拍5到10张打卡前必须走MTCNN对齐流程不能把整张大图塞进特征提取器。我一般把注册采集也做成一个摄像头采集功能而不是导入相册照片这样才能保证分布一致。5.2 换上百万像素摄像头后识别率反而下降现象笔记本自带摄像头能用换一个高分辨率外接摄像头后识别率断崖式下跌。原因外接摄像头分辨率更高人脸框内像素变多缩小到160×160时丢失的信息比例不同。模型是从固定输入尺寸中学习的特征输入分布变化越大特征向量偏移越明显。解决抓帧后先做ROI限制最小人脸尺寸设大一些并保证任何分辨率下都走统一的缩放流程。还有一个办法是把摄像头分辨率固定成640×480省内存还稳定。这种情况不是模型不行是输入管线不稳定。5.3 两个员工互相认错现象A打卡被识别成B而且多次出现单独测A又是准的。原因两个人五官相似是基础诱因但真正的问题是只用了单一距离阈值没有看“第一名和第二名之间的距离差”。当A和B的特征距离都在阈值以内模型只能靠数值更小的那个做决定但两个人本来就接近这个决定不可靠。解决增加相对判定逻辑要求最小距离和第二小距离的差值必须大于一个margin否则判为不确定def recognize_with_margin(embedding, db_features, threshold0.8, margin0.1): dists [(name, np.linalg.norm(embedding - feat)) for name, feat in db_features.items()] dists.sort(keylambda x: x[1]) if len(dists) 1: return None best_name, best_dist dists[0] if best_dist threshold: return None if len(dists) 1 and (dists[1][1] - best_dist) margin: return None # 第一和第二太接近拒绝判定 return best_name逻辑说明margin的意义是让模型只做有把握的判定。加了这个判断以后陌生人误认率会明显下降代价是漏检增加。考勤系统的业务逻辑里漏一次可以再打一次卡但误认一次就可能变成代打漏洞宁可漏也不可错。5.4 同一个人注册照片越多反而越容易认错现象给每个人从5张加到20张照片后准确率没有提升A的样本反而被错认成B。原因采集的照片来自同一段连续连拍姿势和光线几乎一样信息冗余度过高。同时不同人的照片数量不均衡模型对采集多的类别产生了过拟合。解决照片采集要分散到不同日期、不同时段、不同角度每类样本数尽量一致多的裁剪少的补充。注册完以后用验证集跑一遍观察每个员工的个人准确率而不是整体准确率把表现差的单独排查。5.5 导出的CSV中文乱码打卡时间差8小时现象Excel打开导出的CSV文件姓名全是乱码数据库里打卡时间和本地时间对不上。原因CSV默认用UTF-8保存Excel在Windows下默认按GBK读取数据库连接未指定时区pymysql把时间按服务器默认时区处理了。解决导出CSV时使用utf-8-sig编码Excel就能自动识别数据库连接串里加上charset和时区参数。# 导出CSV用utf-8-sigExcel可正常识别中文 df.to_csv(output_path, indexFalse, encodingutf-8-sig) # MySQL连接串统一指定编码和时区 # engine create_engine( # mysqlpymysql://user:pwdlocalhost/db?charsetutf8mb4 # )逻辑说明utf-8-sig会在文件开头写入BOM头Excel根据BOM识别UTF-8编码。连接串里的charsetutf8mb4保证中文以四字节UTF-8存储和读取避免乱码。时间偏移问题在SQLite里不常见一旦换到MySQL必须显式处理时区。6. 进阶技巧用阈值曲线和陌生人负样本把识别准确率调上去大部分毕设测完自己人的照片集就收工答辩时一旦被问“陌生人能不能打进来”往往答不流畅。我的习惯是构造一个陌生人负样本集再把阈值标定做成一条曲线用数据说话。做法是找一批不属于任何注册人员的公开人脸图片把它们当作“攻击样本”跑一遍识别流程。理想情况下系统应该对每张陌生人脸都返回None。同时准备一批注册人员在不同光照下的正样本要求系统准确返回对应姓名。把阈值从0.5到1.5每隔0.02扫一遍记录每个阈值下的命中率和陌生人误认率。def calibrate_threshold(registered_feats, test_pairs, stranger_feats): results [] for t in np.arange(0.5, 1.5, 0.02): hit sum(1 for name, emb in test_pairs if recognize_by_distance(emb, registered_feats, t) name) false_accept sum(1 for emb in stranger_feats if recognize_by_distance(emb, registered_feats, t) is not None) results.append((t, hit / len(test_pairs), false_accept / len(stranger_feats))) return results逻辑说明正样本命中率和陌生人误认率是一对矛盾指标。阈值越紧陌生人越难混入但自己人也容易被拒阈值越松自己人识别率高但陌生人也能通过。把两组数据画在同一张图上两条曲线的交叉点附近就是最优阈值。就考勤场景而言我通常会把阈值往严格方向再调0.05到0.1因为漏一次损失很小误认一次损失很大。除了阈值标定特征空间的可分性验证也值得做。用t-SNE把注册库里的所有128维特征降到二维同一人的样本应该聚成一团不同人的样本团与团之间有明显间隔。如果分布一团乱麻说明注册样本的对齐或采集有问题这时候调阈值没有意义先回去处理数据。from sklearn.manifold import TSNE import matplotlib.pyplot as plt def plot_embedding_tsne(feats, labels): tsne TSNE(n_components2, perplexity5, random_state42).fit_transform(feats) plt.scatter(tsne[:, 0], tsne[:, 1], clabels, cmaprainbow, s20) plt.savefig(embedding_tsne.png, dpi150)逻辑说明t-SNE的结果图可以作为答辩ppt里的佐证材料比口头解释“模型效果不错”有说服力得多。perplexity设成5是针对小样本量的选择注册库只有几十个人时perplexity太大反而会扭曲分布。如果识别速度成为瓶颈还可以把PyTorch模型导出成ONNX格式再用onnxruntime推理。CPU上通常能获得30%以上的速度提升考勤场景里一帧的识别耗时可以压到几十毫秒以内。导出时把输入输出节点命名好方便后续调用。阈值区间系统行为适用场景小于0.6极严格自己人也会被拒门禁安防0.7到1.0均衡误认率低考勤打卡大于1.0陌生人易混入不可用最后说一个习惯从那以后我每次改完模型都强制自己把陌生人负样本跑一遍、把阈值曲线画出来再谈能不能上线。这个习惯帮我挡掉了至少两次答辩现场的翻车也包括一次把陌生人认成员工的尴尬演示。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网