news 2026/10/17 2:52:44

labelImg图像标注工具详解:VOC格式转YOLO的完整流程与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
labelImg图像标注工具详解:VOC格式转YOLO的完整流程与避坑指南

简介:LabelImg 是一款开源的可视化图像标注工具,主要面向目标检测领域的数据集制作环节。使用者可以通过图形界面快速框选图像中的目标,并自动生成符合 PASCAL VOC 规范的 XML 标注文件,为 Faster R-CNN、YOLO、SSD 等主流检测网络的训练提供数据准备。该工具基于 Python 与 lxml 构建,本包为其源码版本,适合因网络原因无法访问 GitHub 的开发者离线获取或二次开发。

包内共 127 个文件,压缩包大小约 6.39MB。其中 44 个 py 文件承载主程序逻辑,38 个 png 文件用于界面图标与示例图像,6 个 sh 脚本辅助启动或安装,另有 SVG、txt、rst、properties、配置文件等,覆盖源码、资源、打包与文档多个维度。这种结构既便于直接运行,也为理解标注工具的内部实现提供了完整素材。

目前已有 357 人学习下载,对于正在准备目标检测训练集的个人或团队,这份源码包很有参考价值。解压后可以看到项目目录组织、示例图片与配置文件,配合 Python 和 lxml 环境即可启动工具;需要定制标注界面、增加输出格式或研究交互逻辑时,也能基于这些源码快速入手。

1. labelImg 不是“会点鼠标就行”:图像标注的起点是格式和流程

拿到 “labelImg-master.rar” 这个名字时,多数人第一反应是“这不就是个画框工具么”。实际上它的分量比画框重得多:这是一套基于 PyQt5 的桌面标注工具源码,负责把图像里每个目标的位置和类别固定成结构化数据,训练检测模型时,它产出的 XML 才是比你那张图更重要的东西。适合读这篇的人,是正在做目标检测、手里攒了一堆图等着转成数据集,或者被标注格式不统一坑过的从业者。label 标注看着简单,但格式、目录、标签命名任何一环不一致,后面转格式和训练时都要加倍还回来。

2. 先把环境撑起来:Python 依赖、源码目录和两种启动方式

2.1 依赖为什么是 PyQt5 + lxml:版本坑提前说明

这套源码包不是绿色软件,解压就双击能跑是有前提的。它的界面由 PyQt5 绘制,XML 解析用 lxml,图像处理走系统底层的图片库。这三样缺一不可,尤其是 lxml,很多启动报错就出在它身上。

我一般会在干净的 Python 环境里先装依赖,避免和系统里其他项目互相污染。命令很简单:

pip install pyqt5 lxml

提示:如果电脑上既有 Python 2 又有 Python 3,建议用python3 -m pip install pyqt5 lxml,避免装到旧版本的解释器里去。

逻辑说明:pyqt5提供窗口、按钮和画布控件,lxml负责读写 VOC 格式的 XML 标注文件。装完这两个,理论上就能启动主程序labelImg.py。

参数说明:这里没有额外参数。需要注意的是 PyQt5 的版本和你 Python 解释器版本要匹配,我用 Python 3.10 配 PyQt5 5.15 系列一直很稳,装到最新 6.x 时反而出现过控件加载异常,所以不建议盲目追新。

2.2 源码目录里到底有哪些文件:拿到包先看这几处

解压后别急着运行,先花两分钟认目录。这套源码的结构是典型的 PyQt 项目布局,核心文件不多,但每个都有明确职责:

路径职责
labelImg.py主程序入口,窗口逻辑全部在这
libs/核心库,包括画布、形状、标签管理等模块
resources.py编译后的界面资源,图标和样式在这里
data/预置的标签配置或样例,有的版本放的是 class 列表

