☰
吸烟行为识别数据集实战:VOC转YOLO与YOLOv8训练避坑指南
2026/10/11 6:19:37 网站建设 项目流程

简介:这份吸烟抽烟行为识别数据集面向计算机视觉开发者、行为识别算法研究者及安防场景落地团队,用于训练和验证吸烟行为检测模型,解决真实场景下姿态多样、背景复杂导致的识别率偏低问题。压缩包共收录2000个文件,全部为VOC格式的xml标注文件,整体约890.21MB,标注覆盖人物吸烟、手持香烟、水烟与烟具陈列等多种画面,可直接对接主流目标检测框架的数据加载流程。内容预览显示样本涵盖不同姿势、光照与场景,包括人物吸烟特写、烟头烟灰静物、烟具组合及水烟售卖等,标注框与类别信息完整,便于直接用于训练与评估。目前已有928人学习下载,适合需要快速构建吸烟识别数据集、开展模型微调或对比实验的开发者参考使用。

1. 吸烟行为识别数据集:近万张真实场景图片能撑起哪些落地场景

吸烟行为识别这件事,真正卡住大多数团队的从来不是模型结构,而是数据。你拿 COCO、Open Images 这类通用数据集去训一个吸烟检测器,大概率会得到一个「看到手指就报警」的玄学模型——因为通用数据集里根本没有足够的吸烟姿态样本,模型学到的只是「手靠近脸」这个粗特征。这个标题指向的正是一份专门解决这个问题的资源:近一万张覆盖各种姿势与场景的吸烟图片,带 VOC 格式标注,主打超高识别率。它适合三类人:做智慧工地、加油站、化工厂等禁烟区域监控的算法工程师;做公共场所行为分析的产品团队;以及想拿一个真实脏数据练手目标检测的开发者。VOC 格式意味着它能直接喂给 YOLO 系列、Faster R-CNN 等主流框架,省掉最痛苦的数据清洗环节。但「近一万张」和「超高识别率」这两个词背后有大量需要你自己验证的细节,下面把它拆开讲透。

2. VOC 标注格式拆解:从 XML 结构到 YOLO 训练格式的转换

拿到一份 VOC 数据集,第一件事不是急着训练,而是搞清楚它的标注到底长什么样、类别怎么定义、有没有漏标和错标。这一步做扎实,后面能省掉大量返工。

2.1 VOC XML 的字段含义与吸烟检测的类别设计

VOC 格式的核心是每张图片对应一个 XML 文件,里面记录了图片尺寸、目标类别和边界框坐标。吸烟行为识别通常有两种标注策略:一种是只标「烟」这个物体(cigarette),另一种是标「人+吸烟动作」的整体框(smoking)。这两种策略直接决定了模型的能力边界。

只标烟头/香烟的框非常小,在远景监控画面里可能只有十几个像素,模型很难学到有效特征,而且一旦被手遮挡就完全丢失。标整体吸烟动作的框则更鲁棒,适合判断「这个人是否在吸烟」这个业务问题。我一般会建议:如果业务是禁烟区域报警,用整体动作框;如果是要精确定位烟头做取证,才考虑小目标标注。这份数据集主打「各种姿势场景」,大概率是动作级别的框,但你必须自己抽查确认。

一个典型的 VOC XML 长这样:

<annotation> <folder>smoking</folder> <filename>img_0001.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>smoking</name> <!-- 类别名,需与你的训练配置一致 --> <pose>Unspecified</pose> <truncated>0</truncated> <!-- 是否被截断,0 表示完整 --> <difficult>0</difficult> <!-- 是否难样本,训练时可选择忽略 --> <bndbox> <xmin>820</xmin> <ymin>310</ymin> <xmax>1010</xmax> <ymax>620</ymax> </bndbox> </object> </annotation>

name字段是类别名,训练前必须统一,如果数据集里混用了smoking、smoke、cigarette等多种写法,模型会当成多个类别处理,直接拉低精度。difficult字段标记的是难以识别的样本,YOLO 转换时通常会跳过或降权,这个字段在评估阶段很有用。truncated表示目标是否被画面边缘截断,截断样本过多会导致模型对完整目标定位不准。

2.2 用脚本把 VOC 转成 YOLO 的 txt 格式

YOLO 系列不直接吃 XML,需要转成归一化的 txt:每行类别索引 x_center y_center width height,坐标都除以图片宽高归一化到 0~1。下面这个脚本我用了很多次,处理近万张图几分钟就跑完:

