ARTICLE DETAIL

资讯详情

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

YOLOv8扶梯梳齿板异物检测:从数据集训练到边缘部署实战

YOLOv8扶梯梳齿板异物检测:从数据集训练到边缘部署实战 简介一套基于YOLOv8的商场自动扶梯梳齿板异物卡滞预警系统专为计算机视觉方向的毕业设计、课程设计或项目初期演示而设计适合在校学生、教师以及企业开发者快速上手。资源包共8个文件包括3个Python脚本分别用于可视化界面、模型训练和视频检测、3个YOLO模型权重以及2个说明文档压缩包仅15.91MB部署简单不占空间。项目附带完整数据集和部署教程代码均测试通过可直接运行生成核心指标曲线图、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果及标签分布图帮助深入理解目标检测模型评估过程答辩展示时更具说服力。已有36人学习下载功能完善、操作简单既可拿来即用也便于在此基础上二次开发扩展更多扶梯或公共场所安全检测场景。1. 商场自动扶梯梳齿板异物卡滞预警为什么要用 YOLOv8 单独做一套扶梯梳齿板卡异物这件事听起来是个小概率事件但一旦发生轻则梳齿变形、梯级跑偏重则卷入乘客鞋带或裙摆直接升级为安全事故。传统方案大多是红外对射或机械防夹误报率极高而且没法告诉运维人员“卡的是什么”。最近在做的这套《基于YOLOv8的商场自动扶梯梳齿板异物卡滞预警系统》本质上就是用目标检测模型盯着梳齿板区域对掉进去的饮料瓶盖、竹签、碎石、口罩、包装袋碎片做实时识别并报警。它不仅输出“有异物”还能输出“哪一类异物、置信度多少、从哪一帧开始卡的”。这套方案适合两类人一是拿它做毕业设计或课程设计的在校生源码、可视化界面、完整数据集和部署教程都齐了简单部署就能出效果二是商场物业或电梯维保公司想低成本试点智能预警一张 GPU 卡或一台边缘盒子就能跑起来。核心不是模型多深而是把检测、报警、界面、记录串成一个闭环。接下来我从数据集怎么处理、训练参数怎么调、界面怎么接、部署有什么坑按一线落地顺序给你讲透。2. 梳齿板异物检测的数据集与标注先让 YOLOv8 知道“异物长什么样”2.1 目标类别怎么定不是越全越好而是按风险分很多人拿到完整数据集后第一反应是“类别够不够多、数量够不够大”。在做梳齿板异物识别时我一般不建议一上来就设十几个类别。先看扶梯梳齿板的物理结构梳齿间隙一般是 5 到 8 毫米能卡进去的东西形状上无非是细长条、片状、颗粒状三类。细长条对应竹签、铁丝、吸管片状对应口罩、包装袋、纸屑颗粒状对应石子、瓶盖碎片、螺丝。把类别压缩成“长条异物、片状异物、颗粒异物”三个大类训练难度会低很多在线推理时的误检率也肉眼可见地下降。如果论文需要展示细粒度能力再在这三个类下做子类拆分。数据来源上有两个常见做法一是直接用公开数据集里“扶手带入口异物”“梯级间隙异物”等相近场景的图片做预训练再用自己的梳齿板实拍图做微调二是自己架一台摄像头对着扶梯口拍两周截取不同光照、不同客流密度、不同时段的关键帧。“完整数据集”如果只给几百张梳齿板空镜那是没法用的必须有一部分是人工把异物放进梳齿缝隙里拍的正样本而且正负样本比例最好在 1:1 到 1:2 之间。负样本太少模型会倾向于“什么都报警”这是后面误报暴增的根源。2.2 标注工具选型和 YOLOv8 标签格式转换标注工具我常用 labelImg 或 X-AnyLabeling。前者轻量、老牌、导出 YOLO 格式直接方便后者支持自动分割辅助标注抠梳齿缝隙里的小目标时能省不少时间。YOLOv8 需要的标注格式是每个图像对应一个同名 txt每行内容为class_id x_center y_center width height坐标是相对于图片宽高的归一化数值。如果你的数据集是从某处拿来的 VOC XML 格式或者是你用 LabelMe 标注的 JSON 多边形格式都要先转成 YOLO 的 txt。下面这个脚本我每次做目标检测项目都会用到专门把 VOC XML 批量转成 YOLOv8 格式import os import xml.etree.ElementTree as ET from pathlib import Path def voc2yolo(xml_path, out_txt, class_map): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.iter(object): cls_name obj.find(name).text if cls_name not in class_map: continue cls_id class_map[cls_name] box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) # 转归一化中心点/宽高注意边界不做截断容易训练报错 x_center (x1 x2) / 2.0 / img_w y_center (y1 y2) / 2.0 / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) with open(out_txt, w) as f: f.write(\n.join(lines)) class_map {长条: 0, 片状: 1, 颗粒: 2} for xml_file in Path(voc_annotations).glob(*.xml): txt_path Path(yolo_labels) / (xml_file.stem .txt) voc2yolo(xml_file, txt_path, class_map)这段代码里最值得说明的是x_center、width的计算全部用归一化值不要直接存像素坐标。YOLOv8 在ultralytics框架里加载标签时如果发现归一化坐标有大于 1 或者小于 0 的值会直接忽略该目标甚至报数据错误。另一个细节是当目标紧贴图片边缘时转出来的x_center可能接近 0 或 1训练时容易产生 anchor 匹配异常我的习惯是转完后做一个 clamp 把坐标限制在 0.0001 到 0.9999 之间别让它等于 0 或 1。2.3 数据划分与增强别把同一场景的连续帧拆进训练集和验证集划分数据集时最容易犯的错是随机打乱后按 8:1:1 拆训练、验证、测试。但扶梯视频是连续帧相邻帧之间几乎一模一样直接随机拆会让验证集和训练集高度重合训练曲线好看一到现场视频就翻车。正确做法是先按视频片段分组同一段视频的帧只能进同一个集合。import random from pathlib import Path img_paths sorted(Path(images).glob(*.jpg)) # 假设文件名格式clip01_frame001.jpg按 clip 前缀分组 clips {} for p in img_paths: clip_id p.stem.split(_)[0] clips.setdefault(clip_id, []).append(p) clip_ids list(clips.keys()) random.seed(42) random.shuffle(clip_ids) train_clips clip_ids[:int(len(clip_ids) * 0.8)] val_clips clip_ids[int(len(clip_ids) * 0.8):int(len(clip_ids) * 0.9)] test_clips clip_ids[int(len(clip_ids) * 0.9):] def write_split(split_clips, split_name): with open(f{split_name}.txt, w) as f: for cid in split_clips: for p in clips[cid]: f.write(str(p.resolve()) \n) write_split(train_clips, train) write_split(val_clips, val) write_split(test_clips, test)这个脚本生成的是图片绝对路径列表后续在 data.yaml 里直接引用。增强方面YOLOv8 默认会做 mosaic、随机翻转、HSV 扰动。对梳齿板场景我建议把 mosaic 开启但概率降到 0.5因为异物是小目标mosaic 拼图后目标被缩得太小反而让模型学到错误的尺度分布。另外增加一个随机旋转 ±15 度和随机亮度对比度扰动模拟扶梯口早晚光照差异比堆更多数据更管用。3. 基于 YOLOv8 训练自己的数据集模型选型、参数调优与损失曲线判读3.1 用 YOLOv8n 还是 YOLOv8s看算力而不是看精度很多人盯着精度选模型但梳齿板异物检测有一个现实约束扶梯口摄像头通常要 7x24 小时跑如果是部署在边缘盒子而不是机房 GPU 服务器模型推理速度直接决定硬件成本。YOLOv8n 在 COCO 上的 mAP 比 s 低 3 个点左右但参数量只有 3.2M一张 GTX 1660 Ti 上推理时长可以压到 10 毫秒以内YOLOv8s 参数量 11.2M精度更高但边缘侧帧率会掉 30% 左右。我一般这样选先在本地 GPU 上用 YOLOv8s 训练拿到精度基准再导出成 ONNX 放到目标设备上用 rk3588 的 NPU 或 Intel 核显跑一遍测帧率如果帧率满足 15 FPS 以上就用 s否则降级到 n。不要一开始就图省事用 n等误报多了再后悔。yolov8 网络结构图在 ultralytics 官方仓库里有清晰的可视化它的 neck 是 PANethead 是 decoupled head。在梳齿板异物这种小目标场景我不建议一上来就改 head 结构。盲目改动 head 的通道数或引入注意力模块在自建小数据集上很容易过拟合。先把默认结构跑通再考虑 yolov8 head 改进比如给检测头加一个小尺度检测层 P2专门照顾梳齿缝隙里的细小异物但在改进前要对 baseline 做完整的评估否则你根本不知道改动是正向还是负向。3.2 一份可以直接改的 data.yaml 和训练启动参数训练前先把数据集结构整理成下面这样dataset/ ├── train/ │ ├── images/ │ └── labels/ ├── val/ │ ├── images/ │ └── labels/ └── test/ ├── images/ └── labels/data.yaml 内容如下path: /home/you/dataset # 数据集根目录建议写绝对路径 train: train/images val: val/images test: test/images nc: 3 names: 0: long_stick 1: sheet 2: particle训练命令yolo detect train \ data/home/you/dataset/data.yaml \ modelyolov8s.pt \ epochs200 \ batch16 \ imgsz640 \ patience30 \ lr00.01 \ lrf0.01 \ mosaic0.5 \ device0 \ projectruns/train \ nameescalator_test参数说明patience30表示连续 30 轮验证集指标不提升就早停对自建小数据集非常关键可以防止后期过拟合mosaic0.5就是我前面说的降低 mosaic 概率lr00.01在 batch 为 16 时是安全的如果 batch 减半到 8建议把 lr0 降到 0.005 附近imgsz640是 YOLOv8 默认输入尺寸梳齿板异物通常只有几十个像素不建议用 320否则小目标直接丢特征。3.3 训练时怎么判断“模型没学好”而不是“数据不够”很多人只看 mAP50 到了 0.9 就觉得大功告成这是误区。梳齿板场景更关键的是 mAP50-95它反映目标在不同 IoU 阈值下的定位精度异物卡滞报警对框的准确率要求很高因为你需要靠框的位置判断是“在梳齿板表面”还是“已经进入缝隙”。训练完先用yolo detect val看各类别的 AP如果长条类 AP 高、颗粒类 AP 低往往是颗粒类样本太少或者标注框大小差异过大。yolov8 画损失函数曲线图可以直接用训练生成的results.png里面有 box_loss、cls_loss、dfl_loss 三条曲线。我的判读习惯是如果 box_loss 在训练后期来回震荡不收敛先看是不是标注框本身噪声太大典型的像梳齿缝隙里小石子的框不同标注员画的框可能差出 10 个像素这种 AA 噪声会让 loss 下不去。如果 cls_loss 收敛了但混淆矩阵显示颗粒类被大量误判为长条别急着加数据先去看标注大概率是某些样本的类别标错了。4. 可视化界面与实时推理把模型输出变成运维人员看得懂的预警信号4.1 界面功能划分视频预览、实时报警、历史记录三块必须齐全这套系统适合毕设展示核心卖点之一就是“可视化界面”。我建议界面用 PySide6 或 PyQt5 做不要用 OpenCV 的窗口直接怼帧那样根本没法向评委或物业人员演示。界面分成三块左侧是视频预览区显示实时画面和检测框右侧上方是报警状态区显示当前报警类别、置信度、连续卡滞帧数右侧下方是历史报警记录表记录时间、类别、置信度、截图路径。报警逻辑不要做成“单帧检测到就报警”那样会被树叶晃动、光影变化打爆。常见的做法是加一个“连续帧确认”机制同一类别目标连续 5 帧以上都被检测到且置信度均超过阈值才触发报警。这个逻辑贴近真实扶梯卡滞场景因为异物一旦卡进梳齿缝短时间内不会消失而行人鞋带扫过、光影造成的误检通常只持续 1 到 2 帧。4.2 推理线程与界面主线程分离避免“画面冻死”用 PySide6 做界面最典型的坑是把模型推理直接写进界面刷新回调里结果模型推理一次要几十毫秒界面帧率骤降鼠标拖动窗口都卡。正确做法是开一个独立的 worker 线程做推理通过信号把帧和检测结果传回主线程更新界面。下面是一个最小可用的推理线程模板import cv2 import numpy as np from PySide6.QtCore import QThread, Signal from ultralytics import YOLO class DetectWorker(QThread): frame_ready Signal(object, list) # 原始帧 检测结果列表 def __init__(self, model_path, source, conf_thres0.35, parentNone): super().__init__(parent) self.model YOLO(model_path) self.source source self.conf_thres conf_thres self.running True def run(self): cap cv2.VideoCapture(self.source) while self.running and cap.isOpened(): ok, frame cap.read() if not ok: break results self.model.predict(frame, imgsz640, confself.conf_thres, verboseFalse) boxes [] for r in results: for box in r.boxes: x1, y1, x2, y2 box.xyxy[0].tolist() cls int(box.cls[0]) conf float(box.conf[0]) boxes.append((x1, y1, x2, y2, cls, conf)) self.frame_ready.emit(frame, boxes) cap.release() def stop(self): self.running False self.wait()核心要点是model.predict里必须加verboseFalse否则模型每一帧都在终端刷日志积累多了程序会被 IO 拖慢。另一个是信号里传frame和boxes主线程只负责绘制不要在 worker 里直接做绘制否则 OpenCV 的图像对象跨线程操作容易崩。还有一点model.predict默认会做多尺度推理在实时场景建议显式传入imgsz640不要依赖默认值不同版本的 ultralytics 默认行为不完全一致。在界面上绘制检测框可以用QPainter把cv2的 BGR 帧转成QImage后按比例缩放绘制。因为检测框坐标是基于原始帧分辨率的绘制前要先做坐标变换换算到当前控件显示分辨率。这里容易踩一个坑视频源是 1920x1080界面预览区只有 800x450直接画框会偏移一位先算出缩放系数scale_x widget_width / frame_width再把框坐标乘上这个系数。4.3 报警判定与记录落库截图和日志一个都不能少报警触发的实现逻辑我建议维护一个字典last_hits键是类别 id值是持续命中帧数。每帧推理后遍历检测框如果有目标置信度超过阈值就把对应类别的计数加 1否则减 1 或清零。计数达到 5 就触发报警。报警后自动把当前帧保存成 jpg文件名带上时间戳和类别方便复现故障现场。历史记录表写入 sqlite 或直接写 CSV毕设场景写 CSV 就够展示时还能用表格控件加载。from datetime import datetime import csv hit_counter {} ALARM_FRAMES 5 def update_alarm_state(boxes, current_frame): now_hits set() for x1, y1, x2, y2, cls, conf in boxes: if conf 0.45: now_hits.add(cls) for cls_id in now_hits: hit_counter[cls_id] hit_counter.get(cls_id, 0) 1 for cls_id in list(hit_counter.keys()): if cls_id not in now_hits: hit_counter[cls_id] max(0, hit_counter[cls_id] - 1) alarms [] for cls_id, cnt in hit_counter.items(): if cnt ALARM_FRAMES: alarms.append(cls_id) # 触发报警后可选重置计数避免重复报警刷屏 hit_counter[cls_id] 0 with open(alarm_log.csv, a, newline) as f: writer csv.writer(f) writer.writerow([datetime.now().isoformat(), cls_id, current_frame]) # 存截图cv2.imwrite(frecord/{datetime.now():%Y%m%d%H%M%S}_{cls_id}.jpg, current_frame) return alarms这个代码里hit_counter的衰减逻辑是“没检测到就减 1 而不是直接清零”实际运行时会更稳定。如果直接清零在扶梯震动导致目标短暂从画面丢失的情况下一帧就会打断计数报警永远触发不了。截图操作在报警触发后只做一次不要在每一帧都写盘否则磁盘 IO 会让界面卡顿。5. 部署避坑与常见问题排查从本地 GPU 到边缘盒子的 5 个血泪经验5.1 现象模型在 GPU 上跑得很好部署到 rk3588 上 FPS 暴跌原因大概率是你直接把.pt文件拷到板上用torch推理而没有走export onnx - rknn的转换链路。YOLOv8 的 PyTorch 模型在 CPU 上跑一张 640x640 的图耗时要 200 到 400 毫秒rk3588 的 NPU 如果正确加载 RKNN 模型单帧可以压到 20 到 40 毫秒。解决方法是先在 PC 上用yolo export modelyolov8s.onnx formatonnx opset12导出 ONNX再在 PC 上装了 rknn-toolkit2 后用脚本转成 rknn。转换时要注意模型的输出维度YOLOv8 的 head 输出是 1 个 tensor 还是 3 个 tensor不同版本导出结果不一样rknn 转换脚本要匹配。5.2 现象报警系统一到中午就疯狂误报原因是扶梯口日照变化导致地面影子强烈模型把影子里的梳齿纹理识别成了异物。解决思路不是猛调置信度阈值而是做“区域屏蔽”用多边形或矩形把感兴趣区域划在梳齿板正上方一条检测框中心点落在区域外直接忽略。置信度阈值调高会让真实小目标也漏报区域屏蔽只过滤背景干扰不影响检测能力。在界面里加一个“区域编辑”功能用鼠标画一个 ROI 矩形报警判定前先做坐标落在区域内的判断。5.3 现象训练完的模型对视频流检测正常但连续跑几个小时后显存溢出原因是视频推理代码里results对象没有释放或者每次predict都会重新创建一次前向计算图。YOLOv8 的 predict 默认封装好了资源管理但如果你在循环里反复创建YOLO(model_path)实例每个实例都会加载权重、申请显存跑上几千帧必然崩。正确做法是整个进程只创建一个YOLO实例循环体内只调用 predict。另外在推理时加上halfTrue可以让 FP16 推理显存占用直接减半对显存小的卡非常管用。5.4 现象换了一台电脑后原先的权重文件加载报错或者结果变了原因大概率是 ultralytics 库版本不一致。YOLOv8 不同小版本的权重文件头部信息有差异尤其时 .pt 里存储的 yaml 结构和新老版本不完全兼容。解决办法是项目里固定依赖版本pip install ultralytics8.1.x并把版本号写进部署文档。还有一个习惯权重文件发布时建议同时导出导出 ONNX 一份ONNX 是跨框架的就算 PyTorch 环境崩了用 onnxruntime 也能跑推理只是精度可能有细微差异。5.5 现象完整数据集里有大量图片但训练曲线一直在低位震荡原因是数据集里混了很多没有目标的负样本图片。负样本要在 val 和 train 里都保留一部分但比例不要超过 30%。如果负样本太多模型会看到大量“没有目标”的背景区域bbox loss 和 cls loss 会始终混杂背景梯度收敛变慢。我的建议是训练前用一个小脚本统计每张图片的标注框数量对全是 0 的图片单独抽出来按 20% 比例混入训练集验证集里保留 10% 左右就够了。6. 把原型推向可用的最后一公里帧率压测与置信度校准模型训练完、界面也接好了距离真正能交给物业试用还有一步做一次完整的在线压测。我一般会在实际扶梯口架一台摄像头录制两小时的视频然后离线跑一遍完整的检测加报警流程统计三件事平均推理帧率、误报次数、漏报次数。把视频用一个脚本逐帧喂给 DetectWorker 的逻辑自动记录每一帧的耗时和报警列表。关于置信度阈值我习惯用“网格搜索”定而不是凭感觉填 0.25 或 0.5。取 val 集里所有检测框的 conf 值画一条 PR 曲线找到误报和漏报的交叉点。常见做法是运行yolo val后查看生成的PR_curve.png那个分数曲线会给出每个置信度下的精确率和召回率。对梳齿板场景我最终阈值常常落在 0.35 到 0.45 之间因为这个区间能让优先级最高的长条类召回率保持在 90% 以上而颗粒类虽然召回率低一些但可以用区域屏蔽来抑制误报。视觉界面里还有一个容易被忽视的细节报警提示的颜色和响度分级。长条类卡滞是高风险我在界面里用红色边框加蜂鸣器提示颗粒类属于低风险用黄色边框加日志记录就好。这样现场维保人员看到红色就会立刻行动看到黄色可以等巡检时处理不会产生“狼来了”式的报警疲劳。最后提一个我吃过亏的习惯所有核心配置模型路径、视频源、置信度阈值、ROI 区域、报警帧数全部写进一个config.yaml文件不要硬编码在代码里。调试时调阈值是高频操作每改一次就改代码再重启进程太浪费时间。把配置外置后现场人员也可以自己调不用动代码。希望这些踩坑记录能帮你在做自己的扶梯异物预警系统时少走几段弯路这套方向的可行性已经验证过了剩下的就是把细节打磨到能 7x24 小时稳定运行。本文还有配套的精品资源点击获取
返回列表