ARTICLE DETAIL

资讯详情

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

YOLOv8吸烟检测实战:991张图片训练与88.3%识别率复现

YOLOv8吸烟检测实战:991张图片训练与88.3%识别率复现 简介这份吸烟行为检测数据集面向计算机视觉开发者与目标检测学习者可用于训练和验证抽烟场景下的目标识别模型适用于安防监控、公共场所行为分析等应用方向。资源共包含1983个文件其中991张jpg原始图片与991个同名txt标注文件一一对应标注采用yolov8格式另附1个yaml配置文件用于定义数据集路径与类别信息压缩包整体约44.7MB结构规整、开箱即用。据描述该数据集在训练后平均识别率可达88.3%可作为基线参考帮助读者快速评估模型效果并在此基础上做数据增强或调参优化。目前已有110人学习下载适合需要现成标注数据开展吸烟检测实验、课程设计或算法对比的初中级视觉开发者使用。1. 吸烟数据集与 YOLOv8991 张原始图片、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: 1names里只写0: smoking。如果后面要加“打电话”类别nc改成 2names加一行1: phone同时标签文件里对应目标的class_id要改成 1。提示改完data.yaml后先用yolo checks命令确认 Ultralytics 能正确解析路径再开始训练。这个命令会打印出数据集的基本信息包括图片数量、类别分布能提前暴露大部分配置问题。3. 用 YOLOv8 在 991 张图片上跑通训练参数怎么设、显存怎么省3.1 从预训练权重出发的最小训练命令991 张图片属于小数据集不建议从零开始训练。常见做法是加载 YOLOv8 的预训练权重在吸烟数据集上做微调。这样即使图片少模型也能借助预训练学到的通用特征快速收敛。# 最小训练命令适合 8GB 显存左右的显卡 yolo detect train \ datasmoking_data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ device0 \ projectsmoking_runs \ nameexp1modelyolov8n.pt用的是 nano 版本参数量最小训练最快适合先跑通流程。epochs100对小数据集来说通常够用如果验证集损失在 60 轮后还在降可以加到 150。imgsz640是 YOLOv8 的默认输入尺寸如果你的图片分辨率远大于 640比如 1920×1080可以尝试imgsz960但显存占用会明显增加。batch16在 8GB 显存上跑yolov8n加imgsz640基本不会爆显存如果报 CUDA out of memory先降到 8 或 4。3.2 关键训练参数的含义与调整逻辑YOLOv8 的训练参数有几十个但真正影响吸烟数据集识别率的主要是这几个学习率、数据增强、置信度阈值和 IoU 阈值。参数默认值作用调整建议lr00.01初始学习率小数据集可降到 0.001避免震荡lrf0.01最终学习率系数保持默认除非训练后期损失不降mosaic1.0马赛克增强概率小数据集建议保持 1.0增加样本多样性fliplr0.5水平翻转概率吸烟场景左右对称保持默认conf0.25推理置信度阈值漏检多就降到 0.15误检多就升到 0.4iou0.7NMS 的 IoU 阈值吸烟目标重叠少保持默认lr0从 0.01 降到 0.001 是我在 1000 张以下数据集上常用的做法。默认的 0.01 适合大数据集小数据集上容易在初期就跳过最优解。mosaic增强会把四张图拼成一张变相增加 batch 内的样本多样性对 991 张这种规模的数据集很有帮助不建议关掉。conf和iou是推理时的参数训练时不用改但验证时会影响 mAP 的计算如果发现验证集 mAP 波动大可以固定conf0.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 \ modelsmoking_runs/exp1/weights/best.pt \ sourcesmoking_dataset/images/val \ conf0.25 \ saveTrue \ projectsmoking_infer \ nameval_checksource可以指向单张图片、一个目录或一个视频文件。saveTrue会把带检测框的图片保存到smoking_infer/val_check下。conf0.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), conf0.25, verboseFalse) 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 \ modelsmoking_runs/exp1/weights/best.pt \ formatonnx \ imgsz640 \ opset12 \ simplifyTrueopset12是兼容性比较好的版本RK3588 的 NPU 工具链对 opset 12 支持较完善。simplifyTrue会调用 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_size8): 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_id1。这种情况常见于从其他格式转换时原始标注有“吸烟”和“非吸烟”两个类别转换脚本没做映射。解决用 2.2 节的校验脚本找出所有class_id如果有多余类别要么在data.yaml里补上names要么把标签文件里的class_id统一改成 0。5.3 图片尺寸不一致导致训练中断现象训练到一半报RuntimeError: stack expects each tensor to be equal size。原因数据集中混入了不同尺寸的图片YOLOv8 的 dataloader 在默认配置下会尝试统一尺寸但如果某张图的长宽比极端比如 2000×200resize 后填充过多可能触发异常。解决训练前统一把图片 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。原因batch16加imgsz640在 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_h0.015、hsv_s0.7、hsv_v0.4让模型对烟雾的色调和亮度变化更鲁棒。如果数据集里吸烟目标偏小把scale0.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, conf0.25): all_boxes [] for model in models: results model(img_path, confconf, verboseFalse) 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(keylambda x: x[1], reverseTrue) 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 张比换更大的模型管用。希望帮到你。本文还有配套的精品资源点击获取
返回列表