资讯详情

labelImg图像标注工具实战:从VOC XML到YOLO格式转换与避坑指南

📅 2026/10/6 20:59:26 | 华诺云谱 👁 阅读
labelImg图像标注工具实战:从VOC XML到YOLO格式转换与避坑指南
简介labelImg-master 图像标注工具源码包面向计算机视觉方向的研究人员、算法工程师与需要自建标注流程的开发者用于解决目标检测与分割任务中边界框、多边形区域标注及 PASCAL VOC、COCO 等数据集格式导出问题。压缩包共 118 个文件约 6.95MB以 38 个 png 界面图标、27 个 py 源码、18 个 pyc 编译文件为主另含 svg、sh、rst、qrc、cfg、in、makefile、license 等配置与资源文件覆盖主程序、安装脚本、构建配置与许可协议目录结构完整。已有 706 人学习下载。通过阅读 labelImg.py 主程序与 setup.py、setup.cfg 等配置可理解图形界面标注逻辑、XML 标注文件生成方式及跨平台打包流程便于二次开发与定制标注工具。1. 拆开 labelImg-master 压缩包从 test.512.512.bmp 到 VOC XML 的完整链路如果你手头正躺着一个labelImg-master图像标注工具.zip解压后看到labelImg.py、setup.py、resources.py、test.512.512.bmp、demo.jpg这些文件却不确定它到底能不能跑、跑起来怎么标、标完的 XML 能不能直接喂给 YOLO 或 SSD那这篇就是写给你的。labelImg 是计算机视觉圈子里被反复提起的图像标注工具核心价值只有一句话把一张图上的目标框出来落成 PASCAL VOC 格式的 XML让训练脚本能直接读。它适合做目标检测数据集的人、需要快速验证标注流程的人以及想改源码做二次开发的人。压缩包里那个test.512.512.bmp不是摆设它是你验证环境是否正常的第一个样本。2. 环境搭建与首次启动从 setup.py 到 labelImg.py 的落地路径2.1 为什么优先用源码包而不是 pip 安装很多人第一次接触 labelImg 会直接pip install labelImg但拿到labelImg-master源码包的人通常有更具体的诉求改快捷键、改默认保存路径、加自定义格式导出。源码包里的setup.py和setup.cfg决定了安装行为labelImg.py是主入口resources.py是 Qt 资源编译产物。常见做法是先用源码跑通再决定要不要装到系统环境。源码包对 Python 版本有要求。labelImg 依赖 PyQt5 和 lxmlPython 3.6 到 3.9 比较稳3.10 以上偶尔会在 PyQt5 的 wheel 上翻车。我一般会单独建一个虚拟环境避免和系统里的 Qt 库打架。# 创建并激活虚拟环境Python 版本建议 3.8 或 3.9 python -m venv venv_labelimg # Windows venv_labelimg\Scripts\activate # macOS / Linux source venv_labelimg/bin/activate # 安装依赖PyQt5 和 lxml 是硬依赖 pip install pyqt5 lxml这两条命令的逻辑很直接虚拟环境隔离依赖PyQt5 提供图形界面lxml 负责 XML 的读写。参数上唯一需要注意的是 PyQt5 的版本如果安装后启动报Could not load the Qt platform plugin通常是 PyQt5 版本和系统 Qt 库冲突可以尝试pip install pyqt55.15.4这类固定版本。2.2 从源码启动并验证 test.512.512.bmp依赖装好后不要急着python setup.py install先在源码目录直接跑主脚本这样出问题能看到完整堆栈。# 进入解压后的 labelImg-master 目录 cd labelImg-master # 直接运行主程序 python labelImg.py如果界面正常弹出说明环境没问题。接下来用压缩包里的test.512.512.bmp做一次完整标注验证。点击左侧Open Dir选择图片所在目录或者Open单张打开。图片加载后按W创建矩形框框住目标输入类别名按CtrlS保存。默认保存路径和图片同目录生成同名 XML。这里有个细节test.512.512.bmp是 512x512 的位图没有 alpha 通道labelImg 读取不会出问题。但如果你换成带透明通道的 PNG某些 PyQt5 版本会显示异常这是 Qt 图像解码的边界不是 labelImg 的 bug。2.3 setup.py 安装与命令行启动的差别源码直接跑和python setup.py install之后再跑差别在于入口脚本和资源路径。安装后可以用labelImg命令直接启动但如果你改了labelImg.py里的代码安装版不会同步必须重新安装。我一般开发阶段用源码跑稳定后再安装。# 在源码目录执行安装 python setup.py install # 安装后可直接用命令启动 labelImgsetup.py里会读取setup.cfg的元信息MANIFEST.in决定哪些非代码文件被打进包。如果你要打包分发给别人MANIFEST.in里漏了resources.py或图标文件对方装完会缺资源。这是打包时最容易忽略的坑。3. 标注流程与 VOC XML 结构从画框到训练脚本能读的文件3.1 矩形框标注的完整操作链labelImg 的核心操作围绕几个快捷键展开。按W画矩形按D下一张按A上一张CtrlS保存CtrlShiftS另存为。这些快捷键在labelImg.py里定义改源码就能改键位。标注时类别名建议用英文不要用中文或空格。VOC XML 里name字段直接写类别字符串训练脚本按字符串匹配中文在某些编码环境下会出问题。我一般会提前准备一个predefined_classes.txt放在源码目录labelImg 启动时会自动读取省去每次手输。# predefined_classes.txt 示例 person car dog bicycle这个文件的作用是预定义类别列表画框时可以直接从下拉选减少拼写错误。文件路径默认在用户主目录的.labelImg下也可以启动时用--class参数指定。3.2 VOC XML 的字段含义与训练脚本对接保存后的 XML 结构如下每个字段都直接影响训练脚本的解析。annotation folderimages/folder filenametest.512.512.bmp/filename path/data/images/test.512.512.bmp/path size width512/width height512/height depth3/depth /size object nameperson/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin120/xmin ymin80/ymin xmax300/xmax ymax400/ymax /bndbox /object /annotationsize里的width和height必须和图片实际尺寸一致否则训练时坐标归一化会错位。bndbox的四个值是左上角和右下角坐标不是中心点加宽高。difficult字段在评估时用来忽略难样本truncated表示目标是否被截断。很多 YOLO 转换脚本只读name和bndbox其他字段忽略但 VOC 评估脚本会用到difficult。3.3 从 VOC 到 YOLO 格式的转换脚本如果你要训练 YOLO需要把 VOC XML 转成 txt 格式每行是类别 x_center y_center width height全部归一化到 0 到 1。import os import xml.etree.ElementTree as ET # 类别映射必须和训练时的 classes 顺序一致 classes [person, car, dog, bicycle] def convert_voc_to_yolo(xml_dir, output_dir, img_width, img_height): 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 root.find(size) w int(size.find(width).text) h int(size.find(height).text) lines [] for obj in root.iter(object): cls_name obj.find(name).text if cls_name not in classes: continue cls_id classes.index(cls_name) bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) # 归一化并转为中心点加宽高 x_center (xmin xmax) / 2.0 / w y_center (ymin ymax) / 2.0 / h width (xmax - xmin) / w height (ymax - ymin) / h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) # 输出同名 txt txt_name xml_file.replace(.xml, .txt) with open(os.path.join(output_dir, txt_name), w) as f: f.write(\n.join(lines)) # 调用示例 convert_voc_to_yolo(./annotations, ./labels, 512, 512)这段脚本的关键点有三个类别映射顺序必须和训练配置一致归一化用的是 XML 里的实际尺寸而不是传参坐标转换是中心点加宽高而不是角点。参数上xml_dir是 XML 所在目录output_dir是 txt 输出目录img_width和img_height只在 XML 缺少 size 字段时作为兜底。常见翻车是类别名大小写不一致Person和person会被当成两个类。4. 避坑与排查标注过程中最容易翻车的五个场景4.1 启动报 Qt platform plugin 错误现象是运行python labelImg.py后直接报Could not load the Qt platform plugin xcb或windows。原因是 PyQt5 的插件路径和系统环境不匹配常见于 Linux 服务器没有图形界面或者 conda 环境里混装了多个 Qt。解决办法是先确认是否有显示环境Linux 下需要export QT_QPA_PLATFORMoffscreen只能用于无头测试正常标注还是要有桌面。如果是 conda 环境尝试pip uninstall pyqt5后重新用 pip 安装不要混用 conda 和 pip 的 Qt。4.2 保存的 XML 坐标越界现象是 XML 里xmax大于图片宽度或者ymin为负数。原因是画框时拖到了图片边界外labelImg 默认不裁剪。这种样本训练时会导致归一化坐标大于 1YOLO 会直接报错。解决办法是在转换脚本里加裁剪把坐标限制在[0, width]和[0, height]范围内。更稳妥的做法是标注时不要贴边画框留一两个像素余量。4.3 类别名带空格或特殊字符现象是训练脚本解析 txt 时类别错位。原因是 XML 里name字段写了traffic light这种带空格的名字转换脚本按空格分割时会把一个类别拆成两个。解决办法是类别名统一用下划线比如traffic_light或者在转换脚本里用固定宽度解析而不是 split。我一般会在predefined_classes.txt里就定好下划线命名从源头避免。4.4 图片和 XML 不同名导致找不到现象是训练时提示找不到标注文件。原因是 labelImg 保存的 XML 默认和图片同名但如果你手动改了图片名或 XML 名对应关系就断了。解决办法是保持图片和 XML 同名同目录或者在转换脚本里建立映射表。批量重命名时图片和 XML 要同步改不要只改一边。4.5 大图标注卡顿现象是打开 4K 以上图片时界面卡死。原因是 labelImg 用 Qt 直接渲染原图没有做缩放缓存。解决办法是先把大图缩放到 1920 宽度以内再标注或者改源码在labelImg.py里加缩放逻辑。常见做法是用ffmpeg或PIL批量缩放标注完再把坐标按比例还原。这个还原步骤容易漏导致坐标全部偏小。5. 二次开发与批量验证改快捷键、加导出格式、跑通全量检查5.1 改快捷键和默认保存路径labelImg.py里搜索shortcut就能找到快捷键定义。比如把下一张从D改成Right找到next_image对应的QShortcut把键值换掉。默认保存路径在save_file函数里改default_save_dir变量即可。改完直接源码运行生效不需要重新安装。# 在 labelImg.py 中定位到快捷键定义区域 # 原代码类似 # self.next_image_shortcut QShortcut(QKeySequence(D), self) # 改为 self.next_image_shortcut QShortcut(QKeySequence(Right), self)改快捷键的逻辑是替换QKeySequence的参数参数可以是Right、CtrlS这类 Qt 标准键名。注意不要和已有快捷键冲突否则会触发两个动作。5.2 加一个 COCO JSON 导出VOC 和 YOLO 之外COCO 格式也越来越常用。可以在labelImg.py的保存逻辑里加一个分支把当前标注写成 COCO JSON。核心是维护一个全局的images、annotations、categories列表每张图保存时追加。import json coco_data { images: [], annotations: [], categories: [{id: i, name: c} for i, c in enumerate(classes)] } def add_coco_annotation(image_id, file_name, width, height, boxes): coco_data[images].append({ id: image_id, file_name: file_name, width: width, height: height }) for box in boxes: coco_data[annotations].append({ image_id: image_id, category_id: box[cls_id], bbox: [box[xmin], box[ymin], box[w], box[h]], area: box[w] * box[h], iscrowd: 0 }) # 全部标完后写出 with open(annotations.json, w) as f: json.dump(coco_data, f)COCO 的bbox是左上角加宽高不是角点和 VOC 不同。area是宽高乘积iscrowd一般填 0。这个导出适合需要同时用 Detectron2 或 MMDetection 的场景。5.3 全量 XML 校验脚本标完几百张图后手动检查不现实。我习惯跑一个校验脚本检查 XML 是否缺字段、坐标是否越界、类别是否在预定义列表里。import os import xml.etree.ElementTree as ET def validate_xml(xml_dir, classes): errors [] for f in os.listdir(xml_dir): if not f.endswith(.xml): continue path os.path.join(xml_dir, f) try: root ET.parse(path).getroot() except ET.ParseError: errors.append(f{f}: XML 解析失败) continue size root.find(size) if size is None: errors.append(f{f}: 缺少 size) continue w int(size.find(width).text) h int(size.find(height).text) for obj in root.iter(object): name obj.find(name).text if name not in classes: errors.append(f{f}: 未知类别 {name}) box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) if xmin 0 or ymin 0 or xmax w or ymax h: errors.append(f{f}: 坐标越界 {xmin},{ymin},{xmax},{ymax}) return errors # 使用 errs validate_xml(./annotations, [person, car, dog, bicycle]) for e in errs: print(e)这个脚本跑一遍能把大部分低级错误筛出来。参数上classes必须和训练配置一致xml_dir是标注目录。我一般会在转换格式之前跑一次避免把脏数据带进训练。从那以后我每次标完一个数据集都会先跑校验脚本再转格式宁可多花十分钟也不让训练脚本在半夜报坐标越界。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