☰
图像标注工具labelImg:从安装配置到Pascal VOC格式转换的完整指南
2026/10/21 21:41:37 网站建设 项目流程

简介:LabelImg 是一款广受目标检测开发者使用的可视化图像标注工具,服务于 Faster R-CNN、YOLO、SSD 等网络训练数据集的制作。通过简单框选即可为图像中的目标生成符合 PASCAL VOC 格式的 XML 标注文件,适合算法工程师、科研人员与入门学习者。压缩包内共 127 个文件,以 44 个 Python 源码文件为核心逻辑,配合 38 个 PNG 图标、SVG 矢量图以及 shell 脚本、txt 说明、rst 文档等辅助资源,整体仅 6.39MB,轻量易携。资源中还包括 demo 演示图片、测试位图、启动配置与 egg 安装包,便于在离线环境下快速验证功能或二次开发。已有 357 人学习下载。这份压缩包完整收录了 labelImg 的源码及配套文件,对不便访问 GitHub 的开发者或希望定制标注界面的用户来说,是一份可直接使用的离线代码包。

1. 图像标注为什么绕不开labelImg:一个标注工具的选择题

做目标检测的人,多半逃不过标注数据这一步。第一次听说图像标注工具时,我第一反应是"标注不是画几个框吗,用什么都行",结果真去做了才发现,选错工具能把整个标注流程拖垮。labelImg是GitHub上被广泛使用的开源标注工具,它解决的就是最原始的需求:在图片上拖出矩形框,把目标类别写进XML文件里,供后续模型训练直接使用。它的特点是轻量、免费、跨平台,安装后不到十分钟就能跑起来,适合个人开发者在跑通YOLOv5、Faster R-CNN这类检测模型之前,快速生成第一份数据集。如果你正打算从零开始攒一批标注样本,又不想为了标注折腾太多安装配置,这款工具是性价比很高的起点。

2. 把labelImg跑起来:安装、界面与第一张图

2.1 环境准备与安装步骤

labelImg本质上是一个Python + PyQt5写的桌面程序,所以安装的关键是把Python依赖补齐。我第一次下载的是打包好的源码包,解压之后直接运行python labelImg.py,结果报错说缺模块。后来养成的习惯是先把依赖装好,再启动。下面是基于源码包的标准启动顺序:

cd labelImg-master pip install -r requirements/requirements.txt python labelImg.py

第一行进入解压目录,前提是rar已用解压工具解开到当前目录。第二行pip install -r会按requirements.txt逐行安装核心依赖,主要是PyQt5和lxml两个大头。PyQt5负责界面渲染,lxml用来读写XML标注文件。如果网络慢,可以在pip命令后追加-i https://pypi.tuna.tsinghua.edu.cn/simple换成国内镜像,能省不少时间。第三行是启动入口,在Windows上直接python labelImg.py即可。

有一点要注意:如果机器上同时装了Python 2和3,终端里默认的python可能指向旧版本,建议先运行python --version确认。或者直接用python3 labelImg.py。我在windows上就踩过一次,命令行里明明装了PyQt5,启动时还是找不到模块,最后发现是打开到虚拟环境的路径不对。所以最推荐的做法是单独建一个虚拟环境再装依赖:

python -m venv labelenv ./labelenv/Scripts/activate # Windows下进入虚拟环境 pip install -r requirements/requirements.txt python labelImg.py

虚拟环境的参数就两个:顶层目录名指定环境位置,activate切换当前解释器。这样后续装别的标注工具也不会和全局Python打架。

2.2 界面布局与快捷键

等程序启动后,界面结构很直接:左边是一竖排工具按钮,中间是图片画布区,右边是标注面板。左侧按钮里最常用的三个是打开目录、打开文件和保存。打开目录适合连续标注一个文件夹里的图,打开文件适合单张处理。右侧面板顶部是签名标注框列表,下面是类别下拉框。

快捷键是labelImg提速的核心,默认绑定如下:

