ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

4881张抽烟数据集实战:XML解析、labelimg复核与YOLO格式转换全流程

2026/9/12 2:16:23 拓冰建站 浏览量
4881张抽烟数据集实战:XML解析、labelimg复核与YOLO格式转换全流程 简介一套由4881张抽烟图像及对应XML标注文件组成的检测数据集面向人工智能、深度学习与计算机视觉方向的开发者和研究人员专为训练高精度抽烟检测模型而设计。压缩包内含9743个文件核心为4880张JPG图片与4861个labelimg生成的XML标注文件另附2个MD说明文档整体大小480.56MB。JPEGImages目录收录不同人物、环境及角度下的抽烟场景有助于提升模型泛化能力Annotations目录则记录精确的边界框坐标和类别标签可直接用于CNN等监督学习任务。目前已有1636人学习下载。借助该数据集可基于VGG、ResNet、YOLO等预训练模型进行迁移学习与微调也可通过数据增强、超参数调整、早停法、权重衰减等手段优化训练流程适合作为智能监控或健康提醒等应用落地前的实验素材能帮助使用者系统掌握目标检测从数据准备、标注解析到模型训练与评估的完整路径。1. 4881张抽烟数据集里比“张数”更关键的是XML标注质量拿到4881张抽烟数据集的压缩包第一反应通常是先找模型权重但这类项目真正值钱的部分是那批从 labelimg 里导出的 XML 文件。抽烟检测常见于加油站监控、仓库安全告警、驾驶员分神分析等场景通常是单类别目标检测也可能混入手部、手机等其他目标。要往下走必须先回答三个问题XML 是否和图片一一对应、类别拼写是否统一、标注框有没有明显越界或漏标。本文按“解析 XML → 用 labelimg 复核→ 批量转成 YOLO 标签 → 可视化校验”的顺序把这套流程讲透适合刚拿到数据、准备做自己第一个检测模型的工程师参考。2. 解析 4881 个 XML 前先把 VOC 字段对齐2.1 目录结构与 annotation 文件里最容易出错的字段用 labelimg 默认保存的 XML 是 Pascal VOC 格式数据集中常见目录如下Annotations/ JPEGImages/ ImageSets/Main/Annotations放 XMLJPEGImages放图片。如果包里有ImageSets/Main它通常是给 Faster R-CNN 或 SSD 训练脚本用的今天不大需要关心只需要确认 XML 文件名和图片文件名一致比如000001.xml对应000001.jpg。一份标准 XML 长这样annotation folderJPEGImages/folder filename000001.jpg/filename size width1920/width height1080/height depth3/depth /size object namesmoking/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin650/xmin ymin420/ymin xmax990/xmax ymax780/ymax /bndbox /object /annotation这里最值得注意的是size里的宽高。很多手工编辑的 XML 会把width和height写成反的导致后面转 YOLO 时所有框都偏掉。另一个容易出错的是filename字段它不一定和磁盘上的实际文件名一致。批量处理前最好先以“文件名 stem 配对”为准而不是直接信任 XML 里的filename值。2.2 用 xml.etree.ElementTree 统计每个 XML 的对象数拿到 4881 个 XML 后第一件事不是打开 labelimg而是写一个脚本把全部文件读一遍统计有多少能正常解析、有多少标签、类别字段是什么。Python 标准库xml.etree.ElementTree足够用。import xml.etree.ElementTree as ET from pathlib import Path from collections import Counter xml_dir Path(Annotations) stats Counter() broken_files [] total_boxes 0 for xml_path in xml_dir.glob(*.xml): try: tree ET.parse(xml_path) except ET.ParseError as exc: broken_files.append((xml_path.name, str(exc))) continue root tree.getroot() # 读取图像尺寸顺便检查是否缺失 try: width int(root.findtext(size/width)) height int(root.findtext(size/height)) except (TypeError, ValueError): broken_files.append((xml_path.name, missing size)) continue for obj in root.findall(object): name obj.findtext(name) stats[name] 1 total_boxes 1 print(类别统计, stats) print(总框数, total_boxes) print(异常文件数, len(broken_files)) for fname, reason in broken_files[:20]: print(fname, reason)root.findtext(size/width)会直接返回该路径第一个匹配元素的文本找不到时返回None。这里把“XML 本身解析失败”和“缺失关键字段”都归为异常文件方便一次性定位。对于 4881 个文件这个脚本只需要几秒输出结果能直接看出类别是否只有smoking一个词还是混了smoke、smoking person、吸烟等多种写法。类别不统一是这种手工数据集最常见的问题。2.3 XML 转义小于号与 的关系XML 解析报错里绝大多数不是坐标算错了而是文本内容里出现了非法字符。比如、、这类符号必须转义所以常会看到“小于号在 xml 中写成 ”的说法。lt是less than的缩写lt;在解析后就是一个。常用转义关系如下原文XML 中的写法lt;gt;amp;quot;apos;注意转义只对文本节点和属性值有意义。像bndbox这类标签名字本身不能写成lt;bndboxgt;。如果你的 XML 里某个name写成Cigarette lighter原始会让解析器认为新一轮实体开始直接报not well-formed。遇到这类问题优先看异常信息所指的行号附近而不是急着重标数据。3. 用 labelimg 复核和修 XML安装、快捷键与正方形不生效3.1 labelimg 安装“不是内部或外部命令”和闪退Windows 上最常见的安装方式是 pippip install labelimg python -m labelimg如果报“labelimg 不是内部或外部命令也不是可运行的程序”说明 pip 的 Scripts 目录没有加到 PATH。直接运行python -m labelimg可以绕过这个环境变量问题这也是我处理这类问题时的第一选择。Ubuntu 上同样适用先确认当前 Python 环境别名是python还是python3必要时改成python3 -m labelimg。labelimg 闪退多半和 PyQt 版本、中文路径、超大图片预览有关。拿 4881 张大图批量打开时建议先把整个数据集复制到纯英文路径下例如D:/smoke_dataset/JPEGImages再用 labelimg 点选小目录而不是直接全量加载。如果闪退发生在第一次画出矩形框时尝试升级或降级 PyQt5 版本这个依赖是 labelimg 图形界面的底层。3.2 labelimg 使用方法同时打开图片目录和类别文件复核标注时可以直接把图片目录和类别文件传给 labelimg。命令里的第二个参数是预定义类别文件每行一个类名python -m labelimg D:/smoke_dataset/JPEGImages D:/smoke_dataset/predefined_classes.txtpredefined_classes.txt里写一行smoking就够了。如果这个文件里没有 XML 中已有的类别labelimg 会把那个框显示成新类别本轮改动时容易顺手改成错误值。先保证类别文件与 XML 的name完全一致再开始翻图。常用快捷键按我自己的习惯是W新建框A上一张D下一张CtrlS保存Delete删除当前选中框。复核 4881 张大图不可能逐张精修更高效的方法是先看统计找到那些只有 1 个框或框面积特别小的 XML再跳到对应图片检查。面积统计可以在上一章的脚本里加一列不单独写代码时labelimg 的右侧列表也会显示当前 XML 的对象数量。3.3 正方形框切换不生效先分清新建框和编辑框这个问题在搜索里出现过很多次实际原因通常不是 bug。多数版本的 labelimg 对“正方形框”的处理是只对下一次新创建的矩形生效。如果你先点了一张已有的框再切换正方形开关当前框不会自动变成正方形这是正常表现。先按Esc取消框的选中状态勾选正方形开关再画新框。另一个原因是不同 fork 版本功能叫法不同有的在 Mode 菜单里有的叫Square图标是一正方形。实际操作里我不太建议对抽烟检测用正方形框。烟头通常是细长条烟雾扩散后是接近水平的大块区域宽高比在 0.3 到 0.8 之间波动都很正常强制 1:1 反而让模型学不到真实目标形状。3.4 类别统一和空标注文件处理用第二节的统计脚本跑完如果发现smoking、smoke、smoking_person三个类别最简单的方式不是打开 labelimg 手动改而是直接改 XML 中的name文本。写一个两行的ElementTree脚本把所有不认识的 name 替换成目标类名即可。替换后再跑 labelimg检查是否出现原来的类别清单以外的选项。空标注 XML 是另一个容易忽略的点。某个 XML 没有任何object对 YOLO 来说就是一个纯负样本。如果训练脚本把所有无目标图片都丢进 batch会让模型收敛变慢如果丢进验证集会因为该图片没有预测对象而被误判为误检。通常做法是训练集可以保留少量负样本验证集最好保证每张都有已知目标否则指标会很难看。4. 把 4881 张抽烟数据集的 XML 批量转为 YOLO 标注4.1 YOLO TXT 的归一化规则现在的检测训练基本都走 YOLO 格式。跟 VOC XML 相比YOLO 一行文本只保留五个值类别 ID、中心点 x、中心点 y、宽、高单位都是相对于图片宽高的浮点数。两者差异很直观维度VOC XMLYOLO TXT类别namesmoking/name数字 ID如 0坐标单位图像像素整数归一化的 0~1 浮点数坐标表达xmin/ymin/xmax/ymaxcx cy bw bh每图文件一个.xml一个.txt每行一个对象所以转换时必须先定names顺序。比如[smoking]这个顺序决定了 TXT 里第一列的数字。后面训练时smoking.yaml的names也要跟它保持一致。4.2 批量转换脚本下面脚本把整个Annotations目录下的 XML 转成 YOLO txt并做越界裁剪和非法框过滤。import xml.etree.ElementTree as ET from pathlib import Path CLASS_NAMES [smoking] # 顺序就是 YOLO 类别 ID不能随意排序 def voc_to_yolo(xml_path: Path, txt_path: Path): tree ET.parse(xml_path) root tree.getroot() width float(root.findtext(size/width).strip()) height float(root.findtext(size/height).strip()) lines [] for obj in root.findall(object): name (obj.findtext(name) or ).strip() if name not in CLASS_NAMES: continue cls_id CLASS_NAMES.index(name) bbox obj.find(bndbox) x1 float(bbox.findtext(xmin).strip()) y1 float(bbox.findtext(ymin).strip()) x2 float(bbox.findtext(xmax).strip()) y2 float(bbox.findtext(ymax).strip()) # 过滤宽高为负或像素颠倒的框 if x2 x1 or y2 y1: continue # 越界裁回图片范围内避免训练时报错 x1 max(0.0, min(x1, width)) y1 max(0.0, min(y1, height)) x2 max(0.0, min(x2, width)) y2 max(0.0, min(y2, height)) cx (x1 x2) / 2.0 / width cy (y1 y2) / 2.0 / height bw (x2 - x1) / width bh (y2 - y1) / height lines.append(f{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) txt_path.write_text(\n.join(lines), encodingutf-8) xml_dir Path(Annotations) txt_dir Path(labels) txt_dir.mkdir(exist_okTrue) for xml_file in xml_dir.glob(*.xml): voc_to_yolo(xml_file, txt_dir / (xml_file.stem .txt))这段代码的核心是CLASS_NAMES它同时承担了类别白名单和 ID 映射两个职责。bbox.findtext(xmin)返回的是字符串转成float后参与计算。越界裁剪不是多余动作手工标注经常在图片边缘留下超出宽高的框不处理会在训练读取标签时报index out of range。4.3 train/val 划分并组织成 YOLO 目录YOLOv5、YOLOv8 这类仓库习惯使用固定目录结构images/train、images/val、labels/train、labels/val。可以直接用下面的脚本划分import random from pathlib import Path import shutil random.seed(42) img_src Path(JPEGImages) txt_src Path(labels) out Path(datasets/smoking) for split in (train, val): (out / images / split).mkdir(parentsTrue, exist_okTrue) (out / labels / split).mkdir(parentsTrue, exist_okTrue) images list(img_src.glob(*.jpg)) random.shuffle(images) val_count int(len(images) * 0.2) for idx, img_path in enumerate(images): split val if idx val_count else train txt_path txt_src / (img_path.stem .txt) if not txt_path.exists(): continue shutil.copy(img_path, out / images / split / img_path.name) shutil.copy(txt_path, out / labels / split / txt_path.name)random.seed(42)保证每次拆分配对一致方便复现实验结果。如果 XML 里存在没有对应 txt 的图片上面直接跳过避免训练集里出现“图片有、标签没有”的情况。4.4 贴合 YOLOv5/YOLOv8 的 smoking.yaml训练自己的数据集时需要写一个数据配置path: D:/datasets/smoking train: images/train val: images/val nc: 1 names: - smokingpath指向刚才生成的数据集根目录nc是类别数。如果你转换时把CLASS_NAMES改成了多个类这里要同步调整。训练命令按照你选的框架写即可以 YOLOv8 为例yolo detect train datasmoking.yaml modelyolov8s.pt imgsz640 batch16 epochs100imgsz640是经验值和图片原尺寸无关显存不足时先降batch不要改imgsz到 320否则烟头这类小目标基本学不到。5. 给 4881 张数据做的最后校验把 TXT 标签画回原图5.1 用 OpenCV 批量绘制检测框转完格式先别急着训练验证集和训练集里的标签是否画在了正确位置用 OpenCV 把 TXT 标签叠加到原图上是效率最高的方法import cv2 from pathlib import Path img_dir Path(datasets/smoking/images/train) label_dir Path(datasets/smoking/labels/train) out_dir Path(check) out_dir.mkdir(exist_okTrue) colors {0: (0, 0, 255)} # 类别 0 用红色 for txt_path in label_dir.glob(*.txt): img_path img_dir / (txt_path.stem .jpg) img cv2.imread(str(img_path)) height, width img.shape[:2] for line in txt_path.read_text(encodingutf-8).splitlines(): parts line.split() if len(parts) ! 5: continue cls_id, cx, cy, bw, bh map(float, parts) x1 int((cx - bw / 2) * width) y1 int((cy - bh / 2) * height) x2 int((cx bw / 2) * width) y2 int((cy bh / 2) * height) cv2.rectangle(img, (x1, y1), (x2, y2), colors[int(cls_id)], 2) cv2.imwrite(str(out_dir / (txt_path.stem _check.jpg)), img)绘制时的关键点YOLO TXT 中cx、bw都是归一化比例要乘回图片实际宽高并换算回左上角和右下角坐标。将生成目录里的图快速翻一遍重点看框是否完全包住烟头和烟雾是否存在一排框全部偏上或偏下的系统误差。如果身形框画到了人像以外通常不是转换脚本的问题而是原始 XML 坐标在人工标注时就没贴紧目标。5.2 小目标处理先看宽高分布再决定切图裁剪后的bw和bh其实可以当成一张表导出统计所有框相对于原图的比例。对抽烟检测来说烟头在 1080p 画面里可能只占图片宽度的 3%~5%这类小目标即使标签完全正确YOLOv8 也不容易学。处理建议是先把所有bw 0.05的框统计出来如果占比超过 20%训练时把imgsz提到 1280或者先把原图按 512x512 切片再训练。这个决策必须在训练前做而不是等模型跑完再看 mAP。本文还有配套的精品资源点击获取