news 2026/9/23 18:08:27

基于YOLOv8的吸烟检测实战:991张图片数据集训练与88.3%识别率复现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于YOLOv8的吸烟检测实战:991张图片数据集训练与88.3%识别率复现

简介:这份吸烟行为检测数据集面向计算机视觉方向的学习者与算法开发者,适用于目标检测模型的训练、微调与课堂实验,可支撑公共场所吸烟行为识别、智能监控等场景的算法验证。资源共包含1983个文件,以991张jpg原始图片与991个同名txt标注文件为主,另附1个yaml配置文件用于声明类别与数据路径,压缩包整体约44.7MB,标注已整理为yolov8可直接读取的格式,省去自行转换与清洗的步骤。图片覆盖多种人物姿态与拍摄角度,标注框对应吸烟、抽烟行为目标,便于直接投入训练与评估。据描述,该数据集在常规训练条件下平均识别率可达88.3%,可作为基线参考,帮助读者快速对比不同模型与超参的效果。目前已有110人学习下载,适合需要现成标注数据开展检测实践、课程设计或算法对比的初中级开发者使用。

1. 吸烟数据集到底能拿来做什么:991 张原始图片背后的真实门槛

手上有一份 991 张原始图片的吸烟数据集,yolov8 格式标注,官方给出的平均识别率是 88.3%。这个数字放在目标检测里不算惊艳,但放在一个只有千张量级的单类别数据集上,已经能说明标注质量和场景一致性做得不错。很多人第一次拿到这类数据集,第一反应是直接丢进 yolov8 训练脚本跑一遍,看能不能复现这个 88.3%,结果往往在 70% 上下徘徊,然后开始怀疑数据有问题。实际上问题通常不在数据,而在于没搞清楚这 991 张图覆盖了哪些场景、标注框的粒度是什么、以及 88.3% 是在什么验证集划分下测出来的。

这份数据集适合三类人:一是想快速验证 yolov8 在自己硬件上跑通全流程的工程师,二是需要做吸烟行为检测原型、但不想从零标注的产品团队,三是拿它当教学素材、带新人熟悉目标检测数据闭环的技术负责人。它解决的核心问题是省掉最耗时的数据采集和标注环节,让你把精力放在模型训练、调参和部署上。但要注意,991 张图属于小样本范畴,直接套用 COCO 级别的训练策略大概率翻车,后面会具体讲怎么调。

2. 把 991 张图喂给 yolov8:从目录结构到第一次训练

2.1 先看清 yolov8 格式标注的目录长什么样

yolov8 官方推荐的目录结构是 images 和 labels 分开存放,且 train、val、test 三个子集各自独立。一份规范的吸烟数据集通常长这样:

smoking_dataset/ ├── images/ │ ├── train/ # 约 693 张 │ ├── val/ # 约 198 张 │ └── test/ # 约 100 张 ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yaml

图片和标签文件名必须一一对应,比如 images/train/001.jpg 对应 labels/train/001.txt。标签文件里每一行是一个目标,格式为class_id x_center y_center width height,后四个值都是归一化到 0 到 1 之间的浮点数。单类别吸烟检测的 class_id 就是 0。

拿到数据集后第一件事不是训练,而是检查标签完整性。我一般会跑一段脚本统计每个子集的图片数、标签数、空标签数,以及标注框的宽高分布。空标签文件是合法的,表示这张图里没有吸烟目标,但如果空标签占比超过 30%,说明负样本太多,需要调整采样策略。

