简介:面向目标检测数据集构建的 LabelImg 可视化图像标注工具源码包,适用于 Faster R-CNN、YOLO、SSD 等网络训练前的人工标定环节。工具基于 Python + lxml 运行,生成的 XML 遵循 PASCAL VOC 格式,可直接对接主流检测框架。压缩包共 127 个文件,大小 6.39MB,以 44 个 Python 源码文件为核心,同时包含 38 张 PNG 图标或界面素材、6 个 Shell 启动脚本、6 个 SVG 矢量图及若干配置文件、说明文档与测试图片,涵盖工具运行、界面资源、环境配置与基础测试所需内容。目前已有 357 人学习下载。这份源码包为无法访问 GitHub 的用户提供了离线获取途径,解压后即可查看核心逻辑、二次开发或按需修改标注类别与快捷键,同时可依据自带示例图片快速验证标注流程,适合入门目标检测数据准备的开发者。1. 图像标注工具 labelImg:目标检测训练前绕不开的一步做目标检测的人应该都有过这种体验:模型结构选好了,预训练权重也下好了,结果卡在数据标注这一步——几百上千张图要一个个画框、填类别、检查导出格式。labelImg 就是最经典的开源图像标注工具,一个基于 Python 与 Qt 开发的桌面程序,干的事情很纯粹:打开图片,用矩形框把目标圈出来,打上类名,保存成 PASCAL VOC 格式的 XML 文件,或者一行一个标注的 YOLO 格式 txt。做目标检测、图像标注相关项目的人几乎都绕不开它,尤其是刚入门、想用少量数据快速验证模型的同学,以及需要按固定规范产出标注数据集的团队。它的价值不在“功能多”,而在“标准、简单、可控”,配合后续的 YOLO 训练流程,是最省心的一条路径。2. 环境搭建与启动:先解决 Python、Qt 和资源文件三件事2.1 从 GitHub 拉源码:推荐直接 clone 而不是下压缩包很多人会把 labelImg-master.rar 解压出来直接双击 labelImg.py,然后发现程序一闪而过或者报 ModuleNotFoundError。这不是压缩包的问题,而是运行环境没配好。labelImg 本质是个 Python 脚本项目,入口是根目录下的 labelImg.py,它依赖 PyQt5 和 lxml 两个第三方库,缺一个都起不来。我一般会这样处理:先建一个独立的虚拟环境,避免把系统 Python 环境搞乱。某开发者喜欢直接用 conda,我习惯用 venv,因为轻量、不污染。项目根目录里其实有 requirements.txt,里面写了 PyQt5 和 lxml,直接用 pip 装就行。这里有个值得注意的版本细节:PyQt5 的版本不能太老,建议 5.15 以上,太老的版本在部分 Linux 发行版上会缺 Qt 平台插件,启动时报 “could not load the Qt platform plugin 'xcb'”。# 进入项目目录 cd labelImg-master # 创建并激活虚拟环境(Windows 用 venv\Scripts\activate) python -m venv venv source venv/bin/activate # 安装核心依赖 pip install pyqt5 lxml # 启动主程序 python labelImg.py第二行是进入项目根目录,所有后续操作都基于这个目录,建议路径里不要带中文和空格,否则 Qt 的字体渲染和路径解析偶尔会出玄学问题。第三行创建虚拟环境的操作在 Windows 和 Linux 上略有差异,Windows 激活命令是venv\Scripts\activate,Linux 和 macOS 是source venv/bin/activate。第五行直接 pip 安装两个核心包就够了,requirements.txt 里其实还有别的,但 labelImg 实际运行只强依赖这两个。最后一行python labelImg.py是启动入口,如果看到弹出的主窗口,说明环境没问题。2.2 Linux 下编译资源文件:别跳过 pyrcc5 这一步在 Ubuntu 这类系统上,直接python labelImg.py启动后界面会“缺胳膊少腿”,按钮图标全空白,甚至某些菜单项显示异常。原因是 labelImg 的资源文件resources.qrc没有被编译成 Python 可导入的.py文件。项目源码里那个resources.py就是编译产物,但如果你是从某些渠道下载的二次打包版本,这个文件可能缺失或者版本不匹配。正确做法是手动执行编译。先安装 PyQt5 的工具包,然后用 pyrcc5 把 qrc 文件转成 py:# 安装 PyQt5 工具(包含 pyrcc5) sudo apt-get install pyqt5-dev-tools # 编译资源文件 pyrcc5 -o resources.py resources.qrc # 重新启动 python labelImg.py第一条命令在 Ubuntu 上对应pyqt5-dev-tools这个包,它提供 pyrcc5 编译工具。第二条命令把resources.qrc编译成resources.py,这个文件里包含了所有图标、界面布局的二进制数据。如果不做这一步,程序能跑但 UI 是残的,快捷键也有一部分失效——因为图标和弹窗文本都写在资源文件里。还有一点要说明:如果你用的是 pip 安装的 PyQt5,pyrcc5 并不一定在 PATH 里。遇到 “command not found” 就改用python -m PyQt5.pyrcc_main -o resources.py resources.qrc,两种方式等价。2.3 启动后黑屏或闪退:先看平台插件和显示环境这一类问题在 Linux 服务器上最明显。用远程终端连服务器、没有桌面环境或者 X11 转发没开,Qt 程序就起不来。报错信息一般长这样:qt.qpa.plugin: Could not find the Qt platform plugin "xcb" in ""。解决思路两条:一条是装系统级的 libxcb 依赖,另一条是改用 offscreen 模式跑。# 方案一:补齐 xcb 相关依赖 sudo apt-get install libxcb-xinerama0 libxcb-cursor0 # 方案二:让 Qt 走 offscreen 模式(不弹窗口,用于调试) export QT_QPA_PLATFORM=offscreen python labelImg.py第一条方案比较通用,libxcb 系列是 Qt 在 Linux 上绘图的基础,缺了它会直接崩。第二条方案适合没有显示器的环境,offscreen 模式下程序能跑但看不到界面,通常配合截图或远程桌面使用。如果两个方案都不行,检查一下是不是用了 Wayland 显示协议,部分老版本 Qt 对 Wayland 支持不好,临时切到 X11 协议往往就正常了。3. 标注流程与界面操作:画框、打标签、切换格式3.1 界面布局与工作目录:先把“打开目录”和“保存目录”分开labelImg 的界面逻辑和普通图像编辑器不太一样,它不是“打开单张图片”然后用完另存,而是面向批量的:设置一个图片目录作为输入,设置一个标注目录作为输出,然后逐张浏览标注。左上角“打开”按钮旁边那两个按钮其实是关键——“打开目录”和“改变标注保存目录”。这两个目录建议从一开始就分开,不要都指到图片文件夹,否则后续整理训练集时要手动挑 XML,容易混。界面上的核心区域就三块:左侧是图片显示区,中间顶部是工具栏,右侧是标注框列表和类名输入框。右侧列表里每一行对应一个标注框,选中某一项时图片上对应的框会高亮,方便后期修改。如果你标注的图片有大量小目标,建议先按Ctrl + +放大图片再画框,精度会高不少。3.2 画框的完整操作流:从新建框到保存到下一张整个标注过程的核心操作流是这样的:打开图片目录后,按快捷键W进入画框模式,鼠标在目标上按住左键拖动,松开后弹出类名输入框,输入类别名回车确认。一个框完成后再按W画下一个。同一张图上多个目标就重复这个流程,全部画完后按Ctrl + S保存 XML,再按D切换到下一张图。看下面这张快捷键总表:功能快捷键说明画框W进入画框模式,按住左键拖拽删除选中框Delete删除当前选中的标注框上一张/下一张A / D切换图片,自动加载同名 XML保存标注Ctrl + S生成 XML 或 txt,文件名与图片同名缩放图片Ctrl + 加号/减号放大缩小,便于精标验证图像Ctrl + Shift + V快速检查标注框是否超出图像边界保存的时候会有一个细节:如果图片同步更新过,程序会提示是否覆盖原 XML。此时要确认图像内容没有发生变化,否则标注框坐标和像素位置就对不上了。保存路径下生成的文件名和图片名完全一致,比如0001.jpg对应0001.xml,这个对应关系决定了后续训练脚本能不能正确解析。3.3 格式切换:PascalVOC 与 YOLO 的底层差别工具栏上有“PascalVOC”和“YOLO”两个格式选项,很多人到了这一步才开始犹豫选哪个。其实它们的差别很明确:PascalVOC 格式用 XML 文件保存,包含图像宽度高度、目标类别、bounding box 的 xmin / ymin / xmax / ymax 四个坐标值,单位是像素;YOLO 格式用 txt 文件保存,每一行是类别id x_center y_center width height,其中四个坐标都已经归一化到 0~1 之间。切换格式前要特别注意一个前置条件:YOLO 模式下必须有classes.txt文件,并且里面的类名顺序要和你想训练的模型类目完全一致。labelImg 会按照 classes.txt 的行号来分配类别 id,第一行是 0,第二行是 1,以此类推。这个文件放在标注输出目录下即可,程序会自动读取。我见过有人用 YOLO 模式标注完,训练时发现类别全错位,查到最后是 classes.txt 里的顺序和训练配置里的 names 不一致,这种问题排查起来非常隐蔽。3.4 批量标注的小习惯:先统一类名,再快速过图标注是个体力活,但节奏可以优化。第一张图先仔细标,把项目里所有可能出现的类别都建一遍;第二张图开始,每画完一个框,类名下拉框会记住之前输入过的值,直接选择就行,不用重复打字。如果图中有大量重复目标,还可以用Ctrl + Shift + X快速复制上一个框的类别和大小,微调位置后放置,效率提升明显。有一个我常用的“抢时间”操作:对同一批连续帧图片,第一张标注完整,后面几张用复制框 + 拖动位置的方式快速完成。具体做法是选中已有标注框,按Ctrl + C,在下一张图上按Ctrl + V,复制出来的框会出现在相同位置,再拖到实际目标上。这种方式适合视频抽帧数据,能省下不少重复劳动的痛苦。4. 避坑指南:标注工具最容易翻车的五个细节4.1 保存后找不到 XML:输出目录设错了现象:标注完按 Ctrl + S,界面上明明显示已保存,但去图片文件夹里一找,根本没有 XML 文件。原因:labelImg 的保存逻辑是“保存到指定的输出目录”,不是图片所在目录。如果你没有点过“改变标注保存目录”,默认的保存路径可能还是上一次操作留下的路径,甚至是指向空的。解决:标注开始前先点“改变标注保存目录”,选好本次标注输出文件夹,然后再打开图片目录。保存后去输出目录确认一次,看到 XML 生成再继续下一张。后续批量检查时,可以用文件数量对比脚本快速核对图片和 XML 是否一一对应。4.2 同样的类名输入两遍就弹重复提示:这是功能不是 bug现象:第一张图输入“cat”后,第二张图再输入“cat”,弹窗提示这个类名已经存在,是否继续使用。原因:labelImg 的类名列表是全局的,它把所有标注过的类名都记在左侧列表中。再次输入相同类名时,程序担心你是要新建一个不同类别的同名对象,所以会确认一次。解决:不用取消,直接确认使用现有条目就行,标注内容不会丢失。如果想避免每次都弹窗,第二张图起用下拉框选已有类名,不要手动打字。这个问题遇到过的人不少,理解它的逻辑后就顺了。4.3 自动保存没开,切换图片后标注丢失现象:画完框,没按 Ctrl + S 直接按 D 切下一张,再切回来发现刚才的标注全没了。原因:labelImg 默认不会自动保存,所有标注都缓存在内存里,只有 Ctrl + S 才会写磁盘。切换图片时会自动加载同名 XML,没有就别怪它不保存。解决:启动前建议点击“自动保存模式”按钮,开启自动保存,这样切换图片时会自动写 XML。但注意,自动保存模式下修改已存在的 XML 可能直接覆盖,操作前确认原有标注不再需要。最稳妥的习惯还是每次画完当前图区的框,连按 Ctrl + S,再切下一张。4.4 YOLO 格式下类别全错位:classes.txt 顺序问题现象:用 YOLO 模式标注完成,训练时输出的检测结果明显不对,对应关系全乱了。原因:YOLO 格式只存类别编号,不存类名。类别编号由 classes.txt 的行号决定,第一行是 0,第二行是 1。如果你标注时用的 classes.txt 顺序和训练配置文件里 names 的顺序不一致,编号就全部错位。解决:训练前强制检查一次对应关系——打开一个标注好的 txt 文件,看第一列数字,去训练配置文件里查这个数字对应的类名,和图片里实际标注的目标做对照。如果错位,要么重建 classes.txt,要么用脚本把 txt 里的类别编号统一映射到正确的值。4.5 标注文件里出现了奇怪的换行符:Windows 和 Linux 混用的坑现象:在 Windows 上标注的 txt 文件传到 Linux 训练服务器上后,训练脚本读取时报格式错误,说每行数据数量不对。原因:Windows 下用记事本编辑过的 txt 文件会带\r\n换行符,Linux 下的 Python 读文件默认按\n切分,每行结尾会残留一个\r,解析时数字数量就多出一个空字符串元素。解决:训练脚本里加一个strip()处理,或者用dos2unix命令批量转换标注文件。规范做法是写训练脚本时统一用line.strip().split()而非直接line.split(),两条路线都能兼容。提示:标注数据是训练的直接输入,任何格式层面的小问题都会在模型阶段放大。每完成一批标注,先用脚本跑一遍格式校验,比后期排查便宜得多。5. 批量校验与格式转换:把标注数据整理成可直接训练的样式5.1 用脚本核对图片与 XML 的对应关系及标注合法性手工标注完几百张图,不能直接拿去训练。最稳妥的方式是写一个校验脚本,把图片尺寸和 XML 里的 width / height、xmin / ymin / xmax / ymax 做交叉比对,确保每个标注框都在图像边界内,类别列表符合预期。下面这段代码是我常用的校验逻辑简化版:import os import xml.etree.ElementTree as ET from PIL import Image img_dir = "images" xml_dir = "annotations" class_list = ["cat", "dog", "bird"] # 预期类别白名单 for img_name in os.listdir(img_dir): if not img_name.lower().endswith(".jpg"): continue base = os.path.splitext(img_name)[0] xml_path = os.path.join(xml_dir, base + ".xml") if not os.path.exists(xml_path): print(f"[MISSING] {img_name} 没有对应 XML") continue img = Image.open(os.path.join(img_dir, img_name)) w, h = img.size tree = ET.parse(xml_path) root = tree.getroot() for obj in root.findall("object"): name = obj.findtext("name") if name not in class_list: print(f"[CLASS] {img_name} 中类名 {name} 不在白名单") bnd = obj.find("bndbox") xmin = int(bnd.findtext("xmin")) ymin = int(bnd.findtext("ymin")) xmax = int(bnd.findtext("xmax")) ymax = int(bnd.findtext("ymax")) if xmin < 0 or ymin < 0 or xmax > w or ymax > h: print(f"[BOUND] {img_name} 中 {name} 坐标越界: {xmin},{ymin},{xmax},{ymax}") print("校验完成")这段脚本做的事是把每张图片的 XML 全部解析一遍:第一处class_list是类别白名单,训练时只包含这个列表里的类别,如果标注师不小心输错了类名或者用了同义词,这里能直接揪出来。第二处bndbox的四个坐标逐一和 PIL 读出的真实图像尺寸做比对,任何一个坐标越界都会输出对应图片名和坐标值,方便快速定位修改。实际项目中我还会加一个“单个框面积是否过小”的阈值判断,比如宽或高小于 3 像素的直接标成可疑项,因为这种框大概率是标注时手抖画出来的。5.2 把 VOC 转成 YOLO 格式:坐标归一化与类名映射labelImg 可以保存两种格式,但很多人拿到的是历史标注好的 VOC XML,现在要训练 YOLO 模型,就得批量转换。VOC 转 YOLO 的核心是坐标转换公式:把像素坐标的 xmin / ymin / xmax / ymax 换算成归一化后的 x_center、y_center、width、height。公式很简单:中心点 = 两端坐标的平均值除以图像宽高,宽高 = 两端坐标之差除以图像宽高。但这里有个坑:YOLO 的宽高是归一化到 0~1 的绝对宽高,不是相对整张图的比例缩小值,别把除法除反了。import os import xml.etree.ElementTree as ET classes = ["cat", "dog", "bird"] # 必须与训练配置的 names 顺序一致 def convert_annotation(xml_path, out_dir, img_w, img_h): tree = ET.parse(xml_path) root = tree.getroot() out_lines = [] for obj in root.findall("object"): name = obj.findtext("name") if name not in classes: continue cls_id = classes.index(name) bnd = obj.find("bndbox") xmin = float(bnd.findtext("xmin")) ymin = float(bnd.findtext("ymin")) xmax = float(bnd.findtext("xmax")) ymax = float(bnd.findtext("ymax")) 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 out_lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}") base = os.path.splitext(os.path.basename(xml_path))[0] with open(os.path.join(out_dir, base + ".txt"), "w") as f: f.write("\n".join(out_lines))用这段代码的时候有几个注意点。第一处是classes列表,它必须和训练配置里的names顺序完全一致,否则类别编号错位就是灾难。第二处是转换时直接传入了img_w和img_h,这两个值必须从对应的图片文件里读,不能从 XML 里取。实践中有的人图省事,直接从 XML 里读 width / height,但 XML 里的宽高其实是标注工具保存时从图片读的副本,理论上没问题,如果图片文件已经被二次处理过,两者就会不一致,导致归一化坐标失真。第三处是输出四舍五入到小数点后 6 位,这在模型训练时精度已经足够。5.3 导入训练前的最后一道检查:可视化预览格式转换完成后,旁门左道的坑还有一个:检查坐标有没有“画到画面外”但校验脚本又没报错的情况。比如一张 1920x1080 的图,某目标的 xmax 写成了 1920,xmin 写成了 0,这个框其实是整条贴边的目标,看起来是真实存在的,但 yolo 训练时对这个框的 IoU 计算会有些偏激。单纯看数字很难发现问题,所以我会在训练前用 OpenCV 或 PIL 把标注框画出来,直接目测一遍。import cv2 img_path = "images/0001.jpg" txt_path = "annotations/0001.txt" img = cv2.imread(img_path) h, w = img.shape[:2] with open(txt_path) as f: for line in f.readlines(): parts = line.strip().split() cls_id, x_c, y_c, bw, bh = parts[0], float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) x1 = int((x_c - bw / 2) * w) y1 = int((y_c - bh / 2) * h) x2 = int((x_c + bw / 2) * w) y2 = int((y_c + bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, str(cls_id), (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1) cv2.imwrite("preview_0001.jpg", img)这段脚本负责把标注坐标还原到原图上并用绿色框画出来,类别 id 用红色字标在框左上角。跑完脚本打开预览图,重点看两类异常:一类是框明显偏离目标,比如框住了背景、目标只占框的一半,这种情况往往是标注时手抖或者坐标写错;另一类是框的区域比真实目标大出去非常多,比如一辆车占了半个屏幕,这种框模型勉强能学但精度会受影响。预览图检查这一步不能跳过,但每次检查所有图又太费时间,通常是抽样 5%~10% 的图即可。从那以后我每批标注完都强制走一遍“校验脚本 + 预览抽样 + 类名逐一核对”的流程,三件事加起来不到十分钟,但至少能避免把带病的数据直接丢给训练流程。希望帮到你。本文还有配套的精品资源,点击获取
简介:面向目标检测数据集构建的 LabelImg 可视化图像标注工具源码包,适用于 Faster R-CNN、YOLO、SSD 等网络训练前的人工标定环节。工具基于 Python + lxml 运行,生成的 XML 遵循 PASCAL VOC 格式,可直接对接主流检测框架。压缩包共 127 个文件,大小 6.39MB,以 44 个 Python 源码文件为核心,同时包含 38 张 PNG 图标或界面素材、6 个 Shell 启动脚本、6 个 SVG 矢量图及若干配置文件、说明文档与测试图片,涵盖工具运行、界面资源、环境配置与基础测试所需内容。目前已有 357 人学习下载。这份源码包为无法访问 GitHub 的用户提供了离线获取途径,解压后即可查看核心逻辑、二次开发或按需修改标注类别与快捷键,同时可依据自带示例图片快速验证标注流程,适合入门目标检测数据准备的开发者。1. 图像标注工具 labelImg:目标检测训练前绕不开的一步做目标检测的人应该都有过这种体验:模型结构选好了,预训练权重也下好了,结果卡在数据标注这一步——几百上千张图要一个个画框、填类别、检查导出格式。labelImg 就是最经典的开源图像标注工具,一个基于 Python 与 Qt 开发的桌面程序,干的事情很纯粹:打开图片,用矩形框把目标圈出来,打上类名,保存成 PASCAL VOC 格式的 XML 文件,或者一行一个标注的 YOLO 格式 txt。做目标检测、图像标注相关项目的人几乎都绕不开它,尤其是刚入门、想用少量数据快速验证模型的同学,以及需要按固定规范产出标注数据集的团队。它的价值不在“功能多”,而在“标准、简单、可控”,配合后续的 YOLO 训练流程,是最省心的一条路径。2. 环境搭建与启动:先解决 Python、Qt 和资源文件三件事2.1 从 GitHub 拉源码:推荐直接 clone 而不是下压缩包很多人会把 labelImg-master.rar 解压出来直接双击 labelImg.py,然后发现程序一闪而过或者报 ModuleNotFoundError。这不是压缩包的问题,而是运行环境没配好。labelImg 本质是个 Python 脚本项目,入口是根目录下的 labelImg.py,它依赖 PyQt5 和 lxml 两个第三方库,缺一个都起不来。我一般会这样处理:先建一个独立的虚拟环境,避免把系统 Python 环境搞乱。某开发者喜欢直接用 conda,我习惯用 venv,因为轻量、不污染。项目根目录里其实有 requirements.txt,里面写了 PyQt5 和 lxml,直接用 pip 装就行。这里有个值得注意的版本细节:PyQt5 的版本不能太老,建议 5.15 以上,太老的版本在部分 Linux 发行版上会缺 Qt 平台插件,启动时报 “could not load the Qt platform plugin 'xcb'”。# 进入项目目录 cd labelImg-master # 创建并激活虚拟环境(Windows 用 venv\Scripts\activate) python -m venv venv source venv/bin/activate # 安装核心依赖 pip install pyqt5 lxml # 启动主程序 python labelImg.py第二行是进入项目根目录,所有后续操作都基于这个目录,建议路径里不要带中文和空格,否则 Qt 的字体渲染和路径解析偶尔会出玄学问题。第三行创建虚拟环境的操作在 Windows 和 Linux 上略有差异,Windows 激活命令是venv\Scripts\activate,Linux 和 macOS 是source venv/bin/activate。第五行直接 pip 安装两个核心包就够了,requirements.txt 里其实还有别的,但 labelImg 实际运行只强依赖这两个。最后一行python labelImg.py是启动入口,如果看到弹出的主窗口,说明环境没问题。2.2 Linux 下编译资源文件:别跳过 pyrcc5 这一步在 Ubuntu 这类系统上,直接python labelImg.py启动后界面会“缺胳膊少腿”,按钮图标全空白,甚至某些菜单项显示异常。原因是 labelImg 的资源文件resources.qrc没有被编译成 Python 可导入的.py文件。项目源码里那个resources.py就是编译产物,但如果你是从某些渠道下载的二次打包版本,这个文件可能缺失或者版本不匹配。正确做法是手动执行编译。先安装 PyQt5 的工具包,然后用 pyrcc5 把 qrc 文件转成 py:# 安装 PyQt5 工具(包含 pyrcc5) sudo apt-get install pyqt5-dev-tools # 编译资源文件 pyrcc5 -o resources.py resources.qrc # 重新启动 python labelImg.py第一条命令在 Ubuntu 上对应pyqt5-dev-tools这个包,它提供 pyrcc5 编译工具。第二条命令把resources.qrc编译成resources.py,这个文件里包含了所有图标、界面布局的二进制数据。如果不做这一步,程序能跑但 UI 是残的,快捷键也有一部分失效——因为图标和弹窗文本都写在资源文件里。还有一点要说明:如果你用的是 pip 安装的 PyQt5,pyrcc5 并不一定在 PATH 里。遇到 “command not found” 就改用python -m PyQt5.pyrcc_main -o resources.py resources.qrc,两种方式等价。2.3 启动后黑屏或闪退:先看平台插件和显示环境这一类问题在 Linux 服务器上最明显。用远程终端连服务器、没有桌面环境或者 X11 转发没开,Qt 程序就起不来。报错信息一般长这样:qt.qpa.plugin: Could not find the Qt platform plugin "xcb" in ""。解决思路两条:一条是装系统级的 libxcb 依赖,另一条是改用 offscreen 模式跑。# 方案一:补齐 xcb 相关依赖 sudo apt-get install libxcb-xinerama0 libxcb-cursor0 # 方案二:让 Qt 走 offscreen 模式(不弹窗口,用于调试) export QT_QPA_PLATFORM=offscreen python labelImg.py第一条方案比较通用,libxcb 系列是 Qt 在 Linux 上绘图的基础,缺了它会直接崩。第二条方案适合没有显示器的环境,offscreen 模式下程序能跑但看不到界面,通常配合截图或远程桌面使用。如果两个方案都不行,检查一下是不是用了 Wayland 显示协议,部分老版本 Qt 对 Wayland 支持不好,临时切到 X11 协议往往就正常了。3. 标注流程与界面操作:画框、打标签、切换格式3.1 界面布局与工作目录:先把“打开目录”和“保存目录”分开labelImg 的界面逻辑和普通图像编辑器不太一样,它不是“打开单张图片”然后用完另存,而是面向批量的:设置一个图片目录作为输入,设置一个标注目录作为输出,然后逐张浏览标注。左上角“打开”按钮旁边那两个按钮其实是关键——“打开目录”和“改变标注保存目录”。这两个目录建议从一开始就分开,不要都指到图片文件夹,否则后续整理训练集时要手动挑 XML,容易混。界面上的核心区域就三块:左侧是图片显示区,中间顶部是工具栏,右侧是标注框列表和类名输入框。右侧列表里每一行对应一个标注框,选中某一项时图片上对应的框会高亮,方便后期修改。如果你标注的图片有大量小目标,建议先按Ctrl + +放大图片再画框,精度会高不少。3.2 画框的完整操作流:从新建框到保存到下一张整个标注过程的核心操作流是这样的:打开图片目录后,按快捷键W进入画框模式,鼠标在目标上按住左键拖动,松开后弹出类名输入框,输入类别名回车确认。一个框完成后再按W画下一个。同一张图上多个目标就重复这个流程,全部画完后按Ctrl + S保存 XML,再按D切换到下一张图。看下面这张快捷键总表:功能快捷键说明画框W进入画框模式,按住左键拖拽删除选中框Delete删除当前选中的标注框上一张/下一张A / D切换图片,自动加载同名 XML保存标注Ctrl + S生成 XML 或 txt,文件名与图片同名缩放图片Ctrl + 加号/减号放大缩小,便于精标验证图像Ctrl + Shift + V快速检查标注框是否超出图像边界保存的时候会有一个细节:如果图片同步更新过,程序会提示是否覆盖原 XML。此时要确认图像内容没有发生变化,否则标注框坐标和像素位置就对不上了。保存路径下生成的文件名和图片名完全一致,比如0001.jpg对应0001.xml,这个对应关系决定了后续训练脚本能不能正确解析。3.3 格式切换:PascalVOC 与 YOLO 的底层差别工具栏上有“PascalVOC”和“YOLO”两个格式选项,很多人到了这一步才开始犹豫选哪个。其实它们的差别很明确:PascalVOC 格式用 XML 文件保存,包含图像宽度高度、目标类别、bounding box 的 xmin / ymin / xmax / ymax 四个坐标值,单位是像素;YOLO 格式用 txt 文件保存,每一行是类别id x_center y_center width height,其中四个坐标都已经归一化到 0~1 之间。切换格式前要特别注意一个前置条件:YOLO 模式下必须有classes.txt文件,并且里面的类名顺序要和你想训练的模型类目完全一致。labelImg 会按照 classes.txt 的行号来分配类别 id,第一行是 0,第二行是 1,以此类推。这个文件放在标注输出目录下即可,程序会自动读取。我见过有人用 YOLO 模式标注完,训练时发现类别全错位,查到最后是 classes.txt 里的顺序和训练配置里的 names 不一致,这种问题排查起来非常隐蔽。3.4 批量标注的小习惯:先统一类名,再快速过图标注是个体力活,但节奏可以优化。第一张图先仔细标,把项目里所有可能出现的类别都建一遍;第二张图开始,每画完一个框,类名下拉框会记住之前输入过的值,直接选择就行,不用重复打字。如果图中有大量重复目标,还可以用Ctrl + Shift + X快速复制上一个框的类别和大小,微调位置后放置,效率提升明显。有一个我常用的“抢时间”操作:对同一批连续帧图片,第一张标注完整,后面几张用复制框 + 拖动位置的方式快速完成。具体做法是选中已有标注框,按Ctrl + C,在下一张图上按Ctrl + V,复制出来的框会出现在相同位置,再拖到实际目标上。这种方式适合视频抽帧数据,能省下不少重复劳动的痛苦。4. 避坑指南:标注工具最容易翻车的五个细节4.1 保存后找不到 XML:输出目录设错了现象:标注完按 Ctrl + S,界面上明明显示已保存,但去图片文件夹里一找,根本没有 XML 文件。原因:labelImg 的保存逻辑是“保存到指定的输出目录”,不是图片所在目录。如果你没有点过“改变标注保存目录”,默认的保存路径可能还是上一次操作留下的路径,甚至是指向空的。解决:标注开始前先点“改变标注保存目录”,选好本次标注输出文件夹,然后再打开图片目录。保存后去输出目录确认一次,看到 XML 生成再继续下一张。后续批量检查时,可以用文件数量对比脚本快速核对图片和 XML 是否一一对应。4.2 同样的类名输入两遍就弹重复提示:这是功能不是 bug现象:第一张图输入“cat”后,第二张图再输入“cat”,弹窗提示这个类名已经存在,是否继续使用。原因:labelImg 的类名列表是全局的,它把所有标注过的类名都记在左侧列表中。再次输入相同类名时,程序担心你是要新建一个不同类别的同名对象,所以会确认一次。解决:不用取消,直接确认使用现有条目就行,标注内容不会丢失。如果想避免每次都弹窗,第二张图起用下拉框选已有类名,不要手动打字。这个问题遇到过的人不少,理解它的逻辑后就顺了。4.3 自动保存没开,切换图片后标注丢失现象:画完框,没按 Ctrl + S 直接按 D 切下一张,再切回来发现刚才的标注全没了。原因:labelImg 默认不会自动保存,所有标注都缓存在内存里,只有 Ctrl + S 才会写磁盘。切换图片时会自动加载同名 XML,没有就别怪它不保存。解决:启动前建议点击“自动保存模式”按钮,开启自动保存,这样切换图片时会自动写 XML。但注意,自动保存模式下修改已存在的 XML 可能直接覆盖,操作前确认原有标注不再需要。最稳妥的习惯还是每次画完当前图区的框,连按 Ctrl + S,再切下一张。4.4 YOLO 格式下类别全错位:classes.txt 顺序问题现象:用 YOLO 模式标注完成,训练时输出的检测结果明显不对,对应关系全乱了。原因:YOLO 格式只存类别编号,不存类名。类别编号由 classes.txt 的行号决定,第一行是 0,第二行是 1。如果你标注时用的 classes.txt 顺序和训练配置文件里 names 的顺序不一致,编号就全部错位。解决:训练前强制检查一次对应关系——打开一个标注好的 txt 文件,看第一列数字,去训练配置文件里查这个数字对应的类名,和图片里实际标注的目标做对照。如果错位,要么重建 classes.txt,要么用脚本把 txt 里的类别编号统一映射到正确的值。4.5 标注文件里出现了奇怪的换行符:Windows 和 Linux 混用的坑现象:在 Windows 上标注的 txt 文件传到 Linux 训练服务器上后,训练脚本读取时报格式错误,说每行数据数量不对。原因:Windows 下用记事本编辑过的 txt 文件会带\r\n换行符,Linux 下的 Python 读文件默认按\n切分,每行结尾会残留一个\r,解析时数字数量就多出一个空字符串元素。解决:训练脚本里加一个strip()处理,或者用dos2unix命令批量转换标注文件。规范做法是写训练脚本时统一用line.strip().split()而非直接line.split(),两条路线都能兼容。提示:标注数据是训练的直接输入,任何格式层面的小问题都会在模型阶段放大。每完成一批标注,先用脚本跑一遍格式校验,比后期排查便宜得多。5. 批量校验与格式转换:把标注数据整理成可直接训练的样式5.1 用脚本核对图片与 XML 的对应关系及标注合法性手工标注完几百张图,不能直接拿去训练。最稳妥的方式是写一个校验脚本,把图片尺寸和 XML 里的 width / height、xmin / ymin / xmax / ymax 做交叉比对,确保每个标注框都在图像边界内,类别列表符合预期。下面这段代码是我常用的校验逻辑简化版:import os import xml.etree.ElementTree as ET from PIL import Image img_dir = "images" xml_dir = "annotations" class_list = ["cat", "dog", "bird"] # 预期类别白名单 for img_name in os.listdir(img_dir): if not img_name.lower().endswith(".jpg"): continue base = os.path.splitext(img_name)[0] xml_path = os.path.join(xml_dir, base + ".xml") if not os.path.exists(xml_path): print(f"[MISSING] {img_name} 没有对应 XML") continue img = Image.open(os.path.join(img_dir, img_name)) w, h = img.size tree = ET.parse(xml_path) root = tree.getroot() for obj in root.findall("object"): name = obj.findtext("name") if name not in class_list: print(f"[CLASS] {img_name} 中类名 {name} 不在白名单") bnd = obj.find("bndbox") xmin = int(bnd.findtext("xmin")) ymin = int(bnd.findtext("ymin")) xmax = int(bnd.findtext("xmax")) ymax = int(bnd.findtext("ymax")) if xmin < 0 or ymin < 0 or xmax > w or ymax > h: print(f"[BOUND] {img_name} 中 {name} 坐标越界: {xmin},{ymin},{xmax},{ymax}") print("校验完成")这段脚本做的事是把每张图片的 XML 全部解析一遍:第一处class_list是类别白名单,训练时只包含这个列表里的类别,如果标注师不小心输错了类名或者用了同义词,这里能直接揪出来。第二处bndbox的四个坐标逐一和 PIL 读出的真实图像尺寸做比对,任何一个坐标越界都会输出对应图片名和坐标值,方便快速定位修改。实际项目中我还会加一个“单个框面积是否过小”的阈值判断,比如宽或高小于 3 像素的直接标成可疑项,因为这种框大概率是标注时手抖画出来的。5.2 把 VOC 转成 YOLO 格式:坐标归一化与类名映射labelImg 可以保存两种格式,但很多人拿到的是历史标注好的 VOC XML,现在要训练 YOLO 模型,就得批量转换。VOC 转 YOLO 的核心是坐标转换公式:把像素坐标的 xmin / ymin / xmax / ymax 换算成归一化后的 x_center、y_center、width、height。公式很简单:中心点 = 两端坐标的平均值除以图像宽高,宽高 = 两端坐标之差除以图像宽高。但这里有个坑:YOLO 的宽高是归一化到 0~1 的绝对宽高,不是相对整张图的比例缩小值,别把除法除反了。import os import xml.etree.ElementTree as ET classes = ["cat", "dog", "bird"] # 必须与训练配置的 names 顺序一致 def convert_annotation(xml_path, out_dir, img_w, img_h): tree = ET.parse(xml_path) root = tree.getroot() out_lines = [] for obj in root.findall("object"): name = obj.findtext("name") if name not in classes: continue cls_id = classes.index(name) bnd = obj.find("bndbox") xmin = float(bnd.findtext("xmin")) ymin = float(bnd.findtext("ymin")) xmax = float(bnd.findtext("xmax")) ymax = float(bnd.findtext("ymax")) 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 out_lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}") base = os.path.splitext(os.path.basename(xml_path))[0] with open(os.path.join(out_dir, base + ".txt"), "w") as f: f.write("\n".join(out_lines))用这段代码的时候有几个注意点。第一处是classes列表,它必须和训练配置里的names顺序完全一致,否则类别编号错位就是灾难。第二处是转换时直接传入了img_w和img_h,这两个值必须从对应的图片文件里读,不能从 XML 里取。实践中有的人图省事,直接从 XML 里读 width / height,但 XML 里的宽高其实是标注工具保存时从图片读的副本,理论上没问题,如果图片文件已经被二次处理过,两者就会不一致,导致归一化坐标失真。第三处是输出四舍五入到小数点后 6 位,这在模型训练时精度已经足够。5.3 导入训练前的最后一道检查:可视化预览格式转换完成后,旁门左道的坑还有一个:检查坐标有没有“画到画面外”但校验脚本又没报错的情况。比如一张 1920x1080 的图,某目标的 xmax 写成了 1920,xmin 写成了 0,这个框其实是整条贴边的目标,看起来是真实存在的,但 yolo 训练时对这个框的 IoU 计算会有些偏激。单纯看数字很难发现问题,所以我会在训练前用 OpenCV 或 PIL 把标注框画出来,直接目测一遍。import cv2 img_path = "images/0001.jpg" txt_path = "annotations/0001.txt" img = cv2.imread(img_path) h, w = img.shape[:2] with open(txt_path) as f: for line in f.readlines(): parts = line.strip().split() cls_id, x_c, y_c, bw, bh = parts[0], float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) x1 = int((x_c - bw / 2) * w) y1 = int((y_c - bh / 2) * h) x2 = int((x_c + bw / 2) * w) y2 = int((y_c + bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, str(cls_id), (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1) cv2.imwrite("preview_0001.jpg", img)这段脚本负责把标注坐标还原到原图上并用绿色框画出来,类别 id 用红色字标在框左上角。跑完脚本打开预览图,重点看两类异常:一类是框明显偏离目标,比如框住了背景、目标只占框的一半,这种情况往往是标注时手抖或者坐标写错;另一类是框的区域比真实目标大出去非常多,比如一辆车占了半个屏幕,这种框模型勉强能学但精度会受影响。预览图检查这一步不能跳过,但每次检查所有图又太费时间,通常是抽样 5%~10% 的图即可。从那以后我每批标注完都强制走一遍“校验脚本 + 预览抽样 + 类名逐一核对”的流程,三件事加起来不到十分钟,但至少能避免把带病的数据直接丢给训练流程。希望帮到你。本文还有配套的精品资源,点击获取