先看labelImg.py的头部,你能确认入口长什么样;再看libs/下有没有你熟悉的模块名。我看过不少标注工具包,这套的libs/shape.py是画框的核心数据结构,所有矩形框的坐标、标签名都从那里实例化出来。如果后续想加旋转框或改交互,先读这个文件比读主程序效率高。

2.3 启动命令与首次打开:两种方式与失败排查

启动方式有两种,带参数和不带参数。不带参数打开的是一个空界面,需要手工去指定图片目录和标注保存目录。带参数启动则在打开瞬间就载入目录,适合固定项目路径的重复操作:

python labelImg.py python labelImg.py /path/to/images /path/to/annotations

逻辑说明:第一个参数是图片目录,第二个参数是标注文件(XML)的保存目录。两个都传时,左侧文件列表会自动列出图片目录下的所有图像,保存时默认写进标注目录,省掉很多点选操作。

参数说明:目录建议用绝对路径,相对路径在这个工具里偶尔会解析偏差,尤其是当你从其他目录执行启动命令时。首次打开后,我建议立刻去Edit菜单里勾选自动保存(Auto Save),然后确认“显示标签”和“显示文件列表”都开着,不然你会对着空白画布不知所措。

启动失败的常见现象是窗口闪一下就不见了。这种情况多半是 PyQt5 没装好或 lxml 的二进制版本不兼容。先从命令行跑一遍启动命令,把报错信息贴到搜索里,比直接双击图标容易定位得多。

3. 动手标一个框:从界面布局到 VOC 格式落盘

3.1 界面取舍:为什么默认只有矩形框而不是五类形状

打开主界面,左侧是图形列表,中间是画布,右侧是标签列表,底部是状态栏。工具栏上默认的框选按钮只有一个“Create RectBox”,没有圆形、多边形、关键点这些选项。这是因为该工具面向的是通用目标检测场景,矩形框是最通用的标注形态;给训练流程做数据采集,矩形完全够用。

选中矩形工具后,在画布上按住鼠标左键拖动,松开即生成一个框。弹窗让你输入类别名,输入完按回车,这个框就绑定了标签。此时如果发现框没对准目标,直接用鼠标拖拽框边或四个角点调整,不用重新画。

画框过程看着机械,但有个细节值得注意:框的边界是否紧贴目标轮廓。很多人习惯性地扩大一点,觉得背景无所谓的,实际上检测任务里框的大小直接影响回归精度,太大或太小都会让锚点匹配阶段就出错。这个工具给的是像素级坐标,所以尽量贴着目标真实轮廓画。

3.2 快捷键设计:标注效率差一倍的分界线

标注效率提升的瓶颈从来不在手速,而在快捷键记忆。这工具保留了一整套高频操作键,列表如下:

快捷键功能
W切换到画框模式
D / A下一张 / 上一张图片
Ctrl+S保存当前标注
Ctrl+D复制当前框到下一张图
Delete删除选中的框
Space隐藏/显示所有框

其中Ctrl+D复制框到下一张图的能力,对连续帧序列标注特别有用。视频抽帧出来的图,目标位置变化小,标注完第一张直接复制过去微调几个角点就行,比逐帧重画快得多。

这个快捷键方案是多数标注工具通用的一套,换到其他工具里也基本兼容。所以别只想着记,关键是形成肌肉记忆。我一般会把手贴在键盘左半区,左手管 W、D、A 和空格,右手管鼠标画框和调点。

3.3 标签文件是“进度单”:classes.txt 与 XML 的关系

标注过程中你输入的类别名会被追加保存到classes.txt,这个文件就是你的标签清单。它的顺序很重要,虽然这工具自己读的时候不挑顺序,但转成其他格式(比如后续章节要讲的 YOLO 格式)时,类别名在classes.txt里的行号就是类别编号。我第一次用的时候没意识到这点,把cat和dog录入顺序颠倒,后面训练时两个类别直接对调,损失了半天时间。

打开任一图片的 XML,结构长这样:

