2026/10/20 9:49:56

YOLOv7数据标注全流程:从LabelImg手工标注到TXT格式转换与自检

YOLOv7数据标注全流程:从LabelImg手工标注到TXT格式转换与自检 做目标检测项目最容易被低估的永远是数据标注。我见过不少刚开始跑YOLOv7的朋友兴致勃勃把网络结构、训练参数研究了个透结果在标注环节草草了事训练完一看mAP怎么都上不去回头挨个查标注文件才发现满屏错框、漏框、类别错位。坦白讲YOLOv7的训练流程已经成熟得接近开箱即用真正决定项目能不能落地的反而是最不起眼的数据标注。这篇文章就把YOLOv7数据集标注这条线完整走一遍用LabelImg做手工标注、把常见的VOC格式XML转成YOLO需要的TXT、按目录规范整理并划分训练验证集最后附上一套标注自检方法。不管你是第一次碰目标检测还是只差最后一步就能开训的老手照着走基本不会翻车。1. 为什么说标注质量直接决定YOLOv7训练上限1.1 训练数据在目标检测里的真实分量先泼一盆冷水模型再好喂进去的标签是错的它会一本正经地把错误学成标准答案。目标检测的损失函数核心就是拿预测框和标注框做对比标注框错了模型收到的监督信号就是错的。训练初期模型还在适应阶段偶尔几个错框还能被其他正确样本稀释掉一旦错框比例超过10%模型的收敛方向就会被带偏表现是训练集loss明明在降验证集mAP却纹丝不动。我帮A同学调过一个数据集他怀疑是不是网络结构有问题我把他的标签文件拉出来一看有一个类别的id在标注中途被改乱了之前存好的txt文件全部对不上号那一类检测精度直接掉了十几个点。改完重新训练什么都没动指标立刻回来了。这种案例我见过不止一次问题从来不在模型在数据。另外还有一个容易被忽略的点类别样本不均衡。如果你的数据集里A类有5000个框B类只有30个框模型大概率会把B类学成A类的背景噪声这类问题靠调参数很难救只能在标注阶段尽量补足和平衡。所以不要觉得标注就是画框它直接决定了你在训练阶段能走多远。1.2 YOLOv7对数据集的基本要求YOLOv7官方仓库支持两种数据输入方式但对新手来说只需要先搞清楚一种一个图片对应一个同名的txt文件txt的每一行是一个标注框。行内字段固定为5个类别id 归一化后的中心点x 归一化后的中心点y 归一化后的框宽 归一化后的框高所有坐标值都除以了图片宽高范围在0到1之间所以叫归一化坐标。这是YOLO系列最常见的标签格式后面章节会重点讲换算。除了标签文件还要准备一个data.yaml配置告诉训练脚本图片路径在哪、验证集路径在哪、类别数是多少、类别名是什么。这三个东西图片、txt标签、yaml配置凑齐了就可以启动训练。有一点要提前说清楚YOLOv7在读取训练图片时会按约定好的规律自动去找对应的标签文件默认规则是把图片路径中的images替换成labels。也就是说你图片放在 dataset/images/train 下标签就必须放在 dataset/labels/train 下目录名不能随意改。这个细节我在第五节会详细演示现在只要心里有个数就行。2. 环境准备与LabelImg安装实录2.1 为什么到现在还推荐LabelImg市面上标注工具不少有网页版的、有要部署服务器的、有号称AI辅助的但我给新手推荐时首选仍然是LabelImg。原因很简单轻量、免费、开箱即用而且原生支持同时导出PascalVOC格式的XML和YOLO格式的TXT不用装一堆依赖双击就能干活。LabelMe是另一个常见工具但它主打多边形标注面向的是分割任务用来做检测框反而绕弯。CVAT功能强大适合团队协作但要起服务、配环境对一个人处理几百张图来说太笨重。云端平台也有不少好用的但数据要上传我认识的很多朋友对数据隐私还是有所顾虑尤其涉及工业场景图片时。LabelImg虽然官方更新频率不高但它的功能已经足够稳定一款标注工具的核心需求它全都覆盖了没必要追新。2.2 Windows/Linux安装踩坑记录Windows上最省事的方式是直接下载官方发布的免安装exe版双击就打开了连Python环境都不用配。你要是愿意用命令行装那就走pip路线但这一步常常踩坑直接执行pip install labelImg运行时报错找不到Qt相关模块这是因为新版Python环境里没有PyQt5。解决办法是先装PyQt5再装LabelImgpip install pyqt5 pip install labelImgLinux下以Ubuntu/Debian为例推荐先装系统级的Qt依赖再pip安装sudo apt install pyqt5-dev-tools python3-pyqt5 pip3 install labelImg如果装完还是启动不了多半是系统里同时存在多个Python版本路径混乱。这时候别硬调直接创建一个干净的虚拟环境最稳python3 -m venv label_env source label_env/bin/activate pip install pyqt5 labelImg labelImg个人建议不管哪个系统都尽量用Python 3.8到3.10之间的版本太新的Python偶尔会和PyQt5出现兼容问题太旧的则可能装不上依赖。如果你只是偶尔标注一次就直接用exe版或系统安装包别折腾环境把时间留给画框。2.3 首次启动与界面功能拆解打开LabelImg后界面比想象中朴素左边竖排工具按钮中间是图片显示区右边是标注列表。先记住几个最常用的按钮位置Open打开单张图、Open Dir打开整个文件夹、Change Save Dir设置标签保存目录。中间显示区下面还有一个PascalVOC/YOLO切换按钮这个按钮决定你保存时生成XML还是TXT新手最容易在这里犯迷糊。快捷键是我最看重的部分标注效率全靠它W在当前图片上画一个矩形框A / D切换上一张 / 下一张图片Ctrl S保存当前标注Ctrl Shift S另存为Delete删除选中的框Ctrl Z撤销上一个框画框的操作逻辑是这样按W后鼠标在图上拖出一个矩形松开就弹一个类别选择框你可以从下拉列表里选已有类别也可以直接输入新类别。选完类别后这个框会出现在右侧的Box Labels列表里。然后再按CtrlS保存的瞬间这个框就被写入标签文件了。开始标注之前一定要先在数据目录下建好一个classes.txt之类的类别清单。更实际的做法是标注完第一张图、把所有类别都加齐之后再逐张往下走。如果你中途频繁新增类别尤其是用YOLO模式直接存TXT时类别id是按顺序排的中途插入新类别会让老文件全部错位这是新手翻车率最高的问题后面我会专门说。3. 从零开始完成一张图的标注全过程3.1 标注前的图像整理与命名规范很多人拿到图片就往LabelImg里一拖这是不对的。开工之前先用十分钟把图像目录整理好能省后面两个小时的返工时间。第一图片命名必须干净。不要用中文名不要含空格不要包含特殊符号。原因很现实很多框架和OpenCV读取中文路径偶尔会出问题报错时你根本分不清是路径的锅还是标签的锅。规范的命名类似 image_0001.jpg 这种建议统一用补零的数字这样文件排序也稳定。第二把模糊的、严重过曝的、明显拍坏的图片先筛掉。这类图模型学不出有效特征标注时你还得硬着头皮画框纯属浪费人力。第三分类别统计一下图片里目标的大致分布。比如你手头900张图里800张只有A类B类只出现在20张里趁还没开始标注先去补充B类图片好过标完再补。第四也是最容易被忽略的写一页标注规则。规则里写清楚每个类别到底包括哪些东西比如安全帽是否包含布帽、车辆是否包含自行车、行人被遮挡一半时要不要标。这不是形式主义是人脑在标注300张图之后一定会开始凭感觉标规则是让你对抗疲劳和主观漂移的。3.2 实际操作框选、类别、保存整理好图片后按这个顺序操作点Open Dir选择图片文件夹点Change Save Dir选择标签保存文件夹先想好这次用XML还是TXT点界面上那个格式按钮切到对应模式确定后就不要中途切换按W在目标物体上拖出矩形框选择或输入类别名按CtrlS保存当前图片的标注按D跳到下一张图重复以上操作。每一步看起来都很简单但有个细节我要专门强调每一次画完框都要养成随手保存的习惯。LabelImg里有自动保存选项可以打开但我实测过每次按完CtrlS再切图的肌肉记忆比什么都可靠。标注是高度重复的机械劳动中间被打断一次回来就忘了上一张存没存这种低级事故我见过太多次。3.3 标注技巧边界框该贴多紧这是标注里争议最多的问题框到底应该画多紧。我的原则很简单——框要贴合目标的主轮廓不含背景、不切进物体。说得具体一点。标一个站立的人框的上下左右边界应该贴着人的外沿头顶上方留一两个像素可以接受但绝不能把大片天空和地面圈进来。框太松会引入大量背景信息模型学到的特征就脏了框太紧把人的边缘裁掉一部分模型又学不到完整的轮廓信息。理想状态是比物体外沿略微紧一点但不能切到主体。遇到遮挡目标标注可见部分。两个物体叠在一起就分别标两个框每个框贴着各自可见的轮廓。如果目标在图片边缘被裁掉一半以上我通常建议不标因为这种样本的框信息严重不完整模型学到的是残缺形状对推理没帮助但如果只是裁掉一小块主体还在画面里那就按可见范围把框画完整。还有一个很多人纠结的问题小目标要不要标。我的回答是必须标而且越小的目标越要标准。模型漏检小目标最直接的原因就是训练时小目标的框数量不足、位置不准。一个小到只有几个像素的框哪怕偏了半个框交并比就掉到谷底梯度更新直接被当成负样本处理了。3.4 标注中途最容易犯的三个错标注这件事错法高度雷同。我自己用LabelImg标过上万张图也帮朋友排查过各种奇奇怪怪的数据问题最常撞见的是下面这三种第一个错中途改类别顺序。YOLO模式下txt只记录类别id不记录类别名id完全按你在标注工具里添加类别的顺序决定。今天标了猫狗明天觉得顺序不好调整成狗猫那之前存的所有txt全部作废训练时模型学到的类别名和实际内容完全错位。对策只有一个开工前把整个数据集的类别清单一次定死整个标注周期内不许变。第二个错不换保存目录就开始画框。LabelImg默认保存目录可能指向上一次项目的路径结果你标了一下午标签全写到了另一个文件夹里图片这边看着每张都有框其实一个txt都没生成。每次打开软件第一件事就是确认右上角的保存路径。第三个错图片和标签文件数量对不上还硬训。标完一批图有些人直接复制粘贴到训练目录就跑了结果有几张图漏了txt有几张txt是0字节空文件。YOLOv7训练时遇到有图无标签会直接报错或者干脆跳过这张图数据量一少模型就学不到位。这个问题的解法就是我第六节要说的自检流程训练前必须跑一遍。4. XML到YOLO格式转换手写脚本比工具更靠谱4.1 VOC格式和YOLO格式到底差在哪很多现成的公开数据集比如部分开源检测数据集下载下来是PascalVOC格式每张图片对应一个XML文件里面用xmin、ymin、xmax、ymax这种像素坐标描述框的位置。而YOLOv7只认TXT格式的归一化坐标所以格式转换是绕不开的一步。这两种格式的核心差异在于坐标表达方式项目VOCXMLYOLOTXT坐标类型像素坐标绝对值归一化坐标相对值框表达xmin、ymin、xmax、ymax中心点x、中心点y、宽、高每条记录一个object标签节点一行5个数字类别表达name字符串类别id数字为什么要用归一化坐标因为同一张图训练时会resize到固定尺寸比如640x640像素坐标在缩放后必须跟着改而归一化坐标本身是比例不管图片缩到多大0.5仍然是中心点标签完全不用动。这就是YOLO系列选择归一化坐标的根本原因。4.2 转换脚本逐行拆解转换工具网上有很多但我一向建议自己写脚本。原因很实在现成工具通常绑定了固定的XML结构你拿到的数据集如果字段略有不同工具直接罢工自己写的脚本出了问题你能看懂、能改。下面是我常用的转换脚本逻辑非常直白import os import xml.etree.ElementTree as ET def voc_xml_to_yolo_txt(xml_path, txt_dir, class_list): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_list: continue class_id class_list.index(name) bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h box_w (xmax - xmin) / img_w box_h (ymax - ymin) / img_h lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) txt_name os.path.splitext(os.path.basename(xml_path))[0] .txt with open(os.path.join(txt_dir, txt_name), w) as f: f.write(\n.join(lines))使用方式也很简单class_list就是你在标注阶段定死的类别清单顺序绝对不能变因为转换后写入txt的就是这个顺序对应的id。调用时遍历整个XML文件夹为每个XML生成一个同名的TXT即可。这里我用了Python标准库的xml.etree.ElementTree零额外依赖任何环境都能跑。4.3 归一化坐标换算的细节与边界情况换算公式本身不复杂但有几个细节值得单独讲。第一x_center和y_center是取xmin和xmax的中点之后再除以图宽不是直接把中点当作比例就完事必须保证除以的是真实的图片宽高而这个宽高来自XML里的size节点。假如XML里记录的尺寸和实际图片不一致——比如图片之前被resize过但XML没更新——转换出的所有坐标都会整体偏移整个数据集基本报废。第二保留6位小数就足够了。归一化坐标的精度在6位小数下已经远超实际需求更多的小数位只会让文件体积变大没有任何收益。第三如果转换时发现某个框的xmin大于xmax说明XML本身是坏的这种文件要单独拎出来修而不是硬转换。另外还要警惕宽或高计算出来是0的情况这种框在训练时会导致损失计算异常我在第六节的自检部分会教你一条命令把它们揪出来。第四也是最容易忽略的转换脚本运行前先确认你的XML里没有未标注但存在的空object节点有些工具会在XML里留下空的object标签这会让转换脚本报错或生成垃圾行。脚本里加一个判断只有bndbox四个值都大于0才写入能挡掉不少脏数据。5. 按YOLOv7目录规范组织数据集并划分训练集5.1 YOLOv7的数据目录结构格式转换完成之后下一步是把散落的图片和TXT按YOLOv7的约定组织起来。我推荐的结构长这样dataset/ ├── images/ │ ├── train/ │ │ ├── image_0001.jpg │ │ └── ... │ └── val/ │ ├── image_0501.jpg │ └── ... └── labels/ ├── train/ │ ├── image_0001.txt │ └── ... └── val/ ├── image_0501.txt └── ...前面说过YOLOv7会自动把图片路径中的images替换为labels去查找标签所以images和labels必须是同级目录下的兄弟关系。train和val两边都要成对出现千万不能出现训练集有标签、验证集没标签这种不对称否则验证阶段直接报错。很多从VOC数据集入手的同学习惯用train.txt和val.txt这两个文本文件里面一行一个图片路径。YOLOv7也支持这种写法data.yaml里直接指向这个txt文件就行。但我的建议是如果不是特别原因直接采用images/labels兄弟目录结构这是最直观、最不容易出错、也最好迁移到其他YOLO系列版本的方式。5.2 训练集/验证集划分脚本划分训练集和验证集别手动拖文件一定要用脚本。手动划分最大的问题是图片和TXT容易落进不同的目录一旦错位训练出来的模型指标毫无意义。下面这个脚本用随机种子固定随机顺序保证可复现import os import random import shutil src_img all_images src_txt all_labels dst_root dataset train_ratio 0.9 seed 42 random.seed(seed) images [f for f in os.listdir(src_img) if f.lower().endswith((.jpg, .jpeg, .png))] random.shuffle(images) split_idx int(len(images) * train_ratio) train_images images[:split_idx] val_images images[split_idx:] for split_name, img_list in [(train, train_images), (val, val_images)]: img_dst os.path.join(dst_root, images, split_name) txt_dst os.path.join(dst_root, labels, split_name) os.makedirs(img_dst, exist_okTrue) os.makedirs(txt_dst, exist_okTrue) for img_name in img_list: stem os.path.splitext(img_name)[0] shutil.copy(os.path.join(src_img, img_name), img_dst) txt_name stem .txt txt_src_path os.path.join(src_txt, txt_name) if os.path.exists(txt_src_path): shutil.copy(txt_src_path, txt_dst) else: print(f警告: 图片 {img_name} 缺少标签文件 {txt_name})这段脚本最值得注意的地方是它以图片文件名为基准来切分然后去对应目录里找同名TXT而不是分别随机切图片和标签。这样从机制上杜绝了图文错位的问题。我习惯把train_ratio设成9:1数据量大时可以调到95:5数据量小的时候至少也要留出5%做验证否则验证集太薄mAP的波动会让你完全无法判断模型好坏。5.3 data.yaml配置文件的必填字段目录就绪后写一份data.yaml。这是训练脚本唯一会读取的数据配置内容很简单train: dataset/images/train val: dataset/images/val nc: 3 names: [cat, dog, bird]train和val的路径相对于你执行train.py时所在的目录来写。如果你把YOLOv7仓库和dataset目录放在同一个项目根目录下那上面这样写就是对的。nc是类别数量names是类别名字列表它有一个硬性要求names的排列顺序必须和TXT里的类别id顺序完全一致。TXT里id为0的框对应names[0]id为1的对应names[1]以此类推。一旦这里错位模型训练出来会把猫识别成狗而且你很难发现因为loss一切正常。还有个容易踩的细节names里不要有重复项不要有中文不要有空格。这些都会在后续的绘图、评估环节引发莫名其妙的报错。写好yaml之后启动训练的命令一般是这样的python train.py --data data.yaml --weights yolov7.pt --img 640 --batch 8第一次训练不要贪大参数先把流程跑通再说。6. 标注质量自检与常见坑位排查6.1 五步自检法就算标注过程再小心也挡不住人脑的疲劳所以训练之前自检环节必须走一遍。我总结了一套五步自检法每一步都可以用脚本快速执行成本极低但能把训练阶段的返工几率降一大截。第一步图文一一对应检查。统计images目录和labels目录下的文件名找出有图无标签和有标签无图的文件打印出来逐一处理。第二步标签内容格式检查。每个TXT的每一行必须是5个数字且后4个数值都在0到1之间。超过1或者小于0说明坐标有问题行内字段数不对说明文件损坏。第三步可视化抽检。这是最直观也最有效的一步把TXT坐标画回图片上人眼扫一遍就什么都清楚了。下面的脚本可以快速实现import cv2 import os img_dir dataset/images/val txt_dir dataset/labels/val names [cat, dog, bird] for img_name in os.listdir(img_dir): stem os.path.splitext(img_name)[0] txt_path os.path.join(txt_dir, stem .txt) if not os.path.exists(txt_path): continue img cv2.imread(os.path.join(img_dir, img_name)) h, w img.shape[:2] with open(txt_path) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue cid, x, y, bw, bh map(float, parts) x1 int((x - bw / 2) * w) y1 int((y - bh / 2) * h) x2 int((x bw / 2) * w) y2 int((y bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.putText(img, names[int(cid)], (x1, max(0, y1 - 8)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) cv2.imshow(check, img) if cv2.waitKey(0) 0xFF ord(q): break cv2.destroyAllWindows()跑完这个脚本按任意键翻下一张。重点看框有没有明显错位、类别文字和框内物体是否一致、有没有漏标了大目标。第四步分类别数量统计。写一个小脚本统计每个类别的框总数和包含该类别的图片数。如果某个类别的数量少到离谱就要考虑补数据而不是硬训。第五步训练初期的健康度检查。训练启动后看前几十个epoch的loss曲线和验证集mAP。如果loss在快速下降但mAP纹丝不动大概率是标注里有系统性错误这时候别继续挂着训练了回头查数据。6.2 常见报错与对应处理训练时遇到的报错有很大一部分根源在数据和标注上。下面列几个我实际遭遇过的高频问题报错或现象常见原因处理方式No labels found 或 cannot load labels路径结构不符合images/labels约定检查目录结构确认labels与images同级index out of range / class id超范围TXT里的类别id大于data.yaml的nc-1检查TXT第一列数值修正id或同步更新names中文路径报错图片或标签路径含中文全部改为英文路径训练loss初期就出现NaN标签中有0尺寸框或坐标越界跑一遍格式自检脚本删掉异常行单个类别mAP始终为0该类别标签id错位或样本过少可视化抽检确认标签与物体是否匹配这些报错有个共同点报错信息往往不直接指向标签问题而是伪装成路径错误、显存不足、模型不收敛。所以我的经验是遇到诡异问题先别调模型和参数花十分钟查一遍标签再做决定。6.3 标注外包与半自动标注的现实建议如果你的数据集规模上千甚至上万张纯手工标注确实不现实这时候有两个现实方案。一个是半自动标注。先手动标几百张相对有代表性的图训练出一个初步模型再用这个模型的预测结果给剩余图片打上初始框最后人工在预测框基础上微调和删除。这个方法能省下至少一半标注时间但前提是初步模型的质量不能太差。如果你是从零开始且图片场景复杂直接上这个方案反而可能越标越乱不如老老实实全手工。另一个是找外包标注团队。这里我的建议非常直接外包之前把你的标注规则文档发过去明确要求对方交付VOC格式的XML还是YOLO格式的TXT同时让他们先试标2到3张图你检查无误后再放量。验收时走我上面说的五步自检法尤其是可视化抽检不要只看文件是否齐全就付款。我见过有人贪便宜把标注外包出去结果一张图上标了重复框、类别张冠李戴返工成本比亲自标注还高。这套流程我前后跑过不下十次最深的体会是工具从来不是瓶颈规范和自检才是。真正让我在项目里少加班的不是更快的标注软件而是开工前写清楚的那页标注规则和训练前那半个小时的检查脚本。建议你第一次做的时候不要急着标几千张先标三五十张用可视化脚本把框画出来仔细看看确认规则没有歧义再多画。这个习惯比任何标注技巧都值钱至少我后来每次项目都靠它躲过了最耗时的返工。