☰
钢筋计数数据集实战:从标注格式到YOLOv8训练避坑指南
2026/9/30 6:32:08 网站建设 项目流程

简介:这套人工智能钢筋计数数据集以VOC格式标注文件为核心,面向计算机视觉开发者和建筑行业钢筋盘点场景,用于训练钢筋目标检测与计数模型。完整数据集包含训练集标注图片与测试集未标注图片,由于单文件大小限制被拆分为多个分包,本压缩包即为其训练集标注部分,共568个xml文件,逐项记录钢筋目标的位置框与类别信息,可直接接入主流检测框架进行训练。资源包整体仅1.07MB,结构清晰,下载后可快速完成训练数据准备,省去自行标注的时间成本。已有659人学习/下载。针对钢筋密集、互相遮挡、尺度不一等实际难点,这批标注能有效支撑计数算法的迭代优化;同时可参考作者博客预览原始图片质量,确认符合需求后再下载,适合钢筋计数课题研究、竞赛或工程落地的开发者使用。

1. 从工地照片到数字台账:钢筋计数数据集究竟解决什么

一次钢筋盘点,工人要蹲在堆场数一遍直径25mm的螺纹钢,天气不好时数错几百根很正常。后来想用人工智能替代,发现钢材缺陷检测、行人计数的公开数据集不少,专门用于钢筋计数、还带完整标注文件的数据集却非常难找。这套“人工智能钢筋计数数据集”指的就是一组经过清洗的钢筋堆场图像,以及和它配套的标注文件——标注形式可能是边界框、点或者密度图,用来训练目标检测或密度估计模型,最终把一张图片里的钢筋根数自动统计出来。它直接服务于钢筋出厂过磅、工地验收、库存盘点这类需要把“数”变成“数字台账”的落地场景。适合正在做智慧工地、建筑物资管理,或者想用AI替代人工点数复制“点数系统”的算法工程师和数据标注团队。这篇笔记不聊泛泛的深度学习理论,而是从拿到数据集后怎么下手、怎么训练、怎么避免模型点数翻车,一步步展开。

2. 拿到标注文件先别急着训:标注格式决定你后面走多远

一份钢筋计数数据集里,标注文件通常比图像更重要。先花半小时搞清楚标注格式和坐标体系,能省下后面几天调模型的精力。常见的钢筋计数标注文件有三种:VOC XML、COCO JSON、YOLO TXT。不少从业者拿到压缩包后直接解压喂给训练脚本,结果要么坐标全错,要么类别对不上,这种翻车我见过太多次。

2.1 VOC、COCO、YOLO:三种常见钢筋标注格式的选型

钢筋计数场景通常只有一个类别“钢筋”,但实际项目里你可能要区分“螺纹钢原材”和“已加工半成品”,或者按直径大小分型号,这就得考虑多类别扩展性。三种格式的差别如下。

格式存储形态结构特点适用框架多类别扩展可读性
VOC XML每张图一个xml文件object节点里嵌套name和bndboxFaster R-CNN、MMDetection、SSD支持,加object节点即可易读,标注人员可直接核对
COCO JSON整个数据集一个json文件images、annotations、categories三段结构Detectron2、MMDetection、官方YOLO也支持支持,categories表加类即可需写解析代码,机读友好
YOLO TXT每张图一个txt文件每行:class_id x_center y_center width heightUltralytics YOLO全系支持,类别ID扩展极简洁,但肉眼易出错

选型思路很简单:如果目标是用YOLOv8做快速迭代和部署,统一转成YOLO TXT,因为Ultralytics仓库对TXT格式支持最顺手,几乎零配置就能开训。如果计划用MMDetection做不同模型对比,保留COCO JSON更方便,因为很多官方配置直接读JSON。我一般建议先定框架再定格式,别等几千张图都标注完了再来回转换,坐标精度在转换过程里流失是最冤的。

稍微展开一下:VOC XML的可读性最好,打开xml就能看到“这个框是rebar,坐标是什么”,适合标注团队自查。但训练前必须经脚本转成TXT或JSON,多一道工序就多一个出错点。COCO JSON适合大数据集,文件动辄几十MB,解析比XML慢,但训练框架支持度高。YOLO TXT最简洁,但是如果要调一下标注内容,必须配合图像尺寸反算像素坐标,否则容易算错。钢筋计数项目中,我几乎都用YOLO TXT,因为训练速度快、部署方便,代价是需要提前照顾好坐标系。

