ARTICLE DETAIL

资讯详情

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

基于YOLOv8的实验室防护服穿戴规范检测实战指南

基于YOLOv8的实验室防护服穿戴规范检测实战指南 简介基于YOLOv8的实验室防护服穿戴规范检测项目面向计算机视觉、人工智能方向的毕业设计或课程设计人群解决实验室安全着装自动识别与预警需求。项目提供源码、完整数据集、可视化界面与部署说明支持输出核心指标曲线、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果及标签分布图便于直接用于答辩展示与效果评估。附带说明文档模型权重可即插即用按步骤复现训练与推理流程有效降低上手门槛。资源共8个文件以Python脚本、PyTorch模型权重和说明文档为主分别对应界面交互、模型训练、视频检测与部署指引压缩包大小15.91MB结构紧凑即下即用。目前已有47人学习下载代码经测试运行成功适合需要快速落地毕设项目、在现有基础上二次开发或初学YOLOv8的读者。1. 实验室防护服穿戴规范检测为什么 YOLOv8 是最省事的开局做安全监控项目这几年我最大的体会是穿戴规范检测最难的不是“认出防护服”而是“判断谁没穿、穿得对不对”。基于YOLOv8的实验室防护服穿戴规范检测本质上是用一个多类别目标检测器同时定位画面里的实验服、手套、护目镜、口罩等目标再由一层规则逻辑判断穿戴是否齐全、目标是否出现在正确位置。它解决的安全巡检里最耗时的人工盯屏问题——摄像头一多人眼根本看不过来。这套方案适合三类人做毕业设计的学生、给实验室做安防改造的工程师、以及想拿现成数据快速验证目标检测全流程的初学者。最大的价值是部署门槛低没有GPU也能用CPU跑通训练和推理拿到代码后很快就能看到检测框和判定结果适合作为毕设或课程设计的完整落地项目。2. 穿戴规范判定逻辑检测目标怎么定、缺失怎么判做这个项目最容易翻车的地方是把“没戴手套”“没穿实验服”直接当成一个类别去训练目标检测器。实际上“违规”不是一个有稳定外形和位置的目标你今天标手套缺失明天标护目镜缺失模型学到的是背景差异而不是目标语义。我一般把整个链路拆成两层第一层用 YOLOv8 做多类别目标检测输出每个目标的框、类别和置信度第二层是规则判定把检测结果和“该有的位置”做比较。这样每一层都好调模型只负责“看到什么”规则负责“该不该有、放没放对位置”。2.1 类别集合怎么定先定穿戴规范再定检测目标我以常见化学实验室为例防护用品不外乎实验服、护目镜、手套必要时加口罩和帽子。那类别可以定为五个labcoat、gloves、goggles、mask、cap。这里的原则是不要贪多每加一个类别就要补一批样本。口罩和帽子在某些拍摄角度下非常接近训练数据不够时模型会把发帽认成口罩把口罩认成下巴上的阴影这类混淆一旦出现后面的规则判定全跟着乱。类别集合必须和最终的规范判定逻辑对齐。如果你们实验室只查“实验服 手套 护目镜”三样那类别就只保留三个规则也只看这三类。别把规范里没有的东西标进去白费标注精力。还有一点容易被忽略标注规范要提前统一。护目镜的框是否包含松紧带手套是否只框手掌不框手指口罩被拉到下巴时算不算正样本这些不写清楚两个标注员标出来的数据分布完全不一样训练时模型会学得摇摆不定。我习惯的做法是开工前写一个两页的标注说明里面贴三张示例图完整穿戴、部分遮挡、目标在画面边缘。标注说明不光是给别人看的也是给自己留的依据。过一个月回看当时标注的几百张图你能想起来当时怎么定义的比靠印象靠谱得多。2.2 关键区域 ROI把“有没有”变成“在不在该在的位置”单看画面一个人手插兜站在摄像头前手套实际上是不可见的。你不能因为检测不到手套就直接判违规这会误报到管理员想关系统。另一个常见场景是画面里另一个人的手套出现在当前目标附近如果只做全局存在性判断就会把别人的手套算成自己的。所以要在画面里预先划定关键区域比如头部区域、躯干区域、手部区域。检测器输出的每个框只有中心点落在对应ROI里才算有效检出。下面这段是判定层最核心的骨架我每次做穿戴类项目都会先写这两个函数def center_in_roi(box, roi): 判断检测框中心点是否落在给定ROI内。roi为 [x1, y1, x2, y2] x1, y1, x2, y2 box cx, cy (x1 x2) / 2, (y1 y2) / 2 rx1, ry1, rx2, ry2 roi return rx1 cx rx2 and ry1 cy ry2 def check_compliance(results, rois, conf_threshold0.5): 输入检测结果与ROI配置返回违规说明列表 violates [] for det in results.boxes: x1, y1, x2, y2 det.xyxy[0].tolist() cls int(det.cls[0]) conf float(det.conf[0]) if conf conf_threshold: continue if cls 1 and not center_in_roi([x1, y1, x2, y2], rois[hands]): violates.append(手套不在手部区域) elif cls 2 and not center_in_roi([x1, y1, x2, y2], rois[face]): violates.append(护目镜位置异常) return violates逻辑说明det.xyxy[0]是检测框的像素坐标YOLOv8 推理结果里直接给出左上角和右下角det.cls[0]是类别索引和训练时的 names 列表一一对应det.conf[0]是置信度。conf_threshold这个参数很关键设太低置信度 0.3 的误检框会被当成有效目标设太高人侧身时护目镜被压成一半置信度掉到 0.4 就被过滤直接漏检。我一般先用 0.5 起步跑几天真实画面再按误报率微调。ROI 怎么获得最简单的方式是在界面里让操作者框选一次头部区域、躯干区域、手部区域。之后把矩形存成配置文件部署时加载。改机位时重新框选不需要重新训练模型这是 ROI 方案比纯端到端方案灵活的地方。ROI 不要画得太准稍微留一点余量。人脸区域如果贴着下巴框转头时护目镜一晃就出界手套区域只覆盖操作台前一小块人抬手到高处就会被判违规。2.3 为什么选 YOLOv8先看精度底线再看落地成本经常有人问为什么不选 Faster R-CNN 或者老一点的 YOLOv5。我按自己的交付经验排个对比模型精度推理速度训练门槛部署成本Faster R-CNN高慢不适合实时高需要 GPU打包麻烦YOLOv5中高快低生态成熟稳定YOLOv8中高快低官方支持 ONNX 导出CPU 可跑实验室防护服检测的难点在于小目标手套、护目镜在远机位时只有十几个像素。Faster R-CNN 精度确有优势但对这类项目不划算标注成本一样训练和调参成本却高得多最后部署还要考虑怎么塞进普通电脑的桌面程序。YOLOv8 是 anchor-free 设计训练配置相对简单官方对 ONNX 导出支持得很好跨平台部署省心。YOLOv8 内部选哪个规格也要说一句。对这个数据量级yolov8s足够yolov8n太快但小目标表现差yolov8m在 GPU 上更好但 CPU 推理会明显吃力。毕设和实验室监控这种场景yolov8s是最平衡的选择。答辩时如果被问“为什么选它”从实时性和部署成本答比说“精度最高”更站得住。2.4 单帧判定的边界什么情况不能只靠一帧单帧判定有个天然缺陷遮挡和短暂离场会让目标“看不见”而不是“不存在”。手插兜、转身、挥手都可能让一两帧里手套或护目镜的置信度骤降直接按单帧报警会把管理员烦死。人眼在这里的做法也类似——看到人转身时护目镜不见不会立刻记违规而是等他转回来再确认。机器也应该这样。另一个边界是光照。实验室的顶灯和窗户反光会让白大褂局部过曝护目镜镜片反光也会让检测框漂移。单帧 mAP 再高落到真实环境都会遇到这些噪声。比较稳妥的做法是先靠 ROI 过滤掉位置离谱的检测框再用时序投票把单帧抖动抹平。具体的 N 帧投票方案放在第 6 章这里先记住一个结论穿戴规范检测的可靠性一半在模型另一半在判定层的容错设计。3. 数据集标注与 YOLOv8 训练跑通第一个 mAP模型再强没有对齐真实场景的数据也是白搭。这个项目的数据集准备并不复杂但里面几个细节直接决定训练效果比调参重要得多。3.1 数据从哪里来自己拍出来的比网上找的靠谱穿戴检测的数据最好是自建。原因是这类数据场景强相关部署机位定在实验室门口数据就应该是那个视角拍的。网上找的穿防护服的图角度、光线、距离全对不上迁移到现场效果会很差。常见做法是拿实验室监控切图再叫同事补拍合规和违规的镜头。录视频时走几遍正常流程穿好防护服、戴好护目镜和手套再故意摘掉手套拍一段。每秒抽 1 到 2 帧一段两分钟的视频能抽出两三百张而且自带时间连续性比一张张找图高效得多。具体量上我一般要求每个类别至少 400 到 600 个标注框整体图片数 800 到 1500 张。样本分布要注意实验中常见的“手在操作台下”“背身取试剂”等场景都要有一定比例否则判定逻辑跑起来全是漏检。合规和违规的比例也要控制我通常让“穿戴齐全”和“至少缺一样”各占一半左右避免模型学会偷懒只输出合规。3.2 用 labelme 标注并转成 YOLO 格式转换脚本与四个边界坑Labelme 导出的是 JSONYOLOv8 训练要的是 txt每个 txt 文件名和图片名一致每行格式是“类别索引 x_center y_center width height”四个坐标值归一化到 0 到 1。转换脚本我直接贴出来import json import os def convert_one(json_path, out_dir, class_map): with open(json_path, r, encodingutf-8) as f: data json.load(f) img_w data[imageWidth] img_h data[imageHeight] lines [] for shape in data[shapes]: label shape[label] if label not in class_map: continue pts shape[points] xs [p[0] for p in pts] ys [p[1] for p in pts] x1, y1, x2, y2 min(xs), min(ys), max(xs), max(ys) # 归一化到 [0,1]YOLOv8 要求中心点宽高 cx (x1 x2) / 2 / img_w cy (y1 y2) / 2 / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h lines.append(f{class_map[label]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) txt_name os.path.splitext(os.path.basename(json_path))[0] .txt with open(os.path.join(out_dir, txt_name), w, encodingutf-8) as f: f.write(\n.join(lines)) class_map {labcoat: 0, gloves: 1, goggles: 2, mask: 3, cap: 4}逻辑说明shape[points]是标注的多边形顶点这里取外接矩形转成检测框。labelme 的 label 名必须和 class_map 完全一致大小写和空格不一致会导致脚本静默跳过训练时才发现某个类别没有样本。转换完不要只看 txt 有没有生成随机抽几张图把框画回去看一眼这个检查只需要几分钟能省掉后面训练失败的两小时。转换里有几个坑我逐个说。第一labelme 的 JSON 字段是imageWidth/imageHeight大小写不能错读不到时按 0 处理归一化坐标会变成 infYOLOv8 训练直接报错。第二标注时画的是曲线多边形外接矩形可能把相邻目标包进去遇到凹形复杂标注建议删掉重画不要将就。第三文件名带中文或空格在 Linux 上读路径经常出幺蛾子训练前统一改成字母数字命名。第四没有目标的图片放训练集时不要生成空 txt直接不放即可YOLO 对只有背景的图片会当作负样本处理。提示脚本跑完以后在转换出的 txt 里检查坐标最大值是否都小于 1、最小值是否都大于 0。出现越界先查原始 JSON 的宽高字段不要急着改模型。3.3 划分训练集和验证集按场景分不要按图片随机分很多新手拿脚本随机 shuffle 所有图片训练集和验证集里全是同一段监控视频的相邻帧mAP 虚高到 0.9 以上拿到真实场景立刻崩。正确做法是按“视频片段”或“拍摄场景”粒度划分录三段视频两段进训练一段进验证补拍的单张照片先按人物和场景分组再拿组做划分。下面是一个按文件名前缀分组的脚本import random from pathlib import Path imgs list(Path(images).glob(*.jpg)) groups {} for img in imgs: prefix img.stem.split(_)[0] # 按文件名前缀代表同一场景 groups.setdefault(prefix, []).append(img) all_groups list(groups.values()) random.Random(42).shuffle(all_groups) val_count max(1, len(all_groups) // 10) val_groups all_groups[:val_count] train_groups all_groups[val_count:]逻辑说明这里假设文件名前缀是场景 ID比如scene1_0001.jpg。按组切分能避免数据泄漏验证集更接近真实部署。random.Random(42)固定随机种子保证多次执行结果一致复现实验时不靠运气。划分完以后图片放进images/train、images/val对应的 txt 放进labels/train、labels/val目录结构要一一对应。只有一路摄像头的话按场景分组后可能没有独立验证集。我的做法是换一个机位高度再录一段视频专门用作用验证。机位差一点没关系关键是验证集必须包含模型没见过的拍摄视角否则训练阶段看到的 mAP 没有参考价值。3.4 训练参数命令与 CPU 环境跑法数据准备好后把项目组织成下面的结构datasets/ppe/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── ppe.yamlppe.yaml内容train: datasets/ppe/images/train val: datasets/ppe/images/val nc: 5 names: [labcoat, gloves, goggles, mask, cap]注意train和val用相对路径跑训练命令时要在这个datasets的上级目录执行路径才不会拼错。接着启动训练yolo detect train \ datadatasets/ppe/ppe.yaml \ modelyolov8s.pt \ epochs100 \ imgsz640 \ batch16 \ patience30 \ device0参数含义我逐个说modelyolov8s.pt是 small 版预训练权重小数据集能更快收敛数据量不多时别上yolov8x会过拟合imgsz640是训练分辨率手套这种小目标建议提到 960但 CPU 机器会明显变慢batch16要看显存8G 显存跑 640 分辨率一般只能到 8报显存不足就减半patience30表示验证指标连续 30 轮不提升就早停防止白等device0是第一块 GPU没有 GPU 就写devicecpu。如果你用的是 Ubuntu 20.04 又没有独显想搭 YOLOv8 的 CPU 版本环境其实很简单。先pip install ultralytics再装 CPU 版的 torch训练和推理都能跑只是速度比 GPU 慢很多。CPU 训练时batch不要设太大4 到 8 比较稳因为 CPU 后向传播的内存开销和处理速度都和 GPU 不一样。单路摄像头的推理在 CPU 上也能跑到接近实时的水平所以部署不依赖 GPU 也完全可行。3.5 训练结果怎么看损失曲线、混淆矩阵与 mAP训练完以后YOLOv8 会在runs/detect/train下生成results.png里面画了 box_loss、cls_loss、dfl_loss 三条损失曲线以及 mAP50 和 mAP50-95。看损失曲线图时要关注“是否在降、到多少轮开始不再明显下降”。如果损失曲线还在降但指标已经不动多半开始过拟合这时看patience早停点落在哪。然后打开confusion_matrix.png看哪些类别互相混淆。穿戴检测里最常见的混类是手套和实验服手臂和实验服同色时手套框容易飘到手臂上。看到这种混淆优先去补“戴手套抬手”的样本而不是盲目调阈值。最后用验证集跑一次yolo detect val \ modelruns/detect/train/weights/best.pt \ datadatasets/ppe/ppe.yaml输出的 mAP50 大于 0.75 属于可接受水平。低于这个数不要急着换模型结构先查数据某个类别样本少就补样本标注框歪了就回炉重标。对这个量级的数据集模型结构不是瓶颈数据质量才是。训练目录里还有几张train_batch*.jpg是增强后的训练样本缩略图。打开看一眼 mosaic 增强有没有把目标裁到画面边缘、有没有出现整张黑图如果增强后的样本太离谱训练指标再好也要先怀疑数据加载那一步。4. 可视化界面与实时推理把模型装进一个小程序模型训完只是走完一半剩下的是如何让非技术用户愿意用。可视化界面在这个项目里不只是给答辩看它是判定逻辑真正落地的地方。4.1 界面方案怎么选PyQt5 桌面端还是 Gradio 演示版“可视化界面”有两条常见路线。一条是用 PyQt5 写桌面程序按钮、状态栏、摄像头画面都本地渲染可以打包成 exe 脱离开发环境运行适合毕设展示和实验室日常使用。另一条是用 Gradio 写网页 Demo十几行代码就能在浏览器里上传图片、看标注结果适合时间紧张、只想快速验证效果的情况。我的建议是如果答辩要讲软件工程结构选 PyQt5如果只要求演示检测效果选 Gradio省下来的时间用于补训练数据和文档。两者的推理内核完全一样都是调用同一个检测类所以先用第 2 章的check_compliance把逻辑跑稳再决定外壳不迟。PyQt5 跑实时摄像头时有一条铁律不能把模型推理放在 UI 主线程里。YOLOv8 在 CPU 上跑一帧要 30 到 80 毫秒放在 Qt 事件循环里界面会直接卡死。4.2 推理管线从摄像头帧到违规清单先把“模型推理 规范判定”封装成一个类这个类可以被 PyQt5、Gradio、命令行脚本复用别把模型调用散落在界面代码里。from ultralytics import YOLO class PPEInspector: def __init__(self, weightsbest.pt, devicecpu, roisNone): self.model YOLO(weights) self.device device self.rois rois or {} def run(self, frame, conf0.4, iou0.45): 输入一帧 BGR 图像返回可视化帧和违规列表 results self.model.predict( frame, confconf, iouiou, imgsz640, deviceself.device, verboseFalse, )[0] visual results.plot() violations check_compliance(results, self.rois, conf_thresholdconf) return visual, violations逻辑说明results.plot()会把检测框、类别名和置信度画在帧上返回省去手动画框的代码。check_compliance是第 2 章的判定函数这一层把“模型看到了什么”翻译成“穿戴是否合规”。conf0.4适合实时摄像头因为人在画面里会走来走去置信度波动大阈值太高会大面积漏检。离线分析单张图片时可以把阈值提到 0.6宁可漏检也不误报。source参数在 YOLO 推理里可以直接传摄像头设备号0、视频文件路径或 RTSP 地址。做成一个run方法的好处是换输入源时界面层完全不用动。摄像头断线重连、文件读取到结尾这些异常都留在调用方处理推理层保持干净。4.3 摄像头线程与界面刷新别再让界面卡成幻灯片下面给出一个可复用的摄像头线程骨架PyQt5 和 PySide6 通用from PyQt5.QtCore import QThread, pyqtSignal import cv2 import time class CameraWorker(QThread): frame_ready pyqtSignal(object, list) def __init__(self, inspector, source0, parentNone): super().__init__(parent) self.inspector inspector self.source source def run(self): cap cv2.VideoCapture(self.source) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) while not self.isInterruptionRequested(): ok, frame cap.read() if not ok: break visual, violations self.inspector.run(frame) self.frame_ready.emit(visual, violations) time.sleep(0.03) # 约 33fps 节奏给界面刷新留出余量 cap.release()逻辑说明CameraWorker继承 QThread摄像头读取和模型推理全在子线程frame_ready信号把处理后的帧和违规列表传回 UI 线程。time.sleep(0.03)是刻意做帧率限制推理本身可能只要 40 毫秒但无限循环会让信号队列堆积画面越来越滞后。1280x720 的输入配合 imgsz640 的推理分辨率能兼顾清晰度和速度。不要直接拿摄像头原生 4K 画面喂模型先降到 1280 再推理耗时会省一半。注意QThread 里不要直接操作 UI 控件所有界面更新都通过信号发回主线程。子线程里调用label.setText这类操作Qt 会在控制台报线程错误严重时直接崩溃。4.4 违规记录把判定结果落成 CSV穿戴检测如果只停留在界面闪烁说服力不够。加一个简单的违规记录功能把每次报警的时间、类别、置信度写进 CSV就能形成“从检测到留痕”的完整链路。这个功能顺带可以做统计按天看报警次数、哪一类缺失最多。import csv from datetime import datetime def save_violation(violations, frame_id): if not violations: return with open(violations.csv, a, newline, encodingutf-8) as f: writer csv.writer(f) writer.writerow([ datetime.now().isoformat(), frame_id, | .join(violations) ])逻辑说明newline避免 Windows 平台 CSV 写入时多出空行violations是字符串列表用 | 拼接让一条记录能同时保存多个缺失项。实际部署时可以在 CSV 里再加一个现场截图字段做法是先cv2.imwrite用时间戳命名再把文件名写进记录。管理员最后认可这类系统往往不是因为 mAP 高而是因为出了问题能查到人、查到时间。5. 避坑穿戴检测项目最常见的 5 个翻车现场5.1 合规样本太多模型学会了“永远说合规”现象训练全流程跑完了验证集 mAP 也不低但在真实监控里一个人没穿实验服走进来系统完全不吭声。翻看验证集图片发现验证集里几乎全是合规画面模型根本没机会学到“缺失”的场景。原因数据收集阶段偷懒了。日常监控拍出来的画面绝大多数是全副武装的合规画面不戴手套的样本只有几十张模型学到的是场景先验而不是目标特征。解决数据收集时专门安排一段“故意违规”的拍摄每次少一样东西拍一遍摘手套、摘护目镜、不系扣子。目标不是让模型直接检测“违规”而是让它在真实的“目标不存在”背景下能稳定输出。每类缺失场景至少补 200 张训练收敛后单独检查这几类样本的召回率。判定层也加一道保险合规时长超过设定阈值后自动降低误报容忍度敏感度提上来。5.2 手套太小AP 一直上不去现象训练日志里gloves的 AP 只有 0.25其他类别都 0.7 以上。验证集图片里手部的框要么没画出来要么只框住半个手。原因手部占整帧图像面积太小。默认imgsz640时手部区域可能只有 20x30 像素特征在下采样过程中被吞掉了。另一个常见原因是手套和实验服袖子同色标注边界不清晰模型被袖子带偏。解决把训练分辨率从 640 提到 960小目标框的实际像素面积翻倍。验证时也要用同样的imgsz960否则看不到提升效果。另外不要关 mosaic 增强YOLOv8 默认开启多尺度拼接能显著拉高小目标召回。如果补数据不方便先把手部区域的图片单独裁剪出来做局部增强AP 通常能涨 5 到 10 个点。5.3 CPU 推理加 PyQt 界面等于幻灯片现象摄像头窗口能打开但画面像幻灯片一样逐帧跳点击按钮也没反应程序像是死了。原因在 QTimer 的槽函数里直接调用model.predict推理耗时就占住了 Qt 事件循环鼠标事件、绘制事件全部排队。CPU 机器上尤其明显。解决把推理挪到后台线程用信号把结果传回界面。同时把传给模型的帧预先缩放cap.set里设 1280 宽就够不要直接把 1920x1080 原图喂进模型。推理分辨率不变输入分辨率降下来预处理开销能省一大截。界面刷新频率控制到 20 到 30fps 就够不需要追 60fps。5.4 反光和同色背景引发误检现象某个时段画面里实验台反光区域经常被识别成手套或者门外穿白大褂的人被框成实验服误报响个不停。原因训练数据里没有覆盖“看起来像目标但不是目标”的硬负样本。反光区域的纹理和亮度分布与手套相似模型学会了看颜色形状没学会看上下文。解决先调高判定层的conf_threshold从 0.4 提到 0.6过滤低置信度误检这是最快的办法。然后去现场截取 30 到 50 张误检帧放进训练集里作为背景图片不标注任何目标。对 YOLO 来说一张无目标的图片就是天然负样本能学会“在这块区域闭嘴”。最后再依赖第 2 章的 ROI 检查就算识别成了手套中心点不在手部区域判定层也会拦下。5.5 换了摄像头机位检测效果直接崩掉现象在 A 实验室调试一切正常同一套权重和代码搬到 B 实验室mAP 下降明显漏检和误检同时出现。原因机位高度、俯仰角、距门口距离都变了。模型见过的人物尺度和遮挡模式与真实场景对不上ROI 还是 A 实验室的绝对像素坐标画到新画面上直接歪掉。解决部署时先在新机位录 10 分钟视频抽 100 帧做快速验证只看两个指标检测框准不准、ROI 对不对。ROI 一定要存相对尺寸不要存绝对像素。下面给一个换算片段def scale_roi(roi_norm, frame_w, frame_h): roi_norm 是 [x1, y1, x2, y2] 归一化坐标转换为当前帧像素坐标 x1 int(roi_norm[0] * frame_w) y1 int(roi_norm[1] * frame_h) x2 int(roi_norm[2] * frame_w) y2 int(roi_norm[3] * frame_h) return x1, y1, x2, y2逻辑说明部署时维护一个rois_norm.json存 0 到 1 的百分比坐标。界面加载时拿当前帧宽高做一次换算机位变化后只需重新框选一次 ROI不用重新训练模型。训练侧则用多尺度采样增强来提升对不同机位距离的包容度。6. 进阶用 N 帧投票把误报压下去再谈部署提速6.1 N 帧滑动窗口投票前面 2.4 节留了一个问题单帧判定会被人转身、手插兜、反光抖动干扰。我实际交付时都会加一个投票器逻辑很简单保存最近 15 帧的“是否违规”结果只有超过 80% 的帧都判违规才真正触发报警。from collections import deque class FrameVoter: def __init__(self, window15, ratio0.8): self.history deque(maxlenwindow) self.ratio ratio def update(self, violations): 每次喂入当前帧的违规列表返回是否触发报警 self.history.append(1 if violations else 0) if len(self.history) self.history.maxlen: return False return sum(self.history) / len(self.history) self.ratio逻辑说明deque(maxlen15)会自动丢弃最旧的一帧不用手动管理队列长度。ratio0.8表示 15 帧里至少 12 帧违规才报警。窗口越大越稳、延迟越高对进出实验室这种场景0.5 秒延迟完全可接受。这样做的好处是人抬手挡住护目镜一两帧不会误报真正摘掉手套被连续检测到才会触发。6.2 导出 ONNX 让推理轻量化如果部署机器不想装整套 torch可以把模型导出成 ONNX 用 onnxruntime 推理yolo export modelbest.pt formatonnx dynamicTrue imgsz640导出后推理的是静态图模型加载更快CPU 部署更友好。不过这类项目里推理不是最大瓶颈摄像头 IO 和界面刷新才是导不导出不影响整体体验。重心放在判定层和时序逻辑上效果提升更明显。6.3 这样验证才算闭环验收这个项目时我会准备三段固定机位的视频一段完全合规、一段缺少手套、一段缺少护目镜。统计两个指标合规视频里的误报次数违规视频里的漏报次数。比 mAP 更能说明问题的是系统的 F1报警准不准、稳不稳。对毕设答辩来说能说出“在 15 帧窗口下误报率为 0、漏报率为多少”比贴一张 mAP 曲线更有说服力。我自己第一次做这个项目时把全部精力花在调高 mAP 上结果现场演示时因为反光误报不断场面很尴尬。后来把重心移到 ROI 和 N 帧投票问题才真正解决。这个教训让我后来做任何检测类项目都会先问一句模型之外的那一层逻辑有没有替现场把好关。希望这篇笔记能帮你少走这段弯路。本文还有配套的精品资源点击获取
返回列表