资讯动态

抽烟检测数据集清洗与YOLOv8训练全流程

发布时间:2026/9/15 5:11:33 来源:尧图企业网站定制
简介本资源是一份面向计算机视觉初学者与算法工程师的抽烟行为检测专用数据集适用于YOLO、Faster R-CNN等目标检测模型的训练与验证特别适配VOC与YOLO双格式开发需求。数据集共22559张高质量JPEG图像全部配有精准标注每图对应1个Pascal VOC格式XML文件共1999个含边界框坐标与类别标签及1个YOLO格式TXT文件共22559个已按标准格式生成总包大小802.04MB压缩为7z格式便于高效传输。已有418人学习下载表明其在烟雾识别、公共安全监控等实际场景中具备较强实践参考价值。用户可直接加载训练无需额外格式转换标注严格遵循labelImg工具规范覆盖“cig-pack”香烟盒与“smoke”烟雾两类关键目标总标注框数28472个其中烟雾实例占比超90%贴合真实检测难点适合开展细粒度分析与模型泛化能力评估。1. 抽烟检测数据集不是“拿来就能训”的资源而是需要验证格式、校验标签、适配训练框架的生产级输入你下载了一个标着“VOCYOLO格式、22559张、2类别”的抽烟检测数据集压缩包解压后发现文件结构混乱、部分XML报错、YOLO的txt标注里坐标越界——这不是数据集质量问题而是VOC与YOLO双格式共存场景下的典型交付断层。这个数据集真正价值不在于图片数量而在于它覆盖了真实监控视角下香烟手持、嘴部含烟、打火机联动等细粒度动作组合适合训练轻量级模型部署到边缘摄像头。但前提是你得先确认VOC的Annotations/和YOLO的labels/是否严格一一对应JPEGImages/里的文件名是否全为.jpg而非.jpeg或大小写混用两类格式的类别名是否统一为smoke和cigarette而非smoking/smoker等歧义词。本文聚焦于从7z解压开始到生成可直接喂入YOLOv8/v5训练管道的clean dataset全过程每一步都附带Linux命令验证、错误日志示例和修复脚本不依赖CVAT或LabelImg等GUI工具。2. 解压7z并校验VOC/YOLO双格式一致性用bash脚本自动比对文件名、类别与坐标合法性2.1 Linux下安全解压7z并规避中文路径乱码风险7z压缩包在Linux中默认不支持UTF-8文件名解压直接7z x可能导致JPEGImages/内文件名损坏如张三_20230501_001.jpg变成±_20230501_001.jpg进而引发后续读取失败。必须显式指定编码# 安装p7zip-fullUbuntu/Debian或p7zipCentOS sudo apt update sudo apt install -y p7zip-full # 使用UTF-8编码解压-o指定输出目录-y跳过确认 7z x 抽烟检测数据集VOCYOLO格式22559张2类别.7z -o./smoke_dataset -y -mmton -scsUTF-8提示-scsUTF-8是关键参数缺失会导致文件名乱码-mmton启用多线程加速解压若报错Cant allocate required memory!加-v1g限制单块内存使用量。解压后进入目录标准结构应为smoke_dataset/ ├── JPEGImages/ # 所有图像扩展名必须统一为.jpg ├── Annotations/ # VOC格式XML与JPEGImages同名不含扩展名 ├── labels/ # YOLO格式txt与JPEGImages同名不含扩展名 └── ImageSets/Main/ # train/val/test划分文件若存在2.2 用shell脚本批量校验三类核心一致性问题创建validate_dataset.sh一次性检查① JPG文件数是否等于XML数且等于txt数② 所有XML和txt是否与JPG同名③ YOLO txt中坐标是否在[0,1)区间内。#!/bin/bash DATASET_DIR./smoke_dataset cd $DATASET_DIR || exit 1 # 统计三类文件基础数量 JPG_COUNT$(find JPEGImages -name *.jpg | wc -l) XML_COUNT$(find Annotations -name *.xml | wc -l) TXT_COUNT$(find labels -name *.txt | wc -l) echo JPG: $JPG_COUNT, XML: $XML_COUNT, TXT: $TXT_COUNT if [ $JPG_COUNT -ne $XML_COUNT ] || [ $JPG_COUNT -ne $TXT_COUNT ]; then echo ❌ 文件数量不一致检查是否有遗漏或多余文件 exit 1 fi # 提取所有JPG文件名无扩展名 JPG_BASENAMES$(find JPEGImages -name *.jpg | sed s/\.jpg$// | sed s/JPEGImages\/// | sort) XML_BASENAMES$(find Annotations -name *.xml | sed s/\.xml$// | sed s/Annotations\/// | sort) TXT_BASENAMES$(find labels -name *.txt | sed s/\.txt$// | sed s/labels\/// | sort) # 比对文件名集合 if ! diff (echo $JPG_BASENAMES) (echo $XML_BASENAMES) /dev/null; then echo ❌ XML文件名与JPG不匹配差异如下 diff (echo $JPG_BASENAMES) (echo $XML_BASENAMES) | grep ^[] fi if ! diff (echo $JPG_BASENAMES) (echo $TXT_BASENAMES) /dev/null; then echo ❌ TXT文件名与JPG不匹配差异如下 diff (echo $JPG_BASENAMES) (echo $TXT_BASENAMES) | grep ^[] fi # 检查YOLO坐标合法性x,y,w,h是否全在[0,1) INVALID_COORDS0 while IFS read -r txt_file; do if [ -s $txt_file ]; then # 提取每行4个浮点数检查是否超出范围 awk -F { if ($2 0 || $2 1 || $3 0 || $3 1 || $4 0 || $4 1 || $5 0 || $5 1) { print 坐标越界: FILENAME , line NR : $0 exit 1 } } $txt_file 2/dev/null || INVALID_COORDS$((INVALID_COORDS 1)) fi done (find labels -name *.txt) if [ $INVALID_COORDS -gt 0 ]; then echo ❌ 发现$INVALID_COORDS个txt文件含非法坐标请检查标注工具导出设置 exit 1 fi echo ✅ 基础校验通过文件数量一致、命名匹配、坐标合法运行该脚本后若输出✅ 基础校验通过说明数据集已具备训练前提若失败脚本会明确指出是哪类问题如XML文件名不匹配便于针对性修复。2.3 修复常见VOC转YOLO不一致问题类别ID映射与坐标归一化重算即使标注工具声称“导出YOLO格式”仍常出现两类错误① VOC XML中name为cigarette但YOLO txt中类别ID写成1而实际classes.txt定义顺序是smoke→0、cigarette→1此时需确保ID严格对齐② XML中bndbox坐标未按图像宽高归一化导致YOLO txt中数值1。修复脚本fix_yolo_labels.pyPython 3.8import os import xml.etree.ElementTree as ET from pathlib import Path # 定义类别映射VOC name → YOLO ID CLASS_MAP {smoke: 0, cigarette: 1} # 必须与你的classes.txt完全一致 def convert_voc_to_yolo(xml_path: str, img_width: int, img_height: int, output_dir: str): tree ET.parse(xml_path) root tree.getroot() # 获取图像尺寸若XML中无size用传入参数 size root.find(size) if size is not None: img_width int(size.find(width).text) img_height int(size.find(height).text) yolo_lines [] for obj in root.findall(object): name obj.find(name).text.strip() if name not in CLASS_MAP: print(f⚠️ 跳过未知类别 {name} in {xml_path}) continue cls_id CLASS_MAP[name] bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 归一化中心点x,y 宽高w,h x_center (xmin xmax) / 2.0 / img_width y_center (ymin ymax) / 2.0 / img_height width (xmax - xmin) / img_width height (ymax - ymin) / img_height # 确保在[0,1)区间防浮点误差 x_center max(0.0, min(0.99999, x_center)) y_center max(0.0, min(0.99999, y_center)) width max(0.001, min(0.99999, width)) # 宽高至少0.001像素 height max(0.001, min(0.99999, height)) yolo_lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) # 写入YOLO txt base_name Path(xml_path).stem txt_path os.path.join(output_dir, f{base_name}.txt) with open(txt_path, w) as f: f.write(\n.join(yolo_lines)) # 批量处理 ANNOTATIONS_DIR ./smoke_dataset/Annotations LABELS_DIR ./smoke_dataset/labels_fixed # 输出到新目录避免覆盖原数据 os.makedirs(LABELS_DIR, exist_okTrue) for xml_file in Path(ANNOTATIONS_DIR).glob(*.xml): # 假设图像宽高为1920x1080根据你实际数据调整或从XML读取 convert_voc_to_yolo(str(xml_file), 1920, 1080, LABELS_DIR) print(f✅ 已生成{len(list(Path(LABELS_DIR).glob(*.txt)))}个修正后的YOLO标签)参数说明img_width/img_height需根据你数据集中图像的实际分辨率设置可用identify -format %wx%h\n JPEGImages/*.jpg | head -5快速抽样查看CLASS_MAP必须与训练时data.yaml中的names顺序严格一致否则模型会学错类别。3. 构建YOLOv8可直训的数据集结构按比例划分生成data.yaml验证标签可视化3.1 用Python脚本按7:2:1比例划分train/val/test并保持类别分布均衡YOLOv8要求数据集目录结构为dataset/ ├── train/ │ ├── images/ │ └── labels/ ├── val/ │ ├── images/ │ └── labels/ └── test/ # 可选 ├── images/ └── labels/手动划分易导致类别倾斜如val集中无cigarette样本。以下脚本使用分层抽样stratified split确保每个子集smoke与cigarette比例接近全局比例import os import shutil import random from collections import defaultdict from pathlib import Path def stratified_split(image_dir: str, label_dir: str, output_dir: str, train_ratio0.7, val_ratio0.2, test_ratio0.1, seed42): random.seed(seed) # 按类别统计所有样本 class_samples defaultdict(list) image_paths list(Path(image_dir).glob(*.jpg)) for img_path in image_paths: label_path Path(label_dir) / f{img_path.stem}.txt if not label_path.exists(): continue # 读取txt第一行类别IDYOLO格式首列为cls_id with open(label_path) as f: first_line f.readline().strip() if not first_line: continue cls_id int(first_line.split()[0]) class_samples[cls_id].append(img_path.stem) # 计算各子集应分配数量 total_samples sum(len(v) for v in class_samples.values()) train_size int(total_samples * train_ratio) val_size int(total_samples * val_ratio) test_size total_samples - train_size - val_size # 对每个类别独立抽样 train_list, val_list, test_list [], [], [] for cls_id, samples in class_samples.items(): random.shuffle(samples) n_train int(len(samples) * train_ratio) n_val int(len(samples) * val_ratio) train_list.extend(samples[:n_train]) val_list.extend(samples[n_train:n_trainn_val]) test_list.extend(samples[n_trainn_val:]) # 创建输出目录 for split in [train, val, test]: (Path(output_dir) / split / images).mkdir(parentsTrue, exist_okTrue) (Path(output_dir) / split / labels).mkdir(parentsTrue, exist_okTrue) # 复制文件 def copy_split(split_name, file_list): for stem in file_list: shutil.copy2(Path(image_dir) / f{stem}.jpg, Path(output_dir) / split_name / images / f{stem}.jpg) shutil.copy2(Path(label_dir) / f{stem}.txt, Path(output_dir) / split_name / labels / f{stem}.txt) copy_split(train, train_list) copy_split(val, val_list) copy_split(test, test_list) print(f✅ 划分完成train{len(train_list)}, val{len(val_list)}, test{len(test_list)}) # 执行划分使用上一步修复后的labels_fixed目录 stratified_split( image_dir./smoke_dataset/JPEGImages, label_dir./smoke_dataset/labels_fixed, output_dir./smoke_dataset/yolo_format, train_ratio0.7, val_ratio0.2, test_ratio0.1 )3.2 生成符合YOLOv8规范的data.yaml并验证路径有效性YOLOv8训练必需data.yaml其内容必须精确匹配你的目录结构和类别# ./smoke_dataset/yolo_format/data.yaml train: ../train/images val: ../val/images test: ../test/images # 若有test集则保留否则删除此行 nc: 2 # 类别数 names: [smoke, cigarette] # 顺序必须与CLASS_MAP一致关键验证步骤在终端执行# 检查路径是否存在且非空 ls -l ./smoke_dataset/yolo_format/train/images | head -3 ls -l ./smoke_dataset/yolo_format/val/labels | head -3 # 验证data.yaml语法用Python简单检查 python3 -c import yaml with open(./smoke_dataset/yolo_format/data.yaml) as f: d yaml.safe_load(f) assert d[nc] len(d[names]), nc与names长度不匹配 assert os.path.exists(./smoke_dataset/yolo_format d[train].replace(.., )), train路径不存在 print(✅ data.yaml格式与路径验证通过) 2/dev/null3.3 可视化YOLO标签验证用OpenCV绘制边界框并保存样例图防止标签虽格式正确但位置偏移。运行以下脚本生成vis_samples/目录内含带框图import cv2 import numpy as np from pathlib import Path def visualize_labels(image_dir: str, label_dir: str, output_dir: str, num_samples5): os.makedirs(output_dir, exist_okTrue) image_files list(Path(image_dir).glob(*.jpg))[:num_samples] for img_path in image_files: img cv2.imread(str(img_path)) h, w img.shape[:2] label_path Path(label_dir) / f{img_path.stem}.txt if not label_path.exists(): continue with open(label_path) as f: for line in f: parts line.strip().split() if len(parts) 5: continue cls_id, x_c, y_c, bw, bh map(float, parts[:5]) # 反归一化 x1 int((x_c - bw/2) * w) y1 int((y_c - bh/2) * h) x2 int((x_c bw/2) * w) y2 int((y_c bh/2) * h) # 绘制框smoke绿色cigarette红色 color (0, 255, 0) if int(cls_id) 0 else (0, 0, 255) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, f{[smoke,cigarette][int(cls_id)]}, (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) cv2.imwrite(f{output_dir}/{img_path.name}, img) print(f✅ 已生成{num_samples}张可视化样例图至 {output_dir}) visualize_labels( image_dir./smoke_dataset/yolo_format/train/images, label_dir./smoke_dataset/yolo_format/train/labels, output_dir./smoke_dataset/vis_samples )运行后打开vis_samples/内图片直观确认框是否准确覆盖香烟/吸烟动作区域。若发现大量偏移需回溯检查convert_voc_to_yolo中的图像尺寸参数。4. YOLOv8训练启动与关键参数调优针对抽烟检测场景的lr、batch、augmentation配置4.1 一行命令启动训练并理解每个参数的实际影响在./smoke_dataset/yolo_format目录下执行# 使用Ultralytics官方YOLOv8pip install ultralytics yolo detect train \ data./data.yaml \ modelyolov8n.pt \ # 轻量级模型适合边缘部署 epochs100 \ imgsz640 \ batch16 \ # 根据GPU显存调整24G卡可设3212G卡建议16 namesmoke_yolov8n_v1 \ project./runs \ device0 \ # 指定GPU编号多卡用0,1 workers8 \ # 数据加载进程数设为CPU核心数一半 patience10 \ # val loss连续10轮不下降则早停 lr00.01 \ # 初始学习率抽烟检测小目标多不宜过大 lrf0.01 \ # 最终学习率 lr0 * lrf即0.0001 hsv_h0.015 \ hsv_s0.7 \ hsv_v0.4 \ # 颜色扰动增强提升对不同光照下香烟的鲁棒性 degrees0 \ translate0.1 \ scale0.5 \ shear0 \ # 几何增强scale0.5允许缩放至原图50%适应远距离小目标 mosaic1.0 \ mixup0.1 \ # 马赛克增强强制模型学习局部特征mixup提升泛化 close_mosaic10 \ # 最后10轮关闭mosaic稳定收敛 save_period10 \ # 每10轮保存一次权重 cacheTrue # 将图像缓存到RAM加速训练需足够内存参数逻辑说明imgsz640抽烟检测中香烟目标常小于32x32像素640分辨率能保留足够细节若显存不足可降至320但需同步调小scale。batch16YOLOv8默认梯度累积步数为4实际等效batch64对小目标检测更稳定。hsv_s0.7大幅增强饱和度扰动因香烟在监控画面中常呈灰褐色提升对低饱和度目标的敏感度。close_mosaic10马赛克增强虽提升mAP但最后阶段关闭可避免伪影干扰最终精度。4.2 监控训练过程解析results.csv并定位过拟合信号训练完成后./runs/detect/smoke_yolov8n_v1/results.csv记录每轮指标。用以下命令快速分析# 提取关键列并查看最后10轮 tail -10 ./runs/detect/smoke_yolov8n_v1/results.csv | \ awk -F, {printf Epoch %s: mAP50%.3f, val_loss%.4f, train_loss%.4f\n, $1, $9, $12, $11} # 计算val_loss最低点早停触发轮次 awk -F, NR1 {if($12min||NR2){min$12; epoch$1}} END{print Best val_loss at epoch, epoch, , min} \ ./runs/detect/smoke_yolov8n_v1/results.csv过拟合判断信号train_loss持续下降但val_loss在某轮后开始上升如第75轮起val_loss连续3轮增加metrics/mAP50(B)达到峰值后回落而metrics/mAP50(M)中等目标仍在升——说明模型过度关注大目标忽略小香烟。此时应① 降低scale增强强度② 在data.yaml中添加rect: True启用矩形推理减少padding③ 加载best.pt后用yolo detect val重新评估各尺度AP。4.3 针对抽烟检测的3个必调后处理参数conf、iou、agnostic_nms训练后模型输出需经NMS非极大值抑制去重。默认参数对抽烟场景不友好参数默认值抽烟检测推荐值原因conf0.250.35过滤低置信度预测减少香烟与手指、打火机误检iou0.70.45香烟常密集出现如多人围坐高IOU导致漏检agnostic_nmsFalseTrue同一位置可能同时预测smoke和cigarette开启后跨类别NMS避免重复框验证命令替换best.pt为你训练好的权重yolo detect predict \ model./runs/detect/smoke_yolov8n_v1/weights/best.pt \ source./smoke_dataset/yolo_format/val/images \ conf0.35 \ iou0.45 \ agnostic_nmsTrue \ save_txtTrue \ save_confTrue \ project./predict_results \ namesmoke_inference_v1生成的./predict_results/smoke_inference_v1/labels/内txt文件每行末尾会追加置信度conf便于后续阈值筛选。5. 模型部署前的终极验证用真实监控视频抽帧测试计算FPS与漏检率5.1 从监控视频抽帧构建测试集并运行批量推理真实场景中抽烟行为持续时间短平均1.2秒需验证模型在视频流中的时序稳定性。抽取1000帧约42秒24fps# 从监控视频抽帧假设video.mp4为1080p H.264 ffmpeg -i video.mp4 -vf fps24,scale1280:720 -q:v 2 ./test_frames/frame_%06d.jpg # 运行批量推理输出JSON便于程序解析 yolo detect predict \ model./runs/detect/smoke_yolov8n_v1/weights/best.pt \ source./test_frames \ conf0.35 \ iou0.45 \ agnostic_nmsTrue \ save_jsonTrue \ project./video_test \ namevideo_inference输出./video_test/video_inference/predictions.json包含每帧的检测结果结构为[ { image: frame_000001.jpg, predictions: [ {class: smoke, confidence: 0.82, bbox: [x1,y1,x2,y2]}, {class: cigarette, confidence: 0.76, bbox: [x1,y1,x2,y2]} ] } ]5.2 计算FPS与漏检率编写Python脚本量化部署就绪度import json import time import cv2 from pathlib import Path def benchmark_inference(model_path: str, image_dir: str, sample_count100): from ultralytics import YOLO model YOLO(model_path) # 预热 dummy_img cv2.imread(str(Path(image_dir).glob(*.jpg).__next__())) _ model(dummy_img, verboseFalse) # 测速 start_time time.time() for i, img_path in enumerate(Path(image_dir).glob(*.jpg)): if i sample_count: break _ model(str(img_path), verboseFalse) end_time time.time() fps sample_count / (end_time - start_time) print(f✅ 推理速度{fps:.1f} FPS{sample_count}帧平均GPU: {model.device}) # 漏检率计算需人工标注100帧作为GT gt_path ./test_frames/gt_annotations.json # 格式同predictions.json if Path(gt_path).exists(): with open(gt_path) as f: gt_data json.load(f) with open(./video_test/video_inference/predictions.json) as f: pred_data json.load(f) # 简单匹配同一帧内pred bbox与GT bbox IOU0.5即为命中 total_gt sum(len(gt[predictions]) for gt in gt_data) hit_count 0 for gt, pred in zip(gt_data, pred_data): if gt[image] ! pred[image]: continue for gt_box in gt[predictions]: for pred_box in pred[predictions]: # 计算IOU此处省略具体实现实际需补全 iou 0.0 # placeholder if iou 0.5: hit_count 1 break miss_rate (total_gt - hit_count) / total_gt if total_gt 0 else 0 print(f✅ 漏检率{miss_rate*100:.1f}%基于{total_gt}个GT框) benchmark_inference( model_path./runs/detect/smoke_yolov8n_v1/weights/best.pt, image_dir./test_frames, sample_count100 )部署就绪判定FPS ≥ 20满足实时视频流漏检率 ≤ 8%行业对抽烟检测的容忍阈值conf0.35下cigarette类别的召回率Recall≥ 0.85因香烟是核心目标smoke可辅助验证。若未达标优先调整conf降则提召回、升则提精度和iou降则减漏检、升则减误检而非重新训练。5.3 导出ONNX模型并验证TensorRT兼容性可选高性能部署YOLOv8支持导出ONNX供TensorRT加速yolo export \ model./runs/detect/smoke_yolov8n_v1/weights/best.pt \ formatonnx \ dynamicTrue \ simplifyTrue \ opset12 \ imgsz640生成best.onnx后用trtexec --onnxbest.onnx --shapesinput:1x3x640x640 --fp16验证能否成功构建TensorRT引擎。若报错Unsupported ONNX data type需将opset降为11或升级TensorRT版本。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价