简介:面向深度学习、机器学习目标检测任务,聚焦公共场所与安防场景中的吸烟行为识别,是一份可直接用于模型训练与算法验证的标注数据集。图片来自真实抽烟场景,包含不同角度、远近与光照条件下的样本,适合作为YOLO、SSD等检测网络训练数据,也可用于毕业设计、竞赛实践或工业级抽烟识别系统的数据准备。包内按Annotations、Imagenet、JPEGImages三个目录组织,原图与XML标注一一对应;文件总数约2000个,以jpg图片与xml标注文件为主,另含少量txt划分清单、Python转换脚本和说明文档,压缩包约298MB,整体便于按目录快速取用。配合格式转换脚本可快速将VOC标注转为YOLO训练所需格式,省去人工整理与清洗时间。已有7526人学习下载,适合目标检测入门及需要现成监督数据的开发者。
1. 抽烟检测数据集:一张标注图片背后看不见的工程
拿到一个「抽烟检测数据集」的压缩包,很多人第一反应是解压、看图片、数一数多少个文件夹,然后直接扔给训练脚本跑一晚上。实际上做过一次完整落地的人都知道:一个能用的抽烟检测数据集,价值不取决于图片张数,而取决于标注是谁做的、怎么做出来的、以及里面藏了多少看不见的脏数据。图片是素材,标注是语义,只有两者都对得上,训练出来的模型才敢往楼梯间、工地入口、车窗边这种真实场景里部署。这篇文章面向准备自己做数据集或者要评估别人数据集的从业者,从数据来源、格式转换、标注规范、质量校验到最小训练验证,把整个链路里实际会碰到的坑逐个说一遍。
2. 数据源头与格式选型:公开数据、自采数据与VOC/YOLO/COCO怎么选
2.1 三种获取渠道各自的适用场景
抽烟检测数据集常见来源有三条路:公开数据集、项目自采、外包标注。公开数据集的好处是省事,坏处是场景往往和你实际部署环境不匹配。比如网上能下到的抽烟图片,很多是欧美街拍、摄影棚摆拍,背景是咖啡馆和酒吧,光线充足,人物正对镜头。你自己要部署的可能是工厂车间、学校走廊、地铁出入口,摄像头是俯视视角,光线偏暗,人物背对镜头。这种差异会让模型在公开数据上训练得不错,一上现场就大量误报漏报。项目自采是更可靠的方向,拿一台手机或复用现场的摄像头,在目标场景里连续采集一周,覆盖早中晚、逆光顺光、晴天雨天,框出真实人物动作。自采的问题在于数据量有限,通常只有几百到一两千张,需要和公开数据混合使用。外包标注则适合已经有原始图片、但没有人力逐张画框的团队,关键在于交付标准和验收流程要提前写好,否则回工率会让你怀疑人生。
2.2 VOC、YOLO、COCO三种标注格式怎么选
格式选型是所有后续脚本的地基。VOC格式是XML文件,图片名和XML同名,框坐标是左上角X、左上角Y、右下角X、右下角Y四个绝对值,优点是直观、跨工具兼容性好,很多开源标注工具默认导出就是VOC。YOLO格式是每张图片一个txt文件,每行内容为类别编号 x_center y_center width height,四个值全部归一化到0到1之间,训练读取时不用实时解析XML,速度更快,是目前YOLO系列训练的主流输入。COCO格式是单个JSON文件,把所有图片的标注聚合在一起,适合需要统一管理的大规模数据集。我的建议很简单:如果训练用YOLO系列,直接存成YOLO格式;如果数据还要给别人复用来做多轮评测,VOC格式更保险;COCO格式通常不必自己造,除非你要跑的目标检测框架只吃COCO。从VOC转YOLO是最高频的操作,因为很多公开数据以VOC形态下发。
2.3 把VOC标注转成YOLO格式:转换脚本与四个边界坑
下面是一个从VOC XML转YOLO txt的常用脚本骨架,核心逻辑是解析XML里的bndbox坐标,结合图片实际宽高计算出归一化的中心点和宽高。
import xml.etree.ElementTree as ET import os from PIL import Image def voc_to_yolo(xml_path, img_path, out_txt_path, class_map): tree = ET.parse(xml_path) root = tree.getroot() # 用图片真实尺寸做归一化,而不是XML里的size节点 with Image.open(img_path) as img: img_w, img_h = img.size lines = [] for obj in root.findall('object'): name = obj.find('name').text if name not in class_map: print(f"跳过未定义类别: {name} in {xml_path}") continue class_id = class_map[name] bndbox = obj.find('bndbox') xmin = float(bndbox.find('xmin').text) ymin = float(bndbox.find('ymin').text) xmax = float(bndbox.find('xmax').text) ymax = float(bndbox.find('ymax').text) # 夹取到图片边界,防止坐标越界 xmin = max(0, xmin) ymin = max(0, ymin) xmax = min(img_w, xmax) ymax = min(img_h, ymax) if xmax <= xmin or ymax <= ymin: print(f"空框跳过: {xml_path} {name}") continue x_center = (xmin + xmax) / 2 / img_w y_center = (ymin + ymax) / 2 / img_h width = (xmax - xmin) / img_w height = (ymax - ymin) / img_h lines.append(f"{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}") with open(out_txt_path, 'w') as f: f.write('\n'.join(lines) + '\n') class_map = {'smoking': 0, 'smoke': 0, 'cigarette': 0}这个脚本的要点有两个:第一,归一化用图片真实读取的宽高而不是XML里写的size节点,很多数据集在压缩图片时改了分辨率但XML没同步更新,用XML尺寸会导致训练时框全部错位,这是最隐蔽的坑;第二,类别映射表要提前统一,同一个数据集里可能出现smoking和smoke两种写法,必须在转换时归并成同一个编号。处理完之后还要检查输出txt是否为空——有些图标注了其他物体但没有抽烟目标,会生成空文件,这个正常但不能删除对应图片,因为训练时图片会被当作负样本使用。
需要额外注意的边界坑有三个:一是坐标值在XML里可能是整数过粗,离边界太近的小目标会被max(0, xmin)夹到边线上,如果原图本身就把香烟截了一半,宁可放弃这张图也不要用残缺框;二是脚本要处理XML里没有bndbox节点或只有部分坐标的脏文件,建议遇到异常直接打印文件名并跳过;三是「图片和txt同名」这个约定不要在转换后改动,YOLO训练逻辑默认按前缀名配对,改一个文件名就要同步改另一个,漏改一张就白跑一轮。
2.4 公开数据混合自采数据的比例建议
混合数据时,如果以自采数据为主,公开数据作补充,常见做法是公开数据占20%到30%,其余为自采数据。比例不是拍脑袋定的,核心是保证自采数据的分布占主导,避免模型被公开数据的背景风格带偏。如果公开数据量很大而自采只有几百张,就反过来用公开数据预训练一个权重,再用自采数据微调。剪枝和清洗公开数据比调整比例参数重要得多——同场景连拍图、带水印图、分辨率过低的图都要先删掉,否则混合后噪声会翻倍。
3. 标注工具与标注规范:把「有人抽烟」变成机器能学习的框
3.1 标注工具选型与项目目录约定
标注环节决定数据质量的底线,工具选型不需要太复杂。常见的开源标注工具里,LabelImg依然是很多团队的默认选择,因为它导出VOC格式直接、操作快捷键顺手、对低配电脑友好。另外一个更现代的选项是label-studio,如果团队有规模化标注需求,可以往后端多人协作的方式靠拢。但要注意:换工具意味着换导出格式和标注习惯,团队内部不要混用两种工具标注同一个数据集,否则验收成本会非常高。
目录组织建议固定成下面这套结构,后续所有校验脚本和训练脚本都以它为约定:
dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── classes.txtimages和labels分开存放,训练和验证子目录一一对应。classes.txt里写类别名,每行一个,顺序和标注时的类别编号保持一致。这个结构即使不用固定工具,手动整理也能一眼看出问题。
3.2 标注框规范:抽烟检测的六个细节约定
标注框怎么画,直接决定模型学到什么。以目标为「香烟」而非「整个人」的检测任务为例,六个约定是我会提前写进标注说明文档的:
第一,框住香烟本体,可以包含持烟的手指,但不要框到整只手。框大了模型会学到「手」而不是「烟」,背景里有只手握拳也会误报。第二,香烟是倾斜的,框要尽量贴合香烟的旋转矩形外接框,不需要画旋转框,但不要把烟直立时的框和水平时的框混用一种比例习惯。第三,香烟只冒出一小截、其余被手掌遮挡时,仍然标注,只框可见部分。第四,电子烟的发热端不一定像传统香烟一样有燃烧点,如果数据集定位是传统抽烟行为,电子烟建议不标注,或者单独开一个类别,由项目需求决定。第五,远处的小目标香烟只有几个像素,标注时尽力而为,如果连续多张图都看不清就删除该图而不是画一个臆测的框。第六,一张图里有多个目标时全部标注,不要「代表性地」只标最明显的那个,否则模型会漏掉次要目标。
这六条写清楚之后,标注人员的返工率会明显下降。还有一个习惯是:标注完成后导出一次VOC格式,人工随机抽50张图检查框位置和图片内容是否对得上。这个动作成本低,但能拦住一批「为了赶进度乱点」的低质量框。
3.3 标注类别的设定:单一类别与多类别的取舍
「抽烟检测数据集」究竟标几个类别,不是越多越好。常见方案是只标smoking一个类别,所有形式的香烟、烟头、持烟动作都归为这一类,好处是类别简单、标注速度快、模型专注度高。另一种方案是把cigarette、electronic_cigarette、smoke分开标,好处是部署时可以分别做策略,比如电子烟不告警;坏处是标注难度骤增,电子烟形态多样,很容易标出大量不一致样本。如果项目刚起步,建议先用单类别把流程跑通,模型能稳定检出后再考虑拆分。数据如果有后续增强计划,单类别基底也更容易做重标注。
4. 质量校验脚本:用数据说话,把坏标注在训练前拦住
4.1 先跑一遍图片与标注的完整性检查
千辛万苦整理好的数据,最怕训练跑到一半报错或者模型训练完才发现标注文件是坏的。第一个动作就是写一个完整性检查脚本,一次性扫出:图片能不能打开、标注文件和图片是否一一对应、标注坐标有没有越界、类别编号有没有超出类别数。下面这段脚本可以复用:
import os from PIL import Image def validate_folder(img_dir, lbl_dir, num_classes): errors = [] img_names = set() # 第一遍:检查图片完整性 for f in os.listdir(img_dir): if not f.lower().endswith(('.jpg', '.jpeg', '.png')): continue img_path = os.path.join(img_dir, f) try: with Image.open(img_path) as img: img.verify() except Exception: errors.append(f"图片损坏: {img_path}") continue img_names.add(os.path.splitext(f)[0]) # 第二遍:检查标签完整性 lbl_names = set() for f in os.listdir(lbl_dir): if not f.endswith('.txt'): continue lbl_names.add(os.path.splitext(f)[0]) lbl_path = os.path.join(lbl_dir, f) for line in open(lbl_path): parts = line.strip().split() if len(parts) != 5: errors.append(f"标注行格式错误: {lbl_path}: {line.strip()}") continue class_id = int(parts[0]) if class_id >= num_classes: errors.append(f"类别编号越界: {lbl_path}: {class_id}") x_c, y_c, w, h = map(float, parts[1:]) if not (0 <= x_c <= 1 and 0 <= y_c <= 1 and 0 < w <= 1 and 0 < h <= 1): errors.append(f"归一化坐标越界: {lbl_path}: {line.strip()}") # 第三遍:图片与标签一一对应 for name in img_names - lbl_names: errors.append(f"图片缺少标注: {name}") for name in lbl_names - img_names: errors.append(f"标注缺少图片: {name}") return errors errors = validate_folder('dataset/images/train', 'dataset/labels/train', 1) for e in errors: print(e) print(f"共发现 {len(errors)} 个问题")这个脚本输出的错误信息直接定位到文件名和具体行,处理起来很快。其中img.verify()只检查文件头完整度,不保证解码无误,但足够拦住大多数损坏文件。要注意的细节是:类别编号检查里num_classes要按实际类别数传,不要传大了,否则类别越界查不出来。
4.2 用框的数量和尺寸分布识别漏标
完整性检查只能发现「格式坏」的问题,发现不了「漏标」的问题。漏标的典型特征是:训练集中一部分图片的标注框数量明显偏少,或者验证集里零标注图片比例异常。可以用统计脚本快速摸底,统计每张图片的框数量、框宽度的像素分布、框面积的分布,再画出直方图人工看一眼。如果大多数图片都是1到2个框,突然出现一批10个框的图片,以及一批0框的图片,前者可能叠加了烟雾误标,后者可能存在大面积漏标。零标注图片在数据集里是合法的负样本,但如果占比超过10%,并且集中在同一批拍摄场景里,说明当时采集的人可能把「没人抽烟」理解成「没有目标」而忘了标注负样本图。抽样打开这批图复核一遍是值得的。
4.3 用相似图片检测处理重复帧
自采数据最容易混入重复帧,尤其是用视频抽帧生成的图片,同一段吸烟动作会被连续抽出十几张几乎一样的画面。这些图片进训练集,会让模型过拟合到某个固定姿态;进验证集,会让验证分数虚高。处理思路是提前做去重。一种成本可控的做法是计算每张图片的感知哈希,两两比较汉明距离,距离小于阈值就归为重复。实际落地时,更快的路径是先检查文件名是否有抽帧规律,比如frame_00123、frame_00124这种连续命名,直接按间隔抽帧。如果你拿到的数据集已经生成完成,没有原始视频,那就只能跑一遍哈希去重或者人工翻查相似图片。
5. 抽烟检测数据集避坑记录:五个最容易翻车的环节
5.1 场景单一化:模型把电梯间当成了烟
现象:模型在训练集上精度很高,拿到现场以后把消防栓、黑色墙角、墙壁阴影全部报成烟。
原因:数据集里大量图片来自同一个场所的早晚两个时段,背景高度相似,模型实际学到的是「这个颜色纹理的区域+手上的物体」的组合特征,一旦背景里有同色系物体就触发误报。这属于数据分布问题,不是模型问题。
解决:扩充不同场所的图片,至少包含室内白墙、室外夜晚、车内、走廊逆光四种典型背景,每种不少于100张。如果不想重新标注,可以在现有图片上做背景替换增强,但效果不如真实采集。
5.2 重复图片导致验证集虚高
现象:训练完验证精度到0.95,部署到现场却只有0.6,差距大得离谱。
原因:数据切分前没有去重,同一个场景的重复帧同时出现在训练集和验证集里,模型相当于「背过答案」,验证Loss完全失真。
解决:先做去重再做划分。至少保证验证集里的图片与训练集任何一张的相似度都不超过阈值,可以通过感知哈希批量比对。
5.3 标注框随意导致边界样本失控
现象:模型对完整露出的烟检测很好,对用手遮住一半、嘴里叼着只露过滤嘴的情况几乎全部漏检。
原因:标注人员只框「整个香烟」,对遮挡样本要么不标、要么框到手指上,导致模型没见过遮挡形态。
解决:规范里明确规定遮挡只标可见部分,并在标注说明里放三张示例图:完整烟、半遮挡烟、只露过滤嘴的烟,要求所有标注人员照图作业。关键测试时,模型漏检最多的就是这类边界样本。
5.4 类别失衡:冷烟头拖累整体精度
现象:模型对正在燃烧的香烟检出率高,对没点燃的烟卷和烟灰缸里的烟头检出率很低。
原因:很多人把烟头和香烟归为不同语义而不标注,或者烟头样本太少被当作背景忽略,模型自然而然学会忽略这类目标。
解决:先明确项目需求,如果烟头也需要告警,就要单独增加烟头样本并把烟头标成独立类别或统一归入smoking类;如果只关心吸烟动作,烟头可以不标,但要保证不标的烟头图片里没有其他烟目标,否则会污染负样本。
5.5 图片压缩与EXIF旋转:训练正常、推理方向不对
现象:训练时数据增强一切正常,换到部署端却出现检测框偏转,尤其是手机拍摄的竖图。
原因:部分图片带有EXIF旋转信息,训练框架读取时做了自动纠正,部署端读图库没有做同样处理,图片实际是旋转后的状态,标注却按旋转前画的,框全部错位。
解决:所有图片在进入数据集前统一做一次转正处理,去掉EXIF旋转信息并按标准RGB保存。这一步不能跳过,用图像处理库批量转一遍,训练和部署就统一了。
6. 用数据集跑通最小训练流程:从yaml配置到一次真实校验
6.1 训练前的数据配置
数据集整理完成后,用它跑通一次最小训练验证是必须的。这个过程不追求最优精度,只求链路通畅。以YOLO系列为例,第一步是写数据配置文件,常见内容是:
path: ./dataset train: images/train val: images/val nc: 1 names: 0: smokingpath指向数据集根目录,train和val用相对路径,nc是类别数,names是一个类别名列表。这里最容易犯的错是把val配成images/train的子集,导致训练和验证互相污染。配置完成后,先跑一个val占位验证,确认模型能读取到数据和标注,再去调整训练参数。
6.2 几个必调的参数
训练参数里,epochs、imgsz、batch、patience四个值优先调整。第一次跑通建议epochs=100,让模型充分收敛;imgsz设置为训练集里最常见的图片分辨率,不要盲目调到最大,图片集里大量小目标的话用imgsz=640足够,缩放到1280只会增加显存占用;batch受显存限制,一般调成8到32之间的2的幂次,不够就降低imgsz而不是硬堆batch;patience控制早停,一般设20到30,如果训练集小,patience=15也能接受。训练完成后,用独立的测试集做一次推理验证,统计每张图的置信度和框数量,重点看漏检的图是不是集中在前面提到的那几类边界样本上。验证通过之前,数据集和训练流程都不算交付,更不要急着追加新图片。
我自己的习惯是:每完成一轮数据清洗,就保留一份当时的校验报告和测试集推理结果,方便后续扩充数据集时对比精度变化。这个习惯帮我避免了很多次「数据越加越差」的困惑。希望帮到你,祝你的数据集和模型一次跑通。
本文还有配套的精品资源,点击获取