ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

LabelImg标注到YOLOv5训练:格式转换与标签校验全流程

LabelImg标注到YOLOv5训练:格式转换与标签校验全流程

做目标检测这行的人,几乎绕不开两个东西:一个是训练框架,另一个就是标注工具。YOLOv5 作为落地项目里出场率极高的检测框架,它的数据入口标准其实非常明确——每张图对应一个同名的 txt 标签文件,里面按类别 归一化中心x 归一化中心y 归一化宽 归一化高的格式逐行写目标框。而 LabelImg 就是把这个格式"喂"给 YOLOv5 之前最常用的一道手工工序。很多人第一次跑 YOLOv5 的train.py报错,十有八九不是模型问题,而是标签格式没对上:要么是 VOC 的 xml 没转,要么是坐标没归一化,要么是类别名和data.yaml里的顺序错位。这篇内容就把 LabelImg 标注到 YOLOv5 可用标签这条链路完整拆开,从工具选型、目录组织、标注规范、格式转换、批量校验到踩坑排查,全部按实操顺序讲清楚。不管你是刚接触目标检测的学生,还是要把一个自采数据集跑通的中小团队开发者,都能照着这套流程直接复现,少走几天弯路。

1. 整体方案设计与工具选型思路

1.1 为什么是 LabelImg 而不是别的标注工具

标注工具这几年冒出来很多,有在线的、有团队协作的、有带半自动预标注的。但落到 YOLOv5 这个具体场景,LabelImg 依然是性价比最高的起点,原因很实在。

第一是输出格式直接对口。LabelImg 原生支持 Pascal VOC 的 xml 和 YOLO 的 txt 两种保存格式,切到 YOLO 模式后,画完框保存出来的就是归一化坐标的 txt,和 YOLOv5 的标签规范完全一致,中间不需要写转换脚本。这一点对新手极其友好,省掉了最容易出错的一环。

第二是轻量、离线、无依赖。它就是个 Python 写的桌面小工具,装完就能用,不联网也能标,图片不出本地,对数据隐私敏感的场景很合适。相比之下很多在线标注平台要把图传上去,团队协作是方便了,但数据流转的顾虑也多。

第三是快捷键体系成熟。标数据集是个体力活,一个几千张图的项目,标注速度直接决定项目周期。LabelImg 的W画框、D下一张、A上一张、Ctrl+S保存这套组合,熟练之后单手就能操作,效率比很多花哨工具还高。

当然它也有明显短板:不支持多人协作、没有预标注、界面比较朴素。所以我的实际做法是——小数据集和冷启动阶段用 LabelImg 手工标,等积累了几百张之后,用训好的模型做预标注再人工修正,这个混合策略后面会展开讲。

1.2 YOLOv5 对标签的真实要求到底是什么

很多人对 YOLO 标签格式的理解停留在"五个数",但真正决定你能不能跑通的,是这几个细节。

YOLOv5 的标签文件是每张图一个 txt,文件名与图片同名(扩展名不同),放在与图片平行的labels目录下。txt 里每一行代表一个目标框,格式为:

<class_index> <x_center> <y_center> <width> <height>

其中后四个值全部是相对于图片宽高的归一化值,范围 0 到 1,保留六位小数左右即可。class_index是从 0 开始的类别索引,必须和data.yaml里names列表的顺序严格对应。

这里有个特别容易被忽略的点:归一化的分母是当前这张图的实际宽高,不是数据集的统一尺寸。也就是说,如果你的图片尺寸参差不齐,每张图的归一化基准都不一样,LabelImg 在 YOLO 模式下会自动按当前图尺寸算,这一点它处理得是对的。但如果你自己写脚本转换 VOC 到 YOLO,就必须读每张图真实的width和height,不能拿一个固定值去套,否则框会整体偏移——这是新手转换脚本里最高频的 bug。

还有一个隐性要求:空标签的处理。如果一张图里没有目标(负样本),YOLOv5 期望存在一个空的 txt 文件,而不是没有文件。有图无标签文件,训练时会被当成"标签缺失"跳过或报错;有空文件才被正确识别为负样本。LabelImg 在 YOLO 模式下如果一张图没画任何框就保存,默认不会生成文件,这个坑后面会讲怎么补。

1.3 目录结构怎么组织才不返工

