☰
带标注吸烟数据集与YOLOv8实战:格式转换、训练与避坑指南
2026/10/6 14:46:09 网站建设 项目流程

简介:面向人工智能、计算机视觉及机器学习领域开发者与研究人员,这份吸烟数据集带标注资源可直接用于训练吸烟行为检测、分类或相关预测模型。包内共1567个文件,以783张jpg图像样本为核心,配套783个xml标注文件提供目标边界框和类别标签,另有1个txt说明文件辅助理解数据组织方式,压缩包整体仅31.71MB,轻量易用,已有159人学习下载。使用时免去自行采集与标注的环节,直接开展模型训练。数据按“smoke”统一命名,结构简洁,便于批处理与切分训练集、验证集和测试集;建议关注标注一致性、类别平衡性以及不同场景下的图像多样性,以提升模型泛化能力。

1. 带标注的吸烟数据集:为什么说它是检测模型的上限

在仓库、加油站、园区出入口的监控画面上识别吸烟行为,是个看着简单、做起来却很磨人的目标检测任务。带标注的吸烟数据集是整个任务的起点:没有它,YOLO 模型就没有地方去学什么是烟头、什么是烟雾、什么是叼烟动作。不过,真正落地过的人都知道,数据集的标注质量比数量更能决定模型精度。模型最终能到多少 mAP,取决于标注框贴不贴、类别定义统不统一、正负样本平衡与否。这篇笔记面向想快速跑通吸烟检测、又不想在数据准备阶段返工的人,从拿到一个带标注的吸烟数据集开始,把格式整理、训练验证、参数设置和踩坑点一条条讲清楚。

2. 带标注的吸烟数据集的格式选型与统一:VOC、COCO、YOLO怎么选、怎么互转

2.1 三种标注格式的字段与选型逻辑

“带标注”听起来像个完整的礼包,但拆开看,第一位要解决的问题是格式。产业界主流格式有三种,字段结构差异很大,直接决定你后续训练代码怎么写。拿到的数据集可能是任何一种,而你需要做的第一件事,是把它们统一成训练框架认识的格式。

格式存储形态核心坐标表示适合的工具链
VOC每张图一个XML左上角和右下角的绝对像素坐标LabelImg 默认导出
COCO整批一个JSON中心点加宽高(绝对像素),附 area、iscrowdDetectron2、MMDetection
YOLO每张图一个同名txt归一化中心点与宽高YOLOv5、YOLOv8、Darknet

VOC 格式最常见,很多标注工具的老版本默认输出就是它。一个 XML 文件里包含图片路径、尺寸,以及若干 object 节点,每个 object 下面有 name 和 bndbox。这种格式胜在人读起来方便,坏处是文件碎片化,每个图片都要单独解析,且没有统一的类别注册表,类别名拼写稍有出入就会产生脏标签。COCO 格式更紧凑,单个 JSON 文件描述所有图片和标注,bbox 字段是[x, y, width, height],适合做实例分割和多任务扩展,但对纯检测项目来说解析成本偏高。YOLO 格式是如今实训阶段的主流选择,每个 txt 文件一行一个目标,五个数字依次是类别索引、归一化中心点 x、y 和归一化宽高。它的优势是训练循环可以直接加载使用,不需要任何解析库,缺点也明显:没有任何图片元信息,也没有类别字符串,必须靠外部 data.yaml 把索引映射回类别名。

回到带标注的吸烟数据集这个具体场景,我一般会在拿到数据后先看一眼数据字典。如果是网上下载的打包数据集,大概率是 VOC 形式,因为做数据标注的团队习惯用旧版 LabelImg。而你要用 YOLOv8 训练自己的数据集,就得先把所有标注转到 YOLO 格式。转换这一步没有技术含量,但坑特别多,下面给一个可以直接改来用的脚本。

2.2 VOC转YOLO转换脚本实操