操作快捷键说明
画矩形框W在画布上按住左键拖拽
删除选中框Del删除当前高亮的框
上一张/下一张A / D批量标注时最常用
保存Ctrl+S写入XML文件
复制当前框Ctrl+D连续标多个同尺寸目标时好用
切换类别列表Ctrl+U重新加载classes.txt

我的习惯是左手放在W、A、D、Del附近,右手握鼠标,全程不碰触摸板。画完一个目标后按Ctrl+S会立刻写入文件,但如果没有勾选自动保存,这个操作是手动触发的。如果只想快速画框不保存,就可以用W画完直接按D切下一张。

2.3 标注流程演示:从打开图片到保存XML

完整标注一类的操作顺序如下:

  1. 点击左侧“打开目录”,选择一个存放所有待标注jpg/png图片的文件夹。
  2. 在右侧点击“选择类别”按钮,加载包含所有类别的classes.txt,或者直接在下方下拉框读入默认类别。
  3. 在画布上按W键,从左上到右下拖出目标框,松手后一个bbox就生成了。
  4. 确认类别正确,不正确的话用右侧下拉框修改。
  5. 按Ctrl+S保存,第一次保存会弹窗选择输出目录,建议选择与图片相同目录,方便后续脚本直接遍历。
  6. 按D进入下一张。

第一次保存后,如果勾选了“使用默认保存路径”,之后的Ctrl+S不会再弹窗。标注完成后,每张图对应生成一个同名的XML文件,里面的数据就是下一步转换格式的原料。这个流程我第一次走完花了大约十几分钟,之后就机械化了,问题出在保存路径上:如果不设置默认目录,每次保存都会弹窗打断节奏。所以拿到源码包后,先把自动保存和默认路径这两个选项摸透,比调快捷键更值。

3. 标注格式与产出:XML字段解析与转换脚本

3.1 Pascal VOC格式解析:XML里每个字段都代表什么

labelImg默认输出的就是Pascal VOC格式,这是检测模型里最常见的中间格式之一。一个典型的XML长这样:

<annotation> <folder>images</folder> <filename>000001.jpg</filename> <path>C:/label_data/images/000001.jpg</path> <source> <database>Unknown</database> </source> <size> <width>1280</width> <height>720</height> <depth>3</depth> </size> <segmented>0</segmented> <object> <name>person</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>100</xmin> <ymin>120</ymin> <xmax>400</xmax> <ymax>500</ymax> </bndbox> </object> </annotation>

训练时真正用到的字段集中在size和object上。size里的width和height是原始图片像素尺寸,depth固定为3,代表RGB三通道。object是每个标注目标,name就是类别名,bndbox四个值分别是目标框的左右上下像素坐标,注意xmin和ymin从0开始,xmax和ymax是实际的边界像素,不是宽度。tailwind里的folder和path只是辅助字段,有些训练脚本会忽略它们,但路径里的反斜杠可能引出一堆麻烦,后面避坑部分细讲。

3.2 标注参数设置:自动保存、默认类别与视图选项

很多人不知道,labelImg的“编辑”菜单里藏着两个实用开关。一个是自动保存,勾选后每画完一个框就立刻写盘,程序崩溃顶多丢最后一个框,不会把整张图的标注全冲掉。我标注到两百多张时遇到过程序闪退,因为开着自动保存,重开后只补了最后一张图,其他全在。另一个是“显示标注框”选项,勾选后所有已标注的框会始终显示,方便检查是否有漏标的重合区域。

类别文件建议单独维护在项目根目录下,一行一个类名,保存为UTF-8格式。加载时在右侧点击“改变标注类别”选中该文件。注意类别顺序会影响转换脚本里类别ID的映射,提前固定好,不要中途增删,否则之前XML里的name不变,但脚本映射表可能需要同步改。

3.3 从XML到YOLO格式的转换脚本

YOLO训练需要的是txt文件,每行表示一个目标:class_id center_x center_y width height,后面的四个坐标都是归一化到0到1的浮点数。必须把VOC的像素坐标换算成相对坐标,否则训练loss会直接发散。下面是核心转换函数:

