☰
抽烟检测数据集实战:从YOLO标注到训练全流程拆解
2026/10/20 7:23:34 网站建设 项目流程

简介:这份抽烟检测数据集面向从事深度学习与计算机视觉的开发者、算法工程师及高校学生,适用于目标检测模型的训练、微调与验证,可支撑公共场所吸烟行为识别、安防监控等场景。资源包共约2000个文件,以4650张jpg图像、3235个xml标注文件为主,另含少量txt划分列表、Python脚本与说明文档,压缩包约298MB。其中JPEGImages目录收录3000余张抽烟场景图片,Annotations目录提供对应的labelImg标注xml,可直接用于VOC格式目标检测训练,并附有voc2yolo4.py等脚本便于格式转换与数据划分。目前已有7530人学习下载,适合需要快速搭建抽烟检测基线、验证模型效果或进行数据增强实验的读者参考使用。

1. 抽烟检测数据集:从标注到训练,一份能直接跑通的资源拆解

做视觉检测项目的朋友多半有过这种经历:模型结构调了半天,loss 曲线看着也还行,一到实际场景就翻车,回头一查,问题根本不在网络,而在数据。抽烟检测这个方向尤其典型——目标小、遮挡多、姿态杂,还夹着手部与嘴部的重叠区域,标注质量差一点,mAP 直接掉一截。这次拆的这份抽烟检测数据集,包含标注文件和对应图片,属于那种拿到手就能直接进训练流程的资源。它适合两类人:一类是想快速验证检测方案、不想从零爬数据标数据的算法工程师;另一类是拿它做课程设计或原型 Demo 的学生和开发者。下面按「这是什么 → 怎么用 → 坑在哪」的顺序,把这份资源从头到尾过一遍。

2. 数据集结构与标注格式:先看清手里有什么

2.1 目录组织与文件构成

拿到一份检测数据集,第一件事不是急着写训练脚本,而是把目录结构摸清楚。常见的组织方式有两种:一种是图片和标注分开放,images/放图,labels/放同名 txt;另一种是图片和标注混在同一目录,靠扩展名区分。这份资源属于前者,结构大致如下:

smoke_dataset/ ├── images/ │ ├── 000001.jpg │ ├── 000002.jpg │ └── ... ├── labels/ │ ├── 000001.txt │ ├── 000002.txt │ └── ... └── classes.txt

classes.txt里通常只有一行smoke或者smoking,代表单类别。如果你的任务需要区分「正在抽」和「手持烟」两种状态,那这份单类别标注就不够用,得自己补标。这一点在选型阶段就要想清楚,别等训练完了才发现类别定义和业务对不上。

图片格式以 jpg 为主,分辨率不统一是常态,有的来自监控截图,有的来自手机拍摄,长宽比差异大。标注文件是 YOLO 格式的 txt,每行五个值:class_id x_center y_center width height,后四个都是归一化到 0~1 的相对坐标。这个格式的好处是跟图片分辨率解耦,换尺寸不用重标。

2.2 YOLO 标注格式的字段含义

把一行标注拆开看:

0 0.512 0.634 0.087 0.156
  • 第一个0是类别索引,对应classes.txt里的第 0 类;
  • 0.512是目标框中心点的 x 坐标,除以图片宽度得到;
  • 0.634是中心点 y 坐标,除以图片高度;
  • 0.087是框宽,除以图片宽度;
  • 0.156是框高,除以图片高度。

这里有个容易忽略的点:归一化用的是各自轴向的尺寸,x 和宽除以图宽,y 和高除以图高。如果标注工具导出时搞混了,框会整体偏移,训练时表现为 loss 降不下去或者框乱飞。验证方法很简单,写个脚本把标注框画回原图,肉眼扫一遍就知道对不对。

import cv2 import os img_path = "smoke_dataset/images/000001.jpg" label_path = "smoke_dataset/labels/000001.txt" img = cv2.imread(img_path) h, w = img.shape[:2] with open(label_path) as f: for line in f: cls, xc, yc, bw, bh = map(float, line.split()) # 反归一化,还原到像素坐标 x1 = int((xc - bw / 2) * w) y1 = int((yc - bh / 2) * h) x2 = int((xc + bw / 2) * w) y2 = int((yc + bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite("check_000001.jpg", img)

这段脚本的作用是把归一化坐标还原成像素坐标并画框。xc - bw/2是左上角 x,xc + bw/2是右下角 x,y 方向同理。跑完打开check_000001.jpg,框应该正好套在抽烟的手部或嘴部区域。如果框偏到画面外或者缩成一点,基本可以判定标注有问题。

2.3 数据清洗:三行脚本筛掉脏样本

