☰
吸烟数据集带标注:从格式解析到YOLO训练全流程
2026/9/30 2:55:47 网站建设 项目流程

简介:这份「吸烟数据集带标注」面向从事人工智能与机器学习方向的开发者、算法工程师及数据科学学习者,用于训练和评估吸烟行为识别与预测模型。数据集以监督学习为目标,每个样本均带有吸烟者或非吸烟者的标注信息,可支撑分类、特征分析等典型任务。压缩包共1567个文件,包含783个xml标注文件与783个jpg图像文件,另有1个txt说明文件,整体约31.71MB;xml与jpg一一对应,便于直接用于目标检测或图像分类流程,txt则可能提供类别或划分说明。目前已有159人学习下载,适合作为课程实验、模型练手或小规模验证的数据来源。读者可据此快速搭建训练、验证与测试集,观察标注格式与图像组织方式,理解数据质量、类别平衡对模型性能的影响,并在此基础上尝试迁移学习或数据增强等扩展实验。

1. 吸烟数据集带标注:从“找不到数据”到“跑通第一个检测器”

做视觉检测的同行多半有过这种体验:想做一个吸烟行为识别的小模型,翻遍公开数据集,要么只有分类标签没有框,要么框得稀稀拉拉,烟头、手指、烟雾混在一起,标完自己都不敢信。吸烟数据集带标注这件事,核心价值不在于“有数据”,而在于“标注质量能不能直接喂给检测网络”。我见过太多人下载完解压一看,标注格式是自家定义的 txt,坐标还是相对值没乘宽高,光写解析脚本就耗掉一整天。这篇内容面向的是想快速验证吸烟检测可行性的一线开发者、做安防或工地场景落地的算法同学,以及需要一份可复现标注流程的数据工程师。接下来我会按“数据长什么样 → 怎么读怎么转 → 怎么训怎么调 → 坑在哪 → 怎么把标注质量再抬一档”的顺序讲清楚,每一步都落到能直接抄的命令和参数上。

2. 吸烟数据集带标注到底标了什么:类别体系与格式选型

2.1 吸烟场景里值得标的四类目标

吸烟行为检测不是单纯识别“烟”这一个物体。实际落地时,模型要区分的东西比想象中多。我一般会把标注体系拆成四类:香烟本体(cigarette)、持烟的手(hand_with_cigarette)、烟雾区域(smoke)、以及人脸/嘴部(face)。前三类是检测主力,第四类主要用于后续判断“烟是否在嘴边”这个动作逻辑。如果只标香烟,模型会把桌上任意一根白色细长物体误检成烟;如果只标烟雾,那基本没法做实时,因为烟雾形态太发散。

标注粒度上,香烟用矩形框足够,烟雾建议用多边形或至少一个宽松的矩形包住扩散区域。持烟的手这个类别最容易被忽略,但它恰恰是降低误检的关键——很多工地场景里,工人叼着烟但手没入镜,这时候只靠香烟框会漏。类别命名建议全小写加下划线,避免中文和空格,后面转 YOLO 或 COCO 格式时不用再洗一遍。

2.2 VOC、COCO、YOLO 三种格式的取舍

拿到一份“带标注”的数据集,第一件事是确认它是什么格式。常见就三种:Pascal VOC 的 XML、COCO 的 JSON、YOLO 的 txt。VOC 可读性最好,一个图一个 XML,适合人工核对;COCO 适合多任务和分割,但 JSON 结构嵌套深,改起来容易出错;YOLO 的 txt 最轻,每行class x_center y_center width height,但全是归一化数值,肉眼没法直接判断对错。

选型建议很直接:如果你只是训 YOLO 系列,直接用 YOLO txt;如果要做数据清洗和可视化核对,先转成 VOC 或 COCO 再看。我一般会保留一份 COCO JSON 作为“母版”,因为它的 categories 和 annotations 分离,增删类别时不用动图片目录。下面这张表是我常用的格式对照,参数含义写清楚,免得转的时候搞混。

格式单图标注载体坐标表示类别字段适合场景
VOCXML绝对像素 xmin/ymin/xmax/ymaxname 标签人工核对、小规模
COCO单个 JSON绝对像素 [x,y,w,h]category_id多任务、分割
YOLOtxt归一化 cx,cy,w,hclass_id 行首直接训练

注意:归一化坐标是除以图片宽高,不是除以最大边。很多人在这里翻车,导致框整体偏移。

2.3 标注质量的三条硬指标