import os from pathlib import Path from collections import Counter dataset_root = Path("smoking_dataset") for split in ["train", "val", "test"]: img_dir = dataset_root / "images" / split lbl_dir = dataset_root / "labels" / split imgs = list(img_dir.glob("*.jpg")) + list(img_dir.glob("*.png")) empty, total_boxes = 0, 0 box_areas = [] for img in imgs: lbl = lbl_dir / (img.stem + ".txt") if not lbl.exists(): print(f"缺失标签: {lbl}") continue lines = [l.strip() for l in lbl.read_text().splitlines() if l.strip()] if not lines: empty += 1 continue for line in lines: parts = line.split() w, h = float(parts[3]), float(parts[4]) box_areas.append(w * h) total_boxes += 1 print(f"{split}: 图片 {len(imgs)} 张, 空标签 {empty} 张, 标注框 {total_boxes} 个") if box_areas: print(f" 框面积占比 min={min(box_areas):.4f} max={max(box_areas):.4f} " f"mean={sum(box_areas)/len(box_areas):.4f}")

这段脚本做了三件事:统计每个子集的图片数量、找出缺失标签的图片、统计标注框的面积分布。框面积占比能帮你判断目标尺度,如果 mean 低于 0.05,说明大量目标是小目标,训练时 imgsz 不能设太小,否则小目标特征在深层特征图上会消失。

2.2 data.yaml 里三个必须改对的字段

data.yaml 是 yolov8 训练入口,内容通常如下:

path: /home/user/smoking_dataset train: images/train val: images/val test: images/test nc: 1 names: ['smoking']

path 写数据集根目录的绝对路径,train/val/test 写相对路径。nc 是类别数,吸烟检测就是 1。names 是类别名列表,顺序必须和标签里的 class_id 对应。这三个字段里最容易错的是 path,很多人写相对路径,结果训练时找不到图片,报No labels found或者Dataset not found。另一个坑是 names 用了中文或带空格,yolov8 解析 yaml 时不会报错,但训练日志里类别名会乱码,影响后续推理结果的可读性。

2.3 第一次训练用哪条命令最稳

环境装好后,第一次训练不要直接上大模型。991 张图用 yolov8n 或 yolov8s 就够了,yolov8m 以上在小样本上过拟合风险很高。我一般先用 yolov8n 跑 50 个 epoch 看 loss 曲线趋势,确认数据管道没问题再换大模型。

yolo detect train \ data=smoking_dataset/data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ project=runs/smoking \ name=exp1

参数逐个说:data 指向 data.yaml;model 用预训练权重,yolov8n.pt 会自动下载;epochs 设 100,配合 patience=20 做早停;imgsz=640 是标准输入尺寸,如果显存不够降到 512 或 416;batch=16 在 8GB 显存上跑 yolov8n 没问题,显存小就降到 8;lr0=0.01 是初始学习率,小样本建议比默认的 0.01 再低一点,比如 0.005,减少震荡。

训练开始后重点看三个指标:box_loss 是否稳定下降、cls_loss 是否在 20 个 epoch 内降到 1.0 以下、mAP50 是否在 50 个 epoch 后超过 0.8。如果 box_loss 下降但 mAP 不涨,大概率是验证集和训练集分布不一致,需要检查数据划分是否随机。

3. 88.3% 识别率怎么复现:验证集划分与评估参数

3.1 验证集划分方式直接决定你看到的数字

88.3% 这个数字,如果没说明验证集怎么来的,参考价值会打折扣。常见三种划分方式:随机划分、按场景划分、按时间划分。随机划分最容易得到高指标,因为同一场景的相似图片可能同时出现在训练集和验证集,模型相当于见过类似样本。按场景划分最严格,如果 991 张图来自多个不同场景,按场景留出验证集,指标通常会掉 5 到 10 个百分点。

我一般会做两次评估:一次用随机划分的验证集,看模型上限;一次用按场景划分的验证集,看泛化能力。如果两者差距超过 15%,说明数据场景多样性不足,需要补充不同光照、不同角度的图片。