import os import glob import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo_one(xml_path, class_names, out_dir): """ 解析单个VOC XML,转成一行一条的YOLO txt标注。 class_names 的顺序就是最终类别索引的顺序,必须和data.yaml保持一致。 """ tree = ET.parse(xml_path) root = tree.getroot() img_w = int(root.findtext('size/width')) img_h = int(root.findtext('size/height')) txt_name = Path(xml_path).stem + '.txt' lines = [] for obj in root.iter('object'): name = obj.findtext('name') if name not in class_names: # 遇到未登记的类别直接跳过,避免把脏类别带进训练 print(f"[warn] unknown class: {name} in {xml_path}") continue class_id = class_names.index(name) xmin = float(obj.findtext('bndbox/xmin')) ymin = float(obj.findtext('bndbox/ymin')) xmax = float(obj.findtext('bndbox/xmax')) ymax = float(obj.findtext('bndbox/ymax')) # 防御脏标注:标注框可能超出图片范围或出现负数,先钳到有效区间 xmin = max(0.0, min(xmin, img_w)) ymin = max(0.0, min(ymin, img_h)) xmax = max(0.0, min(xmax, img_w)) ymax = max(0.0, min(ymax, img_h)) # 把绝对坐标换成归一化中心点与宽高,YOLO训练时一律按此读取 cx = (xmin + xmax) / 2.0 / img_w cy = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{class_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}") if lines: out_file = os.path.join(out_dir, txt_name) with open(out_file, 'w') as f: f.write('\n'.join(lines)) def main(xml_dir, out_dir, class_names): os.makedirs(out_dir, exist_ok=True) for xml_file in glob.glob(os.path.join(xml_dir, '*.xml')): voc_to_yolo_one(xml_file, class_names, out_dir) if __name__ == '__main__': # 类别顺序一旦确定就不要改,改索引必翻车 classes = ['smoking', 'cigarette', 'smoke'] main('datasets/Annotations', 'datasets/labels', classes)

这个脚本的逻辑重点有两个。第一,坐标换算必须在归一化之前先做边界钳制。我在实际处理中见过不少标注工具把 bbox 导出成负坐标或者超出图片尺寸,如果不在转换时拦掉,后边训练时损失会莫名震荡。第二,class_names 的顺序是硬约定,它直接决定 txt 里那个数字代表什么类别。写脚本时顺手把未知类别打印出来,能帮你快速发现数据里有没有拼写不一致的类别名。

2.3 转换后的四步校验法

转换完直接开训是新手最容易踩的坑。我一般会花十分钟做四步校验,这几步能省掉后面大量“玄学”调参。

