吸烟识别数据集实战:COCO格式解析与YOLOv8训练指南
2026/8/27 6:28:09 网站建设 项目流程

简介:目标检测是计算机视觉的基础任务,但检测“香烟”这类小目标却面临尺度小、半透明、遮挡等挑战,使得吸烟识别比常规检测更复杂。COCO格式作为主流标注标准,能有效表达多实例与行为上下文,为模型训练提供高质量数据支撑。通过一个包含10162张覆盖多场景图像的吸烟行为数据集,结合YOLOv8的快速部署能力,可以将识别能力落地到宿舍抽烟预警、工地安全帽下吸烟检测、加油站烟火监控等实际安防场景中。数据集中的姿势多样性、COCO JSON结构解析以及YOLO格式转换,都是提高模型泛化能力的关键环节。围绕这些实践,可以系统解决吸烟识别从数据到训练的技术路径。 上周和同行聊一个园区安防项目,客户点名要加吸烟检测,说最好能同时识别出拿烟的手、烟头位置。我原本觉得,这活儿不就是目标检测吗,标注几万张图训个YOLO完事。真跑起来才发现,吸烟识别比普通检测难得多:烟是半透明的,手会遮挡,灯光一暗烟头几乎看不见,远处的目标框小到只剩二三十个像素。最后换了那份带COCO格式标注的吸烟抽烟行为识别数据集,10162张各种姿势和场景的图片,重训之后识别率才真正能看。这篇文章就从这份数据集入手,把格式解析、训练过程和踩坑记录完整展开。无论你打算做宿舍抽烟预警、工地安全帽下的吸烟识别,还是加油站烟火检测,这篇都适合留下来当参考。

1. 为什么"吸烟识别"比想象中更难:先弄懂你要解决的是什么

1.1 从宿舍到加油站,吸烟检测的真实需求在哪儿

我在过去半年里接触的需求方五花八门。学校管理部门想抓宿舍阳台抽烟,工地管安全的想识别工人是否在禁烟区抽烟,加油站监控想检测站内烟火,还有医院想统计室外吸烟点位。它们的共同点很直接:已经有摄像头,已有简单的监控系统,想加一个"自动看见有人在抽烟"的模块。这个模块如果做得准,就能替代人工盯屏,还能留证据、能够自动联动告警。所以从产品形态看,吸烟识别往往不是独立系统,而是安防大平台里的一个行为分析子模块,和人员越界、区域入侵、打电话识别这些任务并列。

说到这里,很多人会认为,那就在原有检测框架里加一个类别就好。实际不是。吸烟行为识别有一个天然缺陷:你检测的目标"香烟"实在太小。一套1080p枪机画面里,一个人站在二十米外,烟支可能只有不到10个像素宽,很多检测头在这个尺度上直接放弃。而且香烟本身不发光(除非烟头部分),颜色和背景融为一体,漏检率天然比检测"人""车"这种大目标高。所以算法方案必须专门为这个目标设计,这也是这类数据集的价值所在。

另外,客户常说的"识别抽烟"其实有三种口径。第一种是检测"烟支/烟头"这个物体,模型输出香烟的框。第二种是检测"吸烟动作",用整个人的框或者上半身框表达正在抽烟这个行为。第三种是识别"抽烟+持烟手势",需要更精细的关键点或更细粒度的行为分类。拿到数据集的时候,首先要看categories字段定义的是哪一种,否则后面训练对齐会出大问题。这一点我会在第3章详细拆。

1.2 烟是半透明的、手是遮挡的、光是多变的

把难题拆开看,吸烟识别对视觉算法不友好主要是四件事。

第一,目标尺度极端。正常监控画面里,人脸检测已经有难度,但烟支比人脸小得多。同样是1080p,一支香烟的检测框可能只有20×8像素,烟头就更小。YOLO系检测器在640输入下,目标只要小于一个特定的网格尺度,几乎就学不到有效特征。第二,天然遮挡。吸烟时手、嘴、烟三者重叠,手要挡住一部分烟身,嘴唇又会压住烟嘴。模型经常学到的是"手+嘴附近有一个亮色小点",而不是真正学到烟支结构。

第三,半透明与颜色退化。烟身是白色或米黄色,在墙面、皮肤、衣服这些颜色差异不大的背景下对比度很低;烟雾本身半透明,和背景融合后甚至看不到烟支轮廓。第四,光照变化。室内正常光还行,室外背光时烟支全黑,夜里只有烟头那个红点是稳定的信号。可如果模型只学会"找红色亮点",路灯下的红色广告牌、车尾灯都会变成误报源。

这里插一句我个人的认知:很多人以为吸烟识别难在要训练一个特别强的模型,其实难点在数据。为什么一个10162张、覆盖各种姿势场景的数据集能出效果?因为它把上面这些问题用样本量压平了。你不需要一个天才模型,你需要一个见过足够多"刁钻场景"的普通模型。数据集的场景覆盖越广,模型的泛化能力越接近真实部署,这比一味加大模型容量管用。

2. 这份10162张的吸烟数据集,到底覆盖了什么

