简介:LabelImg 是一款可视化的图像标注工具源码包,面向需要自制目标检测数据集的算法工程师与深度学习者,常用于 Faster R-CNN、YOLO、SSD 等模型的训练前标注。使用时需配置 python + lxml 环境,标注结果自动生成 PASCAL VOC 格式的 XML 文件,便于直接接入主流检测框架,减少数据预处理成本。压缩包共 127 个文件、约 6.39MB,其中 44 个 Python 脚本构成核心逻辑,38 张 PNG 图片与 JPG、SVG 文件提供示例与界面图标,另有 shell 脚本、配置文件、qrc 资源描述及说明文档等,项目结构完整,便于离线查阅和二次开发。目前已有 357 人浏览学习。对无法访问 GitHub 的开发者来说,这份源码省去了外网下载不便,解压即可查看 1.8.1 版本完整实现,借助 demo 图片快速上手标注流程,也可以按需修改界面、扩展标注功能或集成到自己的训练流程中。1. 图像标注工具选型:为什么老项目里总还留着 labelImg做计算机视觉项目,数据标注是绕不开的第一道工序。labelImg 这种老牌开源标注工具,界面朴素但功能完整,支持 PascalVOC 和 YOLO 两种主流标注格式,代码全开放、可随意改,直到今天我依然把它当作本机标注的首选工具。它解决的核心问题很直接:给你一套不需要联网、不依赖在线平台的本地标注方案,适合需要自己准备数据集的入门学习者和中小型项目团队。这篇笔记从源码包拆解出发,讲清楚安装启动、标注实操、格式转换和踩坑点,让新手能照做,让熟手能少走弯路。2. 安装与启动:从 labelImg-master 源码包到成功打开界面2.1 源码目录里到底有什么把 labelImg-master.rar 解压后,首先应该看清目录结构,不要急着双击运行。整个项目的核心代码量不大,但文件布局有它的逻辑。labelImg-master/ ├── labelImg.py # 主入口,GUI 逻辑全在这 ├── labelImg.pyw # Windows 无控制台窗口的启动器 ├── libs/ # 核心库,包含画框、数据格式处理等 │ ├── canvas.py # 画布模块,负责矩形框绘制和编辑 │ ├── pascal_voc_io.py # PascalVOC XML 格式读写 │ ├── yolo_io.py # YOLO TXT 格式读写 │ └── ... ├── data/ │ ├── predefined_classes.txt # 预定义标签列表 │ └── ... ├── resources.py # Qt 资源文件编译后的模块 ├── requirements.txt # Python 依赖列表 └── setup.py # 安装脚本这里最值得关注的是libs/目录下的格式读写模块。pascal_voc_io.py负责 XML 格式的解析与保存,yolo_io.py负责 YOLO 格式的转换。如果你后期想要扩展新的标注格式,直接在这两个模块旁边加一个xxx_io.py,然后在主程序中注册即可。2.2 依赖安装:三步让环境跑起来labelImg 是基于 PyQt 的图形界面工具,核心依赖只有两个:PyQt5 和 lxml。不同操作系统下安装方式略有差异,但思路一致。# Linux/macOS 环境 pip install pyqt5 lxml # Windows 环境建议用 pip 安装完整依赖 pip install -r requirements.txtrequirements.txt文件里列出了所有依赖项,直接安装即可。需要注意 PyQt5 的版本兼容性,我遇到过在特定 Python 版本下 PyQt5 安装失败的情况,一般升级到最新的 PyQt5 就能解决。依赖装完后,启动方式有两种。在终端里运行python labelImg.py会附带控制台输出,方便排查问题;双击labelImg.pyw则不会弹出黑色控制台窗口。# 推荐首次运行时用这个命令,能看到完整的调试信息 python labelImg.py启动后出现主窗口就说明环境没问题了。如果你在 Windows 上遇到 DLL 加载失败的报错,多半是 PyQt5 安装不完整,重装一次即可。2.3 启动参数:直接打开图片目录和预定义标签labelImg 支持在启动时传入参数,直接用命令行打开图片文件夹和标签文件,省去每次手动加载的步骤。# 格式:labelImg.py [图片路径] [预定义类别文件路径] python labelImg.py ./images ./data/predefined_classes.txt第一个参数是图片文件夹路径,第二个参数是预定义标签文件路径。启动后自动加载指定图片目录,标签列表也会同步填充。这一步在实际项目中非常实用,尤其是数据集分布在多个文件夹时,可以写脚本批量调用不同参数。注意路径不要带中文,虽然某些版本支持中文路径,但那属于玄学范畴,我通常在项目初始化阶段就把所有路径统一成英文,避免后面训练时各种诡异报错。3. 标注实操:画框、快捷键、类目管理与保存格式3.1 界面布局和标注主流程labelImg 主界面由菜单栏、左侧工具区、中间画布和右侧标签列表组成。打开一张图片后,按W键进入画框模式,在目标物体上按住鼠标左键拖拽,松手后弹出类别选择框,选择或输入类别即可。一个完整的标注流程是:加载图片 → 画框 → 选类目 → 保存 → 切换到下一张,不断重复。画框时尽量贴合目标边界,不要留太多背景,也不要切掉目标边缘,这两点直接影响后续检测模型的训练效果。右侧的标签列表会展示当前图片所有标注框。如果某个框画歪了,选中后可以直接拖动边缘调整;如果需要删除,按Ctrl+R或者右键菜单删除。3.2 快捷键:标注效率的核心差距标注工作重复性极高,用鼠标点菜单的效率远低于快捷键。我整理了一份高频快捷键表,熟练后标注速度能提升一倍以上:快捷键功能使用场景W创建矩形框画新标注框A / D切换上一张/下一张图片连续标注时快速切换Ctrl+S保存标注结果手动保存当前图片标注Ctrl+D复制当前标注框同类物体密集出现时Ctrl+R删除当前选中框画错或框选不当时Del删除选中的标注框同上Ctrl+Z撤销上一步操作误拖动或误删除这里特别说下Ctrl+D复制框的功能。当一张图里有大量同类物体时,画完第一个框后,用复制功能生成新框再移动到目标位置,比重新画框快得多。不过复制出来的框默认位置相同,需要手动拖到目标位置。另一个容易忽略的是缩放功能:鼠标滚轮缩放画布,按住空格键拖动画布。标注大图时,先整体画完所有框,再局部放大微调边界,效率最高。我见过某开发者朋友全程用 100% 缩放逐像素抠框,一个下午只标了 20 张图,这是典型的工具使用不当。3.3 保存在哪里:PascalVOC、YOLO 和 ImageNet保存格式可以在菜单栏的"PascalVOC"和"YOLO"之间切换,默认是 PascalVOC 格式。这两种格式本质区别在于标注数据的表达方式:PascalVOC 格式:每张图片对应一个同名的 XML 文件,里面记录了图片尺寸、通道数和每个标注框的类别及坐标信息。YOLO 格式:每张图片对应一个同名的 TXT 文件,每行一条记录,格式为"类别ID 中心点x 中心点y 框宽度 框高度",所有坐标值都归一化到 0~1。实际项目中,如果模型框架用的是 YOLO 系列,就要选 YOLO 格式保存;如果用 Faster R-CNN 或 SSD,一般选 PascalVOC。切换格式的方法是在菜单栏点击"PascalVOC"或"YOLO"菜单项,当前格式会在标题栏显示。这里有个坑在后面单独说,YOLO 模式保存出的坐标是归一化后的浮点数,XML 里存的是整数像素值,两者不是简单的字段改名关系,格式转换时要注意换算逻辑。4. 格式转换实战:VOC XML 批量转 YOLO TXT 的脚本4.1 为什么要自己写转换脚本虽然 labelImg 支持直接保存 YOLO 格式,但实际使用中,很多人拿到的标注数据是 PascalVOC 格式的,而模型框架只认 YOLO 格式;或者反过来。每次重新标注不现实,手写转换脚本是最高效的方式。还有一个更现实的情况:多人协作的数据集可能混用了两种格式,统一处理时必须用脚本批量转换。我一般是先写一个通用转换脚本,把所有数据集中到一个目录,再统一格式。4.2 核心脚本:XML 解析与归一化计算转换逻辑分三步:读取 XML 文件、解析每个标注框的坐标、计算归一化后的转换结果并写入 TXT。import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, output_dir, class_names): """ 将单个 PascalVOC XML 文件转换为 YOLO TXT 文件 :param xml_path: 输入 XML 文件路径 :param output_dir: TXT 输出目录 :param class_names: 类别名称列表,用于映射类别ID """ tree = ET.parse(xml_path) root = tree.getroot() size = root.find('size') width = int(size.find('width').text) height = int(size.find('height').text) base_name = os.path.splitext(os.path.basename(xml_path))[0] txt_path = os.path.join(output_dir, base_name + '.txt') lines = [] for obj in root.findall('object'): name = obj.find('name').text if name not in class_names: print(f"跳过未知类别: {name} 在 {xml_path}") continue class_id = class_names.index(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) # 计算 YOLO 需要的中心点和宽高,并归一化 center_x = (xmin + xmax) / 2.0 / width center_y = (ymin + ymax) / 2.0 / height box_width = (xmax - xmin) / width box_height = (ymax - ymin) / height lines.append(f"{class_id} {center_x:.6f} {center_y:.6f} {box_width:.6f} {box_height:.6f}") with open(txt_path, 'w', encoding='utf-8') as f: f.write('\n'.join(lines) + '\n') print(f"已转换: {os.path.basename(xml_path)} -> {os.path.basename(txt_path)}")逻辑说明:代码先解析 XML 里的图片尺寸,这是坐标归一化的分母;然后遍历每个 object 节点,获取类别名和 bndbox 子节点里的四个边界坐标。归一化计算中,中心点坐标等于 (xmin+xmax)/2 再除以图像宽度或高度,框的宽高等于 (xmax-xmin) 除以图像宽度。最终写入 TXT 时浮点数保留 6 位精度,够用且文件不会太大。参数说明:class_names列表的顺序必须和模型训练时的类别顺序一致。常见做法是先扫描全部 XML,收集所有类别并按字典序排序,再生成映射表。如果不一致,训练时类别就乱了。批量处理时加一个外层遍历:def batch_convert(xml_dir, output_dir, class_names): """批量转换一个目录下的所有 XML 文件""" os.makedirs(output_dir, exist_ok=True) for xml_file in os.listdir(xml_dir): if xml_file.endswith('.xml'): voc_to_yolo( os.path.join(xml_dir, xml_file), output_dir, class_names )4.3 转换后的验证方式转换不是跑完就结束,必须抽样验证结果。最简单的方式是用 labelImg 打开转换后的目录,切到 YOLO 模式,一张张看标注框是否贴合目标。还可以写个校验脚本,检查坐标是否越界:import os def validate_yolo(txt_path, img_width, img_height): """ 检查 YOLO 标注是否越界 :param txt_path: TXT 文件路径 :param img_width: 图片宽度 :param img_height: 图片高度 """ with open(txt_path, 'r') as f: for line_num, line in enumerate(f.readlines(), 1): parts = line.strip().split() if len(parts) != 5: print(f"格式错误: {txt_path} 第{line_num}行") continue _, cx, cy, w, h = parts cx, cy, w, h = float(cx), float(cy), float(w), float(h) # 还原像素坐标 abs_x1 = (cx - w / 2) * img_width abs_y1 = (cy - h / 2) * img_height abs_x2 = (cx + w / 2) * img_width abs_y2 = (cy + h / 2) * img_height if abs_x1 < 0 or abs_y1 < 0 or abs_x2 > img_width or abs_y2 > img_height: print(f"越界: {txt_path} 第{line_num}行 [{abs_x1:.1f},{abs_y1:.1f},{abs_x2:.1f},{abs_y2:.1f}]")越界通常意味着原 XML 的坐标超出图片边界,这类文件在训练时会报错或被框架丢帧,提前发现能省下不少排查时间。5. 避坑与常见问题:五个最常翻车的点5.1 现象:打开图片程序直接闪退或崩溃原因有两个,最常见的是图片路径包含中文,PyQt 自带的文件读取模块在处理非 ASCII 路径时不稳定;其次是 PyQt5 和 Python 版本不匹配导致的底层错误。解决:把整个项目的路径统一改成英文,从根目录到图片文件夹都不要有中文。或者看报错代码,如果是could not decode to unicode类的错误,就是路径问题。换用虚拟环境安装最新版 PyQt5 能解决绝大多数版本兼容问题。5.2 现象:图片可以打开,但画不了框,鼠标拖拽无反应原因:当前标注工具还停留在编辑模式,没有进入创建模式,或者上一次软件异常退出导致画布状态卡死。解决:按一下键盘上的W键切换到创建模式,看光标是否从箭头变成十字。如果W键无效,重启程序并检查菜单栏里的 Edit 模式是否被误勾选。这个坑多发生在快速连续操作时,画完一张图马上切下一张,画布还没重置就按鼠标。5.3 现象:YOLO 模式下保存的 TXT 文件内容错乱,坐标出现大于 1 的数原因:labelImg 的 YOLO 模式写入采用的是归一化坐标,但如果你手工修改过 XML,或者在部分版本里切换格式时没有重新加载图片,程序可能仍按旧逻辑输出。解决:切换保存格式后,必须重新打开图片目录再继续标注。修改 XML 文件后要彻底关闭再启动 labelImg,确保配置缓存刷新。输出结果可以用 4.3 节的校验脚本检查,坐标越界就说明转换逻辑有问题。5.4 现象:大图标注卡成幻灯片,拖动一条线都要等一秒原因:高分辨率图片载入 GUI 时会全量加载像素数据,内存和 GPU 没有足够的缓冲空间。解决:降低图片预览分辨率,在"视图"菜单里把缩放比例调到 50% 或 25%,画框时用的是相对坐标,不影响最终保存的精度。另一个方法是把图片在标注前先压缩到 2000 像素以内,模型训练时候再恢复原尺寸,这样标注流畅度提升明显。5.5 现象:标注了一下午,关掉软件后 XML 文件全是旧的原因:labelImg 默认是手动保存,如果不点保存直接切换图片或退出,当前图片的标注数据不会写入文件。这个坑几乎每个新手都踩过。解决:养成强制保存的习惯,每标完一张图就按Ctrl+S,或者把菜单栏的"自动保存"选项勾上。自动保存模式下,切换图片时自动写入 XML,不会丢数据。但从那以后我每次标注大数据集前都先开启自动保存,再手动保存一次确认输出目录可写,双保险才安心。6. 进阶技巧:预定义标签、自动保存与备份习惯自定义标签列表是提升标注效率的关键一步。data/predefined_classes.txt文件里每行一个类别名,软件启动时会读取这些类别。你只需要编辑这个文件,填入当前项目的全部目标类别,标注时就不用每次手敲类别名了:# data/predefined_classes.txt 示例 person car bicycle traffic_light启动命令加上类别文件路径后,点击类别选择框会直接出现下拉列表,点一下就能完成选择,比手敲拼音快得多。类目命名建议统一用英文小写加下划线,不要混用中文和英文,否则后续训练框架处理类别映射时会很痛苦。自动保存功能在"视图"菜单里勾选。开启后,切换图片或关闭程序前,当前标注自动写入 XML。我一般还会定期备份标注文件到独立目录:# 简易备份脚本,每小时执行一次 cp -r ./labels ./backup_labels_$(date +%Y%m%d_%H%M)标注工作最怕的不是标错,而是标错后没有后悔药。开启自动保存固然方便,但保存间隔内如果软件崩溃,最近几张图的标注还是会丢。我习惯每标完一批就手动备份一次标注目录,已备不时之需。最后分享个人习惯:拿到一批新标注数据,先抽样打开五六个 XML,对照着图片看坐标与标注框是否一一对应,跑一遍格式转换脚本,再检查转换结果。这套流程走完后才把数据交给训练脚本,避免训练到一半才发现数据格式有问题。希望这套工具与流程能帮你在标注环节节省一点时间,把精力放在模型本身。从那以后我每次拿到新数据集都强制走一遍"预定义标签确认 → 自动保存开启 → 抽样检查坐标 → 转换脚本验证"的完整流程,标注翻车率降了很多,希望帮到你。本文还有配套的精品资源,点击获取
简介:LabelImg 是一款可视化的图像标注工具源码包,面向需要自制目标检测数据集的算法工程师与深度学习者,常用于 Faster R-CNN、YOLO、SSD 等模型的训练前标注。使用时需配置 python + lxml 环境,标注结果自动生成 PASCAL VOC 格式的 XML 文件,便于直接接入主流检测框架,减少数据预处理成本。压缩包共 127 个文件、约 6.39MB,其中 44 个 Python 脚本构成核心逻辑,38 张 PNG 图片与 JPG、SVG 文件提供示例与界面图标,另有 shell 脚本、配置文件、qrc 资源描述及说明文档等,项目结构完整,便于离线查阅和二次开发。目前已有 357 人浏览学习。对无法访问 GitHub 的开发者来说,这份源码省去了外网下载不便,解压即可查看 1.8.1 版本完整实现,借助 demo 图片快速上手标注流程,也可以按需修改界面、扩展标注功能或集成到自己的训练流程中。1. 图像标注工具选型:为什么老项目里总还留着 labelImg做计算机视觉项目,数据标注是绕不开的第一道工序。labelImg 这种老牌开源标注工具,界面朴素但功能完整,支持 PascalVOC 和 YOLO 两种主流标注格式,代码全开放、可随意改,直到今天我依然把它当作本机标注的首选工具。它解决的核心问题很直接:给你一套不需要联网、不依赖在线平台的本地标注方案,适合需要自己准备数据集的入门学习者和中小型项目团队。这篇笔记从源码包拆解出发,讲清楚安装启动、标注实操、格式转换和踩坑点,让新手能照做,让熟手能少走弯路。2. 安装与启动:从 labelImg-master 源码包到成功打开界面2.1 源码目录里到底有什么把 labelImg-master.rar 解压后,首先应该看清目录结构,不要急着双击运行。整个项目的核心代码量不大,但文件布局有它的逻辑。labelImg-master/ ├── labelImg.py # 主入口,GUI 逻辑全在这 ├── labelImg.pyw # Windows 无控制台窗口的启动器 ├── libs/ # 核心库,包含画框、数据格式处理等 │ ├── canvas.py # 画布模块,负责矩形框绘制和编辑 │ ├── pascal_voc_io.py # PascalVOC XML 格式读写 │ ├── yolo_io.py # YOLO TXT 格式读写 │ └── ... ├── data/ │ ├── predefined_classes.txt # 预定义标签列表 │ └── ... ├── resources.py # Qt 资源文件编译后的模块 ├── requirements.txt # Python 依赖列表 └── setup.py # 安装脚本这里最值得关注的是libs/目录下的格式读写模块。pascal_voc_io.py负责 XML 格式的解析与保存,yolo_io.py负责 YOLO 格式的转换。如果你后期想要扩展新的标注格式,直接在这两个模块旁边加一个xxx_io.py,然后在主程序中注册即可。2.2 依赖安装:三步让环境跑起来labelImg 是基于 PyQt 的图形界面工具,核心依赖只有两个:PyQt5 和 lxml。不同操作系统下安装方式略有差异,但思路一致。# Linux/macOS 环境 pip install pyqt5 lxml # Windows 环境建议用 pip 安装完整依赖 pip install -r requirements.txtrequirements.txt文件里列出了所有依赖项,直接安装即可。需要注意 PyQt5 的版本兼容性,我遇到过在特定 Python 版本下 PyQt5 安装失败的情况,一般升级到最新的 PyQt5 就能解决。依赖装完后,启动方式有两种。在终端里运行python labelImg.py会附带控制台输出,方便排查问题;双击labelImg.pyw则不会弹出黑色控制台窗口。# 推荐首次运行时用这个命令,能看到完整的调试信息 python labelImg.py启动后出现主窗口就说明环境没问题了。如果你在 Windows 上遇到 DLL 加载失败的报错,多半是 PyQt5 安装不完整,重装一次即可。2.3 启动参数:直接打开图片目录和预定义标签labelImg 支持在启动时传入参数,直接用命令行打开图片文件夹和标签文件,省去每次手动加载的步骤。# 格式:labelImg.py [图片路径] [预定义类别文件路径] python labelImg.py ./images ./data/predefined_classes.txt第一个参数是图片文件夹路径,第二个参数是预定义标签文件路径。启动后自动加载指定图片目录,标签列表也会同步填充。这一步在实际项目中非常实用,尤其是数据集分布在多个文件夹时,可以写脚本批量调用不同参数。注意路径不要带中文,虽然某些版本支持中文路径,但那属于玄学范畴,我通常在项目初始化阶段就把所有路径统一成英文,避免后面训练时各种诡异报错。3. 标注实操:画框、快捷键、类目管理与保存格式3.1 界面布局和标注主流程labelImg 主界面由菜单栏、左侧工具区、中间画布和右侧标签列表组成。打开一张图片后,按W键进入画框模式,在目标物体上按住鼠标左键拖拽,松手后弹出类别选择框,选择或输入类别即可。一个完整的标注流程是:加载图片 → 画框 → 选类目 → 保存 → 切换到下一张,不断重复。画框时尽量贴合目标边界,不要留太多背景,也不要切掉目标边缘,这两点直接影响后续检测模型的训练效果。右侧的标签列表会展示当前图片所有标注框。如果某个框画歪了,选中后可以直接拖动边缘调整;如果需要删除,按Ctrl+R或者右键菜单删除。3.2 快捷键:标注效率的核心差距标注工作重复性极高,用鼠标点菜单的效率远低于快捷键。我整理了一份高频快捷键表,熟练后标注速度能提升一倍以上:快捷键功能使用场景W创建矩形框画新标注框A / D切换上一张/下一张图片连续标注时快速切换Ctrl+S保存标注结果手动保存当前图片标注Ctrl+D复制当前标注框同类物体密集出现时Ctrl+R删除当前选中框画错或框选不当时Del删除选中的标注框同上Ctrl+Z撤销上一步操作误拖动或误删除这里特别说下Ctrl+D复制框的功能。当一张图里有大量同类物体时,画完第一个框后,用复制功能生成新框再移动到目标位置,比重新画框快得多。不过复制出来的框默认位置相同,需要手动拖到目标位置。另一个容易忽略的是缩放功能:鼠标滚轮缩放画布,按住空格键拖动画布。标注大图时,先整体画完所有框,再局部放大微调边界,效率最高。我见过某开发者朋友全程用 100% 缩放逐像素抠框,一个下午只标了 20 张图,这是典型的工具使用不当。3.3 保存在哪里:PascalVOC、YOLO 和 ImageNet保存格式可以在菜单栏的"PascalVOC"和"YOLO"之间切换,默认是 PascalVOC 格式。这两种格式本质区别在于标注数据的表达方式:PascalVOC 格式:每张图片对应一个同名的 XML 文件,里面记录了图片尺寸、通道数和每个标注框的类别及坐标信息。YOLO 格式:每张图片对应一个同名的 TXT 文件,每行一条记录,格式为"类别ID 中心点x 中心点y 框宽度 框高度",所有坐标值都归一化到 0~1。实际项目中,如果模型框架用的是 YOLO 系列,就要选 YOLO 格式保存;如果用 Faster R-CNN 或 SSD,一般选 PascalVOC。切换格式的方法是在菜单栏点击"PascalVOC"或"YOLO"菜单项,当前格式会在标题栏显示。这里有个坑在后面单独说,YOLO 模式保存出的坐标是归一化后的浮点数,XML 里存的是整数像素值,两者不是简单的字段改名关系,格式转换时要注意换算逻辑。4. 格式转换实战:VOC XML 批量转 YOLO TXT 的脚本4.1 为什么要自己写转换脚本虽然 labelImg 支持直接保存 YOLO 格式,但实际使用中,很多人拿到的标注数据是 PascalVOC 格式的,而模型框架只认 YOLO 格式;或者反过来。每次重新标注不现实,手写转换脚本是最高效的方式。还有一个更现实的情况:多人协作的数据集可能混用了两种格式,统一处理时必须用脚本批量转换。我一般是先写一个通用转换脚本,把所有数据集中到一个目录,再统一格式。4.2 核心脚本:XML 解析与归一化计算转换逻辑分三步:读取 XML 文件、解析每个标注框的坐标、计算归一化后的转换结果并写入 TXT。import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, output_dir, class_names): """ 将单个 PascalVOC XML 文件转换为 YOLO TXT 文件 :param xml_path: 输入 XML 文件路径 :param output_dir: TXT 输出目录 :param class_names: 类别名称列表,用于映射类别ID """ tree = ET.parse(xml_path) root = tree.getroot() size = root.find('size') width = int(size.find('width').text) height = int(size.find('height').text) base_name = os.path.splitext(os.path.basename(xml_path))[0] txt_path = os.path.join(output_dir, base_name + '.txt') lines = [] for obj in root.findall('object'): name = obj.find('name').text if name not in class_names: print(f"跳过未知类别: {name} 在 {xml_path}") continue class_id = class_names.index(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) # 计算 YOLO 需要的中心点和宽高,并归一化 center_x = (xmin + xmax) / 2.0 / width center_y = (ymin + ymax) / 2.0 / height box_width = (xmax - xmin) / width box_height = (ymax - ymin) / height lines.append(f"{class_id} {center_x:.6f} {center_y:.6f} {box_width:.6f} {box_height:.6f}") with open(txt_path, 'w', encoding='utf-8') as f: f.write('\n'.join(lines) + '\n') print(f"已转换: {os.path.basename(xml_path)} -> {os.path.basename(txt_path)}")逻辑说明:代码先解析 XML 里的图片尺寸,这是坐标归一化的分母;然后遍历每个 object 节点,获取类别名和 bndbox 子节点里的四个边界坐标。归一化计算中,中心点坐标等于 (xmin+xmax)/2 再除以图像宽度或高度,框的宽高等于 (xmax-xmin) 除以图像宽度。最终写入 TXT 时浮点数保留 6 位精度,够用且文件不会太大。参数说明:class_names列表的顺序必须和模型训练时的类别顺序一致。常见做法是先扫描全部 XML,收集所有类别并按字典序排序,再生成映射表。如果不一致,训练时类别就乱了。批量处理时加一个外层遍历:def batch_convert(xml_dir, output_dir, class_names): """批量转换一个目录下的所有 XML 文件""" os.makedirs(output_dir, exist_ok=True) for xml_file in os.listdir(xml_dir): if xml_file.endswith('.xml'): voc_to_yolo( os.path.join(xml_dir, xml_file), output_dir, class_names )4.3 转换后的验证方式转换不是跑完就结束,必须抽样验证结果。最简单的方式是用 labelImg 打开转换后的目录,切到 YOLO 模式,一张张看标注框是否贴合目标。还可以写个校验脚本,检查坐标是否越界:import os def validate_yolo(txt_path, img_width, img_height): """ 检查 YOLO 标注是否越界 :param txt_path: TXT 文件路径 :param img_width: 图片宽度 :param img_height: 图片高度 """ with open(txt_path, 'r') as f: for line_num, line in enumerate(f.readlines(), 1): parts = line.strip().split() if len(parts) != 5: print(f"格式错误: {txt_path} 第{line_num}行") continue _, cx, cy, w, h = parts cx, cy, w, h = float(cx), float(cy), float(w), float(h) # 还原像素坐标 abs_x1 = (cx - w / 2) * img_width abs_y1 = (cy - h / 2) * img_height abs_x2 = (cx + w / 2) * img_width abs_y2 = (cy + h / 2) * img_height if abs_x1 < 0 or abs_y1 < 0 or abs_x2 > img_width or abs_y2 > img_height: print(f"越界: {txt_path} 第{line_num}行 [{abs_x1:.1f},{abs_y1:.1f},{abs_x2:.1f},{abs_y2:.1f}]")越界通常意味着原 XML 的坐标超出图片边界,这类文件在训练时会报错或被框架丢帧,提前发现能省下不少排查时间。5. 避坑与常见问题:五个最常翻车的点5.1 现象:打开图片程序直接闪退或崩溃原因有两个,最常见的是图片路径包含中文,PyQt 自带的文件读取模块在处理非 ASCII 路径时不稳定;其次是 PyQt5 和 Python 版本不匹配导致的底层错误。解决:把整个项目的路径统一改成英文,从根目录到图片文件夹都不要有中文。或者看报错代码,如果是could not decode to unicode类的错误,就是路径问题。换用虚拟环境安装最新版 PyQt5 能解决绝大多数版本兼容问题。5.2 现象:图片可以打开,但画不了框,鼠标拖拽无反应原因:当前标注工具还停留在编辑模式,没有进入创建模式,或者上一次软件异常退出导致画布状态卡死。解决:按一下键盘上的W键切换到创建模式,看光标是否从箭头变成十字。如果W键无效,重启程序并检查菜单栏里的 Edit 模式是否被误勾选。这个坑多发生在快速连续操作时,画完一张图马上切下一张,画布还没重置就按鼠标。5.3 现象:YOLO 模式下保存的 TXT 文件内容错乱,坐标出现大于 1 的数原因:labelImg 的 YOLO 模式写入采用的是归一化坐标,但如果你手工修改过 XML,或者在部分版本里切换格式时没有重新加载图片,程序可能仍按旧逻辑输出。解决:切换保存格式后,必须重新打开图片目录再继续标注。修改 XML 文件后要彻底关闭再启动 labelImg,确保配置缓存刷新。输出结果可以用 4.3 节的校验脚本检查,坐标越界就说明转换逻辑有问题。5.4 现象:大图标注卡成幻灯片,拖动一条线都要等一秒原因:高分辨率图片载入 GUI 时会全量加载像素数据,内存和 GPU 没有足够的缓冲空间。解决:降低图片预览分辨率,在"视图"菜单里把缩放比例调到 50% 或 25%,画框时用的是相对坐标,不影响最终保存的精度。另一个方法是把图片在标注前先压缩到 2000 像素以内,模型训练时候再恢复原尺寸,这样标注流畅度提升明显。5.5 现象:标注了一下午,关掉软件后 XML 文件全是旧的原因:labelImg 默认是手动保存,如果不点保存直接切换图片或退出,当前图片的标注数据不会写入文件。这个坑几乎每个新手都踩过。解决:养成强制保存的习惯,每标完一张图就按Ctrl+S,或者把菜单栏的"自动保存"选项勾上。自动保存模式下,切换图片时自动写入 XML,不会丢数据。但从那以后我每次标注大数据集前都先开启自动保存,再手动保存一次确认输出目录可写,双保险才安心。6. 进阶技巧:预定义标签、自动保存与备份习惯自定义标签列表是提升标注效率的关键一步。data/predefined_classes.txt文件里每行一个类别名,软件启动时会读取这些类别。你只需要编辑这个文件,填入当前项目的全部目标类别,标注时就不用每次手敲类别名了:# data/predefined_classes.txt 示例 person car bicycle traffic_light启动命令加上类别文件路径后,点击类别选择框会直接出现下拉列表,点一下就能完成选择,比手敲拼音快得多。类目命名建议统一用英文小写加下划线,不要混用中文和英文,否则后续训练框架处理类别映射时会很痛苦。自动保存功能在"视图"菜单里勾选。开启后,切换图片或关闭程序前,当前标注自动写入 XML。我一般还会定期备份标注文件到独立目录:# 简易备份脚本,每小时执行一次 cp -r ./labels ./backup_labels_$(date +%Y%m%d_%H%M)标注工作最怕的不是标错,而是标错后没有后悔药。开启自动保存固然方便,但保存间隔内如果软件崩溃,最近几张图的标注还是会丢。我习惯每标完一批就手动备份一次标注目录,已备不时之需。最后分享个人习惯:拿到一批新标注数据,先抽样打开五六个 XML,对照着图片看坐标与标注框是否一一对应,跑一遍格式转换脚本,再检查转换结果。这套流程走完后才把数据交给训练脚本,避免训练到一半才发现数据格式有问题。希望这套工具与流程能帮你在标注环节节省一点时间,把精力放在模型本身。从那以后我每次拿到新数据集都强制走一遍"预定义标签确认 → 自动保存开启 → 抽样检查坐标 → 转换脚本验证"的完整流程,标注翻车率降了很多,希望帮到你。本文还有配套的精品资源,点击获取