第一步,对比文件数量。图片总数和 txt 总数必须一致,差一个都说明有 XML 解析异常或漏标。用ls images/* | wc -l和ls labels/* | wc -l对比,数字对不上就先查差集。

第二步,校验坐标范围。写一行 Python 读所有 txt,检查 cx、cy、w、h 是否都落在 0 到 1 之间,且 w 和 h 严格大于 0。出现 0 值说明标注时点重了,出现大于 1 说明图片尺寸读错了。

第三步,可视化叠加。用 OpenCV 把边界框画回原图,肉眼扫二十张。这一步对吸烟数据集格外重要,因为烟头和手指的交界处非常容易被标注成一个大方框,框里一半是手一半是背景,模型学到的语义是错的。

第四步,类别分布统计。统计每个类别在训练集里的框数,按烟头、烟雾、吸烟动作分别看。如果某个类别只有一两百个框,那这个类基本学不出来,后面要么补数据,要么把该类和其他类合并。

3. 用带标注吸烟数据集跑通YOLOv8最小训练管线:从目录整理到指标验证

3.1 数据集目录结构与data.yaml配置

格式统一之后,下一步是装配目录。用 YOLOv8 训练自己的数据集,最容易被忽略的是路径关系和配置文件的一致性。一个标准的目录结构长这样:

smoking_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yaml

images 和 labels 是兄弟目录,图片名和标签名必须完全一致,包括文件前缀。如果图片叫frame_001.jpg,对应的标签就必须是frame_001.txt。YOLOv8 的 DataLoader 在加载时会自动根据图片路径去找同名标签,找不到则把该图片当负样本丢进批次,不会报错,这也是很多人训练到一半才发现“我明明标了怎么没学到”的原因。

data.yaml 是入口配置,最少四个字段:

# 数据集根路径:建议写绝对路径,写相对路径时容易因为启动目录不同而找不到数据 path: /mnt/data/smoking_dataset train: images/train val: images/val test: images/test # 类别顺序必须和转换脚本里的class_names一致 names: 0: smoking 1: cigarette 2: smoke

这里要特别强调 names 的索引顺序。YOLOv8 训练时只关心 txt 里那个 class_id 数字,不关心类别名拼写,data.yaml 只是负责把数字映射回字符串。如果你的转换脚本里classes = ['smoking', 'cigarette', 'smoke'],yaml 里就必须是 0 对应 smoking、1 对应 cigarette、2 对应 smoke。一旦顺序错位,模型训练不会报错,但输出的类别名会整体错位,推理时把烟头显示成吸烟动作,这种错位极其隐蔽。

3.2 训练命令与关键超参数设置

目录和配置就位后,一条命令就能启动训练。最常用的版本是:

yolo detect train model=yolov8s.pt data=data.yaml epochs=50 imgsz=640 batch=16

逐个理解这几个参数。model 选择yolov8s.pt而不是yolov8x.pt,是因为带标注的吸烟数据集通常不会特别大,模型容量过大反而容易对背景纹理过拟合,而且 s 版本在验证集上的 AP 差距不大,推理速度更快。epochs=50 是首次训练的经验值,先跑一轮看损失和指标曲线,确认数据没问题再把轮数加到 150 或 200。imgsz=640 是个平衡点,对监控画面里的烟头来说,更推荐提到 1280,因为小目标需要更高输入分辨率才可能在特征图上留下足够响应。但分辨率翻倍显存消耗大约翻四倍,需要根据显卡实际容量来权衡。batch=16 是 8G 显存配 yolov8s 的安全值,显存吃紧就降到 8。

还有一个容易被忽略的小参数:rect=True。这个参数开启后,训练时会把同一 batch 内长宽比相近的图片分组,避免为了统一尺寸强行把图片拉伸变形,对细长的烟支、烟盒这类目标更友好。代价是训练速度略降,但对小目标检测的召回率通常有正收益。

3.3 验证阶段如何读指标并定位问题

训练完以后,ultralytics 会在runs/detect/train目录下生成weights/best.pt、last.pt、混淆矩阵、PR 曲线和验证指标表。我判断模型状态时只看三个数字。

第一个是 mAP50,即 IoU 阈值 0.5 时的平均精度。工程验收一般以这个为准,它反映的是“框有没有大致框对”。第二个是 mAP50-95,把 IoU 从 0.5 到 0.95 每隔 0.05 算一次再平均,对边界框的贴合精度要求非常苛刻。带标注的吸烟数据集里如果框普遍标得宽松,你会发现 mAP50 还不错、mAP50-95 很难看,这是数据标注风格导致的,不一定是模型能力问题。

第三个要逐类看 AP。如果 smoke 类别的 AP 明显低于其他类,优先复查两件事:这个类别在训练集里有多少框;标注框是否把大片的烟雾轮廓当成背景漏掉了。框数太少,最简单有效的做法是给该类补数据或者并入相邻类别;框完全没标对,那只能做重标或清洗。

我在这个阶段还有一个习惯:把验证集里置信度最高的 20 个误检样本单独拎出来看。所谓误检,就是模型的置信度分数很高但实际框错了。这些样本往往能直接暴露标注规范问题,比盯着曲线猜更高效。

4. 吸烟检测项目避坑:小目标、脏标注、相似误检与场景过拟合

4.1 坑一:烟头和烟盒小目标系统性漏检

现象:验证集里 cigarette 类别的 AP 不超过 30%,一张正常距离的监控画面里,烟头明明在图上却完全没有预测框,或者框的位置整体偏到了手上的某个位置。

原因:烟头在 1080p 画面里通常只有二三十个像素宽,属于典型的小目标。YOLOv8 下采样后的特征图对小目标的响应本来就弱,再加上很多公开的带标注数据集里,标注人员为了省事会把整只手圈进一个框,类别名叫 cigarette,实际学到的却是“手部区域”。模型根本没有见过“只有烟头没有手”的标注形态,推理时自然找不到目标。

解决:按顺序做三件事。第一,把 imgsz 从 640 提到 1280,直接增加小目标在输入图上的像素占比。第二,开启多尺度训练,在训练命令里加scale=0.5,1.5,让模型在不同缩放下都见过烟头。第三,如果效果还不行,就给标注框做腐蚀处理,将手部大框收缩成烟头周围的小框。第三步虽然要动数据,但收益最直接,做完之后 AP 往往能涨 10 个点以上。

4.2 坑二:类别定义不统一导致错标

现象:同一批数据集里,有的图片把整根香烟标成 cigarette,有的把烟盒标成 cigarette,还有的把烟雾直接标成 smoking。训练出来的模型把铅笔、白色小棒状物都误检成香烟。

原因:所谓带标注,不等于标注规范统一。源数据可能由不同标注人员分批完成,每个人对类别的理解不一样。这是数据标注行业里最常见的隐形炸弹,类别清单看起来只有三个词,但背后的视觉语义千差万别。

解决:拿到数据先做类别语义核对。每种类别随机挑二十张样本,只回答一个问题:这个类在所有样本里到底指什么。确认之后,把模棱两可的类别全部归并或删除,不要保留“可能有用”的类别。推理阶段把 conf 阈值提到 0.5 以上,能压掉一小部分边缘误报。真正釜底抽薪的做法是统一标注规范后重新清洗一轮,成本高但值。

4.3 坑三:训练损失降不下去,反复震荡

现象:训练 loss 曲线在一个平台期无限震荡,验证集 mAP 在 30 到 35 之间反复横跳,尝试调整学习率、换优化器都无效。

原因:数据里存在大量“标注框错位”样本。框比真实目标大出一倍,或者框只框住了目标的一半,模型在训练时既看到了物体又看到了过多背景,梯度信号在“物体”和“背景”之间反复横跳,无法收敛。带标注的吸烟数据集经常出现这种问题,因为烟头小、边界不清晰,标注员完全凭感觉拉框。

解决:不要继续调超参了,先回去看图。用可视化脚本把训练集所有框画出来,拼成网格图,找出 20 张框严重不贴合的样本。要么手动修正这些框,要么直接把这类样本丢出训练集。我见过一个项目,清理了 3% 的脏样本后 mAP 从 60 直接到 68,比换任何模型架构都管用。

4.4 坑四:验证集指标好,但换个场景误报率失控

现象:在室内监控训练出来的模型,拿到阳光下的室外画面后,把手机屏幕反射光、树叶间的高光点、甚至空调外机的排水孔全部误报成烟头。

原因:训练集场景太单一,模型学到的不是“烟头”本身,而是“深色小物体在特定光照和背景下的形态”。一旦场景分布发生变化,模型就会把纹理相似的小区域当成目标。这是监控类数据集的通病,也是“带标注”数据集里最容易隐藏的问题。

解决:做场景迁移增强。在训练前提亮度扰动和对比度扰动,同时从真实场景中收集几百张完全没有烟头的负样本图片,混入训练集。注意负样本不是随便找,要找“长得很像烟头”的高光小物体,比如螺丝帽、白色标签、金属反光点。模型需要见过这些“假烟头”才能学会分辩边界。混入比例建议在 5% 到 10% 之间,配成二元判别任务。

5. 进阶技巧:重建验证集与误报归因,把模型调成能上线

5.1 用分层抽样重建验证集,避免指标自欺

首次训练通过之后,我做的第一件事不是加数据,而是重建验证集。很多带标注的吸烟数据集在打包时,train/val 划分是随机的,随机划分会导致同一个场景的连续帧同时出现在训练集和验证集里,模型等于提前见过答案,验证指标虚高,一上线就现原形。

常见的做法是把图片按场景分组,同一个机位、同一天采集的图片放进同一个桶,再从桶里按比例抽验证集。实现上可以用一段小脚本,按文件名前缀或者目录名聚类,确保验证集与训练集在场景维度上完全隔离。这样重建之后,验证指标虽然会掉几个点,但更接近真实上线表现。

5.2 误报归因:让模型学会说“这不是烟头”

模型上线后出现误报,不要急着加负样本。先把误报图片收集起来,用训练好的模型导出中间层特征,按特征相似度聚类。这一步就是误报归因。绝大多数误报会被归成几个典型原型,比如金属反光型、植被纹理型、暗色方块型。每个原型收集二三十张样本,打上负样本标签,混入下一轮训练集。

我在实际项目中靠这个办法把误报率从每百帧 12 次压到了每百帧 2 次以内。要领是负样本本身要有代表性,放进训练集后让模型能明确看到“长得像但不属于”的样本。等模型学会拒绝这些原型,线上误报会自然消失。

这个方向走到后面,数据才是主角。模型架构从 YOLOv5 换到 YOLOv8,再换到更新的检测器,精度提升已经很有限;反而是对带标注的吸烟数据做持续清洗、规范统一、负样本沉淀,收益一次比一次明显。我自己的习惯是每一轮训练都留下一个“错误日志”,把误检样本截图和对应特征一起存档,下次拿到新数据时先对比存档,很多翻车都能提前避开。希望帮到你。

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

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

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

立即咨询