2.2 用一条Python命令解析标注文件,看清钢筋分布

拿到标注文件后,第一件事不是训练,而是统计。要知道每张图大概有多少根钢筋、每根框的宽高分布如何,这决定了模型选型和anchor设计。下面这段代码会遍历VOC XML,把所有标注框的像素尺寸统计出来。

import glob import xml.etree.ElementTree as ET import numpy as np xml_files = glob.glob('annotations/*.xml') all_widths = [] all_heights = [] count_per_image = [] for xml_file in xml_files: tree = ET.parse(xml_file) root = tree.getroot() img_w = int(root.find('size/width').text) img_h = int(root.find('size/height').text) n = 0 for obj in root.findall('object'): name = obj.find('name').text if name.lower() != 'rebar': continue # 跳过非钢筋类别,注意统一大小写 bbox = obj.find('bndbox') xmin = int(bbox.find('xmin').text) ymin = int(bbox.find('ymin').text) xmax = int(bbox.find('xmax').text) ymax = int(bbox.find('ymax').text) # 有些标注会略微越界,这里先做边界裁剪再统计 xmin = max(0, min(xmin, img_w - 1)) xmax = max(xmin + 1, min(xmax, img_w - 1)) ymin = max(0, min(ymin, img_h - 1)) ymax = max(ymin + 1, min(ymax, img_h - 1)) all_widths.append(xmax - xmin) all_heights.append(ymax - ymin) n += 1 count_per_image.append(n) all_widths = np.array(all_widths) all_heights = np.array(all_heights) print(f"目标数分布: 均值={np.mean(count_per_image):.1f} 最大={np.max(count_per_image)}") print(f"框宽分布: 均值={all_widths.mean():.1f} 中位={np.median(all_widths):.1f}") print(f"框高分布: 均值={all_heights.mean():.1f} 中位={np.median(all_heights):.1f}")

这段代码的作用是快速识别异常:如果某张图有300根钢筋,而平均只有20根,说明数据里包含了高密度堆叠场景;如果框宽的均值和中位数差异巨大,说明标注人员画框的手势不够一致,有的大框套小框。我通常会在脚本输出后,把目标数最多和最少的十张图像路径单独列出来,人工抽看一下,确认不是文件名对应错乱。参数上注意,img_w和img_h必须和实际图像尺寸一致,如果尺寸字段写错了,后续所有统计都是错的。

2.3 标注文件里的坐标系与归一化:最常见的翻车点

YOLO TXT的坐标体系是“归一化后的中心点和宽高”,但很多人都栽在这里。一个常见错误是把框的宽度除以图像高度,把高度除以图像宽度,坐标系一错,模型训练直接不收敛。

# 从像素坐标生成YOLO格式标注行的正确做法 dw = 1.0 / img_w dh = 1.0 / img_h x_center = (xmin + xmax) / 2.0 * dw y_center = (ymin + ymax) / 2.0 * dh box_w = (xmax - xmin) * dw box_h = (ymax - ymin) * dh # 注意除以的是图像宽高,不是框自身宽高

反过来,当你从TXT转成像素坐标时,要乘以图像宽高而不是除以。我见过一个团队把YOLO TXT的w和h直接当成像素值传进OpenCV画框,结果所有框都缩成一个点。另一个常见问题是类别ID从1开始编号,但YOLO的类别ID从0开始。如果你的数据集只有一个类别,那么ID必须是0,而不是1。这会让模型把钢筋当背景,mAP直接在0附近徘徊。

所以训练前要跑一个快速校验脚本,读TXT后检查归一化坐标是否在0到1之间,以及x + w是否小于等于1。简单写法如下。

import glob for txt_path in glob.glob('labels/*.txt'): with open(txt_path) as f: for line in f: parts = line.split() if len(parts) != 5: print(f"{txt_path} 行数异常: {line}") continue cls_id, x, y, w, h = map(float, parts) if x < 0 or y < 0 or w <= 0 or h <= 0: print(f"{txt_path} 坐标非正: {line}") if x + w > 1.000001 or y + h > 1.000001: print(f"{txt_path} 框越界: {line}")