import xml.etree.ElementTree as ET def convert_annotation(xml_file, out_txt, class_map): tree = ET.parse(xml_file) root = tree.getroot() img_w = float(root.find('size/width').text) img_h = float(root.find('size/height').text) with open(out_txt, 'w') as f: for obj in root.iter('object'): cls = obj.find('name').text if cls not in class_map: continue bnd = obj.find('bndbox') xmin = float(bnd.find('xmin').text) ymin = float(bnd.find('ymin').text) xmax = float(bnd.find('xmax').text) ymax = float(bnd.find('ymax').text) x_center = (xmin + xmax) / 2.0 / img_w y_center = (ymin + ymax) / 2.0 / img_h width = (xmax - xmin) / img_w height = (ymax - ymin) / img_h f.write(f"{class_map[cls]} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\n")

这里参数说明:class_map是类别名到数字ID的字典,比如{'person': 0, 'car': 1},顺序必须和训练配置里的一致。计算时先把xmin、xmax、ymin、ymax转成float,因为XML里是字符串。归一化的分母是图片宽高,不是目标框自己的宽高。输出保留6位小数足够训练收敛,再多位也是白搭。

3.4 批量转换与数据集清单生成

单张转换太慢,实际使用要写个批量脚本,遍历整个目录处理所有XML,同时生成train.txt和val.txt。下面这段是我在模拟项目里常用的方案:

import os from convert_annotation import convert_annotation # 假设上面的函数已独立到模块里 def batch_convert(xml_dir, out_dir, class_map, train_list, val_list): os.makedirs(out_dir, exist_ok=True) xml_files = [f for f in os.listdir(xml_dir) if f.endswith('.xml')] for i, xml_file in enumerate(xml_files): base = os.path.splitext(xml_file)[0] out_txt = os.path.join(out_dir, base + '.txt') convert_annotation( os.path.join(xml_dir, xml_file), out_txt, class_map ) if i % 5 == 0: train_list.write(os.path.join('images', base + '.jpg') + '\n') else: val_list.write(os.path.join('images', base + '.jpg') + '\n') class_map = {'person': 0, 'car': 1} with open('train.txt', 'w') as train_f: with open('val.txt', 'w') as val_f: batch_convert('xml_annotations', 'yolo_labels', class_map, train_f, val_f)

注意exist_ok=True会静默创建输出目录,不存在时直接建好。划分逻辑这里是按索引模5分验证集,不够严谨,只适合快速测试。正式训练建议按统计分布切分,不然类别不均衡时验证集会偏。这里每个XML都要读到图片尺寸信息,所以XML必须和图片在同一语义目录下,路径别乱动。

4. 常见问题与避坑排查:四个标注现场的典型翻车事件

4.1 打开的图片里看不到已标注的框

现象:双击XML文件旁的图片,画布上只显示原始图片,之前保存的标注框完全看不见。

原因:labelImg打开标注不是靠读取XML自动渲染的,而是需要先“打开图片文件”后,程序才会去找同名的XML并加载。如果用文件管理器直接双击图片,启动的是系统看图器,根本不会进入labelImg的逻辑。另一个常见原因是图片文件名和XML文件名不一致,比如图片是IMG_001.jpg,XML却是1.xml,程序找不到匹配对象,自然不显示。

解决:统一规范命名,图片和XML必须同名且放在同一目录。标注前先批量把图片文件名整理成000001.jpg这类纯数字格式。如果已经标完才发现,可以用脚本按图片列表对角XML,但这类修复脚本比重标还麻烦,不如一开始就用工具强制规范。

4.2 类别下拉框加载不出预设类别

现象:点击“选择类别”加载了classes.txt后,下拉框依然是空的,画框时默认类别也不对。

原因:最常见的是classes.txt编码或换行符问题。在Windows记事本里保存为带BOM的UTF-8,labelImg解析时从第一个字符就读到了不可见字符,导致整行类名被污染;或者类名之间用了空格而不是换行分隔。还有可能是文件路径带了中文,PyQt5读取时回调失败。