标注阶段最忌讳的就是目录乱。我见过太多项目,标到一半发现图片和标签对不上号,回头整理花的时间比标注还多。推荐从一开始就按 YOLOv5 官方认可的结构来:

dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yaml

关键原则是images 和 labels 两棵树的子目录名必须完全一致(都叫 train、都叫 val),YOLOv5 在训练时会自动把images/train/xxx.jpg映射到labels/train/xxx.txt。你只要保证这个平行关系,路径配置就几乎不会出错。

标注阶段的临时文件我习惯单独放一个raw/目录,标完再按比例切分到 train 和 val。切分比例一般 8:2 或 9:1,小数据集可以 7:3 保证验证集有足够样本。切分时要注意按图片切,标签跟着图片走,绝不能把同一张图的图和标签分到不同集合。

提示:目录名、文件名里尽量不要出现中文和空格。YOLOv5 底层读路径时对特殊字符的处理不够健壮,中文路径在部分环境下会直接读不到文件,这个坑非常隐蔽,排查起来很费时间。

2. LabelImg 环境搭建与核心配置细节

2.1 安装方式的选择与取舍

LabelImg 的安装有三条常见路线,各有适用场景,我按推荐度排一下。

路线一:pip 安装(最省事)。直接pip install labelimg,装完命令行敲labelimg就能启动。适合绝大多数人,尤其是 Windows 和 macOS 用户。缺点是某些系统上 Qt 依赖可能冲突,如果启动报 Qt 相关错误,就换路线二。

路线二:源码运行(最可控)。从仓库拉源码,装好pyqt5和lxml依赖后python labelImg.py启动。适合需要改源码、加自定义功能的人,比如你想改默认保存格式或者加个自动编号逻辑。

路线三:打包好的可执行文件(最无脑)。直接下载对应系统的可执行版本,双击运行。适合完全不想碰 Python 环境的用户,但版本可能偏旧。

我个人的习惯是用 pip 装,但固定一个已知稳定的版本。因为 LabelImg 更新不算频繁,新版本偶尔会引入小问题,固定版本能保证团队里每个人环境一致,避免"我这能存你那不能存"的扯皮。

# 推荐的基础安装 pip install labelimg # 如果遇到 Qt 报错,先卸载再装指定版本 pip uninstall pyqt5 pyqt5-tools pip install pyqt5==5.15.9

2.2 启动后必须改的几个默认设置

LabelImg 装完直接开标,大概率会踩格式坑。启动后第一件事是进设置把几个关键项调对,这几步花两分钟,能省后面几小时。

第一,切换保存格式为 YOLO。界面左侧工具栏有个格式切换按钮,默认是 PascalVOC,点一下切到 YOLO。切换后保存出来的就是 txt 而不是 xml。这个一定要在开始标注之前切,标到一半再切会导致前面存的格式和后面不一致,混在一起非常麻烦。

第二,设置自动保存。菜单里勾选Auto Save mode,这样每次切到下一张图时自动保存当前标注,不用手动Ctrl+S。标几千张图的时候,忘记保存丢掉的标注能让人崩溃,这个开关强烈建议打开。

第三,配置预定义类别。在设置里找到Predefined Classes,把你要标的类别一行一个写进去。这样画框时可以直接从下拉列表选类别,不用每次手打,既快又避免拼写不一致。类别名要和后面data.yaml里的names完全一致,大小写都不能差。

第四,确认标注框颜色和显示。这个纯属体验优化,但类别多的时候,给不同类别配不同颜色能显著降低标错的概率,尤其是相似类别(比如"货车"和"客车")挨着出现时。

2.3 快捷键体系与效率提升

标注效率的核心就是少动鼠标。LabelImg 的快捷键我整理成一张表,建议打印出来贴屏幕边上,练熟之后速度能翻倍。

操作快捷键说明
画矩形框W最常用,按下后拖拽画框
下一张D标完一张立刻切下一张
上一张A标错了回头改
保存Ctrl+S开了自动保存可省略
删除当前框Delete选中框后删除
放大/缩小Ctrl+滚轮小目标必用
移动图片按住滚轮拖放大后平移查看
复制上一张的框Ctrl+D连续相似场景提速神器

其中Ctrl+D复制上一张标注框这个功能,很多人不知道。在视频抽帧或者连续拍摄的场景里,相邻帧的目标位置几乎一样,复制过来微调比重新画快得多。我标过一个连续拍摄的数据集,靠这个功能效率提升了差不多三倍。

