简介面向深度学习目标检测研究的抽烟检测数据集压缩包内共有两千个xml格式标注文件整体大小约六百六十三MB。xml标注精确记录了抽烟行为在图像中的目标坐标与位置信息可直接用于训练YOLO、R-CNN、SSD等目标检测模型帮助算法人员快速构造抽烟识别任务的数据集。数据集覆盖不同拍摄角度、光照条件及背景环境下的抽烟场景能够增强模型对复杂实际环境的适应能力为公共区域无烟监控、智能戒烟辅助、安全生产管理等应用提供数据支撑。压缩包以zip格式打包文件组织规范标注文件易于解析和转换适合计算机视觉方向的研究者、算法工程师及高校学生直接使用。目前已有152人学习下载资源量级适中可显著减少数据采集与标注成本有助于快速验证和迭代抽烟检测算法。1. 抽烟检测为什么卡在「数据」而不是「模型」上做行为识别的人多数有个体会抽烟检测在深度学习里不算高难任务网络结构、损失函数都有成熟方案但真正把项目推到交付级别的团队几乎都在抽烟数据集上反复折腾。这个标题看起来只是给了一个数据集资源放到工程里它实际决定了三件事模型能不能在小目标上不丢帧、能不能在暗光室内场景不误报、以及标注成本要烧多少人力。我见过不少团队拿公开数据集训出demo演示效果不错一接到真实监控画面就大量漏检和误检最后查下来不是模型不行而是数据没吃透。下面就从数据集本身讲起把目录结构、标注格式、训练参数和踩坑记录一条条拆开适合正在做安防巡检、工位合规检测或行为识别的算法工程师照着落地。2. 抽烟数据集的真实构成目录、标注、类别分布怎么检查拿到任何一份抽烟检测数据集第一步不是急着训练而是花半小时把它的「家底」盘清楚。这个方向的数据集常见来源有两种一种是从监控视频抽帧后人工标注另一种是从公开图像集合里筛出来的场景图。前者往往带着同一视频段连续帧高度相似的问题后者则容易出现类别混杂、背景单一。两类数据集的目录结构通常也长得不一样但落到深度学习流程里最终都要归一到「图像 标注」的二元结构。2.1 目录结构与标注格式先对齐常见的抽烟数据集目录大概是这样的images下放原始帧Annotations下放 VOC 格式的 XML或者labels下放 YOLO 格式的 txt。有的还会直接给你分好train/val/test三个子目录。拿到手第一件事是把目录结构理清写一个小脚本扫描一遍确认图片和标注文件一一对应别出现有图没标注、有标注没图的情况。# 检查图片与标注文件是否配对 find images -type f -name *.jpg | wc -l find labels -type f -name *.txt | wc -l find Annotations -type f -name *.xml | wc -l三个数字对不上说明数据有缺失。常见的情况是标注文件比图片少原因大多是标注工具中途崩了或者人工筛选时删了图片没删标注。缺失比例超过 5% 的数据集我一般建议放弃重新整理的时间成本比想象中高因为训练时你会不断怀疑 loss 异常是不是缺标注造成的。VOC 格式的 XML 里size节点下的宽高用来计算归一化坐标很多人在转 YOLO 格式时在这里踩坑——用了错误宽高导致所有框偏移。YOLO 格式的 txt 每行是class_id x_center y_center width height坐标都是相对于图片宽高的比例值取值范围在 0 到 1 之间。如果解析出来的坐标有大于 1 或小于 0 的值不用怀疑要么标注时越界了要么宽高读错了。2.2 用脚本统计类别分布与目标尺寸分布接下来是重头戏统计类别分布和目标在画面中的面积占比。抽烟检测数据集最常见的毛病不是类别太少而是「抽烟」这个类被标得太杂。有些标注把香烟单独框出来有些把整只手连烟一起框还有些把烟盒、打火机甚至烟雾也算进正样本。模型在这种标注下训练学到的不是一个稳定特征而是一堆「看起来和烟有关」的形态。# inspect_smoke_dataset.py import os import glob from pathlib import Path import xml.etree.ElementTree as ET annotation_dir Path(./Annotations) records [] for xml_file in glob.glob(str(annotation_dir / *.xml)): tree ET.parse(xml_file) root tree.getroot() size root.find(size) img_w int(size.findtext(width)) img_h int(size.findtext(height)) for obj in root.iter(object): cls obj.findtext(name) box obj.find(bndbox) x1 int(box.findtext(xmin)) y1 int(box.findtext(ymin)) x2 int(box.findtext(xmax)) y2 int(box.findtext(ymax)) rec_w x2 - x1 rec_h y2 - y1 area_ratio (rec_w * rec_h) / (img_w * img_h) records.append({ class: cls, width: rec_w, height: rec_h, area_ratio: area_ratio }) # 分布统计 from collections import Counter class_dist Counter(r[class] for r in records) print(类别分布:, class_dist) tiny sum(1 for r in records if r[area_ratio] 0.005) small sum(1 for r in records if 0.005 r[area_ratio] 0.02) normal sum(1 for r in records if r[area_ratio] 0.02) print(f极小目标(0.5%画面): {tiny}) print(f小目标(0.5%-2%画面): {small}) print(f常规目标(2%画面): {normal})这段脚本解决两个问题。第一类别分布一眼看出标注是否统一如果cigarette、smoking、smoke混在一起必须先合并。第二面积占比直接告诉你这是不是小目标问题。如果极小目标占比超过 30%训练时的输入分辨率就不能用默认的 640后面章节会详细讲参数怎么调整。2.3 空标注与模糊帧清洗很多数据集在构建时不会帮你做质量筛选抽帧抽到运动模糊严重的画面标注员也只能硬着头皮框一个大概位置。这类样本对训练是纯噪声。检查空标注文件用的是解析后object节点数量为 0 的 XML检查模糊帧可以用 OpenCV 的 Laplacian 算子算方差方差越低图像越模糊。# filter_blur.py import cv2 import glob from pathlib import Path image_dir Path(./images) blur_threshold 60 # 低于该值判定为模糊帧 for img_path in sorted(glob.glob(str(image_dir / *.jpg))): img cv2.imread(img_path) if img is None: print(f无法读取: {img_path}) continue gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) var cv2.Laplacian(gray, cv2.CV_64F).var() if var blur_threshold: print(f模糊帧: {img_path} Laplacian方差{var:.2f})阈值 60 是个经验值白天监控画面通常能到 200 以上暗光下会偏低。不要一刀切全删先人工扫一眼删除结果确认剩下的画面还能覆盖夜间和室内场景再执行删除否则数据量会骤降。3. 用YOLOv8跑通抽烟检测数据划分与训练参数调整数据盘清楚之后进入训练环节。现在工业界做抽烟检测的主流方案还是 YOLO 系列YOLOv8 和 YOLOv11 在单类小目标检测上性价比最高训练流程简单部署生态也成熟。这里以 YOLOv8 为例讲一套我常用的最小可复现流程重点放在数据划分和训练参数上。3.1 按视频ID划分数据别让同源帧泄漏如果数据来自视频抽帧最忌讳的就是按帧随机切分训练集和验证集。同一段视频里相邻帧背景和人物几乎一样随机切分会把同一个动作的连续帧同时塞进训练集和验证集验证指标虚高真实场景一测就露馅。正确做法是按视频ID划分保证同一个视频的画面只出现在一侧。# split_by_video.py import re import random from pathlib import Path image_dir Path(./images) # 假设文件名类似: video_001_frame_0042.jpg pattern re.compile(r(video_\d)_frame_\d) groups {} for img in image_dir.glob(*.jpg): m pattern.match(img.stem) key m.group(1) if m else unknown_video groups.setdefault(key, []).append(img.name) video_names list(groups.keys()) random.seed(42) random.shuffle(video_names) val_count max(1, int(len(video_names) * 0.2)) val_videos set(video_names[:val_count]) train_files, val_files [], [] for video, frames in groups.items(): if video in val_videos: val_files.extend(frames) else: train_files.extend(frames) print(f视频总数: {len(video_names)}, 训练视频: {len(video_names) - val_count}, 验证视频: {val_count}) print(f训练帧: {len(train_files)}, 验证帧: {len(val_files)})这段脚本按文件名里的视频ID分组之后把整个视频组划入训练或验证。如果你的数据集文件名是frame_000001.jpg这种没有视频ID的样子就回到源视频重新抽帧否则划分这关过不了。划分完成后生成train.txt和val.txt两个文件内容是图片绝对路径YOLO 训练时会根据这两个文件去找对应的标注文件。3.2 VOC转YOLO格式与最小训练命令大多数公开抽烟数据集给的是 VOC XML 格式YOLO 训练需要 txt 格式。转换公式是中心点坐标和宽高全部除以图片宽高注意输出坐标要保留 6 位小数不然小目标框差一点就偏了。# 训练命令单卡 yolo detect train \ data./smoke.yaml \ modelyolov8n.pt \ epochs120 \ imgsz1280 \ batch16 \ workers8 \ device0 \ patience20smoke.yaml是最容易写错的文件核心内容只有三个字段path指到数据集根目录train和val写划分文件的路径nc是类别数names写类别名。抽烟检测通常就一个类写成path: ./datasets/smoke train: train.txt val: val.txt nc: 1 names: [smoking]参数这里逐个说。imgsz我建议从 1280 起步不要用默认的 640。抽烟这个目标在 1080p 监控画面里烟头部分往往只有几十个像素640 输入经过网络下采样后特征直接消失。batch按显存调12GB 显存跑 1280 分辨率最多开到 16显存不够就降到 8不要动 imgsz。patience20表示验证指标连续 20 轮不涨就提前停省时间。3.3 验证指标只看两个数训练完先别急着看权重文件大小跑一遍验证把指标落在两个数上mAP50和recall。单类小目标检测不看重mAP50-95那个指标对框的 IoU 要求太苛刻烟头周边的框本来就难回归精准硬追只会浪费时间调 anchor。yolo detect val \ model./runs/detect/train/weights/best.pt \ data./smoke.yaml \ conf0.25 \ iou0.5结果输出里重点看mAP50和recall。如果 mAP50 高但 recall 低说明漏检严重先别调模型结构回头查数据里是不是小目标占比太大如果 precision 低说明误检多需要补充负样本。这两个数互相拉扯找到平衡点比追求单一高指标更实用。4. 抽烟数据集训练避坑清单5个让模型翻车的标注与配比问题这一章写的全是实际项目中反复遇到的坑。每条都是「现象 → 原因 → 解决」的结构踩过一个就能帮你在下一个项目里省一周时间。4.1 验证集指标虚高一上线就翻车现象本地验证 mAP50 能到 0.9接到真实视频测试漏检率超过一半。原因数据划分时用了按帧随机切分。视频抽帧数据里相邻帧背景、人物姿态几乎一样随机切分导致训练集和验证集高度相似模型实际上在「背答案」。这是视觉任务里最常见的假象跟模型无关。解决按视频ID划分或者更严格一点按拍摄场景划分。代码参考前面 3.1 节的split_by_video.py。判断一个数据集是否已经泄漏可以随机抽几对训练和验证图像如果看到背景相同、只有人微动那就是泄漏了。4.2 烟头漏检烟盒反而被当成正样本现象模型对远处烟头不敏感对近景的烟盒、打火机、甚至卷起来的白纸都报「抽烟」。原因标注类名不统一。有的框标的是「抽烟行为」把人和烟整块框进去有的框标的是「烟支」只有小小一条。模型在混合标注下学到的是「和烟相关的东西」而不是「正在燃烧的烟头」。烟头在远处是小目标本就不容易学再被烟盒这类大目标干扰特征就学歪了。解决把类别含义定死建议只标「抽烟行为」或只标「烟支」不要混标。混了的话用脚本把所有标注重新梳理一遍把框住手和脸的改成只框烟支虽然费人力但这步省不了。另一个有效做法是把未点燃的烟、烟盒单独提出来作为负样本告诉模型这些都不是目标。4.3 暗光和夜间场景几乎全漏现象白天场景检测率不错傍晚和夜间直接失灵烟头明明是亮的模型就是看不见。原因训练数据里白天场景占比太高模型从没学过「暗背景下的亮目标」这个对比特征。夜间的烟头本质是一个小光斑和周围环境的亮度差很大这种模式在白天数据里不存在模型没有先验。解决两个方向并行。第一训练增强里加大色彩和亮度的扰动范围hsv_h0.02 hsv_s0.6 hsv_v0.5让模型见过压暗的画面第二收集一批夜间数据哪怕只有几百张对夜间指标的提升非常显著。实测下来加 300 张夜间图夜间场景的 recall 能从 0.3 拉到 0.6 以上。4.4 模糊帧导致训练loss震荡现象训练 loss 曲线不收敛到后期还在上下剧烈跳动验证指标也时好时坏。原因数据里混入了运动模糊严重的帧。摄像头的帧率不够或人快速转头时画面上烟支位置是糊的标注员只能凭感觉画框。这些框的中心点和宽高都不可靠模型每轮在这些样本上反复修正又反复推翻损失自然降不到底。解决用 2.3 节的 Laplacian 方差脚本把模糊帧筛出来方差低于阈值的直接删掉或降采样权重。实际项目里我一般保留一点模糊帧防止模型对清晰度过拟合但比例控制在 5% 以内。4.5 负样本缺失导致误检现象precision 上不去手指、笔、吸管、口罩边缘频繁被识别成烟。原因数据集只给了「有烟」的正样本没有「没有烟的人」的负样本。模型没见过「没有烟的手和嘴」自然会把这个区域的特征往正样本上靠。几乎所有公开抽烟数据集都有这个问题。解决手工收集几百张没有抽烟行为的监控画面加入训练目录但不生成标注文件。YOLO 支持在smoke.yaml里配置background_images字段或直接把负样本放进训练目录但保证 txt 标注为空文件。模型看到这些图会把这些约束为背景误检能压下去一半以上。5. 数据不够时怎么办增强策略与硬样本补充抽烟检测数据集的规模通常不大公开的也就几千张加上清洗和划分实际能进模型的可能不到一半。能不能把数据用足主要看增强策略和补充策略这里给几条被验证过有效的路径。5.1 Mosaic增强对小目标是把双刃剑YOLOv8 默认开 Mosaic把四张图拼成一张训练对中等目标效果显著。但抽烟检测的场景里小目标占比高Mosaic 合成后目标在整图里的占比更小检测头等于在学「更小的东西」反而容易学不动。常见做法是训练后期把 Mosaic 关掉。YOLOv8 里close_mosaic10表示最后 10 轮不使用 Mosaic让模型在近似真实分布上 refine。我在自己的项目里习惯前 80 轮开 Mosaic后 40 轮关掉验证指标会比全程开着高 3 到 5 个百分点。如果你的数据集只有一两千张Mosaic 还是要开它防过拟合的作用比小目标损失更重要。5.2 从视频里抽帧比翻图集更划算很多团队忽略了自己录制成本很低的视频数据。拿手机或摄像头录一段工位场景三分钟不同角度的视频用 ffmpeg 按场景切分抽帧一次能拿到几百张不同光照条件的样本。# 按场景变化抽取关键帧 ffmpeg -i scene.mp4 \ -vf selectgt(scene,0.3) \ -vsync vfr \ -q:v 2 \ ./frames/frame_%04d.jpgscene参数是场景变化阈值0.3 表示画面内容变化超过 30% 时才取当前帧。调低到 0.1 会抽出更多帧适合动作变化快的场景调高到 0.5 则只保留显著变化的画面。抽完帧后需要人工看一遍把曝光异常和严重遮挡的删掉剩下补充到训练集尾部然后重新划分数据。5.3 硬样本挖掘把错题集变成训练集模型验证阶段跑出来的误检图是质量最高的补充数据。流程是先跑一次验证把预测置信度高于阈值但实际是误检的图捞出来人工复核后分两类处理一类是标注错误修正后加入训练一类是真正难样本比如手指弯曲、嘴里叼着笔标为负样本。# 提取验证阶段的预测结果 yolo detect predict \ model./runs/detect/train/weights/best.pt \ source./datasets/smoke/val/images \ save_txtTrue \ save_confTrue \ conf0.35预测生成的 txt 里带置信度挑出那些置信度在 0.4 到 0.6 之间的检测框——这个区间通常是模型「有一点把握但把握不大」的样本。把对应图片归拢到一个目录人工过一遍你会看到模型困惑的集中区域比如反光桌面、拿着铅笔的手、透明玻璃上的倒影。这类样本补二三十张比盲目加几百张普通帧管用。5.4 小目标切片重训一版专用模型数据量不够又不想外包标注时切片重训是一条成熟的路。把原图按 640x640 的窗口做重叠滑窗切分窗口重叠 20% 防止目标被切散然后只保留包含目标的小图。相当于把一张 1080p 的大图拆成 4 到 6 张小图目标占比变大模型学起来容易得多。推理阶段同样用切片方式把大图切成小块分别推理再把结果按坐标映射回原图做非极大值抑制。这个方案有一个坑切片会让目标贴边贴边的烟头可能被切没所以切图时必须要重叠且推理时对边缘区域的预测要放宽置信度阈值。工业级做法是直接用 SAHI 这类现成框架做切片推理不用自己重复造轮子。6. 模型训完别急着上线用混淆矩阵和热力图验证边界训练结束到部署之间还差一步验证。这一步不是看 mAP而是搞清楚模型到底「学到什么、哪里还在瞎猜」。YOLOv8 训练完会在runs/detect/train目录下自动生成confusion_matrix.png单类任务里主要看左上角第一个格子真实目标被预测为目标的比率以及第一列里背景被误判为目标的比率。如果背景误判率高说明负样本不足回到第 5 章补数据如果真实目标命中率低说明特征还没学够优先查标注一致性。想看模型在单张图上的行为边界用yolo explain或 Grad-CAM 类工具画热力图。抽烟检测里一个有用的检查方式找一张人手里夹着白色物体的图分别跑原图和裁剪图确认模型是聚焦在烟支的纹理特征上还是聚焦在人的手嘴区域。如果热力图大面积覆盖人脸就说明模型学的是「嘴附近有东西」这种模型的泛化能力会很差换个姿势就容易漏。部署前的最后一个输入处理也容易踩坑直接把 1080p 原图 resize 到 640 喂给模型小目标会丢失。常见做法是保留原图分辨率推理或者用 4.4 提到的切片推理。我遇到的最典型失误是图省事统一 resize结果近处的烟能检到三米外的烟头全丢最后改成 1280 输入才稳定。这让我养成一个习惯——拿到数据集先跑一遍统计脚本把目标尺寸分布打出来再决定输入分辨率而不是让模型迁就数据。这条经验在多个检测项目里都适用希望帮到你。本文还有配套的精品资源点击获取