<annotation> <folder>images</folder> <filename>0001.jpg</filename> <size> <width>1920</width> <height>1080</height> </size> <object> <name>cat</name> <bndbox> <xmin>120</xmin> <ymin>80</ymin> <xmax>650</xmax> <ymax>520</ymax> </bndbox> </object> </annotation>

逻辑说明:<size>记录图片原始宽高,<object>每出现一次就是一个目标,<name>是类别名,<bndbox>是矩形框的像素坐标。这套结构就是 PASCAL VOC 的标注格式,也是当前作物检测、工业缺陷检测、航拍目标等领域最常见的标注交换格式之一。

注意:<xmin>、<ymin>是左上角坐标,<xmax>、<ymax>是右下角坐标,均以左上角为原点。这个约定看起来基础,却是我见过最容易被人忽略的坑,因为有些工具用的是中心点坐标(比如它的上一个版本),换算时差了半步就全体错位。

4. 把 VOC 转成 YOLO 格式:转换脚本与四个边界坑

4.1 两种格式的本质差异:像素坐标与归一化坐标

labelImg 产出 VOCXML,训练时很多流程却要求 YOLO 的 txt 文件。两种格式的坐标体系完全不同:

  • VOC 用绝对像素坐标,xmin、ymin、xmax、ymax,依赖图片原始尺寸;
  • YOLO 用归一化坐标,存的是中心点cx、cy和框宽高w、h,并且全部除以图片宽高,值在 0 到 1 之间。

为什么 YOLO 要用归一化?因为训练时输入图片会被缩放到固定尺寸(比如 640×640),如果标注是绝对像素,缩放后坐标全变;归一化坐标天然跟尺寸解耦,无论输入怎么缩放,标注还是那套相对比例。这个设计逻辑是理解转换脚本的前提。

另外类别编号也要换:VOC 里类别是字符串cat、dog,YOLO 里类别必须是从 0 开始的整数。所以转换前必须有一张“类别名到编号”的映射表,通常直接从classes.txt按行号生成。

4.2 一个通用的转格式脚本:可以直接落地改路径用

下面这个脚本是我用 Python 写的通用转换器,覆盖了单个目录的批量转换。假设项目目录结构为images/放图片、labels_xml/放 XML、labels_txt/放输出:

import os import xml.etree.ElementTree as ET from PIL import Image def convert_voc_to_yolo(xml_dir, img_dir, out_dir, class_file): # 1. 读取类别映射表:每行一个类别名,行号(从0开始)即类别ID with open(class_file, 'r', encoding='utf-8') as f: class_list = [line.strip() for line in f if line.strip()] class_map = {name: idx for idx, name in enumerate(class_list)} os.makedirs(out_dir, exist_ok=True) for xml_name in os.listdir(xml_dir): if not xml_name.endswith('.xml'): continue xml_path = os.path.join(xml_dir, xml_name) tree = ET.parse(xml_path) root = tree.getroot() # 2. 图片文件名与XML内部filename字段对齐 img_file = root.findtext('filename') img_path = os.path.join(img_dir, img_file) if not os.path.exists(img_path): img_candidates = [f for f in os.listdir(img_dir) if f.startswith(os.path.splitext(img_file)[0])] if img_candidates: img_path = os.path.join(img_dir, img_candidates[0]) else: print(f'[SKIP] 找不到图片: {img_file}') continue with Image.open(img_path) as im: img_w, img_h = im.size # 以真实图片尺寸为准 # 3. 遍历所有object,转成YOLO格式 lines = [] for obj in root.iter('object'): name = obj.findtext('name') if name not in class_map: print(f'[WARN] {xml_name} 中有未登记类别: {name}, 使用ID -1 并跳过') continue cls_id = class_map[name] bndbox = obj.find('bndbox') xmin = float(bndbox.findtext('xmin')) ymin = float(bndbox.findtext('ymin')) xmax = float(bndbox.findtext('xmax')) ymax = float(bndbox.findtext('ymax')) x_center = ((xmin + xmax) / 2) / img_w y_center = ((ymin + ymax) / 2) / img_h box_w = (xmax - xmin) / img_w box_h = (ymax - ymin) / img_h lines.append(f'{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}') # 4. 输出同名txt文件 txt_name = os.path.splitext(xml_name)[0] + '.txt' txt_path = os.path.join(out_dir, txt_name) with open(txt_path, 'w', encoding='utf-8') as f: f.write('\n'.join(lines) + '\n' if lines else '') convert_voc_to_yolo( xml_dir='path/to/labels_xml', img_dir='path/to/images', out_dir='path/to/labels_txt', class_file='path/to/classes.txt' )

