吸烟数据集带标注:YOLOv8训练与小目标召回提升实战
发布时间:2026/9/25 2:18:20来源:尧图网络
简介这份「吸烟数据集带标注」面向人工智能与机器学习方向的开发者、算法工程师及高校学生用于训练和评估吸烟行为识别与预测模型。数据集以监督学习为目标每个样本均带有吸烟者或非吸烟者的标注信息可支撑分类、特征分析等典型任务。压缩包共1567个文件包含783个xml标注文件与783个jpg图像文件另有1个txt说明文件整体约31.71MBxml与jpg一一对应便于直接用于目标检测或图像分类流程。目前已有158人学习下载属于小众但结构完整的实战数据。读者可借助该数据集完成从数据读取、标注解析到模型训练与验证的完整链路理解个人信息、生活习惯、健康状况、社会经济状态及环境因素等特征对吸烟行为的影响并在此基础上练习数据划分、过拟合控制与隐私合规处理适合作为课程设计、毕业项目或算法入门的练手素材。1. 吸烟数据集带标注从“找不到”到“跑得通”的那条路做行为识别项目的工程师大多经历过这个场景算法框架搭好了模型结构也调通了结果卡在数据上——尤其是吸烟这类细粒度动作公开数据少、标注质量参差、场景单一翻遍几个常用数据集平台要么只有分类标签没有检测框要么全是摆拍正脸跟真实监控画面差着十万八千里。吸烟数据集带标注这件事核心要解决的就是“让模型真的能学到烟支、手部、嘴部之间的空间关系”而不是只记住“有个人站在那儿”。它适合三类人做智慧安防、工地/加油站禁烟区行为检测的算法工程师需要快速验证检测方案可行性的产品原型开发者以及带学生做课程设计、毕设的高校老师。这一章不展开具体操作先把“为什么吸烟检测的数据这么难搞”和“一份能用的带标注数据集应该长什么样”说清楚后面几章再一步步落到格式转换、训练配置和踩坑排查上。2. 吸烟行为检测的数据集到底该标什么从业务需求反推标注规范2.1 先想清楚模型要输出什么再决定标什么很多团队拿到一批监控截图就开始标标完发现模型学出来的东西跟业务对不上。问题出在标注之前没有做“输出定义”。吸烟检测在实际落地中通常有三种输出形态对应的标注方式完全不同。第一种是图像分类只判断整张图里有没有人吸烟。这种标注成本最低一个文件夹放正样本、一个放负样本就行但误报率极高——手里拿根笔、叼根牙签都可能触发。第二种是目标检测在图中框出“吸烟行为”这个整体或者分别框出“烟支”和“手部-嘴部区域”。这是目前工地、加油站场景最常用的方案因为可以直接在画面上画框告警。第三种是关键点检测除了框还要标出烟支的两个端点、手部关键点、嘴部位置用于判断烟支是否真的在嘴边。这种标注成本最高但误报率能压到很低。我一般建议如果只是做区域入侵式的粗筛目标检测足够如果要做到“烟支在嘴边持续超过2秒才告警”那至少要把烟支和嘴部区域分别框出来让模型自己去学空间关系。标注规范里必须写清楚烟支被手指遮挡超过50%时怎么标、烟支只露出一小截时标不标、多人同时吸烟时是每人一个框还是一个整体框。这些边界情况不提前定好后面返工的成本远高于标注本身。2.2 一份可用的吸烟数据集应该包含哪些场景维度公开渠道能拿到的吸烟数据绝大多数是正面、近距离、光照良好的摆拍图。拿这种数据训出来的模型一到真实监控画面就翻车——侧脸、低头、远距离、逆光、夜间红外全是没见过的分布。所以自建或筛选数据集时场景维度比数量更重要。我通常会按下面这张表来检查一个吸烟数据集是否“够用”维度最低要求理想覆盖拍摄角度正面、侧面各30%正面、侧面、背面、俯视、仰视光照条件白天室内/室外白天、黄昏、夜间红外、逆光距离近景人脸可辨近景、中景、远景人占画面1/10遮挡程度无遮挡手部遮挡烟支、口罩下拉、烟雾遮挡分辨率不低于640×4801080P为主少量低分辨率负样本负样本不吸烟人像拿笔、叼牙签、打电话、喝水、戴口罩负样本的重要性经常被低估。一个只见过正样本的模型会把所有“手靠近脸”的动作都判成吸烟。负样本里必须包含“手拿笔靠近嘴”“叼吸管”“打电话”“摸下巴”这几类硬负样本数量至少是正样本的1.5倍。标注时负样本不需要画框但要在文件名或标注文件里明确标记为negative训练时作为背景图参与。2.3 标注格式选型COCO、YOLO、VOC到底用哪个标注格式没有绝对优劣取决于你下游用什么训练框架。但有一个原则原始标注只存一份用脚本转成其他格式不要手工维护多份否则改一个框要同步三个地方迟早出错。常见做法是标注阶段用LabelImg或CVAT导出VOC格式XML因为工具成熟、标注员上手快然后写一个转换脚本同时生成YOLO格式txt和COCO格式json。YOLO格式每行是class_id x_center y_center width height全部归一化到0-1COCO格式是json包含images、annotations、categories三个顶层字段。VOC格式则是每个图一个XML包含filename、size、object等节点。下面这个Python脚本是我常用的VOC转YOLOCOCO的转换核心逻辑假设你的VOC XML放在annotations/目录图片放在images/目录import os import xml.etree.ElementTree as ET import json from PIL import Image # 类别映射根据你的标注类别修改 CLASS_MAP {smoking: 0, cigarette: 1, mouth: 2} def voc_to_yolo(xml_path, img_dir, out_dir): tree ET.parse(xml_path) root tree.getroot() img_name root.find(filename).text img_path os.path.join(img_dir, img_name) w, h Image.open(img_path).size # 用实际图片尺寸不要信XML里的size lines [] for obj in root.findall(object): cls_name obj.find(name).text if cls_name not in CLASS_MAP: continue cls_id CLASS_MAP[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 lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {bw:.6f} {bh:.6f}) out_txt os.path.join(out_dir, img_name.replace(.jpg, .txt)) with open(out_txt, w) as f: f.write(\n.join(lines)) # 批量处理 for xml_file in os.listdir(annotations): if xml_file.endswith(.xml): voc_to_yolo(os.path.join(annotations, xml_file), images, labels)这段代码的关键点有三个。第一图片尺寸必须从实际图片读取不能直接用XML里写的width/height因为标注工具有时会写错或者图片被重新缩放过后没更新XML。第二归一化后的坐标要保留6位小数YOLO训练时对精度敏感尤其是小目标。第三类别映射表要跟你的data.yaml里的names顺序完全一致否则训练出来的类别全是乱的。参数方面CLASS_MAP里的类别名必须和XML里name节点的文本完全匹配大小写都不能差。如果你的标注里烟支叫“cigarette”但映射表里写的是“cig”那这个框会被直接跳过训练时表现为“某些图明明标了却学不到”。转换完成后建议随机抽10张图用labelImg打开对应的txt可视化检查一遍确认框的位置和类别都对。3. 用YOLOv8跑通吸烟检测从数据组织到训练命令的完整链路3.1 数据目录结构怎么摆才不会被框架坑YOLO系列对目录结构有固定要求摆错了不会报错但会静默跳过数据表现为loss不下降或者mAP恒为0。标准结构如下smoking_dataset/ ├── images/ │ ├── train/ # 训练集图片 │ ├── val/ # 验证集图片 │ └── test/ # 测试集图片可选 ├── labels/ │ ├── train/ # 训练集标注txt │ ├── val/ │ └── test/ └── data.yaml # 数据集配置文件注意images/train/和labels/train/是平级目录不是嵌套关系。YOLOv8在加载时会自动把images替换成labels去找对应的txt文件所以文件名必须一一对应abc.jpg对应abc.txt。如果某张图没有标注纯负样本也要放一个空的txt文件否则框架会报“找不到标签”。data.yaml的内容如下path: /absolute/path/to/smoking_dataset train: images/train val: images/val test: images/test names: 0: smoking 1: cigarette 2: mouthpath必须写绝对路径写相对路径在部分环境下会解析到框架安装目录去。names的索引必须从0开始连续不能跳号。如果你的类别只有“smoking”一个那就只写0: smoking不要留空行。3.2 训练命令与关键参数batch、imgsz、lr0怎么定数据摆好之后训练命令本身不复杂但参数设错会导致显存溢出或者模型不收敛。下面是我在单卡24G显存上跑吸烟检测的常用命令yolo detect train \ datasmoking_dataset/data.yaml \ modelyolov8s.pt \ epochs150 \ imgsz640 \ batch16 \ lr00.01 \ lrf0.01 \ patience30 \ augmentTrue \ mosaic1.0 \ mixup0.1 \ device0 \ projectruns/smoking \ nameexp1逐项说明modelyolov8s.pt是轻量级模型吸烟检测这种单类或三类任务s版本在精度和速度上比较平衡如果部署在边缘设备上可以换yolov8n.pt但小目标召回会降。imgsz640是输入分辨率吸烟场景里烟支往往只占几十个像素如果显存允许建议提到imgsz960小目标召回能明显提升。batch16在24G卡上跑640分辨率基本不会溢出如果报CUDA out of memory先降到8再不行就降到4。lr00.01是初始学习率YOLOv8默认就是0.01但如果你的数据集小于2000张建议降到0.005否则容易过拟合。patience30表示30个epoch验证指标不提升就早停吸烟数据量通常不大这个值设20-30比较合理。mosaic1.0是马赛克增强对小目标检测很有帮助但如果你的图片里烟支特别小mosaic后可能小到看不见可以降到0.5。mixup0.1是混合增强负样本多的时候可以适当提高但不要超过0.3否则正样本被过度稀释。训练过程中重点看三个指标metrics/mAP50、metrics/mAP50-95和train/box_loss。mAP50在第一个epoch通常就能到0.3以上如果一直是0八成是数据路径或类别映射出了问题。box_loss如果持续在1.0以上不降检查标注框的归一化坐标是否在0-1之间超出范围的框会被框架忽略。3.3 推理验证用训练好的权重跑单张图和视频流训练完成后权重默认保存在runs/smoking/exp1/weights/best.pt。先用单张图验证yolo detect predict \ modelruns/smoking/exp1/weights/best.pt \ sourcetest_images/ \ conf0.25 \ iou0.45 \ saveTrue \ projectruns/predictconf0.25是置信度阈值吸烟检测建议先设0.25看召回如果误报多再往上调。iou0.45是NMS的IoU阈值如果同一个人身上出现多个重叠框把这个值降到0.3-0.4。推理结果会保存在runs/predict/下直接看画框的图重点检查三类错误漏检烟支在嘴边但没框、误检拿笔被判成吸烟、框位置偏移框到了手但没框到烟支。如果漏检集中在远景小目标回去把imgsz提到960重新训如果误检集中在硬负样本把那些负样本加进训练集重新跑。视频流推理把source换成视频文件路径或摄像头编号即可但要注意帧率。YOLOv8s在640分辨率下单张1080P图在RTX 3060上大约15-20ms换算成视频流大概50-60FPS足够处理25FPS的监控流。如果部署在Jetson这类边缘设备上建议导出TensorRT引擎速度能翻倍。4. 吸烟数据集标注与训练中的五个血泪坑4.1 坑一标注框把整个头都框进去模型学不到烟支现象训练loss正常下降mAP50能到0.7以上但推理时只要人把手举到脸附近就报警烟支有没有根本不在乎。原因标注时图省事直接把“吸烟的人头”整个框进去模型学到的是“手靠近脸吸烟”而不是“烟支在嘴边吸烟”。这是吸烟检测里最隐蔽的坑因为指标看起来很好上线后误报率却高得离谱。解决标注规范里强制要求框住烟支本身或者至少框住“烟支嘴部”的联合区域不要框整个头。如果烟支太小不好标先把图片放大到200%再标。已经标完的数据写脚本检查框的宽高比如果宽高比接近1:1且面积占图片比例超过15%大概率是框了整个头需要返工。4.2 坑二负样本里没有“手拿笔”“叼吸管”这类硬负样本现象模型在测试集上表现很好一到真实场景工人拿笔记录、叼着吸管喝水全触发吸烟告警。原因训练集里的负样本只有“正常站立的人”模型没见过“手拿细长物体靠近嘴”的负样本把“细长物体嘴”这个组合直接映射成了吸烟。解决专门收集一批硬负样本包括手拿笔、叼牙签、叼吸管、打电话、摸下巴、戴口罩下拉到下巴。每类至少200张混进训练集的负样本目录。如果实在找不到可以用数据增强合成把烟支的标注框去掉只保留手和嘴的区域作为负样本加入。重新训练后误报率通常能降一半以上。4.3 坑三VOC转YOLO时图片尺寸用了XML里的旧值现象转换后的txt里坐标全是0或者大于1训练时框架直接跳过这些标注表现为“某些图明明标了但模型完全没学到”。原因标注工具在图片被裁剪或缩放后XML里的size节点没有同步更新转换脚本直接读了XML里的width/height导致归一化分母错误。解决转换脚本里强制用PIL.Image.open(img_path).size读取实际图片尺寸不要信XML里的值。转换完成后写一个校验脚本遍历所有txt检查每行坐标是否在0-1之间超出范围的打印出来人工核对。4.4 坑四训练集和验证集里出现了同一段视频的相邻帧现象验证集mAP很高但换一个完全不同的场景测试指标断崖式下跌。原因数据划分时按图片随机分同一段视频的相邻帧被分到了训练集和验证集。相邻帧几乎一模一样模型在验证集上相当于“见过原题”指标虚高。解决按视频源划分同一段视频的所有帧只能出现在训练集或验证集其中一个里。如果数据来自多个摄像头按摄像头编号划分。划分完成后检查训练集和验证集的图片文件名前缀确保没有重叠的视频ID。4.5 坑五推理时conf设太高夜间红外画面全漏检现象白天场景检测正常一到夜间红外画面所有吸烟行为都漏检。原因夜间红外画面对比度低、烟支边缘模糊模型输出的置信度普遍偏低。如果conf沿用白天的0.25夜间可能只有0.1-0.15全被过滤掉。解决分场景设阈值。白天conf0.25夜间conf0.1同时把NMS的iou降到0.3避免重叠框。更好的做法是在训练集里加入夜间红外样本让模型自己适应低对比度。如果夜间样本少于总数据的10%建议单独微调一个夜间模型不要和白天混在一起训。5. 把吸烟检测推到可用小目标召回提升与误报压制的几个实操技巧5.1 用切片推理把远景小烟支捞回来监控画面里人占画面1/10的时候烟支可能只有10-20个像素。YOLOv8在640输入下经过5次下采样20像素的烟支在特征图上只剩不到1个像素召回率自然低。我常用的做法是切片推理把原图切成4块或9块每块单独推理再把结果映射回原图做NMS。这样每块里的烟支相对变大召回率能提升15-25个百分点。实现上不需要改模型写一个推理后处理脚本即可import cv2 import numpy as np from ultralytics import YOLO model YOLO(best.pt) img cv2.imread(test.jpg) h, w img.shape[:2] tile_size 640 overlap 128 # 切片重叠避免边缘目标被切断 all_boxes [] for y in range(0, h, tile_size - overlap): for x in range(0, w, tile_size - overlap): tile img[y:ytile_size, x:xtile_size] if tile.shape[0] 32 or tile.shape[1] 32: continue results model(tile, conf0.15, verboseFalse) for box in results[0].boxes: xyxy box.xyxy[0].cpu().numpy() xyxy[0] x xyxy[1] y xyxy[2] x xyxy[3] y all_boxes.append((xyxy, float(box.conf[0]), int(box.cls[0]))) # 对所有框做全局NMS def nms(boxes, iou_thresh0.4): boxes sorted(boxes, keylambda x: x[1], reverseTrue) keep [] while boxes: best boxes.pop(0) keep.append(best) boxes [b for b in boxes if iou(best[0], b[0]) iou_thresh] return keep def iou(a, b): x1 max(a[0], b[0]); y1 max(a[1], b[1]) x2 min(a[2], b[2]); y2 min(a[3], b[3]) inter max(0, x2-x1) * max(0, y2-y1) area_a (a[2]-a[0]) * (a[3]-a[1]) area_b (b[2]-b[0]) * (b[3]-b[1]) return inter / (area_a area_b - inter 1e-6) final nms(all_boxes, iou_thresh0.4)这段代码的关键参数是tile_size和overlap。tile_size建议跟训练时的imgsz一致训练用640就切640。overlap设128是为了让切片边缘的目标至少完整出现在某一块里太小会切断烟支太大计算量翻倍。切片推理的代价是推理时间变成原来的4-9倍如果对实时性要求高可以只对画面中“人”的区域做切片先用一个人体检测模型框出人再在人框区域做切片推理计算量能降一个数量级。5.2 用时序滤波压掉单帧误报单帧检测难免有误报但吸烟是一个持续动作真实吸烟至少持续几秒。利用这个先验可以做一个简单的时序滤波维护一个长度为N的滑动窗口记录每一帧是否检测到吸烟只有当窗口内检测到吸烟的帧数超过阈值时才触发告警。我一般设N15约0.5秒30FPS阈值10。也就是说15帧里至少10帧检测到吸烟才报警。这个参数可以根据业务容忍度调工地禁烟区要求零漏报可以把阈值降到6加油站要求零误报把阈值提到12。时序滤波的代码很简单一个collections.deque就能实现但效果立竿见影单帧误报基本能被压掉80%以上。5.3 模型导出与边缘部署的注意事项如果最终要部署到Jetson、瑞芯微这类边缘设备训练完的.pt需要导出成对应格式。YOLOv8支持导出ONNX、TensorRT、OpenVINO等yolo export modelbest.pt formatengine halfTrue device0formatengine是TensorRT引擎halfTrue开启FP16量化速度能提升1.5-2倍精度损失通常在1%以内。注意TensorRT引擎跟设备架构绑定在RTX 3060上导出的引擎不能直接拿到Jetson上跑必须在目标设备上重新导出。如果目标设备是Jetson建议在Jetson上装好TensorRT和ultralytics后直接在Jetson上执行导出命令。导出后一定要用同一批测试图对比.pt和.engine的推理结果确认框的位置和置信度没有明显偏移。我遇到过FP16量化后小目标置信度整体下降0.1的情况这种时候要么关掉half要么把conf阈值相应调低。5.4 一个我反复用的验证习惯每次训完一个新版本我不会只看mAP指标而是固定做三件事第一从验证集里挑20张最难的正样本远景、遮挡、夜间和20张硬负样本单独跑一遍人眼过一遍框第二把模型接到一段真实监控视频上跑完整段统计告警次数和实际吸烟次数的比例第三把上一版模型和这一版模型在同一段视频上跑对比告警时间点和数量确认新版本没有引入新的误报模式。这个习惯帮我省了很多次“指标涨了但上线翻车”的后悔药。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网