import os import xml.etree.ElementTree as ET # 类别映射,必须和 data.yaml 里的 names 顺序完全一致 CLASSES = ["smoking"] # 如果数据集有多个类别,按字母序或自定义顺序排列,训练配置要对应 def convert_voc_to_yolo(xml_dir, img_dir, out_dir): os.makedirs(out_dir, exist_ok=True) skipped = 0 for xml_file in os.listdir(xml_dir): if not xml_file.endswith(".xml"): continue tree = ET.parse(os.path.join(xml_dir, xml_file)) root = tree.getroot() # 读取图片真实尺寸,注意不要用 XML 里可能写错的 size img_name = root.find("filename").text img_path = os.path.join(img_dir, img_name) if not os.path.exists(img_path): skipped += 1 continue from PIL import Image with Image.open(img_path) as im: w, h = im.size lines = [] for obj in root.iter("object"): cls_name = obj.find("name").text.strip() if cls_name not in CLASSES: continue cls_id = CLASSES.index(cls_name) bbox = obj.find("bndbox") xmin = float(bbox.find("xmin").text) ymin = float(bbox.find("ymin").text) xmax = float(bbox.find("xmax").text) ymax = float(bbox.find("ymax").text) # 归一化并计算中心点 x_center = (xmin + xmax) / 2.0 / w y_center = (ymin + ymax) / 2.0 / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h # 裁剪到 0~1,防止标注越界导致训练报错 x_center = min(max(x_center, 0), 1) y_center = min(max(y_center, 0), 1) bw = min(max(bw, 0), 1) bh = min(max(bh, 0), 1) lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {bw:.6f} {bh:.6f}") out_path = os.path.join(out_dir, xml_file.replace(".xml", ".txt")) with open(out_path, "w") as f: f.write("\n".join(lines)) print(f"转换完成,跳过 {skipped} 个缺失图片的标注") convert_voc_to_yolo("./annotations", "./images", "./labels")

这段代码有几个关键点值得说。第一,我用 PIL 重新读取图片真实尺寸,而不是直接用 XML 里的size字段——很多数据集在标注时图片被缩放或裁剪过,XML 里的尺寸和实际文件对不上,直接用会导致框全部偏移,这是血泪经验。第二,坐标做了 0~1 裁剪,因为标注员手抖写出xmax超过图片宽度的情况太常见了,不裁剪训练时直接报错。第三,类别映射CLASSES的顺序必须和后面data.yaml里的names完全一致,顺序错了模型学出来的类别就是乱的。

2.3 转换后必须做的三项校验

转完不是就完事了,至少做三项检查。一是统计每个类别的框数量,如果某个类别只有几十个框,说明样本严重不均衡,训练时要么过采样要么加类别权重。二是可视化抽查,随机抽 20 张图把框画出来看,重点看有没有框偏、框漏、框到背景的情况。三是检查空标注文件,有些图片转出来是空的 txt,如果空文件占比超过 5%,说明标注质量有问题,需要回头核对。

# 统计空标注文件数量 find ./labels -name "*.txt" -empty | wc -l # 统计总框数 cat ./labels/*.txt | wc -l

空标注文件在 YOLO 训练里会被当作「背景负样本」,适量是好事,能降低误报,但过多会让模型学不到正样本特征。一般控制在 2%~5% 比较合理。

3. 用 YOLOv8 在吸烟数据集上跑通训练:配置、参数与显存权衡

数据准备好了,接下来是训练。这份数据集近万张图,用 YOLOv8 在单张消费级显卡上就能跑,但参数设置直接决定你能不能复现出「超高识别率」。

3.1 data.yaml 配置与数据集划分

YOLOv8 需要一个 yaml 描述数据路径和类别。按 8:1:1 划分训练、验证、测试集:

# smoking_data.yaml path: /data/smoking # 数据集根目录 train: images/train # 训练图片相对路径 val: images/val test: images/test nc: 1 # 类别数,吸烟检测通常为 1 names: 0: smoking # 顺序必须与转换脚本的 CLASSES 一致

划分脚本建议用固定随机种子,保证每次划分一致,方便对比实验:

import os, random, shutil random.seed(42) # 固定种子,保证可复现 imgs = [f for f in os.listdir("images") if f.endswith(".jpg")] random.shuffle(imgs) n = len(imgs) train, val, test = imgs[:int(n*0.8)], imgs[int(n*0.8):int(n*0.9)], imgs[int(n*0.9):] for split, files in [("train", train), ("val", val), ("test", test)]: os.makedirs(f"images/{split}", exist_ok=True) os.makedirs(f"labels/{split}", exist_ok=True) for f in files: shutil.copy(f"images/{f}", f"images/{split}/{f}") shutil.copy(f"labels/{f.replace('.jpg', '.txt')}", f"labels/{split}/{f.replace('.jpg', '.txt')}")

注意图片和标注必须同步移动,漏掉标注文件会导致训练时找不到标签而报错。

3.2 训练命令与关键参数怎么调

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

逐个说参数。model选yolov8s是精度和速度的平衡点,如果显存紧张换yolov8n,如果追求极致精度换yolov8m或l。imgsz=640是默认值,但吸烟场景里如果目标偏小(比如远景监控),建议提到 960 或 1280,代价是显存翻倍、速度下降。batch=16在 8G 显存上跑 640 尺寸基本够用,爆显存就降到 8 或 4。lr0=0.01是初始学习率,如果训练 loss 震荡厉害,降到 0.001。patience=20表示 20 轮验证指标不提升就早停,避免过拟合。

