简介:这份吸烟行为检测数据集面向计算机视觉开发者与目标检测学习者,可用于训练和验证抽烟场景下的目标识别模型,适用于安防监控、公共场所行为分析等应用方向。资源共包含1983个文件,其中991张jpg原始图片与991个同名txt标注文件一一对应,标注采用yolov8格式,另附1个yaml配置文件用于定义数据集路径与类别信息,压缩包整体约44.7MB,结构规整、开箱即用。据描述,该数据集在训练后平均识别率可达88.3%,可作为基线参考,帮助读者快速评估模型效果并在此基础上做数据增强或调参优化。目前已有110人学习下载,适合需要现成标注数据开展吸烟检测实验、课程设计或算法对比的初中级视觉开发者使用。
1. 吸烟数据集与 YOLOv8:991 张原始图片、88.3% 识别率背后的真实落地账
手上只有 991 张原始图片,标注还是 YOLOv8 格式,平均识别率标称 88.3%——这个数字放在论文里不算亮眼,但放在真实项目里,它意味着一个能跑通、能复现、能继续往上堆数据的起点。很多做吸烟行为检测的团队卡在第一步:公开数据集要么场景单一,要么标注格式不统一,要么图片数量虚标。这个数据集的价值不在于它有多大,而在于它已经完成了从原始图片到 YOLOv8 可训练格式的转换,省掉了最耗时的清洗和格式对齐环节。如果你正在做工地、加油站、化工厂或公共场所的吸烟行为识别,或者想找一个中等规模的目标检测数据集练手 YOLOv8 的完整训练流程,这套 991 张图片的吸烟数据集是一个务实的起点。88.3% 的识别率不是天花板,而是基线——知道基线在哪,才知道后面该往哪个方向调。
2. 从 991 张原始图片到 YOLOv8 可训练格式:数据集结构拆解与校验
2.1 吸烟数据集的目录结构与标注格式确认
拿到一个标称 YOLOv8 格式的数据集,第一件事不是急着跑训练,而是确认目录结构是否符合 Ultralytics 的约定。常见做法是根目录下分images和labels两个文件夹,各自再分train、val,有时还有test。图片和标注文件必须同名,只是扩展名不同——图片是.jpg或.png,标注是.txt。
# 查看数据集根目录结构 find smoking_dataset -maxdepth 3 -type d | sort # 预期输出类似: # smoking_dataset # smoking_dataset/images # smoking_dataset/images/train # smoking_dataset/images/val # smoking_dataset/labels # smoking_dataset/labels/train # smoking_dataset/labels/val如果目录层级不对,YOLOv8 训练时会直接报找不到标签文件。另一个容易翻车的地方是图片和标签数量不匹配——比如images/train有 800 张,labels/train只有 795 个.txt,说明有 5 张图漏标了。这种漏标在训练时不会报错,但会让模型学到“有些目标不存在”的错误信号。
import os img_dir = "smoking_dataset/images/train" lbl_dir = "smoking_dataset/labels/train" imgs = {os.path.splitext(f)[0] for f in os.listdir(img_dir) if f.endswith(('.jpg', '.png'))} lbls = {os.path.splitext(f)[0] for f in os.listdir(lbl_dir) if f.endswith('.txt')} missing_labels = imgs - lbls missing_images = lbls - imgs print(f"图片数: {len(imgs)}, 标签数: {len(lbls)}") print(f"有图无标签: {len(missing_labels)} 个 -> {list(missing_labels)[:5]}") print(f"有标签无图: {len(missing_images)} 个 -> {list(missing_images)[:5]}")这段脚本做的是集合差运算。imgs和lbls分别收集图片和标签的文件名(去掉扩展名),然后做差集。如果missing_labels不为空,说明这些图片没有对应的标注文件,训练时会被 YOLOv8 自动跳过或报错,取决于版本。missing_images不为空则说明有多余的标签文件,虽然不影响训练,但说明数据管理混乱,建议清理。
2.2 YOLOv8 标注格式的逐行校验与类别确认
YOLOv8 的标注格式是每行一个目标,格式为class_id x_center y_center width height,所有坐标都是归一化到 0-1 之间的浮点数。吸烟数据集通常只有一个类别,class_id就是 0。但有些数据集在转换时会把类别 ID 写成 1 或者别的数字,导致训练时类别数对不上。
import os lbl_dir = "smoking_dataset/labels/train" class_ids = set() bad_lines = [] for fname in os.listdir(lbl_dir): if not fname.endswith('.txt'): continue with open(os.path.join(lbl_dir, fname), 'r') as f: for line_num, line in enumerate(f, 1): parts = line.strip().split() if len(parts) != 5: bad_lines.append((fname, line_num, "字段数不是5")) continue cls_id = int(parts[0]) class_ids.add(cls_id) coords = [float(x) for x in parts[1:]] if any(c < 0 or c > 1 for c in coords): bad_lines.append((fname, line_num, f"坐标越界: {coords}")) print(f"出现的类别ID: {class_ids}") print(f"异常行数: {len(bad_lines)}") for item in bad_lines[:10]: print(item)这段代码遍历所有标签文件,检查每行是否恰好 5 个字段,类别 ID 是否统一,坐标是否在 0 到 1 之间。如果class_ids输出是{0},说明类别统一;如果是{0, 1},说明标注时把吸烟和非吸烟都标了,但你的data.yaml里只写了一个类别名,训练时就会报索引越界。坐标越界的情况在手工标注中不常见,但在自动转换脚本里容易出现——比如把像素坐标直接写进去忘了归一化。
2.3 data.yaml 的写法与路径陷阱
YOLOv8 训练依赖一个data.yaml文件,里面指定训练集、验证集路径和类别名。这个文件看起来简单,但路径写错是新手最常见的翻车点。
# smoking_data.yaml path: /home/user/smoking_dataset # 数据集根目录,绝对路径最稳 train: images/train val: images/val nc: 1 names: 0: smokingpath建议写绝对路径,train和val写相对于path的子路径。很多人把train写成smoking_dataset/images/train,结果 YOLOv8 会拼成path/smoking_dataset/images/train,直接找不到。nc是类别数,必须和names的长度一致。如果只有吸烟一个类别,nc: 1,names里只写0: smoking。如果后面要加“打电话”类别,nc改成 2,names加一行1: phone,同时标签文件里对应目标的class_id要改成 1。
提示:改完
data.yaml后,先用yolo checks命令确认 Ultralytics 能正确解析路径,再开始训练。这个命令会打印出数据集的基本信息,包括图片数量、类别分布,能提前暴露大部分配置问题。
3. 用 YOLOv8 在 991 张图片上跑通训练:参数怎么设、显存怎么省
3.1 从预训练权重出发的最小训练命令
991 张图片属于小数据集,不建议从零开始训练。常见做法是加载 YOLOv8 的预训练权重,在吸烟数据集上做微调。这样即使图片少,模型也能借助预训练学到的通用特征快速收敛。
# 最小训练命令,适合 8GB 显存左右的显卡 yolo detect train \ data=smoking_data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ device=0 \ project=smoking_runs \ name=exp1model=yolov8n.pt用的是 nano 版本,参数量最小,训练最快,适合先跑通流程。epochs=100对小数据集来说通常够用,如果验证集损失在 60 轮后还在降,可以加到 150。imgsz=640是 YOLOv8 的默认输入尺寸,如果你的图片分辨率远大于 640,比如 1920×1080,可以尝试imgsz=960,但显存占用会明显增加。batch=16在 8GB 显存上跑yolov8n加imgsz=640基本不会爆显存,如果报 CUDA out of memory,先降到 8 或 4。
3.2 关键训练参数的含义与调整逻辑
YOLOv8 的训练参数有几十个,但真正影响吸烟数据集识别率的主要是这几个:学习率、数据增强、置信度阈值和 IoU 阈值。
| 参数 | 默认值 | 作用 | 调整建议 |
|---|---|---|---|
| lr0 | 0.01 | 初始学习率 | 小数据集可降到 0.001,避免震荡 |
| lrf | 0.01 | 最终学习率系数 | 保持默认,除非训练后期损失不降 |
| mosaic | 1.0 | 马赛克增强概率 | 小数据集建议保持 1.0,增加样本多样性 |
| fliplr | 0.5 | 水平翻转概率 | 吸烟场景左右对称,保持默认 |
| conf | 0.25 | 推理置信度阈值 | 漏检多就降到 0.15,误检多就升到 0.4 |
| iou | 0.7 | NMS 的 IoU 阈值 | 吸烟目标重叠少,保持默认 |
lr0从 0.01 降到 0.001 是我在 1000 张以下数据集上常用的做法。默认的 0.01 适合大数据集,小数据集上容易在初期就跳过最优解。mosaic增强会把四张图拼成一张,变相增加 batch 内的样本多样性,对 991 张这种规模的数据集很有帮助,不建议关掉。conf和iou是推理时的参数,训练时不用改,但验证时会影响 mAP 的计算,如果发现验证集 mAP 波动大,可以固定conf=0.25再看。
3.3 训练过程监控与 88.3% 识别率的复现条件
训练启动后,Ultralytics 会在smoking_runs/exp1下生成results.csv和若干曲线图。results.csv里记录了每轮的train/box_loss、val/box_loss、metrics/mAP50等指标。88.3% 的识别率通常指的是mAP50,也就是 IoU 阈值为 0.5 时的平均精度。
import pandas as pd df = pd.read_csv("smoking_runs/exp1/results.csv") df.columns = df.columns.str.strip() # 列名可能带空格 best_epoch = df['metrics/mAP50'].idxmax() print(f"最佳 epoch: {best_epoch + 1}") print(f"最佳 mAP50: {df['metrics/mAP50'].max():.4f}") print(f"对应训练损失: {df['train/box_loss'].iloc[best_epoch]:.4f}") print(f"对应验证损失: {df['val/box_loss'].iloc[best_epoch]:.4f}")这段代码读取训练日志,找到mAP50最高的那一轮。如果最高值在 88% 左右,说明复现成功。如果只有 70% 多,先检查data.yaml的类别数是否正确,再看验证集图片是否和训练集有重叠——有些数据集划分不严谨,验证集图片在训练集里出现过,会导致 mAP 虚高。991 张图片按 8:2 划分,训练集约 793 张,验证集约 198 张,这个比例在小数据集上比较合理。如果验证集少于 150 张,mAP 的波动会很大,88.3% 这个数字的置信度也会打折扣。
注意:训练结束后,
runs/detect/smoking_runs/exp1/weights/下会有best.pt和last.pt。best.pt是验证集 mAP 最高的权重,推理时用这个,不要用last.pt。
4. 吸烟检测推理与部署:从单张图片测试到批量验证
4.1 用训练好的权重跑单张图片推理
训练完成后,第一件事是拿几张验证集里的图片跑推理,肉眼确认检测框是否合理。YOLOv8 的命令行推理很简单,但输出目录和保存格式有几个细节要注意。
yolo detect predict \ model=smoking_runs/exp1/weights/best.pt \ source=smoking_dataset/images/val \ conf=0.25 \ save=True \ project=smoking_infer \ name=val_checksource可以指向单张图片、一个目录或一个视频文件。save=True会把带检测框的图片保存到smoking_infer/val_check下。conf=0.25是置信度阈值,低于这个值的检测框会被过滤掉。如果发现漏检明显,比如远处的小目标没框出来,可以把conf降到 0.15 再跑一次对比。如果误检多,比如把红色衣服的褶皱当成吸烟,就把conf升到 0.4。
4.2 批量推理与结果统计脚本
单张看只能发现明显问题,要量化评估还需要批量跑验证集并统计检测结果。下面这个脚本会遍历验证集所有图片,统计每张图的检测框数量和置信度分布。
from ultralytics import YOLO import os import numpy as np model = YOLO("smoking_runs/exp1/weights/best.pt") val_dir = "smoking_dataset/images/val" det_counts = [] conf_scores = [] for fname in os.listdir(val_dir): if not fname.endswith(('.jpg', '.png')): continue results = model(os.path.join(val_dir, fname), conf=0.25, verbose=False) boxes = results[0].boxes det_counts.append(len(boxes)) if len(boxes) > 0: conf_scores.extend(boxes.conf.cpu().numpy().tolist()) print(f"验证集图片数: {len(det_counts)}") print(f"平均每张检测框数: {np.mean(det_counts):.2f}") print(f"有检测结果的图片占比: {sum(1 for c in det_counts if c > 0) / len(det_counts) * 100:.1f}%") print(f"置信度均值: {np.mean(conf_scores):.4f}, 中位数: {np.median(conf_scores):.4f}")model(...)返回的是一个列表,每张图对应一个Results对象。boxes里包含检测框的坐标、置信度和类别。det_counts记录每张图的检测框数量,如果平均每张只有 0.5 个框,说明漏检严重;如果平均有 5 个以上,说明误检多。conf_scores收集所有检测框的置信度,均值在 0.6 以上说明模型对检测结果比较自信。这个脚本跑完,你对 88.3% 这个数字在验证集上的实际表现就有了直观感受。
4.3 导出 ONNX 模型与推理速度测试
如果后续要部署到边缘设备,比如 RK3588 或 Hi3516CV610,通常需要把 PyTorch 权重导出成 ONNX 格式。YOLOv8 的导出命令很直接,但导出时的imgsz和opset版本会影响后续转换。
yolo export \ model=smoking_runs/exp1/weights/best.pt \ format=onnx \ imgsz=640 \ opset=12 \ simplify=Trueopset=12是兼容性比较好的版本,RK3588 的 NPU 工具链对 opset 12 支持较完善。simplify=True会调用 onnx-simplifier 做图优化,减少冗余节点。导出后的best.onnx可以用onnxruntime做推理速度测试,在 CPU 上跑一遍验证集,看平均单张耗时。如果 CPU 上单张超过 200ms,部署到边缘设备前需要先做量化或剪枝。
5. 吸烟数据集训练避坑:991 张图片踩过的 5 个坑
5.1 验证集 mAP 虚高:图片重复与场景泄漏
现象:训练日志里mAP50很快冲到 90% 以上,但拿新图片推理时漏检严重。
原因:验证集和训练集有重叠图片,或者验证集图片和训练集来自同一段视频的相邻帧,场景高度相似。991 张图片如果是从视频抽帧来的,相邻帧差异极小,模型相当于在“背答案”。
解决:用感知哈希或简单的像素差检查训练集和验证集是否有重复。如果有,按视频来源划分,同一段视频的帧只出现在训练集或验证集之一。
import hashlib from PIL import Image import os def phash(img_path, hash_size=8): img = Image.open(img_path).convert('L').resize((hash_size, hash_size), Image.LANCZOS) pixels = list(img.getdata()) avg = sum(pixels) / len(pixels) return ''.join('1' if p > avg else '0' for p in pixels) train_hashes = {} for f in os.listdir("smoking_dataset/images/train"): if f.endswith(('.jpg', '.png')): h = phash(os.path.join("smoking_dataset/images/train", f)) train_hashes.setdefault(h, []).append(f) duplicates = 0 for f in os.listdir("smoking_dataset/images/val"): if f.endswith(('.jpg', '.png')): h = phash(os.path.join("smoking_dataset/images/val", f)) if h in train_hashes: duplicates += 1 print(f"验证集 {f} 与训练集 {train_hashes[h]} 高度相似") print(f"疑似重复: {duplicates} 张")5.2 类别 ID 不统一导致训练报错
现象:训练启动时报IndexError: index 1 is out of bounds for dimension 0 with size 1。
原因:data.yaml里nc: 1,但标签文件里出现了class_id=1。这种情况常见于从其他格式转换时,原始标注有“吸烟”和“非吸烟”两个类别,转换脚本没做映射。
解决:用 2.2 节的校验脚本找出所有class_id,如果有多余类别,要么在data.yaml里补上names,要么把标签文件里的class_id统一改成 0。
5.3 图片尺寸不一致导致训练中断
现象:训练到一半报RuntimeError: stack expects each tensor to be equal size。
原因:数据集中混入了不同尺寸的图片,YOLOv8 的 dataloader 在默认配置下会尝试统一尺寸,但如果某张图的长宽比极端(比如 2000×200),resize 后填充过多,可能触发异常。
解决:训练前统一把图片 resize 到相近尺寸,或者检查是否有损坏的图片文件。用 PIL 打开每张图确认尺寸。
from PIL import Image import os for f in os.listdir("smoking_dataset/images/train"): if f.endswith(('.jpg', '.png')): try: img = Image.open(os.path.join("smoking_dataset/images/train", f)) w, h = img.size if w < 100 or h < 100 or w > 4000 or h > 4000: print(f"异常尺寸: {f} -> {w}x{h}") except Exception as e: print(f"损坏图片: {f} -> {e}")5.4 显存不足:batch 和 imgsz 的取舍
现象:CUDA out of memory. Tried to allocate 2.00 GiB。
原因:batch=16加imgsz=640在 6GB 显存以下的显卡上容易爆。YOLOv8 训练时的显存占用和batch × imgsz²成正比。
解决:优先降batch,从 16 降到 8 或 4。如果降 batch 后显存还是不够,再降imgsz到 512 或 416。降imgsz会损失小目标检测能力,吸烟目标如果只占图片很小区域,不建议低于 512。
5.5 推理时检测框偏移:归一化坐标还原错误
现象:用best.pt推理时检测框位置明显偏移,框到了背景上。
原因:如果自己写了后处理代码,把 YOLOv8 输出的归一化坐标当成了像素坐标,或者忘了乘以原图宽高。
解决:YOLOv8 的Results对象里boxes.xyxy已经是像素坐标,boxes.xywhn才是归一化坐标。直接用results[0].plot()画图不会出错,自己写后处理时注意区分。
6. 把 88.3% 再往上推:小数据集上的三个提效技巧
991 张图片的吸烟数据集,88.3% 的 mAP50 是一个合理的基线。如果想把识别率再往上推几个点,有三个方向值得试,成本从低到高。
第一个是针对性数据增强。YOLOv8 默认的 mosaic 和 fliplr 是通用增强,对吸烟场景来说,烟雾的形态和颜色变化是关键。可以在训练配置里加hsv_h=0.015、hsv_s=0.7、hsv_v=0.4,让模型对烟雾的色调和亮度变化更鲁棒。如果数据集里吸烟目标偏小,把scale=0.5调低到 0.3,减少随机缩放导致的小目标丢失。
第二个是负样本挖掘。991 张图片里如果全是吸烟正样本,模型没见过“类似吸烟但不是吸烟”的负样本,误检率会偏高。从验证集里挑出误检的图片,把误检区域标成背景,加入训练集重新训练。这个过程迭代两三轮,mAP50 通常能涨 2 到 3 个点。
第三个是模型集成。用yolov8n、yolov8s、yolov8m分别训练,推理时对三个模型的检测框做加权 NMS。代价是推理速度变成单模型的三倍,但如果部署在服务器端而不是边缘设备,这个代价可以接受。我一般会在yolov8n和yolov8s之间做集成,yolov8m在 991 张图片上容易过拟合,验证集 mAP 反而不如小模型。
from ultralytics import YOLO import numpy as np models = [ YOLO("smoking_runs/exp_n/weights/best.pt"), YOLO("smoking_runs/exp_s/weights/best.pt"), ] def ensemble_predict(img_path, conf=0.25): all_boxes = [] for model in models: results = model(img_path, conf=conf, verbose=False) boxes = results[0].boxes for i in range(len(boxes)): xyxy = boxes.xyxy[i].cpu().numpy() conf_val = float(boxes.conf[i].cpu().numpy()) all_boxes.append((xyxy, conf_val)) # 按置信度排序,简单加权 NMS 可以用 torchvision.ops.nms all_boxes.sort(key=lambda x: x[1], reverse=True) return all_boxes[:10] # 返回前10个高分框 boxes = ensemble_predict("smoking_dataset/images/val/smoke_001.jpg") for xyxy, conf in boxes: print(f"框: {xyxy}, 置信度: {conf:.3f}")这段代码把两个模型的检测框合并后按置信度排序,实际部署时需要用torchvision.ops.nms做非极大值抑制,这里只展示合并逻辑。集成后的 mAP50 通常比单模型高 1 到 2 个点,但推理耗时翻倍,适合对精度要求高、对速度不敏感的场景。
最后说一个我自己的习惯:每次改完训练参数或数据增强,不要只看最终的 mAP50,把results.csv里的val/box_loss曲线也画出来。如果验证损失在后期开始上升,而训练损失还在降,说明过拟合了,这时候加数据比调参更有效。991 张图片的吸烟数据集,如果能把误检样本挖出来补标 200 张,比换更大的模型管用。希望帮到你。
本文还有配套的精品资源,点击获取