注意:Ctrl+D复制的是上一张图的框,如果两张图尺寸不同,框的位置会有偏差,复制后一定要检查微调,不能无脑复制。

3. 标注规范与实操全流程

3.1 标注前的规范制定

动手标之前,先花半小时把规范定下来,比标到一半再统一强一百倍。规范要明确三件事:标什么、怎么标边界、类别怎么定义。

标什么:明确哪些目标要标、哪些忽略。比如做车辆检测,远处的、被遮挡超过 70% 的、模糊到人眼都难辨的,是标还是不标?这些边界情况必须提前定规则。我的习惯是——遮挡超过 70% 或尺寸小于 10x10 像素的目标不标,因为这类目标即使标了,模型也学不好,反而引入噪声。

怎么标边界:框要贴紧目标的外接矩形,不能松也不能切。松了会把背景算进目标,切了会丢失目标信息。对于有遮挡的目标,框按可见部分的完整外接矩形来画,被遮挡的部分靠推理补全边界,而不是只框可见部分。这一点新手最容易做错,只框可见部分会让模型学到的尺寸偏小。

类别怎么定义:类别粒度要一致。比如"汽车"这个类别,是只包括轿车,还是包括 SUV、面包车?如果数据集里既有轿车又有卡车,那就要么分成两类,要么合并成一类"车辆",不能一部分标"汽车"一部分标"卡车"还指望模型自己区分。类别定义一旦定下,整个数据集必须严格执行。

3.2 单张图片的完整标注操作

拿一张图走一遍完整流程,把每个动作的意图讲清楚。

打开 LabelImg,Open Dir选中图片目录,第一张图加载出来后,按W进入画框模式,鼠标从目标左上角拖到右下角,松开后弹出类别选择框,选对应类别确认。一个目标标完,继续下一个,直到这张图所有目标都框完。

这里有几个实操细节值得说。画框时先放大:小目标在原始视图下很难框准,Ctrl+滚轮放大到目标占屏幕合适比例再画,精度会高很多。框的顺序无所谓:YOLO 标签不关心框的先后,但养成从左到右、从上到下的习惯,回头检查时更顺。标完一张立刻切下一张:开了自动保存后,按D直接走,不要停下来欣赏,节奏一断效率就掉。

一张图标的合理时间,熟练后普通场景 10 到 30 秒,密集场景可能一两分钟。如果一张图你花了五分钟还在纠结某个模糊目标标不标,说明规范没定清楚,停下来回去补规范,别硬标。

3.3 大批量标注的节奏管理

标数据集是马拉松不是短跑。我的经验是分批推进、定期抽检。一次连续标两小时以上,后面错误率会明显上升,建议每 45 分钟休息一下。

更重要的是抽检机制。每标完 100 张,随机抽 10 张回看,检查框是否贴紧、类别是否正确、有没有漏标。发现系统性问题(比如某个类别总是标松)立刻停下来统一修正,别等标完几千张才发现全错,那时候返工成本是灾难性的。

还有个小技巧:把难标和易标的图分开。先用一批简单清晰的图快速推进,建立手感和信心,把模糊、密集、边界情况复杂的图单独挑出来集中处理,处理时精力更集中,判断也更准。

3.4 从 VOC 到 YOLO 的格式转换(如果你标成了 xml)

有时候团队里有人习惯用 VOC 格式标,或者你接手的是别人标好的 xml 数据集,就需要转成 YOLO 格式。虽然 LabelImg 能直接存 YOLO,但转换脚本这个技能必须会,因为实际项目里格式转换是家常便饭。

转换的核心逻辑就三步:读 xml 拿到框的左上右下坐标和图片宽高,把(xmin, ymin, xmax, ymax)转成归一化的中心点和宽高,按类别名映射成索引写进 txt。