2.1 先看数字:图片量、标注框、场景分布怎么读

拿到一个zip包之后,第一个动作不是立即训练,而是把数据读一遍。先从文件名和目录结构开始,通常会看到 images文件夹(有些叫JPEGImages)和annotations文件夹,annotations下面放着instances_train.json、instances_val.json这类文件。

这份数据集有10162张图,具体标注框数量要解压后看annotations数组的长度,但按我的经验,这类专门为行为识别收集的数据集通常不会每张图只标一个框。一张图里可能有多个人、多支烟、多个持烟手势。我建议解压后自己统计,方法很简单:

import json with open("annotations/instances_train.json", encoding="utf-8") as f: coco = json.load(f) print(len(coco["images"]), len(coco["annotations"]))

如果图片数和标注数都落在合理范围(比如标注数是图片数的1.5到2倍以上),说明数据是"实例级"标注。如果一张图只有一个框,那可能整个框表示"正在吸烟的人"这个行为框,而不是标注每支烟。这两种标注思路训练出来的模型形态完全不同,所以建议先跑一下统计再决定后续策略。

我这里按常见设计,把这类数据集的构成列成一个参考表格,实际以你解压后的json为准:

项目常见内容
图片总数10162张
标注格式COCO JSON(bbox + segmentation + category_id)
图片分辨率多为1920x1080或1280x720(需自行确认)
场景覆盖室内、室外、白天、夜间、背光、侧光
常见标注目标香烟/烟头、吸烟行为框、人员框(视categories定义)
压缩包结构images/ + annotations/ + 说明文档

2.2 "各种姿势场景"意味着什么:低头、抬手、叼烟、弹烟灰

这份数据集的标题里特意写了"各种姿势场景",这是最有信息量的一句话。我见过的很多公开吸烟数据集,问题恰恰在于姿势太单一:全是人对镜头正面、烟夹在食指中指之间、嘴张开的瞬间。这种数据训出来的模型,换个视角就废了。

而覆盖姿势多样性,至少包括这几个维度。角度上,有正脸、侧脸、低头看手机、仰头吐烟;手部动作上,有夹烟、递烟、弹烟灰、把烟放在烟灰缸边、反手夹烟;场景上,有办公室、楼道、阳台、停车场、工棚、室外步行,还有夜间路灯下。这些看似只是"数量多",实际上把模型的决策空间撑开了。模型不再倾向于记住"某个固定角度下手指缝里有个白条",而是慢慢学会了"在手的附近、嘴的附近,出现一个短而细的条状物,并且和背景有边界,这是一个烟支"。

这对部署的价值是决定性的。比如宿舍抓拍场景,学生可能趴在阳台栏杆上,摄像头从侧面斜拍,手还放在窗沿下方。如果你的训练集里没有"低头+手部被栏杆遮挡"的样本,模型大概率漏掉。反过来,如果训练集包含"手在背景中有长条物体的场景",模型就可能把栏杆、窗帘杆误认成烟。

2.3 COCO标注格式:为什么是COCO而不是VOC

我看过不少人拿到数据集之后,先问"能不能转成VOC的XML",或者"为什么不用LabelImg的Pascal格式直接存"。我的回答是,直接用COCO更好,至少在这个任务里是。

COCO格式的核心是单个JSON文件,包含了images、annotations、categories三块。它的嵌套结构和id关联关系,能表达多实例、多类别、甚至同一个目标带细粒度分割边缘的复杂标注。对吸烟识别来说,同一个img_id下可以挂多个annotation,一个表示"香烟",一个表示"持烟的手",给模型训练增加了上下文信息。VOC一套一个XML,文件数量多、管理麻烦,也不方便做多级标签体系。

而且COCO是众多现代检测框架的默认输入格式之一,Detectron2、MMDetection、YOLOv5的COCO支持都很好,Ultralytics也提供了直接的COCO格式示例。后续你要做预训练权重迁移、在更大的COCO预训练模型上微调,数据结构一致就能省掉很多转换API的适配工作。

3. COCO格式标注从解压到喂给模型:json文件完全拆解

3.1 解压后的目录结构,先确定文件是否完整

先说个小坑。很多人解压这个zip后,发现annotations里只有instances_train.json,没有val.json,就开始瞎拆。我建议先核对一下:

unzip smoking_dataset.zip -d smoke_data find smoke_data -type f | head -30 find smoke_data -name "*.jpg" | wc -l

如果只有一份json,可以自己按8:2或9:1切分,但切分时要注意场景隔离,这个坑我在第5章展开。另外Windows上解压可能会因为文件名过长报错,尤其是数据集图片命名很长的时候,建议在压缩包所在路径短一点的目录里解压,避免路径超过系统上限。

解压完不要急着删除zip,如果后续忘了原文件结构,还能重新对照。

3.2 COCO JSON 四大块:images、annotations、categories、info

COCO的JSON结构其实非常直白。我习惯用一个脚本先做"数据体检":

