首页 / 资讯中心 / 文章详情

GC10-DET转YOLO标注避坑指南:从CSV到txt的完整转换与验证

GC10-DET转YOLO标注避坑指南:从CSV到txt的完整转换与验证 ★ FEATURED ARTICLE
简介这份资源面向工业表面缺陷检测方向的算法工程师与YOLO学习者将GC10-DET原始标注转换为YOLO可直接训练的归一化txt格式省去自行解析XML/JSON与坐标换算的繁琐环节。压缩包共2000个文件以1999个txt标签文件和1个data.yaml配置文件为主整体约916.94MBimages目录保留原始JPEG/PNG图像labels目录按同名txt逐行存放类别索引与归一化中心点、宽高data.yaml声明训练/验证路径及10类缺陷名称索引0至9依次对应scratch、dent、stain、crack、impression、color_difference、foreign_object、hole、wear、edge_burr。目录严格遵循Ultralytics YOLOv5/v8/v10官方布局训练集、验证集、测试集按7:1.5:1.5固化划分标签经人工复核与脚本交叉验证边界框紧贴缺陷轮廓文件名统一无特殊字符兼容Linux与Windows。已有30人学习可直接接入train.py启动训练适合快速复现基准实验与验证格式转换完备性。1. GC10-DET 转 YOLO为什么你的第一版 txt 标注总在训练时翻车GC10-DET 数据集转换成 YOLO 可用的 txt 格式数据这件事听起来只是「读个 CSV、写个 txt」但我见过太多人卡在第一步训练脚本不报错loss 却死活不降mAP 长期趴在 0.001 附近。问题往往不在模型而在转换环节——坐标归一化基准搞错、类别索引从 0 还是从 1 开始没对齐、图像尺寸写死成 640 而原图是 2048 宽。GC10-DET 是钢材表面缺陷检测数据集包含 10 类缺陷标注以「图像名 缺陷类别 边界框左上角与右下角坐标」的形式给出而 YOLO 要的是「类别索引 归一化中心点 归一化宽高」。这两套表达之间隔着一次坐标系换算换算错了模型看到的就是一堆错位的框。这篇笔记面向已经拿到 GC10-DET、准备喂给 YOLOv5/v8 或类似框架的从业者把转换脚本、参数边界和排查路径一次讲透新手能照着跑通熟手能对照检查自己的实现。2. 先搞清 GC10-DET 的标注长什么样再决定怎么转2.1 原始标注的字段结构与坐标系GC10-DET 常见的标注文件是一个 CSV 或类 CSV 的文本每行对应一个缺陷框。典型字段顺序是图像文件名、缺陷类别名、左上角 x、左上角 y、右下角 x、右下角 y。这里有两个容易忽略的点。第一缺陷类别名是中文或英文缩写混用不同来源的副本可能不一致必须先做一次类别名归一化。第二坐标是像素绝对值且原点在图像左上角x 向右、y 向下这和 YOLO 的归一化中心点格式方向一致但基准不同。YOLO 要求的是相对于图像宽高的比例值范围 0 到 1。所以转换的核心公式是中心点 x (x1 x2) / 2 / 图像宽中心点 y (y1 y2) / 2 / 图像高宽 (x2 - x1) / 图像宽高 (y2 - y1) / 图像高。公式本身不复杂复杂的是图像宽高从哪里来、类别索引怎么定、以及边界框越界怎么处理。2.2 类别索引映射从 0 开始还是从 1 开始YOLO 系列默认类别索引从 0 开始且训练时的类别数必须和 data.yaml 里的 nc 一致。GC10-DET 的 10 类缺陷如果你在转换时按字母序或出现顺序编号而 data.yaml 里写的是另一套顺序模型学到的就是错位的类别。我一般会先固定一个类别列表写死在转换脚本里再让 data.yaml 引用同一个列表。常见做法是建一个 classes.txt每行一个类别名转换脚本读这个文件生成索引data.yaml 的 names 字段也从这个文件生成保证两边同源。这一步不做后面调参全是玄学。2.3 图像尺寸必须逐张读取不能写死很多人图省事直接把归一化分母写成 640 或 2048。GC10-DET 的图像尺寸并不完全统一不同子集可能有差异。写死分母的后果是对于非该尺寸的图像框会整体偏移或缩放错误训练时表现为某些图上的框明显偏离缺陷区域。正确做法是用 PIL 或 OpenCV 逐张读取图像宽高再代入公式。读取宽高有开销但转换是一次性工作这点开销换来的正确性完全值得。如果图像数量很大可以先把宽高缓存成字典避免重复 IO。3. 写一个可复现的转换脚本从 CSV 到 YOLO txt3.1 目录约定与输入输出结构在动手写代码前先把目录定死后面排查时心里有数。我一般用这样的结构数据集根目录下放 images 和 labels 两个文件夹images 下按 train/val 分labels 下同样按 train/val 分每个 txt 文件名和对应图像文件名一致只是扩展名不同。原始标注 CSV 和 classes.txt 放在根目录。这样 YOLO 训练时只需要在 data.yaml 里写 train: images/train、val: images/val框架会自动去找同名的 labels 文件。目录约定不是强制标准但统一之后转换脚本、训练脚本、验证脚本三者的路径逻辑一致少很多「找不到文件」的破事。3.2 转换脚本主体与关键注释import os import csv from PIL import Image # 类别列表顺序即索引必须与 data.yaml 的 names 完全一致 CLASSES [crazing, inclusion, patches, pitted_surface, rolled-in_scale, scratches, crease, crescent_gap, oil_spot, water_spot] CLASS_TO_IDX {name: i for i, name in enumerate(CLASSES)} # 输入原始标注 CSV输出YOLO txt 目录 CSV_PATH raw_annotations.csv IMG_ROOT images/train # 图像所在目录用于读取宽高 OUT_LABEL_DIR labels/train # txt 输出目录 os.makedirs(OUT_LABEL_DIR, exist_okTrue) # 按图像名聚合一张图可能对应多个缺陷框 from collections import defaultdict boxes_by_image defaultdict(list) with open(CSV_PATH, r, encodingutf-8) as f: reader csv.reader(f) header next(reader, None) # 跳过表头若你的 CSV 无表头则删掉这行 for row in reader: if len(row) 6: continue img_name, cls_name, x1, y1, x2, y2 row[:6] cls_name cls_name.strip() if cls_name not in CLASS_TO_IDX: # 类别名不在预设列表跳过并打印便于发现命名不一致 print(f[WARN] unknown class: {cls_name} in {img_name}) continue boxes_by_image[img_name].append( (CLASS_TO_IDX[cls_name], float(x1), float(y1), float(x2), float(y2)) ) for img_name, boxes in boxes_by_image.items(): img_path os.path.join(IMG_ROOT, img_name) if not os.path.exists(img_path): print(f[WARN] image not found: {img_path}) continue with Image.open(img_path) as im: w, h im.size # 逐张读取不写死 lines [] for cls_idx, x1, y1, x2, y2 in boxes: # 边界裁剪防止标注越界导致归一化后超出 [0,1] x1 max(0.0, min(x1, w)) x2 max(0.0, min(x2, w)) y1 max(0.0, min(y1, h)) y2 max(0.0, min(y2, h)) if x2 x1 or y2 y1: print(f[WARN] invalid box in {img_name}: {x1},{y1},{x2},{y2}) continue cx (x1 x2) / 2.0 / w cy (y1 y2) / 2.0 / h bw (x2 - x1) / w bh (y2 - y1) / h lines.append(f{cls_idx} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) out_name os.path.splitext(img_name)[0] .txt with open(os.path.join(OUT_LABEL_DIR, out_name), w) as f: f.write(\n.join(lines))这段脚本的逻辑分四步读 CSV 并跳过表头、按图像名聚合框、逐张读图宽高并做边界裁剪、归一化后写出 txt。参数上CLASSES 的顺序就是最终类别索引必须和 data.yaml 的 names 逐字一致CSV_PATH 和 IMG_ROOT 按你的实际目录改边界裁剪那两行是后悔药GC10-DET 里偶尔有标注框略微超出图像边界不裁剪的话归一化值可能大于 1某些训练框架会直接报错或静默丢弃。保留六位小数足够YOLO 读取时按浮点解析精度损失可忽略。3.3 生成 data.yaml 与类别文件# 生成 classes.txt 和 data.yaml保证类别顺序同源 with open(classes.txt, w) as f: f.write(\n.join(CLASSES)) yaml_content ftrain: images/train val: images/val nc: {len(CLASSES)} names: {CLASSES} with open(data.yaml, w) as f: f.write(yaml_content)这里 nc 直接由 len(CLASSES) 算出names 直接引用同一个列表避免手写数字和手写类别名带来的不一致。data.yaml 的路径写法因框架版本略有差异YOLOv5 通常用相对路径YOLOv8 也兼容如果训练时报找不到数据集优先检查这个文件里的路径是相对谁而言的。4. 转换结果怎么验证三个不依赖训练的检查手段4.1 用可视化脚本把框画回原图转换完不要急着开训先抽几张图把 txt 里的框画回原图看。做法是读图像、读同名 txt、把归一化值乘回宽高、用矩形画出来。如果框和缺陷区域对得上说明坐标换算正确如果整体偏移多半是宽高读错或中心点公式写反如果框大小对但位置镜像检查 y 轴方向。这个检查花不了几分钟但能拦住大部分低级错误。我一般随机抽 10 张覆盖不同类别和不同尺寸的图像。4.2 统计每类框数量和归一化值分布写个小脚本统计所有 txt 里每个类别的框数以及 cx、cy、bw、bh 的最小值和最大值。正常情况 cx、cy 应在 0 到 1 之间bw、bh 应大于 0 且通常小于 1。如果某个类别的框数为 0说明该类别的类别名没匹配上回去检查 CSV 里的写法。如果 bw 或 bh 出现大于 1 的值说明边界裁剪没生效或图像宽高读错。这个统计还能帮你发现类别不平衡为后续训练时的采样策略提供依据。4.3 用框架自带的校验命令跑一遍YOLOv5 和 YOLOv8 在训练启动时会扫描 labels 目录如果 txt 格式不对会打印警告或直接报错。可以先用一个极小的 epoch 数跑一下看它能不能正常加载。常见报错包括「non-normalized coordinates」「class index out of range」「label file missing」。这些报错信息比你自己猜要直接得多。注意这一步只是验证格式不是验证精度跑一两个 epoch 看 loss 有没有正常下降趋势即可不要指望这时候就有高 mAP。5. 避坑与排查转换 GC10-DET 时最容易踩的五个坑5.1 现象训练 loss 不降mAP 接近 0原因类别索引和 data.yaml 的 names 顺序不一致模型学的是错位标签。解决用同一份 CLASSES 列表生成 txt 和 data.yaml转换后统计每类框数确认没有类别框数为 0。5.2 现象部分图像上的框明显偏移原因归一化分母写死成固定尺寸而该图像实际尺寸不同。解决逐张读取图像宽高不要用常量。如果图像数量大先缓存宽高字典。5.3 现象训练框架报坐标越界或直接丢弃某些框原因原始标注框超出图像边界归一化后值大于 1 或小于 0。解决在转换时做边界裁剪并打印被裁剪的框确认不是大面积越界。5.4 现象某些类别的框全部消失原因CSV 里类别名有空格、大小写不一致或中英文混用没匹配上 CLASS_TO_IDX。解决转换前先对类别名做 strip 和统一大小写打印所有未匹配的类别名人工核对。5.5 现象txt 文件生成了但训练时提示找不到 label原因图像和 txt 的目录结构不符合框架预期或文件名不一致比如图像是 .jpg 而 txt 是 .JPG.txt。解决统一用 os.path.splitext 取主文件名确保 txt 和图像同名同目录层级检查 data.yaml 里的路径。6. 进阶技巧把转换做成可复用流水线转换脚本写一次容易但数据集可能有多个子集、多个版本每次手动改路径很烦。我一般会把上面的逻辑封装成一个函数参数化 CSV 路径、图像根目录、输出目录和类别列表再用一个配置文件驱动。这样换数据集时只改配置不动代码。另一个技巧是加一个 dry-run 模式只统计不写文件先看类别分布和框数是否合理确认无误再真正写出。对于需要划分 train/val 的场景可以在转换前先按图像名做划分再分别转换到对应目录避免转换后再搬文件导致路径混乱。最后把转换脚本和 data.yaml 生成脚本放在同一个仓库里版本对齐下次复现时直接跑不用回忆当时改了哪个参数。我自己就吃过亏半年前转好的数据半年后想复现发现当时类别顺序是拍脑袋定的data.yaml 又丢了只能重新对一遍。从那以后类别列表和转换脚本必须一起进版本管理这是血泪经验。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站