ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

钢筋检测数据集实战:从VOC xml到YOLO txt的完整转换与训练指南

钢筋检测数据集实战:从VOC xml到YOLO txt的完整转换与训练指南 简介这份资源是面向建筑行业智能化检测与计算机视觉学习者的YOLO钢筋检测数据集适合从事目标检测训练、施工安全评估或相关课程实践的中高级开发者使用。压缩包共751个文件包含250张jpg图像、250个xml标注和251个txt标签整体约359.58MBxml遵循PASCAL VOC风格记录钢筋类别与边界框txt则以简洁坐标形式便于转换为YOLO训练格式两类标签可互为补充。已有997人学习下载说明该数据集在钢筋检测方向具有一定参考价值。读者可据此完成从数据解压、标签对齐、格式转换到训练集与验证集划分的完整流程并用于调整学习率、批大小等超参数训练可实时检测图像或视频中钢筋的模型从而减少人工检查工作量为建筑结构安全评估提供数据支撑。1. 钢筋检测数据集落地从 10 张样图到 YOLO 可训练格式的完整链路工地上做钢筋验收最耗人的环节不是绑扎是数根数和量间距。一个标准层几百个节点靠人眼一根根核对漏检和误判几乎是必然。这份dataset_reinforcing.rar就是冲着这个场景来的——它提供了一批已经标注好的钢筋图像标签同时给了 txt 和 xml 两套格式前者是 YOLO 训练直接能吃的归一化坐标后者是 PASCAL VOC 风格的完整元数据。压缩包里能看到97652B3F.jpg、EB8ECF1A.jpg、35346247.jpg这类命名说明图像来源比较杂不是同一个采集批次这对模型的泛化能力反而是好事。适合谁用如果你正在做建筑结构自动化巡检、钢筋间距合规检查或者单纯想拿一个真实工业场景的数据集跑通 YOLO 训练全流程这份资源能省掉从零标注的时间。但要注意它给的是原始标注不是开箱即用的训练集中间还有格式转换、划分、校验几步必须自己走。2. 拆开压缩包先看什么txt 与 xml 双标签的选型逻辑2.1 两种标签格式的本质差异拿到数据集第一件事不是急着转格式而是搞清楚手里这两套标签各自代表什么。txt 标签通常是 YOLO 格式每行一个目标结构是class_id x_center y_center width height所有坐标都归一化到 0 到 1 之间。xml 标签走的是 PASCAL VOC 标准用bndbox包住xmin ymin xmax ymax四个绝对像素坐标外面还有size记录原图宽高、name记录类别名。项目正文里给的例子很典型一张 1920×1080 的图钢筋框在 (200,300) 到 (400,500)xml 里写的是绝对坐标txt 里则要换算成中心点加宽高的归一化值。为什么同时给两套我的判断是xml 更接近标注工具的原生输出LabelImg 默认存的就是 VOC 格式里面保留了truncated、difficult这些字段方便回溯标注质量。txt 则是为了直接喂给 YOLO 训练脚本省去每次训练前现转的麻烦。但这里有个坑如果两套标签不是同一次标注生成的可能存在框位置不一致的情况。我一般会先抽几张图把 txt 反算回绝对坐标和 xml 里的bndbox对一遍确认一致性再往下走。2.2 用脚本做一次标签一致性抽检抽检不用全量跑随机抽 5 到 10 张就够暴露问题。下面这段脚本同时读 txt 和 xml把 txt 的归一化坐标还原成像素值和 xml 的bndbox做差值比对。import os import random import xml.etree.ElementTree as ET def parse_xml(xml_path): tree ET.parse(xml_path) root tree.getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) boxes [] for obj in root.findall(object): bnd obj.find(bndbox) xmin float(bnd.find(xmin).text) ymin float(bnd.find(ymin).text) xmax float(bnd.find(xmax).text) ymax float(bnd.find(ymax).text) boxes.append((xmin, ymin, xmax, ymax)) return w, h, boxes def parse_txt(txt_path, img_w, img_h): boxes [] with open(txt_path, r) as f: for line in f: parts line.strip().split() if len(parts) 5: continue cls_id, xc, yc, bw, bh map(float, parts[:5]) # 归一化坐标还原为绝对像素 xmin (xc - bw / 2) * img_w ymin (yc - bh / 2) * img_h xmax (xc bw / 2) * img_w ymax (yc bh / 2) * img_h boxes.append((xmin, ymin, xmax, ymax)) return boxes # 抽检入口 img_dir ./images xml_dir ./annotations_xml txt_dir ./labels_txt samples random.sample(os.listdir(img_dir), 5) for img_name in samples: stem os.path.splitext(img_name)[0] xml_path os.path.join(xml_dir, stem .xml) txt_path os.path.join(txt_dir, stem .txt) if not (os.path.exists(xml_path) and os.path.exists(txt_path)): print(f{stem}: 标签缺失) continue w, h, xml_boxes parse_xml(xml_path) txt_boxes parse_txt(txt_path, w, h) print(f{stem}: xml {len(xml_boxes)} 框, txt {len(txt_boxes)} 框) for i, (xb, tb) in enumerate(zip(xml_boxes, txt_boxes)): diff max(abs(xb[j] - tb[j]) for j in range(4)) if diff 2: # 允许 2 像素误差 print(f 框 {i} 偏差过大: {diff:.1f}px)这段代码的关键在parse_txt里的还原逻辑YOLO 的x_center和width都是相对整图宽度的比例乘回img_w才能和 xml 的绝对坐标比。diff 2这个阈值是我自己的习惯标注工具之间偶尔有一两个像素的舍入差异超过 2 像素基本就是标注不一致了。如果抽检发现大量偏差说明两套标签来源不同步这时候要么以 xml 为准重新生成 txt要么以 txt 为准反写 xml不能混着用。2.3 类别映射别让rebar变成0之后丢了名字xml 里name写的是rebartxt 里第一列是0。这个映射关系必须显式记下来否则训练完模型你都不知道0代表什么。常见做法是建一个classes.txt每行一个类别名行号就是 class_id。这份数据集目前看只有钢筋一类但如果你后续要加stirrup箍筋或者joint接头类别表就要提前规划好。我一般会在数据集根目录放一个data.yaml把路径和类别名都写进去训练脚本直接读这个文件省得每次改命令行参数。# data.yaml path: ./dataset_reinforcing train: images/train val: images/val nc: 1 names: [rebar]nc是类别数names的顺序必须和 txt 里的 class_id 严格对应。这份数据集只有钢筋一类nc: 1没问题但如果你后面合并了其他来源的数据记得把nc和names同步改掉否则训练时类别索引会错位模型学出来的东西完全是乱的。3. 从 xml 到 YOLO txt转换脚本与划分策略3.1 写一个可复用的 VOC 转 YOLO 脚本虽然数据集里已经带了 txt但实际项目中你拿到的往往是纯 xml或者 xml 和 txt 对不上需要重新生成。下面这个转换脚本我用了很多次处理 VOC 格式的 xml 转 YOLO txt 比较稳。import os import xml.etree.ElementTree as ET # 类别名到 id 的映射顺序要和 data.yaml 里一致 CLASS_MAP {rebar: 0} def voc_to_yolo(xml_path, txt_path): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) lines [] for obj in root.findall(object): name obj.find(name).text.strip() if name not in CLASS_MAP: continue # 跳过未定义类别 cls_id CLASS_MAP[name] bnd obj.find(bndbox) xmin float(bnd.find(xmin).text) ymin float(bnd.find(ymin).text) xmax float(bnd.find(xmax).text) ymax float(bnd.find(ymax).text) # 裁剪到图像边界内防止标注越界 xmin max(0, min(xmin, img_w)) xmax max(0, min(xmax, img_w)) ymin max(0, min(ymin, img_h)) ymax max(0, min(ymax, img_h)) # 转为中心点 宽高的归一化值 xc (xmin xmax) / 2.0 / img_w yc (ymin ymax) / 2.0 / img_h bw (xmax - xmin) / img_w bh (ymax - ymin) / img_h # 过滤掉宽高为 0 的无效框 if bw 0 or bh 0: continue lines.append(f{cls_id} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}) with open(txt_path, w) as f: f.write(\n.join(lines)) # 批量转换 xml_dir ./annotations_xml out_dir ./labels_txt os.makedirs(out_dir, exist_okTrue) for fname in os.listdir(xml_dir): if fname.endswith(.xml): stem os.path.splitext(fname)[0] voc_to_yolo(os.path.join(xml_dir, fname), os.path.join(out_dir, stem .txt))几个参数值得说清楚。CLASS_MAP是硬编码的类别映射如果你的数据集类别多建议从classes.txt动态读避免手改漏掉。坐标裁剪那一步很多人会忽略但实际标注中xmax超出图像宽度的情况并不少见不裁的话归一化值会大于 1YOLO 训练时虽然不报错但框的位置会偏。bw 0的过滤是兜底防止标注工具产生退化框。最后保留 6 位小数YOLO 官方脚本也是这个精度够用了。3.2 训练集与验证集怎么分才不翻车划分比例常见的是 8:2 或 7:3但钢筋检测有个特殊性同一根钢筋可能在多张图里出现如果随机分训练集和验证集里会有高度相似的样本验证指标虚高。我一般会先按图像来源分组比如按拍摄位置或采集批次分同一组的图要么全进训练集要么全进验证集。这份数据集的文件名是哈希风格看不出采集批次那就退而求其次按图像整体亮度或纹理做聚类把相似图分到同一侧。import os import shutil import random img_dir ./images txt_dir ./labels_txt out_root ./dataset_reinforcing # 创建 YOLO 标准目录结构 for split in [train, val]: os.makedirs(os.path.join(out_root, images, split), exist_okTrue) os.makedirs(os.path.join(out_root, labels, split), exist_okTrue) imgs [f for f in os.listdir(img_dir) if f.endswith(.jpg)] random.seed(42) # 固定随机种子保证可复现 random.shuffle(imgs) split_idx int(len(imgs) * 0.8) train_imgs imgs[:split_idx] val_imgs imgs[split_idx:] for split, files in [(train, train_imgs), (val, val_imgs)]: for img_name in files: stem os.path.splitext(img_name)[0] # 复制图像 shutil.copy(os.path.join(img_dir, img_name), os.path.join(out_root, images, split, img_name)) # 复制标签 txt_src os.path.join(txt_dir, stem .txt) if os.path.exists(txt_src): shutil.copy(txt_src, os.path.join(out_root, labels, split, stem .txt))random.seed(42)这行别省否则每次跑划分结果都不一样实验没法对比。split_idx按 0.8 算如果图像总数很少比如只有 10 张那验证集就 2 张指标波动会很大这时候建议用交叉验证或者多跑几次取平均。另外注意YOLO 要求图像和标签文件名一一对应只是扩展名不同目录结构必须是images/train配labels/train不能把标签和图像混在一个文件夹里。3.3 用 YOLO 官方校验脚本做最后一道检查数据划分完别急着开训。YOLO 自带一个数据集校验功能能查出标签越界、类别缺失、图像损坏这些问题。以 Ultralytics 的 YOLOv8 为例跑一行命令就能扫一遍。yolo checks data./dataset_reinforcing/data.yaml如果提示nc和实际类别数不匹配或者某些标签文件为空它会直接报出来。我遇到过一种情况xml 里有object但name是空的转换脚本跳过了导致对应图像没有标签文件YOLO 训练时会把这类图当负样本处理模型学到的背景信息就偏了。校验脚本能帮你提前发现这类问题比训到一半才发现强。4. 避坑与排查钢筋数据集训练前必须过的五道坎4.1 标签坐标越界导致 loss 震荡现象训练头几个 epoch loss 正常下降突然某一轮飙升之后再也降不下去。原因部分 txt 标签的归一化坐标大于 1 或小于 0YOLO 计算边界框损失时会产生异常梯度。解决写个脚本扫一遍所有 txt把每行坐标限制在 [0,1] 区间同时检查width和height是否大于 0。我一般会在转换脚本里就做裁剪但如果你直接用数据集自带的 txt这一步不能省。4.2 图像与标签文件名大小写不一致现象训练时提示找不到标签文件但你去目录里看明明有。原因Windows 下文件名不区分大小写Linux 下区分。数据集里图像是97652B3F.jpg标签可能是97652b3f.txt在 Windows 上能对上传到 Linux 服务器就断了。解决统一转成小写或者用脚本批量重命名确保图像和标签的 stem 完全一致。4.3 验证集里出现训练集同款图现象验证集 mAP 很高但模型在实际工地图上表现很差。原因随机划分时同一根钢筋的不同角度图被分到了训练集和验证集验证集本质上是训练集的子集。解决按图像相似度分组后再划分或者至少用感知哈希去重把相似度超过阈值的图归到同一侧。这个坑我踩过不止一次血泪经验就是验证集必须和训练集在数据分布上有真正的隔离。4.4 类别名映射丢失导致推理结果无法解释现象模型推理输出0但你不知道0是什么。原因训练时data.yaml里的names和 txt 里的 class_id 没有严格对应或者训练完把data.yaml弄丢了。解决把data.yaml和模型权重放在一起推理时显式加载类别名。YOLOv8 的model.names属性会从训练时的配置里读但如果你换了环境最好手动维护一份类别映射表。4.5 小目标钢筋漏检严重现象大直径钢筋检测正常细钢筋或远距离钢筋大量漏检。原因钢筋在图像中占像素少YOLO 下采样后特征几乎消失。解决训练时把imgsz调大比如从 640 提到 1280同时开启mosaic增强让模型多见小目标组合。如果还不行考虑用切片推理把大图切成小块分别检测再合并。这个不是数据集的问题是 YOLO 本身对小目标的局限心里要有预期。5. 训练参数怎么调从默认配置到钢筋场景的微调5.1 学习率和批大小的起步值YOLOv8 默认lr00.01、batch16但钢筋数据集通常不大默认学习率可能偏大。我一般从lr00.001起步配合cos_lrTrue让学习率余弦衰减。批大小看显存8GB 显存跑imgsz640大概能上batch8如果开了mosaic和mixup显存占用会再涨一截适当降到 4 或 6。下面是一个我常用的训练命令模板。yolo detect train \ data./dataset_reinforcing/data.yaml \ modelyolov8n.pt \ epochs200 \ imgsz640 \ batch8 \ lr00.001 \ cos_lrTrue \ patience30 \ augmentTrue \ mosaic1.0 \ mixup0.1 \ project./runs/rebar \ nameexp01patience30是早停耐心值30 轮验证指标不提升就停省时间。mosaic1.0表示始终开启马赛克增强对小目标友好。mixup0.1是轻度混合再高可能让钢筋纹理变得不真实。project和name控制输出目录建议每次实验换个name方便回溯。5.2 置信度门限和 NMS 的推理侧调整训练完模型推理时conf和iou两个参数直接决定检出效果。钢筋检测里我一般把conf设到 0.25 到 0.35 之间太低会引入大量误检太高会漏掉被遮挡的钢筋。iou控制 NMS 的合并阈值钢筋密集区域建议调到 0.5 到 0.6避免相邻钢筋被误合并。yolo detect predict \ model./runs/rebar/exp01/weights/best.pt \ source./test_images \ conf0.3 \ iou0.55 \ saveTrue \ save_txtTruesave_txtTrue会把检测结果存成 YOLO 格式的 txt方便你后续做后处理比如统计每张图里的钢筋根数、计算平均间距。这个在工地验收场景里比单纯画框有用得多。5.3 用验证集指标判断是否过拟合训练日志里重点看三个指标mAP50、mAP50-95和val/box_loss。如果mAP50还在涨但mAP50-95停滞说明模型框的位置不够准可以试试调低学习率或者加更多数据。如果训练 loss 持续降但验证 loss 开始涨那就是过拟合了早停或者加数据增强。钢筋检测的mAP50能到 0.85 以上基本可用但实际部署前一定要拿工地实拍图再测一轮实验室指标和现场表现之间的差距往往比想象中大。5.4 一个容易被忽略的细节图像尺寸对齐数据集里的图像分辨率可能不统一YOLO 训练时会统一 resize 到imgsz但长宽比不一致的图会被拉伸钢筋的细长形状可能变形。我一般会在训练前把所有图 padding 到统一尺寸保持长宽比或者直接用rectTrue让 YOLO 按批次内最大尺寸对齐减少 padding 量。这个参数在train里默认是False小数据集上开启能提升一点精度。从那以后我每次拿到新数据集都强制走一遍「抽检标签一致性 → 转换格式 → 校验 → 划分 → 再校验」的流程宁可前期多花半小时也不愿训到一半发现标签是错的。希望帮到你。本文还有配套的精品资源点击获取
返回列表