import json with open("annotations/instances_train.json", encoding="utf-8") as f: coco = json.load(f) # 1. categories print("categories:", json.dumps(coco["categories"], ensure_ascii=False, indent=2)) # 2. 单张图片 print("image sample:", coco["images"][0]) # 3. 单个标注 print("annotation sample:", coco["annotations"][0])

categories长这样:

[ { "id": 0, "name": "cigarette", "supercategory": "smoke" } ]

也可能长这样:

[ {"id": 0, "name": "smoking", "supercategory": "action"}, {"id": 1, "name": "cigarette", "supercategory": "object"} ]

不同数据包的类别设计不一样,这一步决定你后面训练的是什么。如果categories里既有smoking又有cigarette,说明它把"吸烟行为"和"香烟目标"分开标注;如果只有smoking,则更可能是行为级检测。后面转YOLO格式时,类别索引不是按id顺延,而是要有意排成你想要的检测目标顺序。

annotation里的bbox长这样:

{ "id": 1, "image_id": 0, "category_id": 1, "bbox": [612.0, 318.0, 38.0, 10.0], "area": 380.0, "segmentation": [[...]], "iscrowd": 0 }

这里bbox的四个值是[x, y, width, height],单位是像素,原点在图像左上角。很多第一次接触COCO的同学最容易在这里翻车:他们把x和x+w当成对角线、或者忘记了宽高是绝对像素值导致下采样后坐标错位。记住COCO的bbox不是归一化的,与YOLO格式完全相反。

area字段在评估阶段会被用到,如果你在解压后对图片做了裁剪或缩放,area必须跟着重新算,否则mAP评估会偏。segmentation字段可以是一个多边形数组,也可以是一个RLE编码字符串,取决于标注工具。对大部分检测训练流程来说,bbox就够用了,但如果后续要做实例分割,就保留segmentation。

3.3 可视化检查:我建议每个类别先看20个框

光看json还不够,一定要把标注框可视化到图上。不要相信"标注完成就一定是准的",尤其吸烟数据里,烟支小、边界模糊,标注时很容易出现手误。

一个简单的可视化方法是用OpenCV把框画出来:

import json import cv2 import os with open("annotations/instances_train.json", encoding="utf-8") as f: coco = json.load(f) id2img = {img["id"]: img for img in coco["images"]} ann_by_img = {} for ann in coco["annotations"]: ann_by_img.setdefault(ann["image_id"], []).append(ann) sample = coco["images"][0] img_path = os.path.join("images", sample["file_name"]) img = cv2.imread(img_path) for ann in ann_by_img[sample["id"]]: x, y, w, h = [int(v) for v in ann["bbox"]] cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2) cv2.imwrite("check_sample.jpg", img)

检查时重点看三类问题:烟支框是不是比实际大很多(包进去一段手指或嘴唇);是不是只标了烟头没标烟身;是不是把嘴里的笔、吸管也画了框。标签不干净直接决定了后续训练的上限,所以这一步别省。如果发现整个数据集标注风格和你预期的检测目标不一致,宁可先做一轮修正,也不要直接开训。

4. 用YOLOv8把识别率跑上去:从配置文件到训练结果

4.1 工具链选择:为什么我用YOLOv8而不是更重的检测器

目标检测框架很多,Faster R-CNN、RetinaNet、DETR、YOLOv8各有人气。吸烟识别这个任务,我个人的选择一直是YOLOv8系列的yolov8s或yolov8m,理由有三点。

第一,训练和部署之间的转换链路最短。YOLOv8可以直接导出ONNX,再转成TensorRT或OpenVINO,在市面上大多数盒子里都能跑。两阶段检测器虽然精度上限可能更高,但部署复杂度高,对一个只需要"检测烟支/吸烟行为"的安防场景并不划算。第二,YOLOv8是anchor-free,对任意长宽比的小目标比如香烟这种细长条,不需要像anchor系模型那样微调anchor尺寸。第三,Ultralytics把训练、验证、导出、推理都封装得很干净,训练脚本写起来不费劲,数据集YAML一写就能跑。

如果你手里有更大的算力,我建议先从yolov8m开始,而不是一上来就yolov8x。因为吸烟识别的瓶颈主要在数据和标签质量,模型容量过大会在数据不足时过拟合得更厉害。10162张图训yolov8m完全够用,yolov8x反而可能让训练时间翻倍,识别率的提升却很有限。

4.2 把COCO转成YOLO能直接用的格式

Ultralytics YOLOv8默认读取的标签不是COCO JSON,而是每张图一个同名txt文件,每行是"class_id cx cy w h",且全部是归一化到0-1的数值。所以需要一个转换脚本,把COCO的bbox转换成YOLO格式的txt。我之前写的脚本可以用,注意几个关键点:类别索引必须从0开始连续;归一化时要用每张图原始宽高,而不是统一假设640;如果bbox超出了图像边界,要做clip处理。

import json import os def coco_to_yolo(coco_path, image_dir, label_dir): os.makedirs(label <p> <a href="https://download.csdn.net/download/pbymw8iwm/90061014" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>

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

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

立即咨询