这个脚本会把所有标注文件的几何错误暴露出来。我建议把它加到你的数据集预处理流程里,每次拿到新数据先跑一遍,再决定要不要洗数据。

3. 从零搭一套钢筋计数数据集:采集、标注、增广与格式转换

上一章讲怎么解析现成的标注文件,但更多时候你手里只有一堆工地照片,需要从零搭数据集。这个过程中最花时间的不是训练,而是采集、标注和格式转换。以下是我在实际项目里验证过的路径。

3.1 钢筋图像采集的三个硬要求:环境、角度、分辨率

钢筋堆场的光照非常不友好:强光直射导致金属反光,阴影里钢筋几乎看不清,雨天积水还会反射周边环境。所以采集至少要覆盖晴天上午、阴天、傍晚三种光照;如果项目要覆盖夜间施工,必须增加补光灯下的拍摄数据。否则模型在白天数据上训练,部署到夜间就翻车。角度方面,最容易识别的方式是俯拍,也就是从堆场上方拍下去,能最大程度减少重叠;但如果你部署时是工人举着手机平拍,训练数据里就必须加入低角度和倾斜角度的素材,不然现场会急剧掉点。

分辨率是另一个被低估的点。直径25mm的螺纹钢,在1920x1080的画面里至少要有20x100像素的表现,标注人员才能看清边界,模型也才能学到纹理。如果拍的是整个堆场大全景,每根钢筋只占几个像素,那再好的模型也数不准。常见做法是采集设备贴近钢筋堆,单张图覆盖1到2平方米范围。数据量建议至少3000到5000张标注图,每张图包含10到200根钢筋不等,让模型见到稀疏和密集两种极端。我一般还会用手机和工业相机各拍一批,因为部署端往往用的是低成本摄像头,图像噪声不同,混着训练能提升鲁棒性。

3.2 用LabelImg或X-AnyLabeling标注:框选、类别、存成VOC

标注工具上,我常用LabelImg,因为轻量且不需要联网。安装和启动的命令很直接。

pip install labelImg labelImg images annotations_classes.txt

启动后设置标注目录和保存目录,默认保存为VOC XML。标注规范要提前定死:钢筋只标一个类别rebar;对遮挡超过三分之一、严重模糊、在画面边缘不完整的钢筋,建议不标,标了会干扰模型学习;对堆叠的钢筋,框只套住可见部分,不要试图把被遮挡部分画出来。类别名统一小写,避免后面解析时大小写不一致。

如果你需要半自动化标注,可以用X-AnyLabeling,它内置了YOLO模型预标注功能,先让模型标一遍,人工修正。这样可以大幅提升效率,但对没有初始模型的冷启动场景,不如纯人工标几批先训练一个粗糙模型,再用模型预标注来提高速度。

3.3 将VOC转成YOLO格式:转换脚本与四个边界坑

标注完成后,需要把VOC XML转成YOLO TXT。我提供一个自己常用的转换脚本,并会说明四个最容易踩的坑。

import xml.etree.ElementTree as ET import os class_names = ['rebar'] # 类别列表,顺序决定ID,下标从0开始 def convert_voc_to_yolo(xml_path, txt_out_dir): tree = ET.parse(xml_path) root = tree.getroot() filename = root.find('filename').text img_w = int(root.find('size/width').text) img_h = int(root.find('size/height').text) txt_name = os.path.splitext(filename)[0] + '.txt' output_path = os.path.join(txt_out_dir, txt_name) with open(output_path, 'w') as out: for obj in root.findall('object'): name = obj.find('name').text.strip() if name.lower() not in class_names: continue # 坑1:忽略类别名不一致的目标 cls_id = class_names.index(name.lower()) 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) # 坑2:标注框可能越出图像边界,必须裁剪 xmin = max(0, min(xmin, img_w - 1)) xmax = max(xmin + 1, min(xmax, img_w - 1)) ymin = max(0, min(ymin, img_h - 1)) ymax = max(ymin + 1, min(ymax, img_h - 1)) # 坑3:除以的是图像宽高,不是框宽高 x_center = (xmin + xmax) / 2.0 / img_w y_center = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h out.write(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}\n")

