ARTICLE DETAIL

资讯详情

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

吸烟检测数据集与YOLO实战:VOC转YOLO、训练避坑与ONNX部署

吸烟检测数据集与YOLO实战:VOC转YOLO、训练避坑与ONNX部署 简介本资源是面向计算机视觉初学者与目标检测实践者的吸烟行为识别专用数据集适用于安全监控、公共健康监管及AI伦理审查等实际场景的模型训练与算法验证。数据集共2000余张真实生活与影视画面中人物吸烟的自然场景图像配套2000个PASCAL VOC格式XML标注文件完整提供边界框左上角与右下角坐标信息开箱即用无需额外清洗或格式转换。压缩包为ZIP格式总大小198.63MB全部文件均为图像与对应XML标签对结构规整、命名统一便于直接接入YOLO、Faster R-CNN等主流检测框架进行数据加载与训练。目前已有689人学习下载资源由实战经验丰富的开发者整理发布标注质量稳定覆盖多角度、多光照、遮挡及不同姿态下的吸烟动作可有效支撑模型泛化能力提升与小样本优化实验。1. 吸烟人群检测数据集2000张真实场景图XML标注开箱即用YOLO训练不洗图、不合成、不翻车你有没有试过在安防项目里训一个“抽烟检测”模型结果拿影视截图一跑mAP直接掉到30%不是模型不行是数据太假——合成烟雾遮挡不自然、手部姿态千篇一律、背景全是绿幕抠图。这个吸烟人群检测数据集就是专治这种玄学翻车的2000张来自生活实拍与影视剧自然片段的高清图像每张都配一个同名XML文件严格按PASCAL VOC标准存着左上角(xmin,ymin)和右下角(xmax,ymax)坐标不经任何增强、不重采样、不人工补标原始分辨率从640×480到1920×1080全覆盖。它不是为刷榜设计的玩具数据集而是给一线安防、工厂巡检、医院禁烟监控这类真实落地场景准备的“脏数据”——有逆光、有遮挡、有侧脸、有手拿烟但没点着的模糊样本。如果你正卡在YOLOv8/v11训练时验证集loss震荡、或者被客户指着监控画面问“为什么这人明明在抽烟却没框出来”那这份数据集就是你该立刻拉进train/images目录里的后悔药。适合已经跑通YOLO基础流程、但缺真实负样本和复杂正样本的中级工程师新手建议先用COCO练手再切入。2. 数据结构解析与VOC→YOLO格式转换把2000个XML转成labels/下txt三步到位不丢框这个数据集采用经典PASCAL VOC结构组织但YOLO系列包括Ultralytics最新yolov11默认只认images/labels/train.txt/val.txt三件套。直接扔进去会报错No labels found——不是没标签是格式不对。下面拆解真实操作链从原始目录结构出发到生成可喂给ultralytics train命令的干净输入。2.1 原始目录结构与关键约束条件下载解压后你会看到类似这样的树形smoking_dataset/ ├── JPEGImages/ # 所有.jpg图片命名如001984.jpg ├── Annotations/ # 所有.xml标注命名严格对应001984.xml ├── ImageSets/ # 空文件夹需手动划分 └── README.md注意XML文件中object节点内必须含namesmoking/name且bndbox下四个子节点必须全存在xmin/ymin/xmax/ymax缺一不可。我实测发现37个XML里name写成了cigarette或smoke这些必须统一修正否则YOLO训练时会静默跳过整张图——这是第一个血泪坑后面避坑章细说。2.2 XML解析核心逻辑用xml.etree.ElementTree精准提取坐标不用装OpenCV或LabelImgPython标准库xml.etree.ElementTree足矣。重点不是读XML而是校验坐标合法性YOLO要求所有坐标归一化到[0,1]区间且xminxmax、yminymax否则训练时loss爆炸。以下脚本自动完成读取→校验→归一化→写入txt# convert_voc_to_yolo.py import os import xml.etree.ElementTree as ET from pathlib import Path def convert_annotation(xml_path: str, img_path: str, output_dir: str): tree ET.parse(xml_path) root tree.getroot() # 获取图片尺寸必须YOLO归一化依赖此 size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) # 输出txt路径与jpg同名放labels/下 txt_name Path(xml_path).stem .txt txt_path os.path.join(output_dir, txt_name) with open(txt_path, w) as f: for obj in root.findall(object): # 强制校验类别名关键 cls_name obj.find(name).text.strip().lower() if cls_name not in [smoking, smoke, cigarette]: continue # 跳过非目标类避免污染 if cls_name in [smoke, cigarette]: cls_id 0 # 统一映射为smoking类 else: cls_id 0 bbox obj.find(bndbox) xmin int(bbox.find(xmin).text) ymin int(bbox.find(ymin).text) xmax int(bbox.find(xmax).text) ymax int(bbox.find(ymax).text) # 坐标合法性兜底防XML手误 if xmin xmax or ymin ymax or xmin 0 or ymin 0: print(fWarning: invalid bbox in {xml_path}, skip object) continue # 归一化并转YOLO格式cls_id x_center y_center width height x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h width (xmax - xmin) / img_w height (ymax - ymin) / img_h f.write(f{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\n) # 批量执行 xml_dir Annotations/ img_dir JPEGImages/ labels_dir labels/ os.makedirs(labels_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if not xml_file.endswith(.xml): continue xml_path os.path.join(xml_dir, xml_file) img_path os.path.join(img_dir, xml_file.replace(.xml, .jpg)) if not os.path.exists(img_path): print(fMissing image for {xml_file}) continue convert_annotation(xml_path, img_path, labels_dir)参数说明cls_id0单类别检测固定为0YOLO要求从0开始编号x_center/y_center中心点归一化值保留6位小数Ultralytics官方推荐精度width/height宽高归一化值必须0且1否则YOLO加载时报Invalid label format脚本自动跳过缺失图片、非法坐标、错误类别名比暴力for循环更鲁棒2.3 划分train/val/test并生成路径列表YOLO训练需要train.txt明确列出所有训练图片绝对路径。不能用相对路径否则Ultralytics会报Image not found。以下命令生成符合规范的列表假设你用Linux/macOSWindows用户将find换成PowerShellGet-ChildItem# 进入smoking_dataset根目录 cd smoking_dataset # 创建ImageSets目录YOLO不强制要但方便管理 mkdir -p ImageSets/Main # 用find按文件名排序取前1600个为train80%后400个为val find JPEGImages -name *.jpg | sort | head -n 1600 ImageSets/Main/train.txt find JPEGImages -name *.jpg | sort | tail -n 400 ImageSets/Main/val.txt # 验证数量应输出1600和400 wc -l ImageSets/Main/train.txt ImageSets/Main/val.txt关键细节sort确保每次划分顺序一致避免因文件系统差异导致train/val混杂不建议用random.sample——真实项目需复现性客户验收时要能回溯同一份划分train.txt里每行必须是完整路径如/home/user/smoking_dataset/JPEGImages/001984.jpg不是JPEGImages/001984.jpg3. YOLOv8/v11训练配置详解从data.yaml到超参调优避开学习率陷阱数据准备好后下一步是让Ultralytics识别它。很多人卡在data.yaml写错或--batch设太大导致OOM这里给出经2000图实测的最小可行配置。3.1 data.yaml编写路径、类别、验证逻辑全解析在smoking_dataset/同级新建smoking_data.yaml内容如下# smoking_data.yaml train: /path/to/smoking_dataset/JPEGImages # 注意必须是绝对路径 val: /path/to/smoking_dataset/JPEGImages # 同上YOLOv8支持val用同目录 # 如果你已生成train.txt/val.txt也可用 # train: /path/to/smoking_dataset/ImageSets/Main/train.txt # val: /path/to/smoking_dataset/ImageSets/Main/val.txt nc: 1 # 类别数吸烟检测只有1类 names: [smoking] # 类别名必须与XML中修正后的name一致为什么val路径和train一样因为YOLOv8/v11在读取train.txt时会自动按行加载图片并从同名labels/xxx.txt读标签。val路径只是告诉YOLO去哪里找图片文件实际用哪几张由val.txt控制。若你没生成val.txtYOLO会默认用train.txt里80%的图做train、20%做val——但这样无法保证验证集稳定强烈建议手动生成val.txt。3.2 训练命令与关键参数含义不要直接跑yolo train先确认环境# 确保Ultralytics版本≥8.2.0v11需≥8.3.0 pip install ultralytics --upgrade # 检查CUDA是否可用重要 yolo taskdetect modetrain modelyolov8n.pt datasmoking_data.yaml epochs100 batch16 imgsz640参数逐条解释modelyolov8n.ptnano版轻量模型2000图足够显存占用3GBRTX3060实测batch162000图用16批大小每轮迭代125步梯度更稳若显存不足降到8但需同步调小lr0imgsz640输入尺寸不建议用1280——小目标手指夹烟在大图上反而难收敛640平衡细节与速度epochs1002000图无需300轮100轮后val_map_0.5通常达0.72再训易过拟合3.3 学习率策略为什么默认lr00.01会崩怎么调Ultralytics默认lr00.01对小数据集是灾难。我用相同配置训了3次lr00.01第12轮loss突增至12.5之后持续震荡val_map停在0.41lr00.001平稳下降但收敛慢100轮后map0.68lr00.005cosine衰减最优85轮达peak map0.732正确做法在命令中加入学习率调度yolo taskdetect modetrain modelyolov8n.pt datasmoking_data.yaml \ epochs100 batch16 imgsz640 lr00.005 lrf0.01 optimizerauto \ cos_lr # 启用余弦退火比step更平滑lrf0.01表示最终学习率lr0×lrf5e-5避免后期更新幅度过大抖动4. 避坑指南2000图训练中踩过的5个真实坑附现象、原因与秒解方案训练不是一键run完就完事。这5个坑是我用该数据集在3台不同配置机器RTX3060/4090/A100上反复验证出的高频问题每个都附带print()级定位方法。4.1 现象训练启动后立即报错KeyError: smoking原因data.yaml中names: [smoking]与XML里name值不一致。原始数据中有37个XML写的是namecigarette/name而YOLO严格匹配字符串。解决运行前执行批量修正脚本sed -i s/namecigarette\/name/namesmoking\/name/g Annotations/*.xml sed -i s/namesmoke\/name/namesmoking\/name/g Annotations/*.xml提示sed -i在macOS需加空格sed -i 否则破坏文件4.2 现象val_map_0.5始终为0.0但train_loss正常下降原因labels/下某张图的txt为空如002134.txtYOLO默认跳过该图验证但若大量为空val集失效。解决检查空txt文件find labels/ -name *.txt -size 0c | wc -l # 应为0 # 若0删掉并重新运行转换脚本脚本里有continue逻辑 rm $(find labels/ -name *.txt -size 0c)4.3 现象训练到50轮后loss突然飙升10倍随后震荡原因batch16在部分显卡如A100上触发梯度溢出尤其当某张图含极小烟头目标10像素时。解决启用梯度裁剪在训练命令加参数yolo ... ampFalse grad_clip_norm3.0ampFalse关自动混合精度grad_clip_norm3.0限制梯度L2范数实测消除90%突增4.4 现象推理时大量漏检“侧脸抽烟”或“手部遮挡”样本原因YOLO默认NMS阈值conf0.25太保守侧脸样本置信度常在0.18~0.22之间被滤掉。解决推理时不改模型只调后处理from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) results model(test.jpg, conf0.15, iou0.45) # conf下探到0.15conf0.15提升召回iou0.45防重复框原0.7太严4.5 现象验证集mAP高于训练集如val0.75, train0.62原因数据泄露——train.txt里混入了本该在val.txt中的图常见于手动复制粘贴失误。解决用集合差集强制校验train_set set(open(ImageSets/Main/train.txt).read().splitlines()) val_set set(open(ImageSets/Main/val.txt).read().splitlines()) print(Leak count:, len(train_set val_set)) # 必须为05. 推理优化实战用ONNX加速OpenCV后处理实测FPS从23→89训完模型只是开始真正在边缘设备如Jetson Orin部署时PyTorch原生推理太慢。下面这套组合拳让吸烟检测在1080p视频流上跑出89FPSOrin NX实测且保持mAP不降。5.1 导出ONNX并验证等效性Ultralytics导出ONNX后需手动验证输出是否一致否则部署后结果错乱# 导出指定动态batch和input shape yolo export modelruns/detect/train/weights/best.pt formatonnx \ dynamicTrue imgsz640 opset12 # 验证用同一张图对比PyTorch与ONNX输出 import torch import onnxruntime as ort import numpy as np # PyTorch推理 model_pt YOLO(runs/detect/train/weights/best.pt) im cv2.imread(test.jpg) pt_result model_pt(im, verboseFalse)[0].boxes.data.cpu().numpy() # [x1,y1,x2,y2,conf,cls] # ONNX推理 ort_session ort.InferenceSession(best.onnx) im_rgb cv2.cvtColor(im, cv2.COLOR_BGR2RGB) im_resized cv2.resize(im_rgb, (640,640)) im_norm im_resized.astype(np.float32) / 255.0 im_tensor np.transpose(im_norm, (2,0,1))[None] # [1,3,640,640] onnx_result ort_session.run(None, {images: im_tensor})[0] # [1,84,8400] # 转YOLO格式需自己实现NMS或用ONNX自带后处理 # 关键验证点top5置信度误差1e-3 print(Max diff:, np.max(np.abs(pt_result[:5,4] - onnx_result[0,:5,4])))注意ONNX默认输出是[1,84,8400]8441nc需用non_max_suppression后处理Ultralytics的ops.py里有现成函数直接复用5.2 OpenCV DNN加速推理比onnxruntime快1.7倍在Jetson上cv2.dnn.readNetFromONNX比onnxruntime快且内存占用低import cv2 import numpy as np net cv2.dnn.readNetFromONNX(best.onnx) cap cv2.VideoCapture(test.mp4) while cap.isOpened(): ret, frame cap.read() if not ret: break # 预处理OpenCV专用 blob cv2.dnn.blobFromImage(frame, 1/255.0, (640,640), swapRBTrue, cropFalse) net.setInput(blob) outputs net.forward(net.getUnconnectedOutLayersNames()) # 输出list of arrays # 解析outputs[0]为[x1,y1,x2,y2,conf,cls]此处省略NMS代码同上 # ... # 绘制结果用cv2.putText比plt快10倍 for box in boxes: x1,y1,x2,y2 map(int, box[:4]) cv2.rectangle(frame, (x1,y1), (x2,y2), (0,255,0), 2) cv2.putText(frame, fsmoking {box[4]:.2f}, (x1,y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0,255,0), 2) cv2.imshow(Smoking Detect, frame) if cv2.waitKey(1) ord(q): break实测性能Orin NX 16GB方案输入尺寸FPS显存占用PyTorch CPU640×6403.2—PyTorch GPU640×64023.12.1GBONNX ORT640×64041.71.8GBONNX OpenCV DNN640×64089.31.2GB5.3 部署级后处理过滤“疑似抽烟”误报的3条硬规则即使mAP0.73真实场景仍有误报。我在工厂摄像头实测发现82%误报源于香烟盒反光误判为点燃烟头手指夹笔/筷子形态近似远处模糊人影YOLO置信度0.15但实际非抽烟加入以下规则后误报率↓67%F1从0.65→0.78def post_filter(boxes, frame): h, w frame.shape[:2] filtered [] for box in boxes: x1,y1,x2,y2,conf,cls box area (x2-x1) * (y2-y1) aspect_ratio (x2-x1) / max(y2-y1, 1) # 规则1面积太小300像素且长宽比5 → 可能是反光条 if area 300 and aspect_ratio 5: continue # 规则2框在图像顶部1/3且宽高×2 → 可能是横握笔 if y1 h/3 and (x2-x1) 2*(y2-y1): continue # 规则3框离图像边缘20像素 → 模糊伪影 if x1 20 or y1 20 or (w-x2) 20 or (h-y2) 20: continue filtered.append(box) return np.array(filtered) # 在推理循环中调用 boxes non_max_suppression(outputs[0], conf_thres0.15) final_boxes post_filter(boxes, frame)从那以后我每次交付吸烟检测模块都强制在客户现场用10段真实监控视频跑一遍这三条规则再调整阈值——不是为了炫技是避免凌晨三点被电话叫醒修bug。希望帮到你。本文还有配套的精品资源点击获取
返回列表