import os import xml.etree.ElementTree as ET from PIL import Image # 类别名到索引的映射,顺序必须和 data.yaml 的 names 一致 classes = ["person", "car", "bicycle"] class_to_id = {name: i for i, name in enumerate(classes)} def convert_annotation(xml_path, img_path, out_txt_path): tree = ET.parse(xml_path) root = tree.getroot() # 读图片真实宽高,这是归一化的基准 with Image.open(img_path) as im: w, h = im.size lines = [] for obj in root.iter("object"): name = obj.find("name").text if name not in class_to_id: continue # 跳过未定义类别,避免索引错乱 bbox = obj.find("bndbox") xmin = float(bbox.find("xmin").text) ymin = float(bbox.find("ymin").text) xmax = float(bbox.find("xmax").text) ymax = float(bbox.find("ymax").text) # 转中心点 + 宽高,再归一化 x_center = (xmin + xmax) / 2.0 / w y_center = (ymin + ymax) / 2.0 / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h lines.append(f"{class_to_id[name]} {x_center:.6f} {y_center:.6f} {bw:.6f} {bh:.6f}") with open(out_txt_path, "w") as f: f.write("\n".join(lines))

这段脚本里有两个地方是新手最容易写错的。一是归一化基准,必须用Image.open读每张图真实尺寸,不能拿固定值。二是类别映射,classes列表的顺序就是最终索引,一旦定了就不能改,改了所有标签都得重转。转换完一定要抽几张图可视化验证,把框画回图上看看位置对不对,别转完直接拿去训练。

4. 标签质量校验与常见问题排查

4.1 训练前的标签自检清单

标签转完、数据集组织好,别急着开训。先跑一遍自检,把问题挡在训练之前。我整理了一份检查清单,按这个顺序过一遍,能拦掉九成以上的标签问题。

检查项检查方法不合格表现
图片标签一一对应对比两个目录的文件名集合有图无标签或有标签无图
坐标范围合法脚本扫描所有值是否在 0~1出现负数或大于 1
类别索引合法检查索引是否小于类别总数索引越界
空标签文件存在统计空 txt 数量负样本缺文件
框宽高非零检查 width/height 是否大于 0出现 0 宽高框
类别分布合理统计各类别框数量某类极少或极多

其中坐标范围和类别索引这两项,用几行脚本就能扫完,强烈建议每次转换后都跑一遍。

import os def check_labels(label_dir, num_classes): problems = [] for fname in os.listdir(label_dir): if not fname.endswith(".txt"): continue path = os.path.join(label_dir, fname) with open(path) as f: for line_no, line in enumerate(f, 1): parts = line.strip().split() if not parts: continue if len(parts) != 5: problems.append(f"{fname}:{line_no} 字段数不对") continue cls = int(parts[0]) vals = [float(v) for v in parts[1:]] if cls < 0 or cls >= num_classes: problems.append(f"{fname}:{line_no} 类别索引越界 {cls}") if any(v < 0 or v > 1 for v in vals): problems.append(f"{fname}:{line_no} 坐标越界 {vals}") if vals[2] <= 0 or vals[3] <= 0: problems.append(f"{fname}:{line_no} 宽高为零") return problems

跑完如果problems是空的,标签基本就干净了。有输出就按提示逐条修,别带着问题训练,训练出来的模型精度上不去,你还得回头怀疑是模型问题,白白浪费时间。

4.2 高频问题速查与解决

实际标注和转换过程中,有几类问题反复出现,我把它们和对应解法整理成速查表,遇到直接对号入座。

现象可能原因解决办法
训练报"标签缺失"图片无对应 txt补空 txt 或检查文件名
框整体偏移归一化用了错误尺寸用每张图真实宽高重转
框位置对但类别全错类别索引与 yaml 顺序不符统一 classes 顺序重转
训练 loss 不降标签坐标越界或宽高为零跑自检脚本清理
部分图不参与训练文件名含中文或空格批量重命名
验证集指标异常低train/val 切分时图标签错位按图切分重新组织

其中框整体偏移这个坑最典型。表现是训练能跑,但预测出来的框总是偏一个固定方向或比例。根因几乎都是转换时归一化基准错了——要么用了固定尺寸,要么读错了宽高。排查方法很简单:随便挑一张图,把转换后的 txt 反算回像素坐标,和原图上的框对比,一眼就能看出偏差。

类别全错这个也很常见,尤其是多人协作时。A 同学按["car", "person"]标,B 同学按["person", "car"]标,合并后索引全乱。解决办法是类别定义写进项目文档,所有人从同一份classes.txt读,LabelImg 的预定义类别也从这份文件导入,从源头保证一致。

4.3 预标注加速与人工修正的混合流程

纯手工标几千张图,时间成本太高。等你有了一批标注数据、训出一个能用的初版模型后,就可以切到预标注 + 人工修正的模式,效率能提升好几倍。