四个边界坑分别是:

  • 类别名大小写不一致,脚本里应该统一用name.lower()去匹配,否则会漏标目标;
  • 标注框越界,如果不裁剪,后续训练时损失变成NaN;
  • 坐标归一化用错分母,必须用图像宽高;
  • 文件名严格对应,xml里的filename字段必须和实际图片名一致。有的工具会写绝对路径,需要取出basename再拼接,否则txt保存成错误路径,训练时找不到标签。

转换完再跑一下2.3里的校验脚本,确认没有越界框。这一步花不了几分钟,但能避免训练到一半才暴露问题。

3.4 数据增广:一张顶十张,但别把钢筋增广成麻花

数据量不够时,增广是很有效的方法,但钢筋有自己的形态特殊性。钢筋是长条物体,长宽比往往在5:1以上,旋转超过20度就可能让它从“横向”变“斜向”,如果模型没见过斜向钢筋,部署时遇到斜放钢筋就容易漏检。所以我不建议做90度旋转或90度翻转,除非原始数据里本来就包含这种摆放方向。更安全的做法是亮度、对比度、马赛克和轻度旋转。

import albumentations as A transform = A.Compose([ A.RandomBrightnessContrast(p=0.5), A.RandomGamma(p=0.3), A.HorizontalFlip(p=0.5), A.RandomRotate90(p=0.0), # 钢筋场景不启用90度旋转 A.Rotate(limit=15, border_mode=0, p=0.5), A.Mosaic(p=0.3), # 四张图拼一张,增加密集度 ])

参数说明:Rotate(limit=15)表示最多转15度,既能模拟现场钢筋不规整摆放,又不会转成竖着的形态。Mosaic会把四张图拼成一张,变相增加每张图的目标密度,对计数模型很有帮助,但它会改变框的坐标,所以只能用于训练时在线增广,不能把增广后的图存回数据集。实际上Ultralytics YOLO自己带了Mosaic增广,如果你用的就是YOLOv8,可以不开albumentations,直接用默认增广即可,避免双重增广导致分布偏移。

4. 用YOLOv8跑通钢筋计数:数据组织、训练参数与评估

当数据集准备好后,最快验证路径是直接用YOLOv8训练目标检测模型。这里的数据组织方式和参数调整,决定你最后能不能拿到一个计数误差可接受的结果。

4.1 把数据集组织成YOLOv8需要的目录结构

YOLOv8要求的目录组织是 images 和 labels 分离,train/val/test 按子目录划分。结构如下。

datasets/rebar/ ├── images/ │ ├── train/ │ │ ├── img_001.jpg │ │ └── ... │ ├── val/ │ └── test/ └── labels/ ├── train/ │ ├── img_001.txt │ └── ... ├── val/ └── test/

划分比例我一般用70%训练、20%验证、10%测试。划分时要注意打乱顺序,避免同一天拍摄、光照相同的图片都堆在一个集里。可以用一个简单的Python脚本按文件名哈希值分配,而不是顺序切。

data.yaml内容如下。

path: /absolute/path/to/datasets/rebar train: images/train val: images/val test: images/test names: 0: rebar

这里最坑的是path字段。如果用相对路径,YOLOv8会以你执行命令的工作目录为基准去找,一旦不在该目录运行就会报错。我统一用绝对路径,服务器上部署也方便。names里的ID必须和标签文件里的class_id对应,而且是0开始,不是1。

4.2 训练命令与六个最值得调的参数

组织好数据集后,训练命令相对简单。

yolo detect train \ data=rebar.yaml \ model=yolov8s.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=10

六个参数里,最值得花心思的是:

  • model:从yolov8s.pt开始通常比从头训练快很多,因为它在COCO上预训练过。如果想最快验证流程,用yolov8n.pt,训练速度大约快一倍,但精度稍低;
  • imgsz:决定输入分辨率。钢筋堆场里单根钢筋如果小于16x16像素,640这种默认尺寸会漏检,我建议先尝试imgsz=1280,但显存不够时要配合减小batch;
  • batch:显存占用和训练稳定性直接相关。16GB显存跑1280输入时,batch降到4是常态;
  • lr0:初始学习率。小数据集、小batch时,0.01可能偏高,降低到0.001更稳;
  • patience:早停轮数。如果验证集指标连续10轮不涨就停,能省时间;
  • epochs:100轮对于钢筋这类小目标任务往往不够,我常设150到200轮,但依赖早停防止过拟合。

训练日志里要看两个数值:train/box_loss是否稳定下降,val/mAP50是否还在上涨。如果box_loss反复横跳,很可能是学习率太高或batch太小。训练结束后,runs/detect/目录下会生成最佳权重best.pt和最后一轮权重last.pt。评估时一定用best.pt,而不是last.pt。

4.3 评估指标不只是mAP:计数任务还要看MAE和MSE

目标检测常用mAP衡量,但计数业务更关心总数误差。mAP高不一定计数准,因为mAP是逐框匹配后积分,对遮挡导致漏掉的框并不敏感。为了验证真实计数值,我每次都会额外算平均绝对误差(MAE)和均方误差(MSE)。

# 计算测试集每张图的实际数与预测数之间的 MAE / MSE import glob import numpy as np def count_labels(label_dir): counts = [] for txt_file in sorted(glob.glob(f"{label_dir}/*.txt")): with open(txt_file) as f: n = sum(1 for line in f if line.strip()) counts.append(n) return np.array(counts) gt = count_labels('datasets/rebar/labels/test') pred = count_labels('runs/detect/predict/labels') mae = np.abs(pred - gt).mean() mse = ((pred - gt) ** 2).mean() print(f"MAE: {mae:.2f} 根/图") print(f"MSE: {mse:.2f}")

这个脚本要求预测标签路径里每张图的txt文件与测试集一一对应。如果预测时用了不同的文件名前缀,要先对齐文件名。MAE控制在5根以内,说明模型基本可用;如果MAE超过10根,通常问题不出在模型,而是数据里高密度样本太少,或者标注本身不一致。我见过一个项目mAP50有0.92,但MAE高达30,打开预测结果一看,模型把地面阴影当成钢筋,多出来一大片假目标。

5. 钢筋计数落地避坑与排查:从标注到推理的五个真实问题

训练推演过程中,你会遇到一些很“玄学”的问题。以下是五个我踩过的坑,按现象到原因再到解决的顺序写出来,希望能省掉你几天时间。

5.1 标注框与图像不对齐,训练后mAP惨不忍睹

现象:训练结束,mAP50只有0.1左右,损失曲线看似正常但验证集一团糟。把预测框可视化到原图上,发现每个预测框都偏了十几个像素。

原因:标注工具在图片被压缩后保存了旧坐标,或者VOC转YOLO时坐标除错了分母。最常见的是标注图片分辨率与训练时读取的图片分辨率不一致,导致坐标翻倍或减半。

解决:训练前随机抽20张图,把标注框画在原图上人工目检一遍。下面这个脚本可以快速验证YOLO TXT标注是否对齐。

import cv2 import glob for txt_file in glob.glob('labels/val/*.txt'): img_path = txt_file.replace('labels', 'images').replace('.txt', '.jpg') img = cv2.imread(img_path) h, w = img.shape[:2] with open(txt_file) as f: for line in f: cls_id, x, y, bw, bh = map(float, line.split()) px = int((x - bw / 2) * w) py = int((y - bh / 2) * h) pxx = int((x + bw / 2) * w) pyy = int((y + bh / 2) * h) cv2.rectangle(img, (px, py), (pxx, pyy), (0, 255, 0), 2) cv2.imwrite('check_' + img_path.split('/')[-1], img)

目检时重点看框是否紧紧贴着钢筋边缘。如果框整体偏左或偏上,说明坐标中心点计算有误;如果框比钢筋大一圈,说明标注人员画框时压住了相邻钢筋。

5.2 类别名大小写不一致,模型把钢筋当背景

现象:数据里明明有钢筋标注,但训练时每个epoch的目标数都在0附近,模型完全学不到前景。

原因:标注文件里有的写成rebar,有的写成Rebar,转换脚本没统一大小写,导致部分目标被跳过,甚至类别ID对应错。

解决:在转换脚本中把类别名改成全小写匹配,并在标注规范里写明只允许rebar这个字符串。更保险的做法是转换完后统计一下每个类别ID的目标数量,如下。

awk '{print $1}' labels/train/*.txt | sort | uniq -c

如果类别ID不是0,或者0的数量为0,说明解析或转换有问题,赶紧回头查。

5.3 显卡显存不足,16GB照样爆

现象:设置imgsz=1280, batch=8,一启动训练就OOM,batch降到2还是内存溢出。

原因:钢筋堆场原始图分辨率高,输入尺寸又大,特征图占用内存远超预期。特别是开了Mosaic增广后,四张图拼接产生的临时张量会挤爆显存。

解决:先关掉Mosaic,mosaic=0.0;然后开自动混合精度amp=True,这能把显存占用减少大约30%;再不行就把模型换成yolov8n.pt。如果业务必须用1280分辨率,还可以在训练后使用SAHI切片推理来替代大图训练,成本低很多。

5.4 高密度堆叠区域漏检严重

现象:钢筋堆叠紧密的中心区域,模型只数出外围一圈,内部一根都检测不到。验证集mAP不低,但MAE高得离谱。

原因:NMS阈值过高,重叠框互相抑制;钢筋本身是长条物体,密集时目标特征被遮挡,模型学习不足。

解决:推理时把置信度阈值调低,比如conf=0.1,同时把NMS的IoU阈值从默认0.7调到0.3。如果还漏,考虑用SAHI切片推理,把大图切成512x512小块,每块单独检测再合并计数。最彻底的做法是换密度估计模型,比如CSRNet,它直接回归密度图再积分计数,对密集场景更友好。

5.5 模型部署到工地点检机后速度慢得离谱

现象:测试时单张图不到1秒,部署到工地小主机上要3秒多,视频根本跑不动。

原因:部署设备CPU或GPU性能弱,原图分辨率高,模型直接整图推理计算量巨大。

解决:先用imgsz=1280训练,然后部署时保持1280输入但只对ROI区域推理,减小实际推理面积。如果设备支持TensorRT,用trtexec将best.pt转成FP16 engine,通常能快2到3倍。另一个工程做法是降低帧率,每隔一秒取一帧计数,对钢筋盘点场景完全够用。

6. 把计数值做成可信台账:自动评估与一张小抄

当模型在测试集上的MAE控制在要求范围内,下一步不是急着展示,而是做一次端到端验证:从一张原始图片输入,到输出一个带计数结果的CSV表格。我每次完成钢筋计数项目,都会把以下流程固定下来作为验收标准。

首先,用训练好的权重跑一次预测,输出每张图的计数结果和置信度。

yolo detect predict \ model=best.pt \ source=datasets/rebar/images/test \ conf=0.1 \ save_txt=True \ save_conf=True

这一步会在runs/detect/predict下生成每张图的txt预测结果。接着用4.3里的脚本计算MAE和MSE,同时把预测计数与标注计数按文件路径合并成一张表。

图像文件名人工标注数模型预测数误差
img_001.jpg3432-2
img_002.jpg128121-7

我一般还会额外输出“误差超过10根”的图片列表,逐张看是漏检还是多检。如果漏检集中在小目标区域,检查是否推理分辨率过低;如果多检集中在阴影区域,给训练数据加几个“无钢筋但带阴影”的负样本,比调模型参数更有效。

最后一个小习惯:每次调参前,先把数据集版本号记录在CSV表头里。钢筋计数数据集经常会被追加新批次,一旦下游模型指标波动,你能快速定位是数据变了还是参数变了。这个“记录版本”的习惯,救过我很多次。

回头看我做过的几个钢筋计数项目,真正花时间的不是模型训练,而是处理标注文件里的坐标系问题、类别名不规范问题以及密集场景的漏检策略。AI模型只是一个能在上面跑的点数工具,能不能替工人把数数清楚,取决于训练集质量和你对边界情况的处理。希望以上这些踩坑记录能帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询