ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

9442张吸烟检测数据集:YOLO训练全流程拆解与避坑指南

9442张吸烟检测数据集:YOLO训练全流程拆解与避坑指南 简介面向从事目标检测和计算机视觉的工程师与学生提供公共场所与室内场景吸烟行为识别数据集共9442张图像统一标注“吸烟”单一语义目标覆盖商场、地铁、办公楼、餐厅、医院、学校等数十类真实场景类别边界清晰可直接用于YOLO系列模型训练与验证。资源包共2000个文件其中1998个XML标签文件承载标注信息另含PDF与Markdown说明文档整体约466.87MB目录结构简洁便于批量读取和二次开发。数据按约7:1.5:1.5划分训练、验证与测试集配套文档详细阐述采集设备、光照分类、标注质控和难点案例能帮助减少数据准备踩坑。数据集已在控烟平台、医院巡检等场景实测验证YOLOv8n模型mAP0.5约78.3%误报率低于2.7%推理速度稳定在42FPS以上工程落地价值较高。已有57人学习下载适合需要快速启动吸烟检测项目的开发者参考。1. 吸烟检测数据集9442张标注图能做多少事公共场所的吸烟行为检测一直是YOLO算法落地里比较尴尬的场景算法开源的一大把真正能用的训练数据却少得可怜。网上能找到的吸烟数据集要么是国外街头场景要么标注的是香烟本身而不是吸烟动作拿来做室内监控识别漏检率能高到让人怀疑模型是不是坏了。这份9442张的吸烟行为目标检测数据集标注类别就是“吸烟”这一类场景集中在公共场所和室内基本覆盖了楼道、电梯间、餐厅、办公室、会议室、卫生间这类真实摄像头位。对做安防监控、智慧楼宇、校园宿舍管理的开发者来说它最直接的价值是不用再从零标注省掉的不是几周而是几个月的时间成本。我拆这份数据集时最关心三件事标注框贴合度怎么样、场景分布会不会太单一、负样本有没有被处理过。这三件事直接决定模型训练出来是能上线还是只能在测试集上自嗨。如果你是打算用YOLOv5或YOLOv8训练吸烟检测模型的工程师这篇笔记会把数据集的构成、训练前的检查步骤、格式转换脚本和典型踩坑点都拆开讲透。2. 数据构成与标注质量先看场景分布再看标注规范2.1 数据集的整体构成与场景覆盖9442张图标注类别只有“吸烟”一个这意味着它是一个单类别目标检测数据集。单类别的好处是背景干净、任务聚焦坏处是你没法靠多类别互相约束来降低误检——比如同时检测“人”和“吸烟”可以用人的检测框去过滤掉落在人体之外的吸烟误检。所以拿到这份数据后第一件事不是急着训练而是先看它的场景构成是否匹配你的实际部署环境。从常见公共场所吸烟检测项目的拍摄来源看这类数据集通常包含以下场景室内走廊与楼梯间、公共卫生间、办公室工位、会议室、食堂与餐厅、地下车库、网吧与棋牌室。不同场景的光线和背景差异很大——卫生间瓷砖反光、走廊灯光昏暗、办公室有窗户自然光这些都会直接影响YOLO算法的特征提取效果。建议你用以下方式快速统计场景分布import os from collections import Counter img_dir smoking_dataset/images scene_counter Counter() for img_name in os.listdir(img_dir): # 按文件名前缀区分场景通常是 scene_xxx_001.jpg 的命名方式 scene img_name.split(_)[0] scene_counter[scene] 1 for scene, count in scene_counter.most_common(): print(f{scene}: {count}张)场景分布的意义在于如果你的部署环境是学校宿舍走廊而数据集里大量样本是餐厅和棋牌室那训练出来的模型在宿舍走廊上的表现会打折扣。虽然YOLO算法本身有较强的泛化能力但训练数据和部署数据之间的分布差异太大时mAP会明显下降。标注质量方面需要检查的是标注框是“紧贴”还是“宽松”。吸烟行为检测的标注框通常有三种画法只框烟头、框手部到嘴部区域、框包含头肩的完整上半身。第三种画法对模型最友好因为它给足了上下文信息。第一种画法最难学因为烟头在画面里往往只有几十个像素。拿到数据集后我一般会随机抽200张左右用可视化脚本确认标注框的画法一致性这是判断一份标注数据能不能直接用的关键。2.2 标注格式与兼容性检查这份数据集最常见的标注格式是YOLO格式的txt文件每张图对应一个同名txt每行是一个目标class_id x_center y_center width height坐标值是相对于图片宽高的归一化比例。这种格式的好处是直接适配YOLOv5和YOLOv8的目录结构要求省去转换步骤。# 某张训练图的标注内容示例YOLO格式 0 0.5321 0.4187 0.2135 0.3246 0 0.7812 0.6398 0.1842 0.2865第一列0表示类别ID因为只有一个类别所以永远是0。后面四列分别是归一化后的中心点x、中心点y、框宽、框高。拿到数据集后我建议先写一个脚本做基础检查看有没有坐标值小于0或大于1的情况以及标注框面积是否异常小。import os label_dir smoking_dataset/labels error_list [] for label_file in os.listdir(label_dir): with open(os.path.join(label_dir, label_file), r) as f: lines f.readlines() for line in lines: parts line.strip().split() if len(parts) ! 5: error_list.append((label_file, 字段数不为5)) continue _, cx, cy, w, h parts cx, cy, w, h map(float, [cx, cy, w, h]) # 归一化坐标必须在0-1之间 if not (0 cx 1 and 0 cy 1 and 0 w 1 and 0 h 1): error_list.append((label_file, f坐标越界: {line.strip()})) # 标注框面积过小可能是误标注或无效标注 if w * h 0.0001: error_list.append((label_file, f标注框过小: {line.strip()})) if error_list: print(f发现 {len(error_list)} 处问题前10条) for item in error_list[:10]: print(item) else: print(标注文件基础检查通过)这个检查脚本很基础但能过滤掉最常见的低级错误。坐标越界的标注会导致训练时loss直接变成NaN标注框过小的情况则意味着该目标可能只有几个像素让模型去学这种特征几乎等于浪费时间。实际项目里我还遇到过txt文件内容是空的——图片存在但没有对应标注这在数据划分时会造成训练样本和标注文件不匹配的问题后面会详细说。2.3 基于场景特点的模型选型建议对于吸烟检测这类小目标为主的场景YOLO算法的选型有一些规律。如果是室内固定摄像头画面中人的占比通常比较大吸烟动作的可辨识区域——手部到嘴部——在画面里能有足够的像素YOLOv5s或YOLOv8n这类轻量模型就能跑出不错的效果。如果是走廊全景摄像头人离镜头远烟头和手部区域很小就需要考虑YOLOv8m甚至更大的模型或者在预处理阶段做图像分块。从计算资源角度看我一般建议先拿YOLOv8n跑一版baseline看mAP和漏检率到底是什么水平再决定要不要换更大的模型。轻量模型在小目标上的表现很多时候不是模型容量不够而是数据本身的问题——标注框画太大把背景学进去了或者训练时的马赛克增强把小目标变得更小。YOLOv5和YOLOv8都默认开启马赛克数据增强这对小目标检测其实是把双刃剑具体怎么调在后面的训练配置章节会说。3. 训练前必做数据校验、格式转换与划分策略3.1 数据校验用脚本快速排查坏图和标注不匹配9442张图听起来数量不小但实际项目中图片损坏、标注缺失、图片与标注错位的情况并不少见。一个合格的工程师拿到任何数据集第一步永远是校验而不是训练。我习惯先检查图片能不能正常读取以及每张图片对应的标注文件是否存在且非空。import os from PIL import Image img_dir smoking_dataset/images label_dir smoking_dataset/labels corrupted_imgs [] missing_labels [] empty_labels [] for img_name in os.listdir(img_dir): img_path os.path.join(img_dir, img_name) # 尝试打开图片打不开就是损坏 try: img Image.open(img_path) img.verify() except Exception: corrupted_imgs.append(img_name) continue # 检查对应标注文件是否存在 label_name os.path.splitext(img_name)[0] .txt label_path os.path.join(label_dir, label_name) if not os.path.exists(label_path): missing_labels.append(img_name) else: if os.path.getsize(label_path) 0: empty_labels.append(img_name) print(f损坏图片: {len(corrupted_imgs)}) print(f缺少标注: {len(missing_labels)}) print(f空标注文件: {len(empty_labels)})空标注文件的情况需要单独说明如果一张图确实画面里没有人吸烟空标注是正常的应该作为负样本参与训练。但如果大量图片都是空标注说明数据集的标注覆盖率有问题。标注覆盖率直接决定模型的召回率负样本太少模型会倾向于把什么都判成吸烟负样本太多又会让模型过于保守。3.2 统一目录结构转换成YOLOv8的标准数据集组织方式无论下载的数据集原本是什么目录结构训练前都要统一成YOLO算法框架认可的形式。以YOLOv8为例标准结构是smoking_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yaml如果你的数据集下载下来是图片和标注分开的但train/val目录还没划分可以用脚本自动完成。这里我采用随机划分的方式训练集、验证集、测试集按8:1:1的比例分配import os import random import shutil random.seed(42) # 固定随机种子保证每次划分结果一致 img_dir smoking_dataset/images label_dir smoking_dataset/labels output_dir smoking_dataset_yolo train_ratio, val_ratio 0.8, 0.1 for split in [train, val, test]: os.makedirs(f{output_dir}/images/{split}, exist_okTrue) os.makedirs(f{output_dir}/labels/{split}, exist_okTrue) all_imgs os.listdir(img_dir) random.shuffle(all_imgs) n_train int(len(all_imgs) * train_ratio) n_val int(len(all_imgs) * val_ratio) n_test len(all_imgs) - n_train - n_val splits {} splits[train] all_imgs[:n_train] splits[val] all_imgs[n_train:n_train n_val] splits[test] all_imgs[n_train n_val:] for split, img_names in splits.items(): for img_name in img_names: src_img os.path.join(img_dir, img_name) dst_img os.path.join(output_dir, images, split, img_name) shutil.copy(src_img, dst_img) label_name os.path.splitext(img_name)[0] .txt src_label os.path.join(label_dir, label_name) if os.path.exists(src_label): dst_label os.path.join(output_dir, labels, split, label_name) shutil.copy(src_label, dst_label) print(ftrain: {len(splits[train])} 张) print(fval: {len(splits[val])} 张) print(ftest: {len(splits[test])} 张)固定随机种子是这里的关键操作。数据集划分如果不固定种子每次运行脚本得到不同的训练集和验证集实验结果之间就没有可比性你在调参时根本分不清效果提升是来自模型改动还是数据变动。另一个值得注意的点是这张划分脚本保留了空标注文件——前面说了空标注对应的是负样本是模型学会不误检的重要素材。3.3 编写data.yaml类别配置与路径设置目录结构就绪后接下来是data.yaml配置文件。这个文件告诉YOLOv8去哪里读图、读标注、有几个类别、类别名是什么。路径建议用绝对路径或相对于项目根的路径尽量避免使用相对层级太深的路径容易在换机器后失效。# data.yaml path: /home/user/smoking_dataset_yolo # 数据集根目录改成你自己的路径 train: images/train val: images/val test: images/test nc: 1 names: [smoking]这段配置有几点要注意。path是根目录train和val都是相对于根目录的路径YOLOv8会自动拼接。nc: 1表示单类别如果你的标注文件里出现了类别ID大于0的值训练时索引会越界报错需要在上一步的校验脚本里就拦住。names列表的索引对应标注文件第一列的class_id这里只有smoking一个类别。写完后可以用一行命令验证配置和数据的匹配情况python -c from ultralytics import YOLO; model YOLO(yolov8n.pt); print(model.task)这条命令的作用是快速确认Ultralytics环境已经装好、YOLO模型能正常加载。如果环境没装会直接报ModuleNotFoundError方便你在训练前排查环境问题而不是在训练中途才发现。4. 避坑与排查吸烟检测训练中的五个典型问题4.1 训练loss为NaN标注坐标越界或图片损坏现象训练刚开始或者中途loss突然变成NaN终端输出里出现nan字样训练进程不再收敛。原因最常见的是标注文件的坐标值超出了[0,1]范围比如x_center写成1.2YOLO算法在计算边界框损失时就会出现NaN。其次是图片本身损坏读取出来的数组包含无效像素值前向传播时梯度爆炸。我拆这份数据集时就遇到过某些标注文件的坐标因为格式转换出错变成负数的情况恰好是第2章检查脚本能拦截的类型。解决用3.1节的校验脚本全量跑一遍把越界坐标和损坏图片过滤掉。如果只是少部分文件直接删除对应图片和标注即可如果占比超过5%说明标注文件在制作或导出过程中出了系统性问题需要重新生成或手动修复。4.2 模型在室内场景漏检严重训练数据里夜间和暗光样本太少现象训练时mAP看起来不错但部署到实际室内环境后在走廊昏暗灯光下大量吸烟行为检测不到。原因吸烟检测数据集的采集场景往往以白天或光线充足的室内为主夜间、暗光、背光场景覆盖不足。YOLO算法对光线变化的鲁棒性有限训练时没见过暗光下烟头微弱亮光的特征推理时就无法激活对应特征。另一个容易忽略的点是摄像头的红外夜视模式输出的灰度图和训练数据的彩色图差异很大。解决我一般会在训练前做数据增强补偿把训练集中的部分图像随机降低亮度、增加高斯噪声、转成灰度图模拟暗光环境。这部分增强可以用Albumentations库在训练脚本里动态执行比离线生成增强图片更节省磁盘空间。如果增强后效果还不够就需要补充暗光场景的真实数据。4.3 标注框尺寸差异过大模型对不同距离的目标表现不稳定现象同一个模型检测近处的吸烟行为准确率很高但画面远端半米大小的目标持续漏检。原因数据集里的标注框尺寸分布跨度大——特写镜头的标注框可能占图片面积的30%远景镜头的标注框可能只占1%不到。YOLO算法的多尺度检测头分别负责不同尺寸的目标如果某个尺度的训练样本过少对应检测头的参数就训练不充分。这本质上是一个样本分布不均衡问题。解决统计标注框宽高的分布用直方图确认三个尺度上的样本比例。常见做法是按标注框面积将训练样本分层采样或者用图像分块策略把大图切割成多张小图训练。YOLOv8本身有自适应锚框计算但如果某一个尺度的目标样本太少自适应也救不回来。4.4 误检率高把喝水、吃东西也判定为吸烟现象模型在测试集上precision很高但实际推理时误报频发喝水、吃零食、用手擦嘴等动作都被识别成吸烟。原因吸烟行为的视觉特征本质是“手部靠近嘴部”这个特征和喝水、吃东西高度相似。如果训练数据中负样本——有人但没吸烟的图片——太少模型就没有机会学到“手靠近嘴但不一定在吸烟”的区分边界。还有一个原因是标注框画得太宽松把嘴部和手部周边的大量背景也框进去了模型实际学的是“人脸附近有手”这个特征。解决补充负样本是根本方案从无吸烟行为的监控视频里抽帧放入训练集但保留空标注。如果暂时没有额外数据可以尝试调整标注框紧缩——把标注框从包含头肩改为只框手部到嘴部区域强制模型关注局部细节。我在自己的项目里试过只做这一项改动误检率能降30%以上。4.5 训练速度慢到无法接受图片尺寸和batch大小没匹配好现象训练一个epoch要很久GPU利用率上不去显卡占用只有30%到40%。原因吸烟检测数据集的图片分辨率通常偏高很多监控摄像头输出的是1920x1080甚至2560x1440。YOLOv8默认imgsz是640但如果训练时没有显式指定框架可能按原始分辨率进行resize到640这个过程在高分辨率图片上耗时明显。另一个瓶颈是batch大小设得太小导致GPU并行计算能力没发挥。解决显式设置imgsz640让所有图片统一缩放到640x640同时调大batch size到显卡显存能承载的最大值。对于RTX 3060 12G这类显卡YOLOv8n可以试试batch32YOLOv8m建议从batch16开始调。如果显存不够考虑开启rectTrue让同一batch内的图片采用接近的长宽比填充减少无效计算。这些参数写在训练命令里后面会给一版可以直接套用的模板。5. 一份可直接套用的训练配置与验证技巧5.1 超参数模板从YOLOv8n开始做基线数据集校验、格式转换、目录划分全部做完后训练命令本身其实很简单。关键是几个超参数怎么设。这里给出一份我调试吸烟检测数据集时用的基线配置拿过去改一下数据路径就能跑yolo train \ datasmoking_dataset_yolo/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch32 \ lr00.01 \ lrf0.01 \ momentum0.937 \ weight_decay0.0005 \ warmup_epochs3 \ workers8 \ device0逐个解释关键参数的取值依据。modelyolov8n.pt是加载预训练权重用COCO上训好的权重做迁移学习初始化比从零训练收敛快很多尤其在数据量不到一万张的情况下预训练权重的价值非常大。lr00.01是初始学习率这个值在YOLOv8里是官方默认推荐值对大多数检测任务都适用。warmup_epochs3表示前3个epoch学习率从很小的值逐渐升到设定值目的是避免模型在训练初期因为权重还没稳定就大步更新导致震荡。workers8是数据加载线程数如果机器CPU核数少或者硬盘读取速度慢可以降到4这个参数不影响训练精度只影响数据读取速度。5.2 训练过程监控mAP曲线和PR曲线怎么看训练启动后不要只是干等要学会看训练日志和曲线。YOLOv8训练过程中会在终端实时打印每个epoch的box_loss、cls_loss、mAP50、mAP50-95等指标同时把曲线图保存到runs/detect/train目录下。对吸烟检测这个场景我最关注的是mAP50和recall召回率。# 训练结束后用YOLO自带的验证接口快速评估测试集 from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) results model.val( datasmoking_dataset_yolo/data.yaml, splittest, imgsz640, conf0.25, iou0.5 ) print(fmAP50: {results.box.map50:.4f}) print(fmAP50-95: {results.box.map:.4f}) print(fprecision: {results.box.mp:.4f}) print(frecall: {results.box.mr:.4f})这段代码在训练结束后对测试集做一次独立评估。conf0.25是置信度阈值低于这个值的目标会被过滤掉iou0.5是NMS的IoU阈值控制重叠框的合并力度。评估时有一个容易被忽略的点训练时用的验证集和最终评估用的测试集不要混用否则mAP会虚高。这就是第3章为什么要把数据集划分成三份而不是两份的原因。验证完如果recall低说明漏检多优先调整置信度阈值和检查小目标增强策略如果precision低说明误检多优先检查负样本质量和标注框紧密度。这两个指标的权衡贯穿整个调参过程。5.3 可视化推理用自己的图片或视频验证效果模型训练完最后一步是可视化验证。我不会只看mAP数字一定会拿几张训练时没见过的真实场景图做推理肉眼确认检测框的位置和置信度是否合理。这一步能发现数值评估反映不出的问题比如检测框偏移、同一目标重复出框、对背景纹理的误检等。from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) results model.predict( sourcetest_images/, conf0.25, iou0.5, saveTrue, imgsz640 )推理输出会保存到runs/detect/predict目录逐一翻看检测结果。我一般会重点看两类图片一类是画面中有多人但只有一人在吸烟的检查模型有没有把没吸烟的人也框出来——框出来了说明误检另一类是烟头很小、距离较远的检查能不能检测到——没检测到说明漏检。这两类样本是检验吸烟检测模型能不能上线的试金石。关于置信度阈值还有一条经验吸烟检测的落地场景通常要求高召回宁可多报几个烟头让人工复核也不要漏掉真正的吸烟行为。所以实际部署时我经常把conf从训练评估用的0.25降到0.15或0.1以换取更高的召回率。这个参数在部署推理时调不需要重新训练。降阈值后误检会变多但配合一个简单的人体检测框过滤——吸烟框必须在人体框内——就能把大部分误检滤掉。这是监控场景里很常见的组合方案。拆完这份数据集我最大的一个教训是数据集的真实价值不在“9442张”这个数字上而在标注框质量和场景分布。第一次用类似数据集训练时我跳过了校验步骤直接开训结果花了两天时间在一个标注坐标越界的问题上绕圈——loss反复跳到NaN又被我当成是学习率问题反复调翻了大半天日志才定位到是数据问题。从那以后我每次拿到数据集都强制先跑一遍校验脚本再谈训练绝不跳过。希望这份拆解笔记能帮你绕过我已经踩过的坑把时间花在真正有价值的调参上希望帮到你。本文还有配套的精品资源点击获取
返回列表