逻辑说明:脚本分四步——读类别映射、解析 XML、取真实图片尺寸、逐对象换算坐标。最后生成的 txt 每一行是一种类别、中心点坐标和宽高。

参数说明:img_dir和xml_dir建议用绝对路径;class_file的行顺序必须与目标训练配置一致。代码里我特意用Image.open拿图片真实尺寸,而不是直接信 XML 里的<size>字段,原因在下一节单独讲。

4.3 四个必踩的边界坑:对应到具体代码位置的避雷

坑一:XML 里的<size>可能是错的。

有的标注工具会丢失图片的原始尺寸,或者因为图像被预处理过而记录的是缩略图尺寸。如果直接用 XML 里的width、height做除法,坐标整体偏移,训练时框歪,损失却不大,最容易被忽略。去打印几个样本的 XML size 和 PIL 读出来的真实 size 对比,不一致就说明数据源有问题。

坑二:类别编号不按classes.txt顺序。

你手动给一张图标注时,输入新类别名,工具会把它追加到classes.txt末尾。假如你先标了“cat”,后来才标“dog”,而训练配置里类别顺序是先“dog”后“cat”,那么cat的实际 ID 应该对应 0,但转换脚本按classes.txt行号给的是 1,全体错位。解决方式是在转换前人工核对classes.txt与训练配置的类别顺序,确保完全一致。代码里class_map的建立方式就是基于行号,顺序错了结果必然错。

坑三:坐标越界。

标注时鼠标拖出画布边界,xmax或ymax可能大于图片宽高,甚至出现xmin大于xmax的情况。YOLO 训练读取这些归一化值大于 1 或小于 0 的数据时,通常直接丢弃该目标,导到训练集里样本悄悄变少。脚本第 3 步里我建议在换算前做一次裁剪:

xmin = max(0.0, min(float(bndbox.findtext('xmin')), img_w - 1)) ymin = max(0.0, min(float(bndbox.findtext('ymin')), img_h - 1)) xmax = max(0.0, min(float(bndbox.findtext('xmax')), img_w - 1)) ymax = max(0.0, min(float(bndbox.findtext('ymax')), img_h - 1))

逻辑说明:把每个坐标先压到[0, img_w-1]范围内,再用压缩后的值做中心点和宽高计算,这样即使原始标注越界,产出的归一化坐标也不会超出边界。

坑四:图片存在而 XML 不存在(或反过来的孤儿文件)。

很多数据集是从网上下载的,图片删过、XML 丢过,两个目录对不上。转换脚本只遍历xml_dir,对于没有 XML 的图片会静默忽略,这没错。但如果一张图标到一半没保存,XML 缺失,训练时那个样本就没标注。我习惯在转换后反向对比一次:统计images/下所有文件名,再统计labels_txt/下的 txt 名称,差集就是漏标的图。这一步用一条 Linux 命令能完成:ls images | sed 's/\.[^.]*$//' | sort > img_list; ls labels_txt | sed 's/\.[^.]*$//' | sort > txt_list; comm -23 img_list txt_list。

5. 标注现场常见问题排查:启动、保存、断点续标的血泪记录