真实数据集里总有几张图是坏的——文件损坏、尺寸为 0、标注为空。这些样本混进去,训练时要么报错中断,要么让模型学到噪声。我一般会在训练前跑一遍清洗:

import os from PIL import Image img_dir = "smoke_dataset/images" lbl_dir = "smoke_dataset/labels" bad = [] for name in os.listdir(img_dir): img_path = os.path.join(img_dir, name) lbl_path = os.path.join(lbl_dir, name.replace(".jpg", ".txt")) try: img = Image.open(img_path) img.verify() # 校验图片完整性 except Exception: bad.append(name) continue if not os.path.exists(lbl_path) or os.path.getsize(lbl_path) == 0: bad.append(name) # 标注缺失或为空 print("问题样本:", bad)

img.verify()会检查文件头是否完整,损坏的图会抛异常。标注为空的情况在抽烟检测里不少见,有些图里其实没人抽烟,标了反而误导模型,这种直接剔除或者归到负样本集。清洗完记得把bad列表里的文件从 images 和 labels 里同步删掉,别只删一边。

3. 从标注到训练:YOLO 训练配置与参数调优

3.1 数据集配置文件怎么写

YOLO 系列训练前需要一个 yaml 描述文件,告诉框架去哪找图、有几类、类名是什么。以常见的 YOLOv5/v8 结构为例:

# smoke.yaml path: ./smoke_dataset train: images/train val: images/val nc: 1 names: ["smoke"]

path是数据集根目录,train和val是相对路径。这里有个前提:你得先把数据按比例切成 train 和 val 两个子集。切分脚本不复杂,但要注意别让同一段视频的连续帧同时进训练和验证,否则验证指标虚高,实际部署就翻车。

import os import random import shutil random.seed(42) img_dir = "smoke_dataset/images" names = [n for n in os.listdir(img_dir) if n.endswith(".jpg")] random.shuffle(names) split = int(len(names) * 0.8) for subset, files in [("train", names[:split]), ("val", names[split:])]: os.makedirs(f"smoke_dataset/images/{subset}", exist_ok=True) os.makedirs(f"smoke_dataset/labels/{subset}", exist_ok=True) for n in files: shutil.move(os.path.join(img_dir, n), f"smoke_dataset/images/{subset}/{n}") shutil.move(os.path.join("smoke_dataset/labels", n.replace(".jpg", ".txt")), f"smoke_dataset/labels/{subset}/{n.replace('.jpg', '.txt')}")

random.seed(42)保证每次切分结果一致,方便复现。8:2 是常见比例,数据量少的时候可以调到 9:1,但验证集别少于几十张,否则指标波动大得没法看。

3.2 训练命令与关键参数

配置写好就能起训。以 YOLOv8 为例:

yolo detect train \ data=smoke.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ project=runs/smoke

逐个说参数。model=yolov8n.pt用的是 nano 版本,参数量小、推理快,适合先跑通流程;如果精度不够再换 s 或 m。imgsz=640是输入尺寸,抽烟目标通常偏小,640 是底线,再低小目标就糊了。batch=16看显存,8G 显存跑 640 大概能到 16,不够就往下调。lr0=0.01是初始学习率,YOLO 默认值一般够用,数据量特别小的时候可以降到 0.001 防止过拟合。patience=20是早停,20 轮验证指标不涨就停,省时间。

训练过程中重点盯两个指标:mAP@0.5和mAP@0.5:0.95。前者宽松,后者严格。抽烟检测里如果 mAP@0.5 能到 0.8 以上,基本可用;mAP@0.5:0.95 通常低不少,0.5 左右算正常。如果两者差距特别大,说明框的位置不够准,可能是标注框偏大或偏小。

3.3 小目标检测的增强策略

抽烟检测的难点在于目标小。手部夹烟这个区域,在 640 的图里可能就几十个像素。默认增强策略对小目标不够友好,可以针对性调几个参数:

# 在训练配置里覆盖增强参数 mosaic: 1.0 # 马赛克增强,四图拼接,提升小目标占比 scale: 0.5 # 随机缩放幅度 copy_paste: 0.1 # 复制粘贴增强,把目标贴到其他图上

mosaic是 YOLO 的招牌增强,把四张图拼成一张,小目标相对变大,效果明显。但注意训练末期最好关掉 mosaic,让模型在真实分布上收敛,很多框架有close_mosaic参数控制最后几轮关闭。copy_paste对稀有类别有用,但抽烟检测如果正样本够多,开太高反而引入不自然的拼接痕迹。

提示:增强参数不是越多越好。数据量本来就少的时候,过度增强会让模型学不到真实特征,表现为训练集指标高、验证集指标低。