import random from pathlib import Path import shutil src_imgs = sorted(Path("smoking_dataset/images/all").glob("*.jpg")) random.seed(42) random.shuffle(src_imgs) val_ratio = 0.2 val_count = int(len(src_imgs) * val_ratio) val_imgs = src_imgs[:val_count] train_imgs = src_imgs[val_count:] for split, imgs in [("train", train_imgs), ("val", val_imgs)]: for img in imgs: dst_img = Path(f"smoking_dataset/images/{split}") / img.name dst_lbl = Path(f"smoking_dataset/labels/{split}") / (img.stem + ".txt") shutil.copy(img, dst_img) shutil.copy(Path("smoking_dataset/labels/all") / (img.stem + ".txt"), dst_lbl)

这段脚本用固定随机种子做 8:2 划分,保证可复现。seed=42 是习惯用法,换成别的数字也行,但一旦定了就不要改,否则每次划分结果不同,指标没法对比。

3.2 评估时看哪几个指标才不被忽悠

yolov8 验证命令会输出一排指标,重点看四个:mAP50、mAP50-95、precision、recall。mAP50 是 IoU 阈值 0.5 时的平均精度,88.3% 大概率指的是这个。mAP50-95 更严格,通常比 mAP50 低 10 到 20 个百分点。precision 和 recall 要一起看,如果 precision 高但 recall 低,说明模型保守,漏检多;反过来则是误检多。

yolo detect val \ model=runs/smoking/exp1/weights/best.pt \ data=smoking_dataset/data.yaml \ imgsz=640 \ batch=16 \ conf=0.25 \ iou=0.5

conf=0.25 是置信度阈值,低于这个值的预测框会被过滤。iou=0.5 是 NMS 的 IoU 阈值。这两个参数直接影响 precision 和 recall,调高 conf 会提升 precision 但降低 recall。复现 88.3% 时,先确认对方用的 conf 和 iou 是多少,不同设置下指标能差好几个点。

3.3 用混淆矩阵和 PR 曲线定位问题类别

单类别检测没有多类别混淆问题,但混淆矩阵仍然有用,能看出背景被误判为吸烟目标的比例。PR 曲线则展示不同置信度阈值下 precision 和 recall 的权衡关系。如果 PR 曲线在 recall 0.6 之后急剧下降,说明模型在高召回区间精度不够,实际部署时要么接受漏检,要么把 conf 调高牺牲召回。

yolo detect val \ model=runs/smoking/exp1/weights/best.pt \ data=smoking_dataset/data.yaml \ plots=True \ save_json=True

plots=True 会在输出目录生成混淆矩阵、PR 曲线、F1 曲线等图。save_json=True 会把每张图的预测结果存成 json,方便后续做错误分析。我一般会挑出置信度在 0.3 到 0.5 之间的预测框,人工看一遍,这些是模型最不确定的样本,往往对应遮挡、小目标或光照异常的情况。

4. 小样本训练避坑:991 张图最容易翻车的五个地方

4.1 现象:训练 loss 正常下降,但验证 mAP 始终低于 0.6

原因通常是数据泄漏或验证集太简单。991 张图如果来自视频抽帧,相邻帧高度相似,随机划分会让训练集和验证集出现近乎重复的图片,模型记住了而不是学会了。解决方法是按视频来源或时间戳分组划分,确保同一段视频的帧只出现在一个子集里。如果数据来源不明,可以用图片哈希做去重,把相似度高的图片归到同一组再划分。

4.2 现象:推理时大量误检,背景被识别成吸烟

原因是负样本不足或标注不一致。991 张图里如果正样本占绝大多数,模型没见过足够多的“无吸烟”场景,就会把类似吸烟动作的背景误判。解决办法是补充负样本,或者用 mosaic 和 mixup 增强时提高负样本参与比例。另外检查标注,有些图里手部遮挡导致吸烟目标只露出一小部分,标注时如果漏标,模型会学到错误的背景特征。

4.3 现象:小目标漏检严重,远处吸烟的人检测不到