具体做法是:用初版模型对未标注的图跑推理,把预测结果按 YOLO 格式写成 txt,和图片一起放进 LabelImg 打开。LabelImg 会自动加载已有的 txt 并画出框,你只需要检查、微调、删掉误检、补上漏检,然后保存。因为大部分框模型已经画好了,人工只做修正,单张图耗时能从几十秒降到几秒。

这个流程有两个注意点。一是预标注模型的精度要够,如果初版模型漏检严重,人工补框的工作量反而更大,不如纯手工。一般 mAP 到 0.5 以上再上预标注比较划算。二是修正时要警惕"确认偏误",人看到模型画好的框容易默认它是对的,草草扫一眼就过。建议修正时对每个框都问一句"这个框贴紧了吗、类别对吗",保持和纯手工一样的严格标准,否则预标注引入的错误会悄悄污染数据集。

提示:预标注生成的 txt 一定要和原图同名同目录结构,LabelImg 才能自动关联。如果打开后看不到框,先检查 txt 文件名和图片名是否完全一致(包括大小写)。

5. 数据增强前的标签一致性保障

5.1 增强会不会破坏标签

YOLOv5 内置了 Mosaic、随机缩放、色彩抖动等增强,这些在训练时实时做,标签会跟着变换,不需要你手动处理。但如果你在标注阶段之后、训练之前自己做离线增强(比如用脚本把图片翻转、旋转生成新样本),就必须同步变换标签,否则图和标签对不上,训练直接崩。

离线增强里,水平翻转是最常用也最容易实现的一种。翻转图片后,框的中心 x 坐标要变成1 - x_center,宽高不变。这个变换很简单,但很多人忘了同步改标签,结果翻转后的图和原标签错位。

def flip_label_horizontal(line): parts = line.strip().split() cls = parts[0] x, y, w, h = map(float, parts[1:]) x = 1.0 - x # 水平翻转,中心 x 镜像 return f"{cls} {x:.6f} {y:.6f} {w:.6f} {h:.6f}"

旋转、裁剪这类增强的标签变换就复杂得多,涉及坐标旋转矩阵和边界裁剪,稍不注意就出错。我的建议是——离线增强只做水平翻转这种简单可靠的,复杂增强交给 YOLOv5 训练时内置处理,省心又不容易错。

5.2 类别不平衡的处理时机

标注完统计一下类别分布,如果发现某类特别少(比如 1000 个"人"只有 20 个"消防栓"),就要考虑处理。处理时机有两个:标注阶段补数据,或者训练阶段用增强和采样策略。

标注阶段能补就补,多找一些含稀有类别的图来标,这是最根本的解法。如果实在找不到更多数据,训练时可以用 YOLOv5 的类别权重或者过采样,但这些是补救手段,效果不如直接补数据。我的原则是——稀有类别样本数低于总样本 5% 时,优先想办法补数据,别指望训练技巧硬扛。

6. 我个人踩过的几个坑

说几个文档里不会写、但实际会遇到的坑。

第一个是 LabelImg 的自动保存和手动保存混用。开了自动保存后,切换图片会存,但如果你在同一张图上改完框没切图就关软件,改动可能丢。我的习惯是改完重要标注手动补一个Ctrl+S,双保险。

第二个是图片旋转信息(EXIF)。手机拍的图常带旋转标记,LabelImg 显示时可能已经转正,但读图脚本按原始像素读,导致归一化基准和显示不一致,框偏移。解决办法是标注前统一用脚本把 EXIF 旋转"烧"进像素,消除这个隐患。

第三个是文件名大小写。Windows 不区分大小写,Linux 区分。在 Windows 上标好的IMG_001.jpg和img_001.txt,到 Linux 训练环境可能匹配不上。统一用小写命名能避开这个跨平台坑。

第四个是标注中途改类别名。标了一半发现类别名拼错了,直接改data.yaml是不够的,所有已标 txt 里的索引也得跟着改。所以类别名在开标前就要反复确认,定下来就别动。

这套流程走下来,从 LabelImg 标注到 YOLOv5 能直接训练的干净数据集,基本就是一条顺畅的流水线。真正花时间的从来不是画框本身,而是格式对齐、质量校验和那些隐蔽的路径、编码、归一化问题。把自检脚本和规范提前做好,后面训练阶段的很多"玄学问题"根本不会出现。

返回列表