4. 避坑与排查:标注和训练里最容易翻车的几件事

4.1 类别索引对不上,模型全预测成背景

现象:训练 loss 一直很高,推理时一个框都不出。

原因:classes.txt里类别顺序和标注文件里的 class_id 不一致。比如标注里抽烟是 0,但配置文件 names 写成了["background", "smoke"],模型学的是 1 类,推理时阈值一卡就全滤掉了。

解决:打开classes.txt和 yaml 里的names逐行比对,确保索引和名称一一对应。单类别的话,names 里只写["smoke"],不要加 background,YOLO 会自动处理背景。

4.2 标注框归一化用错基准,框整体偏移

现象:画框验证时框的位置系统性偏左或偏上。

原因:标注工具导出时把 x 除以了图高、y 除以了图宽,或者用了绝对坐标没归一化。

解决:写脚本抽查一批标注,统计 x_center 和 y_center 的分布。正常情况下两者都应该在 0.3~0.7 之间集中,如果某一轴明显偏小或偏大,基本就是基准用错了。重新导出或者写脚本转换。

4.3 训练集验证集泄漏,指标虚高

现象:验证集 mAP 很高,部署到新视频上效果断崖式下跌。

原因:切分时没打乱,或者同一段视频的相邻帧被分到了两边。相邻帧几乎一样,模型等于在验证集上见了训练集。

解决:切分前先按视频来源分组,同一来源的帧只进一个子集。没有来源信息的话,至少用随机打乱,别按文件名顺序切。

4.4 图片和标注不同步,训练中途报错

现象:训练跑着跑着报FileNotFoundError或者标注解析失败。

原因:清洗时删了图片没删标注,或者移动文件时只移了一边。

解决:写个一致性检查脚本,遍历 images 里的每张图,确认 labels 里有同名 txt,反之亦然。不一致的直接列出来处理。

import os img_names = {n.replace(".jpg", "") for n in os.listdir("smoke_dataset/images/train")} lbl_names = {n.replace(".txt", "") for n in os.listdir("smoke_dataset/labels/train")} print("有图无标:", img_names - lbl_names) print("有标无图:", lbl_names - img_names)

4.5 显存爆了,batch 调小还是 OOM

现象:batch=16报 CUDA out of memory,调到 8 还是报。

原因:图片分辨率不统一,框架按最大尺寸对齐,实际占用比预期高。或者开了 mosaic 增强,四图拼接后等效 batch 翻倍。

解决:先把imgsz降到 512 试,再逐步往上加。同时确认没有其他进程占着显存。如果还是不行,用梯度累积模拟大 batch:batch=8配合accumulate=2,效果接近batch=16。

5. 推理验证与进阶技巧:把模型跑成能用的东西

训练完拿到best.pt,别急着庆祝,先做推理验证。最直接的方式是用命令行跑一批测试图:

yolo detect predict \ model=runs/smoke/weights/best.pt \ source=test_images/ \ conf=0.25 \ iou=0.45 \ save=True

conf=0.25是置信度阈值,低于这个值的框不输出。抽烟检测里这个值可以适当调低到 0.2,宁可多报别漏报,后处理再筛。iou=0.45是 NMS 的 IoU 阈值,重叠框超过这个比例就合并。如果发现同一个目标出了好几个框,把 iou 调低;如果相邻两个目标被合并成一个,把 iou 调高。

验证的时候重点看几类难样本:远距离的小目标、手部遮挡嘴部的情况、逆光或暗光下的画面。这几类如果漏检严重,说明训练数据里这类样本太少,得补数据而不是调模型。

进阶一点的做法是导出 ONNX 做部署验证。PyTorch 模型在服务器上跑得好,不代表转成 ONNX 或 TensorRT 之后还一样。导出命令:

yolo export model=runs/smoke/weights/best.pt format=onnx opset=12

opset=12是算子集版本,太低有些算子不支持,太高某些推理引擎不认,12 是比较稳的选择。导出后用 onnxruntime 跑一遍同样的测试图,对比 PyTorch 和 ONNX 的输出框,偏差应该在几个像素以内。如果偏差大,多半是预处理没对齐——比如 PyTorch 用的是 letterbox 填充,ONNX 推理时忘了同样的填充逻辑。

最后说个我自己的习惯。每次拿到新数据集,不管标注看起来多规整,我都会先抽 20 张画框验证,再跑一遍一致性检查,然后才起训。这个流程多花不了十分钟,但能省掉后面几个小时的无效训练。抽烟检测这个方向,数据质量对结果的影响远大于模型选型,把标注这关把住,后面的事就顺了。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询