资讯动态

YOLOv7数据准备实战:LabelImg标注到XML转YOLO格式全流程

发布时间:2026/10/21 12:27:20 来源:尧图企业网站定制
1. 项目整体思路与工作流设计1.1 从原始图片到YOLOv7训练集总共分几步我先直接说结论YOLOv7要能正常训练数据文件夹里必须同时存在图片文件和同名txt标注文件。这个txt不是随便写的每一行代表一个目标框格式固定为class_id x_center y_center width height注意这里的x_center、y_center、width、height全部是归一化坐标取值范围在0到1之间。什么意思就是目标框中心点的x坐标要除以图片的像素宽度框的宽度要除以图片的像素宽度。同理y和高度全部除以图片的高度。之所以要归一化是因为模型在训练时会把输入图片统一缩放到固定尺寸比如640x640如果标注文件里存的是绝对像素值缩放后就没法对齐了。很多新手第一次接触YOLO拿到的数据集可能是Pascal VOC格式的XML文件或者COCO格式的JSON文件直接拿去训练就会报错报错信息往往又臭又长根本看不懂。所以这篇博文要解决的就是帮你在本地把数据从最原始的图片一步步整理成YOLOv7能直接吃进去的目录结构整个过程可以拆成五步收集并整理图片统一命名规则。用LabelImg打开图片逐张框选目标标注类别。导出XML格式的标注文件LabelImg默认格式。运行转换脚本把XML转成YOLO格式的txt。划分训练集和验证集按YOLOv7要求的目录结构存放。这五步看着简单实际操作中每一步都有坑后面我会把每一步掰开揉碎讲清楚。1.2 工具选型为什么这个场景选LabelImg目标检测标注工具不少转了一圈你会发现主流的就几种LabelImg、Labelme、Label Studio、CVAT、X-AnyLabeling。为什么这篇教程选LabelImg我是基于三个实际考量第一上手成本最低。LabelImg是纯桌面软件不需要搭服务端不需要数据库下载即用双击就能打开对没有运维基础的人来说几乎零门槛。第二格式兼容性好。它原生支持Pascal VOC XML和YOLO txt两种格式的输出直接在界面里一键切换。对于YOLOv7这种要求txt格式的训练框架LabelImg的适配性非常直接。第三轻量、稳定。标注是一个高重复性劳动工具稳定性很重要。LabelImg虽然在界面美观度上不如新出的工具但胜在打开速度快、卡顿少处理几千张图片也不容易崩。X-AnyLabeling这类新工具我也试过功能确实更强支持自动标注、SAM分割模型集成但它的自动标注依赖模型权重需要下载模型文件环境配置也更复杂。如果你只是做几百张图的简单标注LabelImg完全够用没必要为了追新把时间耗在环境搭建上。2. 环境准备与LabelImg安装实操2.1 安装方式对比与选择LabelImg的安装有两种主流方式我分别说下优缺点。第一种是pip安装适合只想快速用起来的人pip install labelImg装完之后在命令行输入labelImg就能启动。这个方法最省事但有一个隐患依赖库的版本兼容问题偶尔会爆发比如某些环境下PyQt5和Python版本对不上界面能打开但按钮点不了。第二种是源码运行适合对稳定性要求高、或者需要二次修改的人git clone https://github.com/HumanSignal/labelImg.git cd labelImg pip install -r requirements/requirements-windows.txt # Windows用这个 # 或者 requirements-linux.txt 对应Linux pyrcc5 -o libs/resources.py resources/resources.qrc python labelImg.py源码运行多了一步pyrcc5编译资源文件但这步能有效避开pip安装时偶尔出现的资源文件缺失问题。我个人的建议是如果你在Windows上优先用源码方式如果你在Linux服务器上做标注有些人是通过远程桌面操作pip方式就够了。注意LabelImg依赖Python3和PyQt5。安装前确认你的Python版本在3.6以上PyQt5如果报错装不上试试换镜像源pip install PyQt5 -i https://pypi.tuna.tsinghua.edu.cn/simple2.2 打开软件后先做的三个设置安装好之后第一次打开LabelImg界面很朴素菜单栏加左侧工具栏中间是图片显示区。别急着开始框先把设置调对不然标注到一半会非常痛苦。第一个设置切换标注格式。默认情况下LabelImg的保存格式是PascalVOC界面右下角或者工具菜单里可以看到当前格式标识。你要在菜单栏View里找到Auto Saving Mode自动保存模式同时确认当前格式是YOLO还是PascalVOC。我建议你统一用默认的PascalVOC先标注后面用脚本转YOLO格式这样可以保留一份XML原始文件万一后期需要改类别名可以直接JSON或脚本来批量处理比改txt方便得多。![注意这里原本放的是实际操作截图占位你本地操作时观察右下角格式指示即可]第二个设置打开自动保存模式。在View菜单下勾选Auto Save Mode这样每切换一张图片系统会自动保存上一张的标注结果避免手工CtrlS。标注几千张图的时候手动保存非常分散注意力而且容易忘。第三个设置配置类别文件。LabelImg在界面上有一个框可以让你输入类别名但如果每次标注都手动输入类别效率太低且容易打错字。正确做法是准备一个predefined_classes.txt文件里面每行写一个类别名称注意顺序因为后面转换出来的txt里每个类别对应的序号是按这个文件的顺序自然排列的。比如你的类别文件内容person car bicycle那在最终的txt文件里person对应的class_id就是0car对应1bicycle对应2。这个顺序一定不能乱乱了整个数据集就废了。3. 核心实操用LabelImg逐张标注图片3.1 标注前的图片预处理要点很多人拿到图片直接就开始标注结果标到一半发现有的图片太大打开特别慢或者图片尺寸不统一训练时报错。我建议标注前先做一轮预处理能省掉后面很多麻烦。具体做两件事。第一统一图片格式和命名。把所有图片转成.jpg格式YOLO训练对jpg支持最好png也可以但体积大命名规则建议用纯数字加固定前缀比如0001.jpg、0002.jpg。千万别用中文名、带空格的名称或者完全无规律的hash名后面代码处理的时候会非常难受。第二批量缩放超大图片。如果你的图片是几千万像素级别的超高清图建议先统一缩放到一个合理范围比如最长边2000像素以内。LabelImg虽然能打开大图但标注框选时拖动会很迟钝而且YOLOv7训练时本身会把图片缩放到640x640原图太细节的信息对训练贡献有限反而拖慢标注速度。图片预处理可以用Python的PIL库写个十行脚本批量处理也可以用看图软件批量转换这部分方法很多这里不展开讲。3.2 逐张框选的操作细节预处理完成打开LabelImg点击左侧Open Dir选择图片所在文件夹再点击Change Save Dir选择标注文件保存文件夹。这两个目录可以相同但更推荐分开比如images和labels两个文件夹后面整理目录结构更清晰。接下来就是核心操作了按W键进入创建框模式鼠标在图片上按下左键拖动到目标物体的右下角松开左键弹出类别选择框选择或输入类别名完成一个框的标注。这里有一个细节我要重点强调框一定要贴着目标物体的边缘不要包太大面积的多余背景也不要切到目标本身。我当时做第一个项目的时候为了让模型更好检测特意把框画大了一些想着把目标周围一点环境也包进去结果模型训练出来检测框明显偏大而且误检率很高。后来才意识到标注框边缘贴合程度直接影响模型回归精度。YOLOv7的损失函数里回归损失很大一部分依赖标注框的精确位置你标得越准模型学到的边界就越紧。还有一个技巧是关于遮挡重叠的标注。如果一个目标被另一个目标挡住了一部分露出来的部分也要正常框选不要因为遮挡就跳过。YOLO系列模型能处理一定程度的遮挡但前提是训练数据里确实有这类样本。当然如果你的是极端情况——比如目标只露出不到10%的像素那这框标了反而会引入噪声实践中可以根据项目需求自己把握一个阈值。3.3 快捷键效率表与标注节奏标注是一个非常吃效率的重复劳动快捷键熟练度直接决定你一天能标多少张图。我整理了一份最常用的快捷键表建议你打印出来贴显示器旁边快捷键功能备注W创建标注框最常用的键按完直接画框D下一张图片配合自动保存全程不用碰鼠标菜单A上一张图片标错了要回头改就靠它CtrlS保存没开自动保存时用Del删除选中框画错了立刻删Ctrl滚轮缩放图片细节标注时必备空格拖动手势按住空格左键拖动图片放大时用我的个人节奏是左手控制键盘W、D、A右手控制鼠标画框标完一张按D直接进下一张。这样一个流程下来每小时大概能标80到120个框具体取决于每张图的密集程度。按这个速度500张图、每张平均2个框的标注量一个下午基本能完成。标注到中间阶段一定记得检查一下当前进度看一眼保存目录里已经生成了多少XML文件和图片数量做比对能避免因为中间某张图没保存而漏标。4. 格式转换从XML到YOLOv7格式的txt4.1 两种格式的本质差异LabelImg标注出来的XMLPascal VOC格式和YOLOv7要的txt最核心的区别在于坐标系表达方式。VOC格式记录的是目标框的绝对像素坐标object namecar/name bndbox xmin100/xmin ymin150/ymin xmax300/xmax ymax280/ymax /bndbox /object这里xmin、ymin是框左上角的像素位置xmax、ymax是右下角的像素位置。这种表达方式直观但问题在于它依赖图片原始尺寸。同样的一个目标在1000x800的图片和在1920x1080的图片里XML坐标数值完全不同模型没法跨尺寸学习。YOLO的txt格式则全部归一化到0-1区间把坐标的绝对值信息抹掉了表达的是相对位置。这样模型训练时无论输入图怎么缩放标注坐标系都稳定。格式转换脚本要做的就是把绝对像素坐标换算成归一化坐标。换算公式非常简单x_center (xmin xmax) / 2 / img_widthy_center (ymin ymax) / 2 / img_heightwidth (xmax - xmin) / img_widthheight (ymax - ymin) / img_height4.2 完整的转换脚本实现下面这个脚本是我平时项目里一直在用的读取XML文件解析出每个目标的类别和坐标然后转成YOLO格式的txt文件并且带了一轮数据校验防止出现坐标越界、宽高为0之类的脏数据import os import xml.etree.ElementTree as ET from pathlib import Path def convert_xml_to_yolo(xml_path, output_path, class_names): 将单个Pascal VOC XML标注文件转换为YOLO格式txt class_names: 类别名称列表顺序与类别文件保持一致 tree ET.parse(xml_path) root tree.getroot() # 从XML中读取图片尺寸 size root.find(size) img_width int(size.find(width).text) img_height int(size.find(height).text) # 图片宽度或高度为0直接跳过 img_name os.path.basename(xml_path).replace(.xml, ) if img_width 0 or img_height 0: print(f[WARN] {img_name} 图片尺寸为0跳过) return False yolo_lines [] for obj in root.iter(object): # 跳过难识别目标 difficult obj.find(difficult) if difficult is not None and int(difficult.text) 1: continue cls_name obj.find(name).text # 类别不在预定义列表里跳过并提示 if cls_name not in class_names: print(f[WARN] {img_name} 包含未定义类别: {cls_name}) continue class_id class_names.index(cls_name) 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 / img_width y_center (ymin ymax) / 2.0 / img_height width (xmax - xmin) / img_width height (ymax - ymin) / img_height # 数据校验归一化后坐标必须在0~1之间 if not (0 x_center 1 and 0 y_center 1): print(f[WARN] {img_name} 的 {cls_name} 框中心点越界: ({x_center}, {y_center})) continue if width 0 or height 0: print(f[WARN] {img_name} 的 {cls_name} 框宽高异常: w{width} h{height}) continue yolo_lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) if not yolo_lines: print(f[WARN] {img_name} 没有可用标注生成空txt) # 写入txt文件 txt_path Path(output_path) / f{img_name}.txt with open(txt_path, w, encodingutf-8) as f: f.write(\n.join(yolo_lines)) return True def batch_convert(xml_dir, output_dir, class_names): 批量转换XML目录下的所有文件 os.makedirs(output_dir, exist_okTrue) xml_files [f for f in os.listdir(xml_dir) if f.endswith(.xml)] success_count 0 skip_count 0 for xml_file in xml_files: if convert_xml_to_yolo(os.path.join(xml_dir, xml_file), output_dir, class_names): success_count 1 else: skip_count 1 print(f转换完成成功{success_count}个跳过{skip_count}个) print(f输出目录{output_dir}) # 使用示例 if __name__ __main__: # 类别顺序必须与predefined_classes.txt保持一致 CLASS_NAMES [person, car, bicycle] batch_convert( xml_dir./annotations/xml, output_dir./annotations/labels, class_namesCLASS_NAMES )这段脚本里最容易被忽略的地方就是类别列表的顺序。你在第2.2节配置predefined_classes.txt的时候写的是什么顺序这里的CLASS_NAMES就必须保持一样的顺序。如果不同转换出来的class_id就对不上了而且这种错误非常隐蔽——脚本不会报错标注文件看起来也正常但模型训练出来类别完全混乱。4.3 转换结果的逐个验证方法转换脚本跑完不代表就完事了必须验证生成的数据有没有问题。我常用的验证方法有三个按性价比排序第一统计文件数量。转换出来的txt文件数量应该和XML文件数量一致打开你的数据目录看一眼是不是对得上。有对不上的基本就是有脏数据被跳过了。第二抽样可视化。这是最直观、最能发现问题的方法。写一个简短的Python脚本把txt里的归一化坐标还原成绝对像素坐标然后用OpenCV在图片上画框人工肉眼看一遍画出来的框和真实目标的位置是否吻合。如果发现大量框偏了说明转换脚本里有坐标换算错误。import cv2 import numpy as np def draw_yolo_boxes(image_path, txt_path): img cv2.imread(image_path) h, w img.shape[:2] with open(txt_path, r) as f: for line in f: parts line.strip().split() cls_id int(parts[0]) x_center float(parts[1]) * w y_center float(parts[2]) * h box_w float(parts[3]) * w box_h float(parts[4]) * h x1 int(x_center - box_w / 2) y1 int(y_center - box_h / 2) x2 int(x_center box_w / 2) y2 int(y_center box_h / 2) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imshow(check, img) cv2.waitKey(0) # 抽样检查 draw_yolo_boxes(images/0001.jpg, labels/0001.txt)第三空文件检查。转换过程中如果一张图的XML里没有任何可用的标注脚本会生成空txt文件。YOLO训练遇到空txt不会报错但会把这个当成负样本不一定是好事。你检查一下所有txt文件的大小0字节的文件单独拎出来确认是图片真的没有目标还是解析出了问题。5. 数据集划分与目录结构搭建5.1 YOLOv7训练要求的目录标准YOLOv7官方仓库对数据集目录有一套约定俗成的规范。虽然不同人的项目习惯不完全一样但我建议直接用官方推荐的结构省得后面跑命令时踩坑。标准结构是这样的dataset/ ├── images/ │ ├── train/ │ │ ├── 0001.jpg │ │ ├── 0002.jpg │ │ └── ... │ └── val/ │ ├── 0501.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── 0001.txt │ │ └── ... │ └── val/ │ ├── 0501.txt │ └── ... ├── train.txt └── val.txt其中images和labels是必有目录分别存放图片和对应的标注文件。两个目录下的子目录结构必须完全一致而且train和val的图片文件和标注文件必须一一对应images/train/0001.jpg对应labels/train/0001.txt文件名前缀相同扩展名不同。train.txt和val.txt是训练时用到的索引文件每一行写一张图片的绝对路径YOLOv7训练时通过读取这两个文件来定位训练数据。有些版本支持直接用data.yaml里的路径配置代替这两个txt但我个人建议还是把这两个文件生成出来兼容性最好。5.2 划分脚本按比例拆分并生成索引划分数据集有一个原则要记住划分前先打乱顺序。如果图片是按某种规则排序的比如按拍摄时间不打乱就直接划分会导致训练集和验证集分布不一致。我一个朋友的项目就是顺序划分结果验证集全是雨天样本训练集全是晴天样本模型验证集精度惨不忍睹。import os import random from pathlib import Path def split_dataset(image_dir, label_dir, output_dir, train_ratio0.8, seed42): 划分数据集为训练集和验证集并生成train.txt和val.txt random.seed(seed) # 收集所有图片文件 images [str(p) for p in Path(image_dir).glob(*.jpg)] images [str(p) for p in Path(image_dir).glob(*.png)] images.sort() # 打乱顺序 random.shuffle(images) split_idx int(len(images) * train_ratio) train_images images[:split_idx] val_images images[split_idx:] # 创建目录结构 for split in [train, val]: os.makedirs(os.path.join(output_dir, images, split), exist_okTrue) os.makedirs(os.path.join(output_dir, labels, split), exist_okTrue) def process_split(img_list, split_name): txt_lines [] for img_path in img_list: # 找到对应的标注文件 img_name Path(img_path).stem label_path Path(label_dir) / f{img_name}.txt if not label_path.exists(): print(f[WARN] 缺少标注文件: {label_path}) continue # 复制图片和标注文件到新目录 new_img_path Path(output_dir) / images / split_name / Path(img_path).name new_label_path Path(output_dir) / labels / split_name / label_path.name # 这里用shutil.copy也可以改成move import shutil shutil.copy(img_path, new_img_path) shutil.copy(str(label_path), str(new_label_path)) # 记录图片绝对路径到txt索引 txt_lines.append(str(new_img_path.resolve())) # 写入索引文件 index_file os.path.join(output_dir, f{split_name}.txt) with open(index_file, w) as f: f.write(\n.join(txt_lines)) print(f[{split_name}] 处理图片{len(txt_lines)}张) process_split(train_images, train) process_split(val_images, val) print(f原始图片总数: {len(images)}) print(f训练集: {len(train_images)}, 验证集: {len(val_images)}) # 使用示例 split_dataset( image_dir./images, # 原始所有图片目录 label_dir./labels, # 转换后的所有txt目录 output_dir./dataset, # 输出目录 train_ratio0.8 # 训练集比例 )关于划分比例目标检测领域常用的比例是8:1:1训练:验证:测试或者9:1只分训练和验证。如果你只是做实验、不需要最终部署评估8:2就够了如果要严谨评估模型性能再加一个测试集。另外提醒一下图片数量比较少的时候比如少于200张划分比例不要太激进验证集数量太少会导致评估结果方差很大。这种情况下优先考虑用交叉验证的思路或者尽量多收集数据。6. 常见问题与避坑指南6.1 标注环节的高频失误我在多个项目里踩过、也帮别人排查过各种标注数据的问题挑几个出现频率最高的直接列成速查表问题现象可能原因解决方案训练时类别标签错乱predefined_classes.txt顺序和转换脚本CLASS_NAMES不一致两个文件比对确保顺序完全一致单张图标注特别多但训练效果差框选不够紧贴目标背景占比过大重新标注框紧贴目标边界图片打不开或崩溃图片格式特殊、尺寸过大统一转jpg最长边缩放至2000像素内漏标严重遮挡目标被跳过、小目标被忽略后期用模型预测结果辅助检查遗漏类别名输入错别字手动输入类别时拼写错误强制使用predefined_classes.txt选择最常见也最致命的是第一个问题——类别顺序不一致。这种错误在训练初期几乎不会暴露模型loss正常下降但等到验证的时候模型预测的car你一看实际是bicycle。排查这类问题非常痛苦因为要从头到尾检查数据。所以大家记住一句话category顺序定了就不要再改全流程保持唯一。6.2 转换环节的边界情况处理我在第4.2节的转换脚本里已经写了一些校验逻辑但还有一些边界情况值得再展开聊聊。一个典型场景是图片里有特别小的目标比如一张1920x1080的街景图中远处的行人只有20x40像素。转换后这个目标框的width和height归一化值大约只有0.01和0.037接近0但不是0。YOLOv7训练时对极小目标比较敏感太多这种标注可能导致训练不稳定。我的建议是如果小目标不是你这项目的重点可以设置一个阈值比如归一化后框面积小于0.001的自动跳过如果小目标恰恰是你要检测的核心那就保留但最好单独统计一下小目标的数量占比。另一个场景是标注框触碰图片边界。比如车辆的一部分在画面外你在LabelImg画框的时候只能框到画内部分这会直接导致x_center或y_center的计算结果偏向边界方向而且归一化后的坐标也不对称。这种数据不能说完全不能用但你要知道它会给模型带来一定的偏置。如果边界框占比不高小于总框数的5%保留问题不大如果占比很高建议重新采集或裁剪图片保证目标完整。6.3 训练前的最终检车清单数据准备好之后正式进入训练前建议花五分钟跑一遍下面的检查清单能帮你避免浪费几个小时的无效训练检查images和labels两个目录下train和val文件数是否一一对应。全局搜索所有txt文件确认里面没有空文件。空文件用find . -size 0 -name *.txt一眼就能扫出来。抽查5-10个txt文件把第一行的class_id拿出来确认它们的类别符合预期。确认所有图片都能正常打开没有损坏的图片。确认train.txt和val.txt里的路径存在把路径复制到文件管理器里能直接跳转到对应图片。这一套检查下来如果没问题数据的质量就基本有保障了。我当时第一次做YOLO项目因为漏了第4步的图片损坏检查训练到一半模型直接把loss跑飞了排查了一个下午才发现是3张截断的半张图片混进了数据里。这种低级错误多花五分钟就能规避。6.4 标注工具本身的小问题处理LabelImg偶尔也会闹脾气。比如有时候打开图片后画框区域不刷新显示的还是上一张图的内容。遇到这种情况不用慌这是界面渲染的缓存问题切换一下窗口或者重新打开这张图片就能解决。还有在Windows系统上LabelImg启动时偶尔会报QWindowsBackingStore: 双重缓冲不匹配之类的警告这是PyQt5和显卡驱动的老兼容性问题一般来说不影响功能可以直接忽略。如果频繁崩溃可以试着更新显卡驱动或者降低PyQt5版本。另外如果你们团队需要多人协作标注LabelImg本身是没有协同能力的需要自己维护一个共享文件夹通过文件锁或者分工命名来避免两个人同时改同一张图。我之前一个项目就是靠网盘共享目录加上每人分一段编号范围的方式协作几千张图一周内搞定效率也不低。写在最后的实操心得在这个流程里我反复走了好几轮个人最大的体会是标注工作看起来低技术含量但实际上决定了项目能走多远。YOLOv7再好用数据不对都是白搭。最后分享一个小技巧标注不要一次标完所有图片再转换格式。建议先标20张样张跑一遍完整的转换和验证流程确认没有问题再大批量标注。这个小样先行的做法能帮你提前发现类别配置、格式转换、目录结构里的各种问题而不是等标了500张图之后才被某个低级错误一锅端。数据集这块搞定之后后面可以做的扩展就很多了。比如用训练好的模型对新的未标注数据做预标注人工只需要修正效率能再提一倍。也可以试着用数据增强扩展现有数据集让小样本项目也能训出不错的效果。总之先把手上的数据流程跑顺这是所有目标检测任务最靠谱的第一步。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