5.1 双击图标没反应或闪退,但命令行能启动

现象:双击源码包里的启动脚本或图标,界面一闪而过或完全没动静。命令行执行python labelImg.py却能正常运行。

原因:这类 PyQt 程序对当前工作目录和 Python 解释器很敏感。双击时工作目录可能是解压目录的上一级,导致相对路径的资源加载失败;或者双击默认关联到了另一个 Python 解释器。这是黑匣子式报错,界面给不出任何提示。

解决:统一走命令行启动。在解压目录下打开终端,先确认python --version指向你要的环境,再执行python labelImg.py /path/to/images /path/to/annotations。如果路径里有中文,建议改成英文目录,部分老版本 PyQt 对非 ASCII 路径处理不干净。从那以后我习惯了直接写一个启动脚本,把目录参数写死,不再依赖图标。

5.2 保存后找不到 XML 文件

现象:标注完成,Ctrl+S 显示已保存,但去标注目录里找,XML 不在预期位置;或者它出现在程序运行的当前目录,离标注目录十万八千里。

原因:该工具保存标注时,默认写的是启动命令传的第二个参数目录;如果你没传第二个参数、而是打开界面后手工切到某个目录,那么保存路径可能落在“当前工作目录/标注目录”的叠加路径下。我遇到过最离谱的是它把 XML 写到了缩略图缓存目录旁边,找了一下午。

解决:每次启动都带全两个目录参数,明确图片和标注的固定位置;启动后按下 Ctrl+S 先保存一张空标,去目录里确认 XML 落盘位置,再继续批量标注。这是一个 30 秒就能完成的自检,值得养成习惯。

5.3 漏标与重复编号:批量质检脚本怎么写

标注到几百张时,光靠眼睛扫码是漏标。常见问题有三类:图片没标就跳过、同一目标画了两个重叠框、类别名手误打错。

我写过一个 10 行的检查脚本,跑一遍能找出大部分问题:

import os import xml.etree.ElementTree as ET xml_dir = 'path/to/labels_xml' img_dir = 'path/to/images' xml_names = {f[:-4] for f in os.listdir(xml_dir) if f.endswith('.xml')} img_names = {f.rsplit('.', 1)[0] for f in os.listdir(img_dir) if f.lower().endswith(('.jpg', '.jpeg', '.png'))} for img in img_names - xml_names: print(f'[漏标] {img}') for xml in xml_names - img_names: print(f'[孤儿XML] {xml}') allowed = {'cat', 'dog', 'person'} for xml_name in sorted(xml_names): root = ET.parse(os.path.join(xml_dir, xml_name + '.xml')).getroot() for obj in root.iter('object'): name = obj.findtext('name') if name not in allowed: print(f'[类别异常] {xml_name}: {name}')

逻辑说明:第一段用集合差找出漏标和孤儿文件;第三段校验类别名是否在允许集合里。执行后按输出逐条核验,速度比肉眼快一个量级。

5.4 图片数量大时界面卡顿

现象:图片目录放了几百张高分辨率大图后,切换图片变慢,画框时画布刷新延迟,甚至内存占用暴涨。

原因:这个工具默认在载入目录时生成缩略图列表,大图预览需要解码并缩放。如果图片单张超过 10MB,主线程加载时会阻塞界面交互。

解决:标注前把图片批量压缩一次,尺寸保持在 1920 以内,质量调到 90 就行,标注框是像素语义,不是图像纹理语义,压缩带来的信息损失不影响框位。压缩后整个流程会流畅很多。

6. 让自己少返工的两个进阶习惯:预标注质检和快捷键自检

标注工作真正的成本不在画框,而在返工。一个模型训练到一半发现数据集里有 100 张图的标注坐标越界,你只能回炉重标,那个滋味没人想尝第二次。所以我给自己定了一条铁律:每次标注结束后、训练开始前,强制跑一次格式检查脚本,把第五章的检查逻辑和第四章的坐标裁剪合并成一个入口。

