
简介这是一套面向目标检测学习者和开发者的专用数据集围绕吸烟行为与烟盒识别场景构建适用于yolo系列各版本算法的模型训练与验证。包内含2117张带标签图像共2000个文件以xml与txt两种标注格式为主分别对应VOC格式和yolo格式标签满足不同训练框架的输入要求。其中yolo格式标签包含类别索引、归一化中心坐标及宽高信息可直接用于yolov5、yolov8、yolov9、yolov7、yolov10、yolo11等模型训练。数据集已完成训练集与验证集划分并附有配置文件data.yaml可帮助使用者省去手动整理标签和划分数据的时间快速进入模型迭代环节。压缩包整体约241.97MB文件命名规范便于按图像索引查找对应标注。适合刚接触目标检测的学生进行算法实践也适合有检测需求的技术人员直接作为微调训练的基础数据。目前已有138人浏览学习该项目。1. yolo算法训练吸烟检测这2117张带标签图像到底能干什么yolo算法做吸烟和烟盒检测训练数据是最容易卡住的一环。这个标题里的数据集2117张图像带标签压缩包打开就是按YOLO约定组织的图像和同名txt标签适合直接拿来训练一个用于禁烟场景的检测模型。禁烟区监控、工地安全生产、食堂后厨巡查这类需求在真实项目里出现频率很高而吸烟检测又恰好是目标小、场景杂、实时性要求高的典型任务很适合作为yolo算法入门到落地的完整练手项目。2117张图不算多但胜在类别集中、目标明确训练得当完全可以在边缘设备上跑到实时。这篇笔记按我实际做此类项目的过程来写从解压验数开始到训练调参、结果分析、踩坑修复最后给出一个能直接用于验收的落地技巧。2. 先读懂数据集再动手YOLO标签格式与图像配套关系拿到这种压缩包第一件事不是急着训练而是把内部结构彻底摸清。很多数据集本身没问题反而是在使用环节因为格式理解偏差导致训练结果稀烂。YOLO系列的标签体系从v5到v8基本一致读懂这套约定后面所有操作才顺。2.1 压缩包内的文件结构与命名规律解压后常见布局是图像文件与同名txt标签放在一起或者分了images和labels两个顶层目录。无论哪种核心规律都是一张图像对应一个同名txt比如IMG_20240101_100001.jpg对应IMG_20240101_100001.txt。mkdir -p smoking_dataset cd smoking_dataset unzip ../yolo算法-吸烟-烟盒数据集-2117张图像带标签.zip find . -type f | head -20 find . -name *.txt | wc -l find . -name *.jpg -o -name *.png | wc -l先用head看文件命名样例再用wc -l统计txt和图像数量是否都能对上2117。如果txt数量比图像少说明存在空标签图或者漏标样本如果txt比图像多多半混入了没用的说明文件。这个步骤虽然简单却能提前发现最基础的数据完整性问题。2.2 YOLO txt标签的四列数字到底在描述什么YOLO标签每一行代表一个目标框格式固定为五列类别id、归一化中心点x、归一化中心点y、归一化宽、归一化高。注意这三个关键点坐标必须除以图像宽高做归一化宽高是框的实际尺寸除以图像宽高不是像素值也不是右下角坐标。列序字段取值范围说明第1列class_id0 ~ n-1对应类别列表中的索引第2列x_center0.0 ~ 1.0框中心点x除以图像宽度第3列y_center0.0 ~ 1.0框中心点y除以图像高度第4列width0.0 ~ 1.0框宽除以图像宽度第5列height0.0 ~ 1.0框高除以图像高度举个例子一张1920x1080的图像里某个烟盒框左上角在(480, 270)、右下角在(960, 540)那标签就是0 0.375 0.375 0.25 0.25。中心点x0.375对应(480960)/2/1920宽0.25对应(960-480)/1920。如果看到txt里有大于1的数值说明标注工具导出时没归一化或者图像被缩放后标签没跟着更新这是训练翻车的高频源头。2.3 用Python脚本核对标签坐标是否越界手工检查2117个txt不现实我习惯写一个校验脚本一次性扫完所有标签统计类别分布、越界框、空标签、零尺寸框四个指标。import os from pathlib import Path label_dir Path(labels) stats {files: 0, boxes: 0, empty: 0, out_of_range: 0, zero_size: 0} class_count {} for txt in label_dir.glob(*.txt): stats[files] 1 lines [l.strip() for l in txt.read_text().splitlines() if l.strip()] if not lines: stats[empty] 1 continue for line in lines: parts line.split() if len(parts) ! 5: print(f格式错误: {txt.name}: {line}) continue cid, cx, cy, w, h int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) class_count[cid] class_count.get(cid, 0) 1 stats[boxes] 1 if not (0 cx 1 and 0 cy 1 and 0 w 1 and 0 h 1): stats[out_of_range] 1 print(f越界: {txt.name}: {line}) if w 0 or h 0: stats[zero_size] 1 print(f零尺寸: {txt.name}: {line}) print(stats) print(类别分布:, class_count)这个脚本输出三类关键信息空标签文件数用于判断漏标程度越界框用于定位需要修复的坏标签类别分布则直接反映数据平衡性。如果越界框超过10个建议用脚本自动裁剪修正后再训练而不是带着脏数据硬跑。2.4 类别平衡与样本分布2117张图的训练/验证划分吸烟和烟盒检测通常有两个类别类别id一般定义为0: smoking、1: cigarette_box但不同数据集作者定义可能相反。以校验脚本统计出的类别分布为准先确认目标类别覆盖。如果烟盒框数量明显多于吸烟行为框属于正常情况因为一个画面里烟盒往往静止出现在桌面而吸烟行为需要手持卷烟靠近面部标注成本更高、样本更难采。训练/验证划分不能简单用random.shuffle后按8:2切那样容易把同一段监控视频的连续帧同时分进训练集和验证集导致验证指标虚高。常见做法是先把图像按场景或时间段分组再从组里按比例抽样到验证集确保验证集覆盖白天、夜晚、室内、室外等不同条件。import random from pathlib import Path images list(Path(images).glob(*.jpg)) random.seed(42) random.shuffle(images) val_ratio 0.2 val_count int(len(images) * val_ratio) val_set set(images[:val_count]) for img in images: label_path Path(labels) / (img.stem .txt) if img in val_set: print(f{img} - val) else: print(f{img} - train)这里最关键的是固定随机种子保证每次划分结果一致方便后续复现实验结果。数据量只有2117张时验证集大约423张这个量级已经够观察模型泛化趋势但如果发现验证集里全是某一类场景就需要手动调整划分逻辑。3. 把数据集喂给yolov8训练自己的模型目录整理与关键参数数据验完下一步就是把数据集按YOLO约定的目录结构重组写配置文件跑通第一次训练。这个阶段常见的误区是直接拿原始目录开训结果路径对不上报错或者参数设置不合理导致训练几百轮也不收敛。3.1 按YOLO约定重建数据集目录结构虽然YOLOv8支持直接指定图像目录和标签目录但最稳妥、最通用的还是标准结构images/train、images/val、labels/train、labels/val四个目录。后续换用YOLOv5、YOLOv8甚至mmrotate时都能复用不用再折腾。cd smoking_dataset mkdir -p images/train images/val labels/train labels/val python move_split.py # 按2.4节划分结果移动文件移动的核心逻辑是把属于训练集的图像放进images/train对应txt放进labels/train验证集同理。务必保证图像和标签文件名完全一致只差扩展名。任何文件名的后缀差异都会导致训练时报unable to find label错误。3.2 data.yaml的类别名与路径配置YOLO训练依赖一个data.yaml描述数据集路径和类别名称这个文件的路径一定要写绝对路径或者相对于当前工作目录的路径写错一个字训练都会直接中断。# data.yaml path: /home/user/smoking_dataset # 数据集根目录绝对路径 train: images/train val: images/val nc: 2 names: 0: smoking 1: cigarette_boxpath字段是根目录train和val相对于根目录填写不需要加斜杠前缀。nc必须与names列表长度一致这里最容易犯的错是类别数写成3但只定义了两个名字训练会不报错但mAP全部乱套。另外建议在训练前确认数据集的类别定义和这个文件完全一致因为不少数据集在发布时会调整类别顺序旧模型权重直接复用会串号。3.3 用YOLOv8命令行跑通第一次训练的最小命令目录和配置文件就绪后一条命令就能启动训练。第一次跑不要追求精度目标是验证数据链路完整、loss正常下降。cd ~/smoking_dataset yolo detect train datadata.yaml modelyolov8n.pt epochs100 imgsz640 batch16 patience20modelyolov8n.pt是nano版本预训练权重速度最快适合先验证流程。epochs100对2117张图的规模来说足够了后面可以根据验证集曲线决定是否提前停止。patience20是早停耐心值验证集mAP连续20轮不提升就自动终止既省时间又防止过拟合。3.4 关键训练参数imgsz、batch、epochs的合理取值训练参数里影响最大的三个是输入分辨率、批次大小和训练轮数。参数之间互相牵制不能单独拍脑袋。参数本项目建议值说明imgsz640吸烟和烟盒属于中小目标640能保留足够细节再大对显存压力大batch16单卡8GB显存可跑显存不够降到8或4epochs1002117张图100轮足够配合早停防过拟合patience20连续20轮无提升即停optimizerauto让YOLOv8自动选优化器省心device0有GPU指定卡号CPU训练要大幅减少epochsbatch大小和imgsz强相关。同样的8GB显存imgsz640时batch16勉强能跑如果imgsz提到1280batch得降到4甚至2。对小目标检测宁可用640分辨率保证batch别太小因为过小的batch会让BN层统计不稳定训练震荡明显。如果目标是部署到Jetson之类的边缘设备训练完还要用val模式重新测评确认输入分辨率从640降到416后mAP损失是否可接受。4. 训练结果怎么看mAP、混淆矩阵与漏检定位训练跑完不等于工作结束更难的环节是把训练日志里的数字翻译成模型真实能力。很多人看到results.csv里mAP50到了0.85就觉得成了结果部署到现场才发现漏检一堆。这一章讲怎么看训练产物怎么定位模型到底哪里不行。4.1 results.csv与confusion_matrix.png的判读方法训练结束后在runs/detect/train目录下会生成一系列文件其中results.csv是每一轮的完整指标记录confusion_matrix.png是逐类别的预测结果分布图。先看这两个文件能够快速判断模型训练是否健康。results.csv里需要重点盯三个量train/box_loss、val/box_loss和metrics/mAP50-95。训练早期的box_loss急速下降是正常的但如果训练中段出现训练loss持续下降而验证loss反弹就是典型的过拟合信号。对吸烟检测这种样本量不大的项目过拟合通常在第60到80轮之间出现这也是设置patience20的原因。confusion_matrix.png的行列含义是行是真实类别列是预测类别对角线越亮越好。关注几个典型异常真实smoking样本被预测成cigarette_box说明两个类别在视觉上产生了混淆常见原因是标注框把持烟的手和烟盒同时包进去了。background列偏亮说明存在大量误把背景当目标的假阳性通常是光照复杂或标注时背景区域有相似纹理。4.2 用val模式批量导出预测框并与真值对比命令行工具本身就支持验证和导出但默认输出是图片不方便程序化分析。我习惯在val时同时导出预测标签文件再做逐图对比。yolo detect val modelruns/detect/train/weights/best.pt datadata.yaml imgsz640 save_jsonTrue conf0.25save_jsonTrue会把所有验证图像的预测结果输出为predictions.json里面记录了每个框的类别、置信度和坐标。拿到这个文件后可以用脚本批量找出置信度高于0.8却仍然预测错的框这些通常是标注质量问题也可以找出置信度低于0.3但真值存在的框这些是模型识别能力不足的难例。import json import numpy as np with open(runs/detect/val/predictions.json) as f: preds json.load(f) for p in preds: if p[score] 0.3 and p[category_id] 0: print(f低置信度吸烟预测: {p[image_id]}, score{p[score]:.3f})这个脚本的价值在于把几百张验证图的漏检集中筛出来直接看到模型在哪些角度、哪些光照条件下表现差。如果低置信度漏检集中在侧脸、背光、手持烟在身体下方这类位置说明训练数据的角度覆盖不够后续补充数据要往这些方向倾斜。4.3 对吸烟场景的专项评估把白天和夜晚分开看全局mAP是一个平均值会掩盖模型在不同场景下的巨大差异。吸烟检测在真实项目中通常同时覆盖白天和夜晚而夜间的红外或低照度画面与白天差异极大混在一起评估往往看不出问题。常见做法是在验证时按图像文件名或目录分组分别计算mAP。比如文件名里带night的图像归到夜间组其余归到白天组分别跑了val之后对比两组指标。如果夜间组的mAP50比白天低0.2以上那这个模型直接部署到夜间场景必然翻车需要针对性补充夜间图像或使用图像增强手段拉高夜间亮度后再推理。分组评估还有一个用途判断训练集本身是否偏科。如果验证集里夜间图像只有几十张即使夜间mAP很高也不能说明鲁棒因为统计量太小。这时候应该回到第2章的划分逻辑重新审视验证集在场景维度上的覆盖度。5. 避坑用这个数据集训练最常见的6个翻车现场这部分是我实际跑这类数据集时反复遇到的坑每一条都对应具体现象、原因和解决路径。提前看完能省下不少盲目调试的时间。5.1 标签越界导致训练loss直接nan现象训练到第几轮box_loss突然变成nan之后全部指标消失训练中止。 原因标签坐标或宽高中出现了大于1或小于0的数值YOLO在计算IoU时对非法框做归一化导致梯度爆炸。 解决回到2.3节的校验脚本把所有越界框找出来。对坐标略超边界的框用脚本把数值裁剪到0~1区间对完全错误的行直接删除该行。修复后重新划分数据集再训练一般不会再出现nan。5.2 类别id错位导致烟盒识别成吸烟现象模型训练完成后烟盒预测结果全部显示成吸烟类别或者反过来。 原因数据集的类别定义顺序与data.yaml不一致。比如原始标签里0代表烟盒、1代表吸烟而data.yaml里0写了smoking、1写了cigarette_box模型学到的映射关系整体颠倒。 解决训练前用2.3节的脚本输出类别分布再对照data.yaml逐项确认。训练完用confusion_matrix.png里的对角线顺序反推类别映射一旦发现错位重新调整data.yaml的names顺序或批量改写txt第一列数值二选一不能两边同时改。提示类别id错位在训练时不会报任何错误loss也正常下降只有到验证阶段看混淆矩阵或者实际推理时才会暴露务必在训练前确认。5.3 随机划分导致验证集全是同场景现象训练时mAP波动剧烈随机抽查验证集图像发现几乎全是同一时间段或同一机位的画面指标看起来很高但换个场景全废。 原因划分数据时没有按场景分组连续帧被均匀切进了训练集和验证集模型其实已经见过验证集内容。 解决按第2.4节的思路先把图像按目录或文件名前缀分组从每个组内分别抽取20%进验证集。如果数据集没有明显的分组标识至少按采集时间戳排序后再间隔抽样避免连续帧出现在两侧。5.4 过拟合到烟盒包装上的文字和图案现象训练集mAP接近1验证集mAP卡在0.75上下不再上升且训练轮数越多差距越大。 原因烟盒本身是印刷品上面有大量品牌文字和颜色块模型学了这些纹理特征而不是烟盒的几何轮廓。尤其当训练集里烟盒品牌过于集中时换个牌子就检测不出来。 解决优先用YOLOv8自带的数据增强打开mosaic1.0和copy_paste0.5增加训练样本的背景多样性。另一个有效手段是降低训练轮数并配合早停让模型在学到文字特征前停止。如果验证集mAP仍然上不去需要补充不同品牌烟盒图像做数据增强或迁移学习初始化而不是继续硬训。5.5 光照剧烈变化和遮挡导致漏检现象白天场景mAP50有0.9傍晚或逆光场景掉到0.6。手指夹烟遮挡烟盒、烟在嘴边被手挡住的情况几乎全部漏检。 原因吸烟和烟盒都属于小目标光照变化会直接影响边缘和纹理特征提取遮挡又让目标的有效像素进一步减少。 解决在训练前把数据增强中的hsv_h、hsv_s、hsv_v适当调大模拟不同光源条件。同时把imgsz从640提到768或896小目标在更高分辨率下的特征更充足能明显缓解遮挡漏检。显存不够就降低batch到8不要因此放弃分辨率。5.6 漏标样本让mAP虚低现象模型推理时肉眼看着明明检出了香烟但验证指标的recall一直上不去看了标注才发现某些图像里的烟盒压根没被标注。 原因数据集作者在标注时漏标了部分目标导致模型预测结果被判为假阳性拉低precision和recall。 解决用4.2节的方法导出预测框把置信度大于0.6但真值中不存在的框筛出来加上原图人工确认。确认是漏标就手动补充这些标签修正一批漏标样本往往比增加训练轮数更能提升mAP。真实项目中我给这种数据集补标过大约100个漏框mAP50提升了接近5个点性价比远高于调参。6. 把模型放进真实场景的一个验收技巧滑窗推理与置信度阈值标定模型训练和评估都通过后离上线还差最后一步在接近真实场景的数据上验证实时性和误报率。我曾经直接把训练好的模型丢到监控视频流里跑结果发现1080p画面下延迟高、误报多回头排查才发现问题不在模型结构而在输入分辨率和阈值没按场景标定。我的习惯做法是先录一段30分钟的现场视频从中均匀抽帧用滑窗方式跑推理。这里有个关键参数目标检测在1080p大画面下直接缩放输入小目标会被压没。正确做法是把原图像切分成2x2或3x3的重叠子图分别推理再合并结果。import cv2 from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) cap cv2.VideoCapture(scene.mp4) fps cap.get(cv2.CAP_PROP_FPS) frame_count 0 window 640 overlap 100 while True: ret, frame cap.read() if not ret: break frame_count 1 if frame_count % fps: # 每秒取一帧 continue all_boxes [] for y in range(0, frame.shape[0], window - overlap): for x in range(0, frame.shape[1], window - overlap): crop frame[y:y window, x:x window] res model(crop, conf0.4, imgsz640) for box in res[0].boxes: x1, y1, x2, y2 map(int, box.xyxy[0].tolist()) all_boxes.append((x x1, y y1, x x2, y y2, float(box.conf[0]), int(box.cls[0]))) # 按坐标聚类去重同一目标多个窗口的重复框取最高置信度 print(fframe {frame_count}: {len(all_boxes)} candidates)这个脚本的价值在于检验模型在真实监控视角下的表现尤其是窗口重叠区域的重复检测问题。多个窗口框到同一个目标时取置信度最高的框作为最终输出。如果滑窗推理的mAP和单图推理差距过大说明模型对目标尺度的泛化能力不足需要回看第4章的分组评估结果。收尾时我会把现场跑出的置信度阈值记录下来作为交付给部署同事的参数依据。不同的现场环境对误报容忍度不同有的项目宁可漏检也不接受误报那就把conf从0.4提到0.6有的项目需要尽可能捕捉吸烟行为就降低到0.25并接受部分误报。这个标定过程往往比训练本身更花时间但决定了模型能不能真正留在现场跑下去。这些经验是我踩过几次坑才积累的希望帮到你。本文还有配套的精品资源点击获取