提示:第一次训练先用小样本(比如 500 张)跑 10 轮,确认流程通了再上全量,能省掉大量等待时间。

3.3 训练过程看什么指标、怎么判断收敛

训练时重点盯三个东西:box_loss和cls_loss是否稳定下降,mAP50和mAP50-95是否持续上升。如果 loss 下降但 mAP 不涨,大概率是过拟合,需要加数据增强或减模型容量。如果 loss 一开始就爆 NaN,检查学习率是不是太大、标注里有没有坐标越界。YOLOv8 默认开启了 mosaic、翻转、HSV 等增强,对吸烟这种姿态多样的场景很友好,一般不用额外改。

训练完成后,用验证集跑一次评估:

yolo detect val model=runs/smoking/exp1/weights/best.pt data=smoking_data.yaml

看输出的mAP50,如果低于 0.85,说明数据或参数还有问题,别急着上线。吸烟检测因为涉及隐私和误报成本,我一般要求 mAP50 至少 0.9 才敢往业务里推。

4. 吸烟识别落地避坑:从误报到部署的 5 个真实翻车点

模型训出来指标好看,一上真实场景就翻车,这是行为识别最常见的剧本。下面 5 个坑我基本都踩过。

坑一:模型把「打电话」「托腮」识别成吸烟。现象是误报率极高,尤其在办公场景。原因是训练集里「手靠近脸」的正样本太多,负样本不足。解决办法是往训练集里补充大量打电话、吃东西、托腮的负样本图片,让模型学会区分「手靠近脸」和「手拿烟靠近嘴」的细微差别。负样本比例建议占到 20%~30%。

坑二:夜间和逆光场景几乎全漏。现象是白天正常,晚上或强逆光下检测率断崖下跌。原因是这份数据集虽然号称「各种场景」,但夜间样本占比通常很低。解决办法是统计数据集的光照分布,如果夜间样本不足,要么补采,要么在训练时加强亮度、对比度增强,要么直接上红外相机单独训一个模型。

坑三:小目标烟头检测框抖动严重。现象是框在烟头附近来回跳,置信度忽高忽低。原因是烟头像素太少,特征不稳定。解决办法是提高输入分辨率到 1280,或者改用带 P2 小目标检测层的模型结构。如果业务只关心「有没有人吸烟」而不关心烟头位置,直接用动作级大框更稳。

坑四:视频流里同一根烟被反复报警。现象是逐帧检测导致一秒报警几十次。原因是没做跟踪和去重。解决办法是接一个轻量跟踪器(如 ByteTrack),对同一个目标的报警做时间窗口去重,比如 5 秒内同一 ID 只报一次。

坑五:部署后推理速度跟不上。现象是服务器上跑得好好的,一到边缘设备就卡成幻灯片。原因是没做模型量化和推理优化。解决办法是用 TensorRT 或 ONNX Runtime 加速,INT8 量化后 yolov8s 在主流边缘设备上能跑到实时。量化后精度会掉 1~2 个点,需要重新验证。

5. 把识别率再往上推:难样本挖掘与置信度阈值的调参技巧

指标卡在 0.9 上不去的时候,别急着换模型,先把难样本挖出来。YOLOv8 训练完会输出每张验证图的预测结果,把那些「有标注但没检出」和「检出但框偏」的图挑出来,人工看一遍,你会发现模型翻车的场景高度集中——通常是遮挡、小目标、极端光照这三类。

具体做法是写个脚本对比预测和标注,筛出漏检和误检样本:

# 用验证集预测结果和 GT 对比,找出漏检样本 import json # YOLO val 时加 save_json=True 会输出 predictions.json with open("runs/val/exp/predictions.json") as f: preds = json.load(f) # 按 image_id 分组,对比每个 GT 框是否被匹配 # 未被任何预测框匹配上的 GT 即为漏检,重点分析这些图

把这些难样本单独拎出来,复制多份加入训练集(过采样),或者针对性做数据增强(比如对遮挡样本加 cutout、对小目标样本加 mosaic),通常能把 mAP 再拉 2~3 个点。

另一个容易被忽略的是置信度阈值。默认 0.25 是通用值,但吸烟检测的误报成本高,我一般会把阈值提到 0.5 甚至 0.6,宁可漏报也不误报。阈值不是拍脑袋定的,要在验证集上画 PR 曲线,找到业务能接受的误报率对应的阈值点。如果业务要求「零误报」,那就把阈值拉到 0.8 以上,同时接受漏报上升,再靠跟踪器的时间累积来补召回。

最后说个习惯:每次调完参数,我都会把当次的配置、指标、关键样本截图存一个实验记录,不然跑了几十轮之后根本记不清哪次改了什么。吸烟行为识别这个方向,数据质量决定下限,难样本挖掘和阈值调优决定上限,模型结构反而是最不重要的变量。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询