原因是输入分辨率不够或 anchor 尺度不匹配。yolov8 默认 imgsz=640,如果原图里吸烟目标只占几十个像素,缩放到 640 后特征更少。解决办法是把 imgsz 提到 960 或 1280,但显存占用会成倍增加。另一个办法是切图推理,把大图切成小块分别检测再合并,适合监控场景。如果显存有限,可以只对包含小目标的区域做高分辨率裁剪训练。

4.4 现象:训练到 30 个 epoch 后 mAP 不再提升,loss 也不降

原因是学习率太大导致在局部最优附近震荡,或者数据增强过强。小样本训练时,默认的 lr0=0.01 可能偏大,改成 0.005 或 0.001 再配合余弦退火调度。数据增强方面,mosaic=1.0 和 mixup=0.5 在千张级数据上可能过强,适当降到 mosaic=0.5、mixup=0.1,让模型看到更多原始分布。

4.5 现象:换一台机器或换一个 yolov8 版本,指标对不上

原因是随机种子、cuDNN 确定性、数据加载顺序不同。yolov8 训练涉及大量随机操作,不同硬件和版本下结果有波动是正常的。要复现指标,需要固定 seed、关闭 cudnn benchmark、使用相同的 ultralytics 版本。如果差异超过 3 个百分点,检查数据划分是否一致,以及验证时的 conf 和 iou 参数是否相同。

5. 从 88.3% 再往上走:三个我常用的提点技巧

第一个技巧是标签平滑和分类损失调整。单类别检测里,分类损失对最终 mAP 影响不小。yolov8 默认用 BCE 损失,可以在训练配置里加label_smoothing=0.1,让模型对标注边界不那么敏感,尤其适合标注框有轻微偏差的数据集。我在这类千张级数据上试过,mAP50 通常能涨 1 到 2 个点。

第二个技巧是推理阶段的 TTA。yolov8 支持测试时增强,推理时对图片做翻转、缩放等多尺度变换,再把结果融合。命令里加augment=True即可。代价是推理速度慢 2 到 3 倍,适合对精度要求高、对延迟不敏感的场景。如果部署在边缘设备上,这个技巧要慎用。

yolo detect predict \ model=runs/smoking/exp1/weights/best.pt \ source=test_images/ \ imgsz=640 \ conf=0.3 \ augment=True \ save=True

第三个技巧是难例挖掘。先用训练好的模型跑一遍训练集,挑出置信度低或预测错误的样本,人工重新检查标注,修正漏标和错标后再微调。这个过程通常做一到两轮,mAP 能再涨 2 到 3 个点。我自己的习惯是每轮训练后都导出验证集里置信度在 0.3 到 0.6 之间的预测结果,人工过一遍,积累到 50 张左右就重新微调一次。

技巧预期涨幅代价适用场景
label_smoothing=0.11-2 点几乎无标注有轻微偏差
TTA augment=True1-3 点推理慢 2-3 倍离线检测、精度优先
难例挖掘微调2-3 点人工成本有标注人力

最后说一个我踩过的坑:不要为了刷高 mAP 而反复在验证集上调参。验证集调多了,指标会虚高,实际部署时打回原形。我一般会留一个 test 集,只在最终确定模型后跑一次,那个数字才是真正能拿出去说的。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/23 18:07:56

基于YALMIP与CPLEX的节点边际电价出清模型实现

简介:电力市场节点边际电价出清优化的完整复现方案,面向电力市场研究人员、高年级本科生及研究生。资源基于史新红论文《机组运行约束对机组节点边际电价的影响分析》,在单时段模型下采用YALMIPCPLEX求解器,通过KKT对偶条件解出拉…

作者头像 李华
网站建设 2026/9/23 18:02:08

交通灯检测数据集:XML转TXT与YOLO训练实战指南

简介:这是一份面向交通场景目标检测实验的交通标志与交通信号灯数据集,原创并由LabelImg手工标注,覆盖限速牌、警告牌以及红灯、绿灯、黄灯等常见类别,图片为真实路况高清照片,适合目标检测入门、模型效果对比和毕业设…

作者头像 李华