
简介面向智能安防与火灾隐患监测等场景的YOLOv8吸烟行为检测完整方案整合了训练好的烟雾检测模型、五千余张LabelImg标注的吸烟图片数据以及训练曲线可直接用于二次开发、模型微调也可作为目标检测实战的学习基线。资源包共两千个文件压缩后约三百四十六MB其中txt标注与配置文件、md说明文档、Python脚本及YAML模型配置占据主要部分另附C推理代码和shell脚本覆盖数据标注、模型训练、部署推理全流程适合目标检测初学者与需要快速验证方案的开发者。目前已有七百二十八人浏览学习项目目录结构清晰包含各类训练曲线图便于对照指标进行调参和复现。除了预训练权重还提供xml与txt两种格式的标签文件免去手动标注的时间成本同时配套PDF说明文档可帮助读者更快跑通吸烟检测实验并迁移至自有数据集。1. 吸烟行为检测为什么我推荐直接用这套 YOLOv8 方案做安防、工地管理、校园协管这类场景的开发者多半都遇到过同一个诉求用摄像头自动识别吸烟行为不等烟雾飘起来就能预警。传统方法要么靠红外传感器要么靠人工盯屏成本高、漏报多。基于深度学习的视觉检测是目前最务实的路线而 YOLOv8 又是在精度和推理速度之间平衡得最好的那一档。这份资源直接给到了训练好的权重、完整数据集和可运行的检测代码拿到的不是半成品是已经能跑出框的模型。适合两类人一是想快速验证效果、给甲方看 demo 的从业者二是想自己重新训练、把检测能力嵌进现有业务系统的工程师。它的核心价值在于省掉了最耗时的数据采集和标注环节让你把精力放到业务逻辑和部署上。2. 数据集拆解合成数据不会自己变真标注质量决定模型上限2.1 数据集的目录结构、标注格式与场景构成拿到资源后第一步不是跑训练而是先看清数据集长什么样。这套数据集的目录结构非常规整符合 YOLO 系列项目的标准组织方式dataset/ ├── images/ │ ├── train/ # 训练集图片 │ ├── val/ # 验证集图片 │ └── test/ # 测试集图片 ├── labels/ │ ├── train/ # 训练集标注文件 │ ├── val/ # 验证集标注文件 │ └── test/ # 测试集标注文件 ├── data.yaml # 数据集配置文件 └── classes.txt # 类别列表标注文件是 YOLO 格式的 TXT 文件每一行代表一个目标框五个数值依次是类别 ID、归一化中心点 X 坐标、归一化中心点 Y 坐标、归一化宽度、归一化高度。比如0 0.4521 0.3885 0.1423 0.2756这就是某个吸烟行为目标框的标注内容类别 ID 为 0框中心在图幅的 45.21% 横向位置和 38.85% 纵向位置框的宽度约占整个图宽的 14.23%高度约占图高的 27.56%。这里有一个值得注意的点数据集里标注的目标框不只是烟本身而是覆盖了手部到嘴部区域。这个细节很关键。如果只标烟头模型学到的特征是一个发光的白色小点容易跟环境里的反光混淆标成手部嘴部区域模型学到的是手靠近嘴且嘴部附近有异物的完整姿态鲁棒性会好很多。验证集和测试集是独立划分的没有和训练集混在一起这意味着训练过程中验证集能真实反映模型的泛化能力不会出现验证集泄露这种低级问题。2.2 数据分布的偏向性与适用边界我翻了一遍数据集后发现这套数据的主要来源是网络公开场景图和部分模拟场景图场景集中在室内办公区、工厂车间、校园走廊这些监控摄像头常见部署位置。但有一个问题必须提前说清楚数据集中没有夜间红外场景、没有强逆光场景、也没有雨天玻璃遮挡的场景。也就是说这个模型在常规室内光线下表现优秀但如果你要部署在室外吸烟区或者夜间环境需要自己补充数据做二次训练。另一个偏差点是拍摄角度。数据集中大部分是平视或略俯视角度摄像头安装高度比较低。如果是架在 6 米高的立杆上往下俯拍画面里人的头部占比变小、手部动作变形模型的检测置信度会明显下降。这个我在实际项目里遇到过把模型直接用在高度监控场景检测率从 90% 掉到 70% 左右。解决思路有两个一是用这个预训练权重做迁移学习加入自己场景的俯拍数据微调训练量不大几百张标注图就能见效二是调整摄像头安装位置尽量让画面接近训练集的分布——但这往往受现场条件限制所以更推荐前一种办法。2.3 data.yaml 和类别文件的使用要点YOLO 项目训练前需要确认 data.yaml 里的路径配置。打开文件看内容大致是这样的train: dataset/images/train val: dataset/images/val test: dataset/images/test nc: 1 names: [smoking]nc 为 1 表示只有一个类别names 列表里就是该类别对应的名称。这里有两个坑第一train 和 val 的路径是相对路径你在哪个目录下执行训练命令它就会去找哪个目录下的 dataset 文件夹换了项目路径就会报错第二names 里的类别名称必须和标签文件里的类别 ID 一一对应ID 0 对应 names[0]否则训练时类别会错位。我习惯把 data.yaml 改成绝对路径虽然迁移性差一些但至少不容易莫名其妙地报找不到图片的错误train: /home/user/projects/smoking_detection/dataset/images/train val: /home/user/projects/smoking_detection/dataset/images/val改完之后用一条简单命令验证路径和标注文件是否匹配。YOLOv8 提供了现成的检查脚本yolo detect train data/home/user/projects/smoking_detection/dataset/data.yaml epochs1 batch1跑一个 batch 的训练如果数据集路径有问题、标注文件解析失败这里就会直接报错。比训练到一半才发现数据没加载进去要省时间得多。3. 模型训练YOLOv8 参数调优与迁移学习策略3.1 为什么拿预训练权重直接跑会比从零训练更划算先看一下这份资源的模型架构。权重文件默认对应 YOLOv8n 或者 YOLOv8s 这类轻量级网络。两者的区别很好理解n 系列是 nano网络最浅、参数量最小、推理最快适合边缘设备s 系列是 small在精度上有明显提升但推理时间也会相应增加。在吸烟检测这个任务上我建议直接使用资源里训练好的模型做推理验证这是性价比最高的路径。原因有二你不需要额外花时间训练资源模型见过的数据量比自己临时凑数据大得多。如果一定要重新训练再考虑用预训练权重做迁移学习。刚入门的同学容易踩一个误区拿到数据集后不管三七二十一直接拿 YOLOv8s 从零开训。这里解释一下为什么我不推荐COCO 预训练权重已经学到了大量通用视觉特征——比如纹理、边缘、形状——这些特征在人体姿态、物体轮廓识别上是通用的。从零开始训练意味着模型需要自己摸索这些基础特征需要更多的数据、更长的训练时间而最终精度往往还不一定打得过迁移学习。3.2 训练脚本、超参数含义与常见配置如果你决定自己训练训练命令其实很简洁核心参数按下面的方式设置yolo detect train \ data/path/to/dataset/data.yaml \ modelyolov8s.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ workers8 \ device0 \ project/path/to/project \ namesmoking_experiment参数逐项说明一下这些参数不是随便填的每一项都影响训练结果的走向epochs100是训练轮数。100 轮在中小数据集上是比较稳妥的选择猫腻在于轮数太少学习不充分太多则容易过拟合。训练到后期观察 val_loss 曲线如果连续 20 轮不再下降就可以停了。imgsz640是输入图片的尺寸。YOLOv8 默认就是 640 乘 640这也是模型训练时用的分辨率。如果你用 416 训练推理时也要用 416否则模型输出异常用 640 以上的分辨率能提升小目标的检测能力但训练显存占用会线性增长。batch16是批量大小。这个值受显存限制极大。如果是 8GB 显存的卡batch 建议降到 8 或者 4显存不足时训练会直接报 CUDA out of memory。lr00.01是初始学习率默认值 0.01。迁移学习场景下我一般会调低到 0.005防止微调阶段权重震荡太剧烈把原来学好的特征给冲掉。workers8是数据加载线程数Windows 上经常因为多线程数据加载导致奇怪的报错改成 0 或 2 跑 CPU 加载会稳定很多。device0指定使用第一块 GPU。没有 Nvidia 显卡就改成devicecpu但训练时间会大幅拉长100 轮可能要跑十几个小时。还有一个容易被忽略的组合参数cache。如果显存够大加上cacheTrue会把图片提前缓存到内存大幅减少每个 epoch 读盘的时间yolo detect train \ data/path/to/dataset/data.yaml \ modelyolov8s.pt \ epochs100 \ imgsz640 \ batch16 \ cacheTrue3.3 迁移学习的数据规模与迭代策略迁移学习不是直接跑完整训练流程就行它的核心策略在于只微调最后几层的分类头而冻结前几层的特征提取骨干网络。YOLOv8 的训练脚本没有直接暴露冻结层数参数但你可以在训练前把模型加载进来手动冻结主干部分from ultralytics import YOLO model YOLO(yolov8s.pt) # 冻结 backbone 层只训练 head 部分 for name, param in model.model.named_parameters(): if cv1 in name or cv2 in name or stage in name: param.requires_grad False model.train( data/path/to/dataset/data.yaml, epochs50, imgsz640, batch16, lr00.005, )这段代码做的事情是遍历模型的全部参数把主干网络层名称包含 cv1、cv2、stage 的层的梯度关闭只让后面的检测头参与学习。为什么要这么做因为预训练骨干已经学会了通用的边缘、纹理、颜色特征这些特征在吸烟检测中同样有用不需要大幅度更新而检测头是任务相关的需要专门适配手部嘴部区域这种特征组合。冻结骨干训练能有效防止数据集太小导致的过拟合。数据量不同迭代策略也不一样。如果新增的补充数据只有一两百张50 轮微调就足够了跑多了容易过拟合到新数据上如果有上千张那就用第 3.2 节的完整训练配置100 轮起跑让模型充分学习新分布的数据特征。4. 训练结果分析与验证从指标曲线到权重选型4.1 训练日志、生成文件与实际效果评估训练跑完之后项目目录下会生成runs/detect/smoking_experiment文件夹里面最值得关注的是results.png和weights/目录。results.png包含了完整的训练曲线图重点看两个指标train/box_loss和val/box_loss。box_loss 是边界框回归损失衡量预测框和真实标注框的重合程度数值越低代表定位越准。如果训练集 loss 持续下降但验证集 loss 到了某个阶段开始回升这就是典型的过拟合信号——模型记住了训练集里的具体画面但没法泛化到新场景。weights/目录下有两个权重文件best.pt验证集上表现最好的权重通常用它做部署。last.pt最后一轮训练结束时的权重。很多新手容易搞反直接拿 last.pt 去跑推理结果发现效果不对劲。原因很简单最后一轮不一定是最优的一轮训练后期往往出现过拟合验证集指标已经开始反弹。用 best.pt 做推理验证前先跑一下官方自带的评估命令让指标说话yolo detect val \ modelruns/detect/smoking_experiment/weights/best.pt \ data/path/to/dataset/data.yaml输出里核心看三个数字mAP50IoU 阈值为 0.5 时的平均精度、mAP50-95从 0.5 到 0.95 的区间平均精度和Precision/Recall。吸烟检测属于单类别的二分类问题mAP50 达到 0.9 以上就算合格。要注意mAP50 高不代表实测好用因为它计算的是预测框和真实标注框重叠度超过 50% 即算正确在很多倾斜角度、遮挡场景下mAP50-95 更能反映模型的真实定位精度。4.2 在图片和视频上做一次端到端推理评估指标都是数值层面的最终还得亲眼看检测效果。这里提供一个标准推理脚本from ultralytics import YOLO model YOLO(runs/detect/smoking_experiment/weights/best.pt) # 检测单张图片 results model.predict( sourcetest_images/office.jpg, conf0.35, # 置信度阈值 iou0.5, # NMS 的 IoU 阈值 saveTrue, projectruns/detect, namedemo_inference, )几个参数结合实际场景说conf0.35置信度阈值。低于 0.35 的检测结果全部丢弃。想要更严格就把值调高到 0.5漏检会增加但误检减少想抓得更全就调到 0.25代价是背景误报变多。实际部署时我习惯采用动态阈值安装在室内固定摄像头位0.4 起步人脸抓拍机那种画面更干净的场景可以放到 0.3。iou0.5NMS 阶段两个重叠框合并的阈值。两个框的交并比超过 0.5 就会被合并成一个框。这个参数影响的是同一个目标被重复框选的问题一般不用调但如果画面里人多拥挤、相互遮挡严重可以把 iou 降到 0.4避免漏检。视频文件的推理同理把source指向视频文件路径即可代码不需要改动。推理结果会保存在runs/detect/demo_inference/目录下直接打开就能看到每帧画面中检测框的位置和置信度。4.3 损失函数曲线的正确读法训练过程里还有一个很容易被忽略的信号cls_loss和dfl_loss曲线。cls_loss是分类损失衡量模型区分吸烟和非吸烟的能力。dfl_loss是分布焦点损失负责让边界框回归更精确。在吸烟检测场景下如果cls_loss降得很慢往往不是模型能力问题而是数据里背景太单调导致的。数据集中大量图片是干净的室内环境模型很快学会了看见人或者没看见人的粗浅区分但对手里有什么东西这个细粒度特征学得不够。这时最有效的操作是补充负样本——没有吸烟行为的同场景人物照片让模型学会在有人但没吸烟的情况下保持静默。5. 模型导出与部署ONNX 转换、尺寸对齐和推理加速5.1 从 PyTorch 权重到 ONNX 的导出流程训练和验证都通过后就该考虑部署了。绝大多数实际项目不会直接拿 PyTorch 在服务器上跑推理而是导出成 ONNX 格式再用 ONNX Runtime 或 TensorRT 加载。导出命令很简单yolo export \ modelruns/detect/smoking_experiment/weights/best.pt \ formatonnx \ imgsz640 \ opset12opset12是 ONNX 算子集的版本号。注意导出时的imgsz必须和你训练时用的一致这里训练用了 640导出也必须是 640。如果训练时用的是 416导出用 640模型输出层的特征图尺寸会对不上推理结果直接错乱。导出完成后会生成 best.onnx 文件。用 ONNX Runtime 加载并做一次推理验证import onnxruntime as ort import numpy as np import cv2 # 加载 ONNX 模型 session ort.InferenceSession( runs/detect/smoking_experiment/weights/best.onnx, providers[CUDAExecutionProvider, CPUExecutionProvider], ) # 读取图片并预处理 img cv2.imread(test_images/office.jpg) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img cv2.resize(img, (640, 640)) img img.astype(np.float32) / 255.0 input_tensor np.transpose(img, (2, 0, 1))[None, ...] # 推理 outputs session.run(None, {session.get_inputs()[0].name: input_tensor})这里有个细节值得单独说明input_tensor的维度是(1, 3, 640, 640)通道在第二维即 CHW 格式。YOLOv8 训练时内部就是这么处理的用 OpenCV 读图默认是 HWC 格式必须用np.transpose把通道维度挪到前面否则模型输出的是显然错误的结果。这个坑我踩过不止一次实际排查时发现就是通道顺序没对齐画面看起来是正常的但模型输出的框飘到奇怪的位置。ONNX 输出的原始张量维度是(1, 84, 8400)。84 的含义是4 个边界框坐标 80 个 COCO 类别概率如果是单类别吸烟检测就是 4 1。8400 是 YOLOv8 在不同尺度特征图上生成的锚框总数需要再做一次后处理置信度阈值过滤 NMS才能得到最终的检测框。5.2 TensorRT 加速和端侧部署的参数边界如果部署在 Nvidia Jetson 或者带 Nvidia GPU 的服务器上可以把 ONNX 再转成 TensorRT 引擎推理速度能提升 2 到 3 倍trtexec --onnxbest.onnx \ --saveEnginebest.engine \ --fp16--fp16表示用半精度浮点数存储模型权重和计算。这里有一个需要注意的点开了 fp16 后推理速度提升明显但精度的损失需要实际验证。如果检测框的定位精度出现肉眼可见的偏移说明这个模型对 fp16 不友好退回 fp32 或者只对部分层做 fp16。Jetson 系列设备上部署时还要注意内存带宽限制。我看到有开发者在 Jetson Nano 上跑 YOLOv8s帧率只有 5 到 8 FPS原因是 Nano 只有 4GB 内存CPU 与 GPU 的数据拷贝开销太大。解法是改用 YOLOv8n 权重或者把输入尺寸从 640 降到 480牺牲部分小目标检测能力换取帧率。6. 避坑手册吸烟检测项目里最常见的六个坑与对策6.1 部署阶段置信度阈值乱调导致误报满天飞现象推理时把置信度阈值调到 0.1结果画面里空调指示灯、白墙反光点全部被识别成吸烟行为一帧画面能标出十几个框。原因阈值调太低模型把低置信度的预测结果也当成了有效输出。吸烟检测的类别特征和白色亮点高度相似阈值一旦低于 0.25误检率会指数级上升。解决生产环境置信度阈值不低于 0.35宁可漏掉一半嫌疑事件也要保证报警的准确率。在告警模块里再加一个同一目标连续 3 帧检出才触发报警的逻辑效果会更好。因为单帧误检是随机性的不太可能连续出现在同一个位置而真实吸烟行为通常持续数秒连续帧检测能过滤掉大量瞬时误报。6.2 训练集和验证集场景重叠指标虚高现象验证集 mAP50 到了 0.95但拿到现场实测识别率只有 60%差距巨大。原因数据划分时没有按场景隔离同一个场景的相似图片同时出现在训练集和验证集。模型在验证集上表现好本质上是在回忆训练时见过的画面而不是在理解未被见过的场景。解决重新划分数据集确保同一个场景的图片全部进入训练集验证集和测试集使用完全没有出现过的场景。划分维度可以是摄像头编号、房间号或拍摄时间段。我在划分时按场景名分组后随机分配保证同一场景只出现在一个集合里。6.3 数据不平衡让模型变成只报喜不报忧现象训练过程中 loss 一直在降但验证时发现大量漏检尤其是距离远、目标小的吸烟场景一个都检不出来。原因数据集里近景和特写图片占了绝大多数模型学会了大的目标框才代表吸烟对远景小目标没有足够的正样本学习。解决在数据增强阶段增加 Mosaic 增强和随机缩放。YOLOv8 在训练时会自动做 Mosaic 数据增强把四张图片拼接成一张目标框大小分布会更多样。也可以在数据准备阶段手动对小目标图片做拼接保证每个 batch 里都有小目标样本参与训练。6.4 ONNX 导出的尺寸不匹配模型输出全乱现象本地 PyTorch 推理正常导出 ONNX 后用 ONNX Runtime 推理检测框全部偏移或者完全错误。原因导出时 imgsz 设置和推理时预处理尺寸不一致。YOLOv8 的模型输出特征图尺寸是由输入尺寸决定的输入 640 时输出特征图尺寸与输入 416 时完全不对应后续的坐标解码自然全错。解决导出时的 imgsz 参数必须等于推理预处理时的 resize 尺寸同时还要保持宽高比。用cv2.resize时如果原图不是正方形先等比缩放到一边为 640另一边不足的部分用灰色填充而不是直接拉伸变形。我常用的一段预处理代码如下def letterbox(img, new_shape640, color(114, 114, 114)): shape img.shape[:2] ratio min(new_shape / shape[0], new_shape / shape[1]) new_unpad (int(round(shape[1] * ratio)), int(round(shape[0] * ratio))) img cv2.resize(img, new_unpad, interpolationcv2.INTER_LINEAR) dw new_shape - new_unpad[0] dh new_shape - new_unpad[1] top, bottom dh // 2, dh - dh // 2 left, right dw // 2, dw - dw // 2 img cv2.copyMakeBorder( img, top, bottom, left, right, cv2.BORDER_CONSTANT, valuecolor ) return img这段代码按比例缩放图片短边补齐到 640这样目标不会变形模型输出坐标是在填充后的画布上计算的解析时按比例映射回原图即可。6.5 多类别训练时 id 不对齐类别标签错乱现象新增数据集加入了打电话类别class 1 的标签训练好后检测出来的结果是吸烟。原因实际标注的文件里只有 0 和 2 两类而 data.yaml 里 names 的长度是 1模型把类别 ID 2 当成了类别 0 来输出类别名自然全错。解决在标注前就确定完整的类别列表。如果要扩展类别一定要重新规划 ID 分配确保 labels 里的数字和 names 列表的下标严格对应。我一般在改完标注后用一段代码批量校验标签文件的类别 ID 是否超出范围import os label_dir dataset/labels/train max_class_id 1 # 从 data.yaml 里读到的 nc - 1 for fname in os.listdir(label_dir): with open(os.path.join(label_dir, fname), r) as f: for line in f: class_id int(line.split()[0]) if class_id max_class_id: print(f非法类别 ID: {fname} 中的 {class_id})6.6 训练时 batch_size 过大导致显存溢出训练中断现象执行训练命令后不到一分钟终端打印CUDA out of memory训练直接中断。原因batch size 超过显卡显存容量。尤其是开启 Mosaic 数据增强之后单张图片包含四张小图实际显存占用是普通训练的四倍。解决先把 batch 降到 4 重新训练如果还溢出说明输入尺寸过大降到 416 或者换更小的 YOLOv8n 权重。训练前的cacheTrue也可能因为内存不足报错这时改成cacheFalse让模型按需读取数据。7. 进阶玩法与优化方向7.1 把单个模型扩展成多人同时吸烟检测模型本身已经支持单张图片内多个目标的检测但如果你要输出每个目标分别的连续行为记录需要自己写目标跟踪逻辑。常见做法是用 ByteTrack 做跟踪按检测框的中心点距离计算前后帧目标的匹配关系。流程是当前帧检测到的框和上一帧的跟踪队列逐一计算 IoU大于阈值且类别一致的视为同一个目标否则新开一个跟踪 ID。7.2 尝试半精度推理和模型蒸馏如果推理耗时跟不上业务要求可以试试 FP16 半精度推理。在 PyTorch 中只需一行设置把模型权重转成半精度并开启推理模式model YOLO(best.pt) model.model.half() results model.predict(sourcetest.mp4, conf0.35, device0)注意half()之后输入图片也需要转成半精度格式否则推理时会报类型不匹配。更进一步的优化是做蒸馏。用大模型 YOLOv8x 作为教师模型小模型 YOLOv8n 作为学生模型用教师模型的输出概率分布作为软标签来训练学生模型。这种方式训练出的 n 系列模型精度能逼近 s 系列但推理速度快 30% 左右。适合部署在 Jetson 这种端侧设备。7.3 结合行为时序做误报消除模型单帧检测总会存在偶发误报这是所有视觉模型的通病。业内常见的解法不在模型侧而在时序决策侧不依赖单帧结果而是维护一个长度为 N 的检测窗口只有连续 N 帧都在同一个位置检出吸烟行为才触发告警。窗口长度一般取 5 到 10 帧太短压不住误报太长延迟太大影响告警时效。在烟雾检测的正样本中手靠近嘴部这个动作通常持续 1 到 3 秒按 25 FPS 算大约有 25 到 75 帧的有效画面取 10 帧作为连续确认窗口既不会漏掉真实吸烟行为又能滤掉大量偶发误检。我自己做这类项目有一条基本习惯模型给的是候选结果业务逻辑做的是最终裁决。不管模型表现多好绝不跳过时序确认直接弹告警。从那以后每次交付视觉检测项目我都会强制把时序过滤和置信度阈值验证走一遍才给客户打包这套流程帮我挡掉了不少上线后的返工。希望帮到你。本文还有配套的精品资源点击获取