解决:从“查看”菜单里打开“显示标注类别”,然后用VSCode或Notepad++把classes.txt另存为UTF-8 (无 BOM),每一行顶格写一个类名,结尾是\n而不是\r\n。保存后点一下“改变标注类别”重新加载。如果仍然不行,把文件挪到纯英文目录下再试一次,中文路径我遇到过的概率不低。

4.3 保存的XML里路径带反斜杠导致训练脚本报错

现象:转换脚本或者next训练框架读取XML后报路径错误,仔细一看,XML里的path字段写的是C:\Users\xxx\images\000001.jpg,反斜杠在Linux环境或read_pickle时被当作转义符。

原因:labelImg在Windows下保存绝对路径时,会自动生成反斜杠格式。这个字段对训练本身没用,但某些脚本会用os.path.basename或Path库读取,一旦跨平台就会翻车。

解决:批量处理时先做一次清洗,把所有XML的path字段里的反斜杠换成正斜杠,或者直接换行不读path字段。下面的代码片段可以顺手塞进3.4的批量转换脚本里,在读取XML时替换根节点:

import re xml_content = xml_content.replace('\\', '/')

参数说明只针对字符串处理,把路径统一成正斜杠后,Windows和Linux下都能正常解析。另一个更干净的做法是标注前取消勾选“保存路径”选项,让labelImg只写文件名字段,不写绝对路径。这个选项在保存设置里,取决于你拿到的源码包版本,但大部分版本都有。

4.4 标注大图时画布卡顿、画框延迟

现象:加载一张6000x4000像素的航拍图,拖动出框时鼠标肉眼可见地掉帧,光标都移动了但框还停在原地。

原因:labelImg中用QGraphicsView渲染大图,默认开启了平滑缩放和抗锯齿,加载大图后内存占用高,重绘时CPU满载。这个和标注工具本身没绝对关系,属于Qt性能瓶颈,但可以通过参数调整缓解。

解决:在视图菜单里关闭“自动滚动”和“自动缩放”,并把显示比例锁定在50%或更低。实际经验是,把大图先在外部缩放到训练需要的分辨率再标注,比在标注工具里硬扛效果好太多。我在标注无人机数据时会把原图切成512x512的切片,再在切片上画框,这样不仅工具流畅,后续训练也不用做大幅resize。切片的步骤可以用OpenCV脚本批量做,和标脚本配合起来效率提升明显。

5. 从标注到训练集:三个能直接提效的落地技巧

5.1 修改快捷键,把画框放到最顺手的位置

如果你和我一样用小尺寸笔记本键盘,默认W画框会把手腕往左偏很多。借用src/lib/路径下配置文件,可以改掉快捷键绑定。在源码包里找到libs/options.py里的DEFAULT_SHORTCUTS字典,把画框动作绑到E键或空格键。改完重启labelImg,新键位立即生效。注意这个文件是Python字典,改完记得保持缩进一致性,不然启动时import会报SyntaxError。

5.2 标注完成后的三分钟自检脚本

一次发给训练流程前,我都会跑一个简单的脚本检查XML和图片是否对齐。核心逻辑是遍历所有XML,确认每个filename都能在图片目录找到对应文件,同时检查bndbox是否超出图片边界。脚本本身不复杂,但这三分钟能挡掉一堆训练时才爆出来的坑。我习惯把它加到标注目录里,每标完一批就跑一遍。

5.3 生成可直接喂进训练器的数据集清单

用第3.4节的批量转换脚本生成train.txt和val.txt后,再补两条命令就能做成主流框架的输入格式。可以把每个txt文件路径转成绝对路径,或者直接在脚本里用os.path.abspath包一层。这套流程走通后,新项目拿到图片只要半小时就能产出训练数据,不用再和XML格式搏斗。

以前我贪快,标注完直接丢给训练,结果loss不降才发现某个XML坐标越界是正片,排查了半天。从那以后,每批数据我都会先跑一遍校验脚本再导出,顺手把路径清洗也做了。希望帮到你。

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

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

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

立即咨询