一份吸烟数据集带标注能不能用,我只看三点。第一,框是否贴边:香烟框应该紧贴烟体,留白不超过 2 像素,否则回归分支学不准。第二,遮挡是否标:手挡住半截烟时,框要包住可见部分并延伸到被遮挡区域,不能只框露出来的那一小段。第三,负样本是否够:没有吸烟的图片也要放进去,且不带任何标注,否则模型会把“有人脸”和“有烟”强行关联。这三条不满足,后面调参调到天亮也白搭。

3. 把标注读进训练管线:解析、转换与可视化核对

3.1 用 Python 解析 VOC 标注并统计类别分布

拿到 XML 格式的吸烟数据集,第一步不是急着转,而是先统计每个类别的框数量。分布严重不均时,训练前就要想好重采样或加权。下面这段脚本遍历标注目录,输出类别计数和每类平均框面积,面积异常小的类别往往是漏标重灾区。

import os import xml.etree.ElementTree as ET from collections import defaultdict anno_dir = "annotations" # VOC xml 所在目录 counts = defaultdict(int) areas = defaultdict(list) for fname in os.listdir(anno_dir): if not fname.endswith(".xml"): continue tree = ET.parse(os.path.join(anno_dir, fname)) root = tree.getroot() for obj in root.findall("object"): name = obj.find("name").text.strip().lower() 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) counts[name] += 1 areas[name].append((xmax - xmin) * (ymax - ymin)) for k in counts: avg_area = sum(areas[k]) / len(areas[k]) print(f"{k}: {counts[k]} boxes, avg_area={avg_area:.1f}")

逻辑说明:ET.parse逐文件读取,findall("object")拿到每个目标,name统一转小写避免大小写混用。areas用来算平均面积,如果某个类别平均面积小于 200 像素,基本可以判定标注过小或图片分辨率太低。参数上,anno_dir换成你的实际路径即可,不需要额外依赖。

3.2 VOC 转 YOLO:归一化坐标的四个边界坑

转 YOLO 格式时,最常翻车的是坐标越界和类别映射。下面脚本把 VOC 转成 YOLO txt,同时生成classes.txt。注意xmax可能等于图片宽度,归一化后是 1.0,YOLO 允许,但有些训练框架会报错,所以我一般会 clamp 到 0.999。

import os import xml.etree.ElementTree as ET from PIL import Image classes = ["cigarette", "hand_with_cigarette", "smoke", "face"] cls_map = {c: i for i, c in enumerate(classes)} def voc_to_yolo(xml_path, img_dir, out_dir): tree = ET.parse(xml_path) root = tree.getroot() fname = root.find("filename").text img_path = os.path.join(img_dir, fname) w, h = Image.open(img_path).size lines = [] for obj in root.findall("object"): name = obj.find("name").text.strip().lower() if name not in cls_map: continue 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) # clamp 防止越界 xmin, xmax = max(0, xmin), min(w - 1, xmax) ymin, ymax = max(0, ymin), min(h - 1, ymax) cx = (xmin + xmax) / 2 / w cy = (ymin + ymax) / 2 / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h lines.append(f"{cls_map[name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") out_path = os.path.join(out_dir, fname.replace(".jpg", ".txt")) with open(out_path, "w") as f: f.write("\n".join(lines))

四个边界坑分别是:图片宽高读错(用了缩略图尺寸)、xmax等于宽导致归一化 1.0、类别名大小写不一致被跳过、以及文件名后缀不匹配(.jpeg和.jpg)。参数上classes顺序必须和训练时data.yaml里的names完全一致,否则类别全乱。跑完记得抽查几个 txt,用可视化脚本画框确认。

3.3 可视化核对:把框画回图上再决定要不要洗数据

转完格式别急着训,先画图。下面这段用 OpenCV 把 YOLO txt 画回原图,颜色按类别区分。我一般随机抽 20 张,肉眼过一遍,重点看香烟框有没有包住手指、烟雾框有没有大到离谱。

import cv2 import os colors = [(0,0,255), (0,255,0), (255,0,0), (0,255,255)] def draw_yolo(img_path, txt_path, out_path): img = cv2.imread(img_path) h, w = img.shape[:2] with open(txt_path) as f: for line in f: cid, cx, cy, bw, bh = map(float, line.split()) cid = int(cid) x1 = int((cx - bw/2) * w) y1 = int((cy - bh/2) * h) x2 = int((cx + bw/2) * w) y2 = int((cy + bh/2) * h) cv2.rectangle(img, (x1,y1), (x2,y2), colors[cid % 4], 2) cv2.imwrite(out_path, img)

逻辑很直白:反归一化乘回宽高,colors按类别索引取色。如果发现某类框颜色全错位,说明classes顺序和标注时不一致,回去改映射。这一步花十分钟,能省掉后面几小时的无效训练。

4. 用带标注的吸烟数据集训一个基线检测器

4.1 数据划分与 data.yaml 的必填字段

