简介:这是一份面向目标检测学习者与YOLO使用者的“是否吸烟检测”专用数据集,针对公共场所吸烟行为识别与禁烟区域监控场景,共覆盖no_smoking、no_smoking_area、smoking三个类别,可帮助用户快速上手吸烟检测模型的训练与验证。资源共412个文件,包含204张jpg图片、204个txt标注文件和4个yaml配置文件,压缩包整体约11.48MB;目录已按照train、val、test划分完毕,data.yaml中已配置好各集合路径,yolov5、yolov7、yolov8等主流算法可直接读取数据开始训练。已有658人学习下载,适合数据科学初学者、算法工程师及智慧安防项目开发人员使用。借助这份数据集,读者无需自行整理标注与目录结构,即可验证目标检测流程,也可结合检测结果参考对照模型效果,快速建立自己的吸烟检测基线。这份资源整体轻量紧凑,适合用于教学演示、相关课题实验或快速跑通吸烟检测流程。
1. 吸烟检测数据集为什么值得专门找现成的YOLO格式打包
监控画面里有人抬手放到嘴边,两三秒后又放下,要不要报警提示?这类“是否吸烟检测”和常规行人检测最大的不同,在于目标是烟头而非整个人——烟头在1080p画面里经常只有十几个像素,又细又小,对标注和训练都不友好。很多人第一反应是自己收集图片、用labelimg打标完YOLO格式的标注,再折腾环境训练,但仅打标几百张图就要大半天,烟头这种目标还特别容易漏标。smoking-dataset-yolo-1.zip 这类打包好的YOLO吸烟检测数据集,解决的就是这部分重复劳动:解压后就是图片加同名txt,YOLOv5、YOLOv8 等主流训练流程直接能读取。它适合刚接触目标检测、想快速跑通吸烟识别验证方案的从业者,也适合做监控边缘设备预研的同学。不过,拿到压缩包先别急着训练,标签质量决定了训练上限,下面这套处理流程才是真正省时间的部分。
2. 拆开 smoking-dataset-yolo-1.zip:先盘数据和标签再谈训练
数据集不是越大越好,而是“标签对得上、格式合法、目标画得准”才算能用。我拿到任何 YOLO 格式数据集,第一步永远是解压、配对、检查 txt,最后再抽图人工看。这三步做完,基本能判断这个压缩包是“直接能喂给 yolo 模型训练”还是“需要动手清理一遍”。
2.1 解压与目录结构识别:images 和 labels 如何一一对应
先解压,再看目录长什么样。常见做法是把压缩包解压到独立目录,避免和项目代码混在一起:
unzip smoking-dataset-yolo-1.zip -d smoking_data find smoking_data -type f | head -50 find smoking_data -name "*.txt" | wc -l find smoking_data \( -name "*.jpg" -o -name "*.png" \) | wc -l第一行把压缩包解压到 smoking_data 目录,避免当前目录被文件铺满;第二行看整个目录结构,确认是 images/labels 两级目录,还是 train/val 下的二级结构;第三四行分别数 txt 和图片数量,初看图片与标签是否配平。如果解压后发现里面还嵌套一层 zip,说明原数据集分了卷或做了二次打包,需要再解一次。
目录确认后,写个小脚本逐对检查每个子集。YOLO 数据集要求每张图片存在同名 txt,缺失标签等于这张图被当作纯背景参与训练,会影响负样本比例:
from pathlib import Path root = Path('smoking_data') for split in ['train', 'val', 'test']: img_dir = root / split / 'images' lab_dir = root / split / 'labels' if not img_dir.exists(): continue imgs = {p.stem for p in img_dir.iterdir() if p.suffix in ('.jpg', '.jpeg', '.png')} labs = {p.stem for p in lab_dir.iterdir() if p.suffix == '.txt'} print(split, '图片数:', len(imgs), '标签数:', len(labs)) print('缺标签的图片:', len(imgs - labs), '无图片的标签:', len(labs - imgs))这段代码把每个子集里“有图无标”和“有标无图”都数出来。图片和标签文件名不一致是压缩包最常见的翻车点,比如某张图叫 img_001.jpg,标签却叫 0001.txt,训练时 yolo 会静默跳过这张图,你甚至注意不到。发现配不平也别急,先看差异集中在哪,是全目录统一改名,还是只有零星几张。
2.2 把 txt 标签读出来:验证归一化框与类别 ID 是否在预期范围
YOLO 格式的每一行固定是“class_id x_center y_center width height”,坐标全部归一化到 0~1。很多人只检查了文件存在,没看内容,结果训练时 loss 直接起飞。我一般会全局扫一遍所有 txt 的类 ID 分布和坐标范围:
from collections import Counter from pathlib import Path lab_dir = Path('smoking_data') / 'train' / 'labels' cls_counter = Counter() bad_count = 0 for txt in lab_dir.glob('*.txt'): for line in open(txt): parts = line.split() if len(parts) != 5: print('行格式异常:', txt, line.strip()) bad_count += 1 continue cls = int(float(parts[0])) coords = [float(v) for v in parts[1:]] if not all(0 <= v <= 1 for v in coords): print('坐标越界:', txt, line.strip()) bad_count += 1 cls_counter[cls] += 1 print('类别分布:', cls_counter) print('异常行数:', bad_count)这段脚本做的事很朴素:行数必须是5列,class_id 转成整数后进计数器,四个坐标必须落在0到1区间。坐标大于1说明原标注不是归一化坐标,常见原因是标注工具导出的不是 YOLO 格式,被人手动改了扩展名;坐标全部是0或1也有问题,说明框退化成了点或整张图,对训练没有正向帮助。类别分布同样关键——如果 class_id 最大值是0,说明这是单类数据集,后面的 yaml 只写一个类别;如果出现0和1两个 ID,而你的业务只需要“是否吸烟”二分类,就要确认这两个类分别代表什么,别把“不吸烟的人”当正样本喂进去。
2.3 数据可视化抽检:用 OpenCV 把标签画回图片,肉眼查漏标与错标
脚本能查出格式错误,查不出语义错误。最可靠的验证方式是随机抽几十张图,把标注框原样画回去,用眼睛看。这一步能发现“标注框框住了整只手而不是烟头”“烟头漏标了大半”“该标烟的位置标了嘴唇”这些让模型越学越乱的坏标签:
import cv2 from pathlib import Path img_dir = Path('smoking_data') / 'train' / 'images' lab_dir = Path('smoking_data') / 'train' / 'labels' for img_path in sorted(img_dir.glob('*.jpg'))[:30]: img = cv2.imread(str(img_path)) h, w = img.shape[:2] txt = lab_dir / (img_path.stem + '.txt') if not txt.exists(): continue for line in open(txt): 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_' + img_path.name, img)代码本身是逐行解析 txt、把归一化坐标乘回图像宽高、画绿色框、另存为新文件,不覆盖原图。抽检重点看三类内容:小目标是否多数被框住且框贴合;是否存在大量“难分样本”,比如棒棒糖、手指、烟盒被框进来;图片是否全部来自同一个场景或同一个角度的截图。如果抽30张图里有超过5张明显漏标,这个数据集就需要先清洗再训练,否则后期补标成本会远高于重新标注一批。
3. 基于该数据集训练 YOLOv8:数据集 yaml 与最小可跑命令
数据和标签盘清楚后,就可以进入训练。模型选型上,我默认用 YOLOv8 起步,原因不是它一定比 v5 或 v11 准,而是它对小目标的数据增强组合和命令行参数都写成了默认可用,几乎不用调,适合作为基线。同样是处理吸烟检测数据集,YOLOv8 会在训练过程中自动做 mosaic、翻转、色彩扰动,对烟头这种小目标多、背景杂的数据集有天然的容错。选型理由先放这儿,下面直接走到能跑的步骤。
3.1 数据集 yaml 怎么写:路径、类别名、train/val 拆分规范
YOLO 训练前要准备一个 yaml,告诉框架“数据在哪、类别叫什么”。这是新手最常写错的一步,路径写挂会导致训练直接找不到标签。以解压目录 smoking_data 为例,标准写法如下:
path: ./smoking_data train: train/images val: val/images names: 0: smoking如果前面检查类别分布时看到两个 class_id,说明数据集里已分好“吸烟”和“非吸烟”两个类别,那么 names 改成:
names: 0: smoking 1: normal这段配置里,path 是相对训练命令执行目录的路径,也可以换成绝对路径;train 和 val 的取值是 images 目录的相对路径,框架会自动把路径中的 images 替换成 labels 来定位标注文件;names 必须与 txt 里的 class_id 顺序一致,顺序错位是“模型训练完但输出类别张冠李戴”的头号原因。注意 train 和 val 都必须有 labels 目录,val 只有图片没有标签,训练能启动但验证阶段会报错。如果压缩包没有划分 train/val,我一般按 9:1 或 8:2 随机分,并保证同一场景的图片不被拆到两边。
3.2 训练命令与关键参数:从默认值到针对小目标调优
环境装好 ultralytics 包后,训练命令极短:
yolo detect train \ model=yolov8s.pt \ data=smoking.yaml \ epochs=100 \ imgsz=640 \ batch=16 \ workers=8 \ seed=42 \ patience=20模型选择 yolov8s 是速度和精度的折中,监控场景图片量大,s 比 n 的误检少,比 m 快不少,适合第一次跑通;epochs=100 对几百张图的数据集够用,配合 patience=20,验证集指标连续20轮不涨就提前停;imgsz=640 是默认推理尺寸,这里有个针对烟头小目标的调整点——显存够用就把 imgsz 提到 896 或 1024,能显著改善小目标召回,代价是单卡训练时间变长。batch=16 是8GB显存附近的安全值,如果训练时 OOM,优先把 batch 降到8而不是换模型。
针对吸烟检测这类小目标数据集,我通常额外加上两个参数:
yolo detect train \ model=yolov8s.pt \ data=smoking.yaml \ epochs=100 \ imgsz=896 \ batch=8 \ mosaic=1.0 \ mixup=0.0 \ close_mosaic=10mosaic=1.0 表示前90轮启用四图拼接增强,能让模型在小目标上见多识广;close_mosaic=10 表示最后10轮关掉 mosaic,让模型在接近真实分布的画面里收尾,避免验证时对拼接图产生依赖。mixup=0.0 是针对烟头这种小目标关掉的选项,mixup 会融合两张图,导致小目标变得更模糊,我在小目标数据集上默认关掉。这些参数不是玄学,mosaic 和 close_mosaic 的组合在公开目标检测里已是标准操作,值得作为固定配置写进自己的训练模板。
3.3 训练结果判断与第一次推理:看指标不如看错图
训练结束后,runs/detect/train 目录下会生成 weights/best.pt 和 weights/last.pt。best.pt 按验证集 mAP 挑选,是部署首选。先别急着看 loss 曲线,先跑一次测试图推理,直观感受模型输出:
yolo detect predict \ model=runs/detect/train/weights/best.pt \ source=test_samples \ conf=0.3 \ iou=0.7 \ save=True把 test_samples 换成你准备的带吸烟场景的测试图片目录,conf=0.3 是置信度门限,只保留得分高于0.3的框;iou=0.7 是 NMS 的 IoU 阈值,两个框重叠超过这个值就合并成一个。运行结束后打开 save 目录下的标注图,重点看三件事:真正的烟头有没有框出来、人手或嘴唇有没有被误框、方框是紧贴目标还是偏了一半。指标再好看,错图检查这一关过不了,部署上线也迟早翻车。把验证集的 val_batch 图和混淆矩阵也打开,看漏检的是漏了大多数还是只漏了小目标,这决定下一步是补数据还是调门限。
4. YOLO训练吸烟检测避坑:小目标、坏标注和误检的5条翻车记录
数据集类项目的血泪经验,往往不在训练代码里,而在标签和场景分布里。下面这几条是我在这个方向上反复踩过的坑,按“现象 → 原因 → 解决”记录,每条都值得你在跑之前先对照一遍。
4.1 漏标严重:连自己数得清的框,模型一个都学不会
现象:训练完 mAP 看起来不低,但推理时同一个场景里总有烟头漏检。回到训练集检查,发现不少图片里明明有人拿着烟,标签文件里却没有这个框。
原因:吸烟检测数据集的标注对象是“烟头或烟支”,目标太小,标注员很容易漏。人脸的框可以看到脸就标,烟头必须放大到像素级才看得清,漏标率天然比行人检测高几个百分点。训练时这些漏标区域会被当作背景,模型在相同位置学习到“这里是背景”,推理时自然不敢输出框。
解决:训练前必须做可视化抽检,漏标率超过一定比例就把图挑出来重标。重标时建议统一标注策略:是标“烟支整体”还是“烟头燃烧点”,两种策略对应不同业务需求,全数据集必须保持一致。采用我自己固定下来的规则:标注燃烧的烟头端,并在烟支清晰可辨认时把整根烟框住,宁可框略大,不要框一半。
4.2 烟头只有十几个像素:640 输入下特征不够,框在嘴和手之间漂
现象:模型能“感知”到嘴巴附近有东西,但输出的框一会儿落在手上,一会儿落在嘴上,置信度在0.2到0.4之间摇摆,死活稳定不下来。
原因:1080p 监控画面里,烟头经常只占十几个像素。yolo 下采样到 640 尺寸后,这个目标只剩几个像素宽,特征图上的语义信息几乎为零,模型只能靠“手在嘴边”这个上下文猜,所以框的位置随上下文偏移。
解决:实测最有效的一招是提高输入分辨率,imgsz 从 640 提到 960,小目标召回能明显上涨。显存受限时改用 yolov8n 加高分辨率,比用 s 但低分辨率效果更好。另外,把小目标区域切图训练——离线把图片按滑动窗口切片、对应标签裁切后单独训练,是监控场景的常见做法,代价是切图会产生大量无目标子图,需要在数据处理时过滤。
4.3 负样本缺失:背景被当成“不吸烟”,推理时到处是误报
现象:在测试视频里,模型把棒棒糖、手指、甚至衣服上的花纹都框成吸烟目标,误检多到没法用。
原因:很多现成吸烟检测数据集只标注“烟”的正样本,所有不吸烟的画面全靠背景图撑。如果压缩包里全是人拿着烟的近景,模型看到的负样本就只有“手没拿东西”,没看过“手拿棒棒糖”“手拿笔”“手指弯曲”这些近似场景,推理时就会把一切棒状物当烟。
解决:在训练集里补负样本,不要求负样本是空场景,关键是包含容易混淆的物体。挑几十张手拿笔、棒棒糖、吸管、火柴的近景图,不标注任何框,作为纯背景加入训练。模型只有见过“手拿棒棒糖不是烟”,上线后才不会被这类目标骗到。另外训练时 data yaml 里不用给负样本单开类别,保持单类正样本、背景为负即可。
4.4 class_id 顺序错位:损失正常下降,预测输出却驴唇不对马嘴
现象:训练过程 loss 曲线正常,mAP50 也不低,但推理时模型把所有目标都标成“吸烟”,另一类目标一个都出不来。
原因:yaml 里的 names 顺序和 txt 里的 class_id 对应关系错了。比如标注数据里 class_id=1 代表“正常”,yaml 里却把索引1写成了“smoking”,模型在训练时不会报错,只会默默把这两个语义学反。这是 labelimg 打标完 YOLO 格式的数据最常见的一类问题,标注工具自定义类别顺序时,许多人没看 txt 就把 yaml 按自己想象的顺序写了。
解决:训练前把 2.2 里统计出的类别分布打印出来,按 class_id 数值从0开始核对每类语义。如果你用 labelimg 重新打标,注意 classes.txt 文件里的顺序就是 class_id 顺序,别在导出后手动调整名字。
4.5 数据划分泄漏:连续帧同时进训练和验证,精度虚高到不敢信
现象:训练时验证集 mAP 很高,一到真实监控视频立刻打折,漏检和误检都成倍增加。
原因:不少数据集从视频里抽帧,连续几帧几乎一样。随机按 8:2 划分时,同一段视频的相邻帧会被同时分进训练集和验证集,验证集约等于“开卷考试”,模型见过的画面再考一遍,自然拿高分。这个现象在吸烟检测数据集里尤其普遍,因为烟头小、背景单一,模型背场景比学目标容易得多。
解决:划分数据必须按“视频片段”或“场景”划分,而不是按图片编号随机切。切分前先看一眼数据文件名,如果文件名带时间戳或视频ID,就按这个名字的粒度分组再划分。没有分组信息时,可以按图片相似度做去重,把高度相似帧从验证集移除,宁可验证集小一点,也要保证模型没见过这些画面。
5. 部署前最后一关:用测试图和置信度门限把模型调到能上线
训练完成不等于能上线。吸烟检测在真实场景里的误报代价很高——频繁报警会让管理人员直接关掉系统。所以部署前,我会专门跑一批“难测试图”,把置信度门限和 NMS 参数实打实地调一轮。
先写个批量预测脚本,统计每张图的检测数量并保存画框结果:
from ultralytics import YOLO from pathlib import Path model = YOLO('runs/detect/train/weights/best.pt') out = Path('result') out.mkdir(exist_ok=True) for img_path in sorted(Path('test_samples').glob('*.jpg')): results = model.predict(str(img_path), conf=0.30, iou=0.7) for r in results: print(img_path.name, '目标数:', len(r.boxes)) r.save(filename=out / img_path.name)这段脚本把 test_samples 目录下所有图片过一遍模型,逐个输出检测数量并保存框画图。跑完先做第一轮人工判断:哪些图是漏检,哪些是误检,分别属于哪一类。
然后针对置信度门限做一张小表,把门限从 0.15 到 0.60 各跑一遍:
| conf | 效果观察 | 适用场景 |
|---|---|---|
| 0.15 | 小目标多召回,误报明显变多 | 报警后可人工复核,漏报代价高的场景 |
| 0.30 | 常规默认值,误报与漏报相对均衡 | 通用场景起步值 |
| 0.45 | 误报少,只认高置信特征 | 无人值守、自动处罚类场景 |
| 0.60 | 只输出非常明确的吸烟动作 | 需要绝对低误报的展示场景 |
这里没有唯一正确答案,取决于业务更受不了漏报还是误报。我的习惯是先固定在漏检率可接受的最低门限,然后用负样本集把误报压下去。之前在类似场景里吃过亏:一开始为了少漏检把门限压到 0.05,结果系统一天弹出几百条假报警,最后不得不重新设计门限和逻辑。后来我总结出一条规则:监控报警场景优先保证“误报低于每日1条”,再在阈值范围内追求召回。每调整一次门限,就把当天采集的测试视频整段跑一遍,统计真实触发率。最后提一句,部署时把模型导出 ONNX 或用 TensorRT 加速,能在保持相同门限效果下把推理耗时降下来。希望这些流程和踩坑记录帮到你少走几步弯路。
本文还有配套的精品资源,点击获取