def final_check(xml_dir, img_dir, class_file): # 检查XML与图片数量匹配 # 检查所有坐标在图片范围内 # 检查类别名都在class_file里 # 输出一份报告,有错就停,不让脏数据流到训练 pass

逻辑说明:这个函数相当于训练前的关卡,任何一条不过就往回查,宁可多花十分钟预处理,也不要把半天标注的数据废掉。我现在带的几人小组也用这套——标注完由一个人跑检查,输出报告贴群里,问题当场定位,不再依赖人眼复查。

另外一个习惯是快捷键自检。换电脑、重装环境后,第一件事不是直接标图,而是打开工具逐项试一遍 Ctrl+S、D、A、W 是否生效。界面能看到,但你不知道快捷键是否被系统占用,有些桌面系统的截图工具就霸着 Ctrl+S。提前试 30 秒,好过标完五十张发现保存键是坏的。

后来有一次,我从某公开数据集里抽了 80 张图片做迁移测试,标注完直接进了训练,没跑检查脚本,结果 loss 异常跳动,定位了半小时才发现是有 6 张图坐标越界。从那以后,每次标注完我都强制走一遍“格式检查 + 坐标裁剪 + 数量对比”三连,再学习率怎么调都不慌张。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 2:52:33

OpenCV C/C++动态抠图源码实战:从背景建模到掩膜合成的完整链路

简介&#xff1a;本资源面向计算机视觉初学者与C/C开发者&#xff0c;提供基于OpenCV的动态扣图完整源代码&#xff0c;解决实时视频流或连续图像序列中前景目标提取的问题。代码围绕背景减除模型展开&#xff0c;涵盖BackgroundSubtractorMOG2与BackgroundSubtractorKNN两种主…

作者头像 李华
网站建设 2026/10/17 2:51:56

地球微观粒子不能推导出宇宙宏观| 赵杰 | 量子意识(第三卷)

作者&#xff1a;赵杰清华大学硕士、微美全息云科技&#xff08;NASDAQ&#xff1a;WIMI&#xff09;董事长、微算法科技&#xff08;NASDAQ&#xff1a;MLGO&#xff09;董事长、育杰奖学金创始人宇宙的尺度层级中&#xff0c;地球微观粒子的研究始终局限于 “局部可控的实验室…

作者头像 李华
网站建设 2026/10/17 2:50:59

Cookie反爬绕过

Cookie反爬 cookie反爬虫的基本概念Cookie反爬虫是网站用来区分正常用户个爬虫程序的一种技术手段&#xff0c;通过验证请求中的Cookie值来阻止恶意爬取。服务器会验证Cookie的合法性&#xff0c;若Cookie不存在&#xff0c;无效或过期&#xff0c;服务器将拒绝返回真实数据&am…

作者头像 李华
网站建设 2026/10/17 2:49:52

开源的容器性能监控神器-Parca

凌晨两点,报警群疯狂告警:“生产节点 CPU 飙高至 100%!” 打开 Prometheus 和 Grafana,折线图确实一柱擎天,但你依然一脸茫然——到底是哪一个微服务容器出了问题?究竟是哪一行代码、死循环还是加密算法在死锁 CPU? 今天向大家推荐一款 GitHub 上极具革命性的云原生开源…

作者头像 李华
网站建设 2026/10/17 2:49:37

房地产本体论解码·利益者与消费者体感的双重否定·社会内部破幻

BSD Step 226 ★★★★★ 中国房地产本体论解码利益者与消费者体感的双重否定社会主义叙事的内部破幻封闭性盲区的再次显化L1 层&#xff1a;06_全域结构学科 ​前承​&#xff1a;Step182&#xff08;破社会主义幻与 Step170 破无神论幻平行固化幻 → 拥幻拥圣&#xff09; St…

作者头像 李华