训练前先划分 train/val,比例我一般 8:2,如果总图少于 500 张就 7:3。目录结构按 YOLO 惯例:images/train、images/val、labels/train、labels/val。data.yaml里四个字段不能少:path、train、val、names。names用列表,顺序和转格式时完全一致。

path: ./smoke_dataset train: images/train val: images/val nc: 4 names: ["cigarette", "hand_with_cigarette", "smoke", "face"]

参数说明:nc是类别数,必须等于names长度;path用相对路径时,训练脚本的工作目录要对。很多人nc写错导致训练时类别索引越界,报错信息还不明显,只提示 label out of range。

4.2 训练命令与三个必调参数

以 YOLOv8 为例,基线命令如下。我一般先跑 50 epoch 看趋势,不直接上 300。

yolo detect train \ data=smoke_dataset/data.yaml \ model=yolov8n.pt \ epochs=50 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=10 \ name=smoke_baseline

三个必调参数:imgsz决定输入分辨率,吸烟场景里香烟框往往很小,640 是底线,有条件上 960;batch看显存,16 是 8G 卡的稳妥值;lr0初始学习率,0.01 是默认,如果 loss 前几轮就炸到 nan,降到 0.001。patience是早停,验证集 10 轮不升就停,省时间。

4.3 看什么指标判断标注能不能用

训练跑起来后,别只盯 mAP。我重点看三个:cls_loss是否稳定下降、box_loss是否在 0.5 以下、以及每类的 AP 分布。如果cigarette的 AP 远低于face,大概率是香烟框太小或漏标多;如果smoke的 AP 波动大,说明烟雾标注边界不一致。验证集预测图也要抽看,重点找“框到了但类别错”的样本,这类往往是标注时类别定义模糊导致的。

5. 吸烟数据集带标注的避坑与排查

5.1 现象:训练 loss 正常但验证 mAP 始终为 0

原因:data.yaml里names顺序和 txt 里的class_id对不上,或者 val 路径下没有对应 label 文件。解决:用脚本统计 val 的 txt 数量和图片数量是否一致,再打印一个 txt 的 class_id 范围,确认没超过nc-1。

5.2 现象:模型把所有人脸都框成 cigarette

原因:负样本里大量人脸无标注,但正样本里人脸和香烟共现太频繁,模型学到了虚假关联。解决:补充一批“有人脸无烟”的负样本,并在标注时确保这类图没有任何框;同时检查是否把face类误标成了cigarette。

5.3 现象:烟雾框大小差异极大,同一场景忽大忽小

原因:不同标注员对烟雾边界理解不一致。解决:定一个规则,烟雾框以可见最外缘为准,且不超过香烟框面积的 8 倍;超过的重新标。训练时对smoke类单独做尺度增强,缓解标注噪声。

5.4 现象:转 YOLO 后框整体偏移一个固定值

原因:VOC 的xmin/ymin是 1-based,有些工具导出是 0-based,转换时没减 1。解决:统一在解析时判断,如果xmin最小值是 1 且没有 0,就整体减 1;或者直接用PIL读图后按 0-based 处理。

5.5 现象:训练到一半报 label out of range

原因:某个 txt 里 class_id 写成了 4 或更大,但nc=4。解决:全量扫描 labels 目录,找出 class_id 最大值,超过nc-1的直接定位文件,多半是类别映射表写错或手工改标注时手滑。

6. 把标注质量再抬一档:半自动预标注与一致性校验

当你手里已经有一份吸烟数据集带标注,想扩量时,纯手工标成本太高。我现在的习惯是先用基线模型做预标注,再人工修。具体做法:用第 4 章训好的权重跑yolo detect predict,把预测结果转成 YOLO txt,置信度阈值设 0.4,低于这个的不生成框,避免引入太多噪声。然后人工只做三件事:删误检、补漏检、改类别。实测下来,预标注能把单图标注时间从 3 分钟压到 40 秒左右。

一致性校验我一般用两个指标:同一张图两次标注的 IoU 均值,以及类别混淆矩阵。IoU 低于 0.7 的框全部打回重标;混淆矩阵里cigarette和hand_with_cigarette互相错得多的,就回去把类别定义再写细一版,比如规定“手占框面积超过 50% 才算 hand_with_cigarette”。这套流程跑两轮,标注质量基本能稳住。

最后说个我自己的教训:早期做吸烟检测时,我图省事只标了香烟,结果模型在工地场景里把钢筋头、白色扎带全检成烟,误报率高到没法用。后来补了hand_with_cigarette和负样本,又用预标注滚了两轮,才把误报压下来。数据标注这件事没有后悔药,前期多花一天定规则,后期少熬一周调参。希望帮到你。

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

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

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

立即咨询