简介面向钢筋计数算法开发者的VOC格式训练集标注包聚焦建筑工地场景下的钢筋自动盘点与数量复核需求适用于YOLO、Faster R-CNN、SSD等主流目标检测模型的训练与性能调优。由于完整数据集按文件大小限制被拆分为多个部分本部分为其中训练集标注文件共包含568个XML文件压缩包仅1.07MB单份标注轻量下载与本地使用均很便捷。每个XML文件遵循Pascal VOC标准记录图像中钢筋目标的边界框坐标、类别标签等关键信息字段规整统一兼容LabelImg等常用标注工具可灵活转换为YOLO、COCO等格式供模型直接读取。目前已有659人学习浏览适合正开展钢筋计数算法研究的科研人员、AI开发者及相关竞赛团队。借助这批标注数据能省去人工框选钢筋的大量重复劳动快速搭建钢筋检测训练流程可结合参考博客中的示例图片事先核验图像质量再配合未标注测试集图片检验模型泛化能力为钢筋智能盘点、自动验收等应用落地提供扎实的数据基础。1. 钢筋计数数据集在解决什么从人工清点到视觉识别的最后一公里一车钢筋进场按规矩要清点根数。800根螺纹钢两个人拿计数器逐根划最快也要20分钟还经常数出两个互相不认账的结果。人工智能钢筋计数数据集标注文件就是给这个场景准备的把现场拍到的钢筋端面图逐根框出来、贴上标签做成模型直接能吃的监督信号。它解决的不是“能不能检测到钢筋”而是“数得准不准以及怎么把标注文件变成可训练的计数模型”。适合正在做工地智能化、钢筋加工厂清点系统或者拿目标检测做入门项目的人。下面按一线做法把标注格式、训练命令、踩坑和验收讲清楚。2. 读懂标注文件钢筋计数数据集的三种格式与标签字段拆解2.1 目标检测数据集与计数数据集的核心差异拿到一个人工智能钢筋计数数据集第一件事不是跑模型而是拆开看标注文件。大多数这类数据集在底层格式上和目标检测数据集完全一样一张图配一个标注文件标注文件里挨个写出图上钢筋端面的位置。差别在于任务目标。目标检测要的是“每个目标在哪里、是什么”而钢筋计数要的是“全图一共有多少根”位置信息反而成了中间产物。由此产生两个特征。第一类别通常只有一个钢筋端面很多数据集的classes.txt里只有一行类别ID固定为0。第二标注粒度是一根钢筋一个框不是一捆一个框也不是一条线一个框。这意味着标注文件里有多少个有效目标这张图的“真实根数”就是多少统计行数等价于统计数量。这个特性让后续的数据清洗和评估变得非常直接。常见的数据集标注文件有三种组织方式拿到手先看扩展名。VOC XML适合传统工具链和可视化COCO JSON适合mmdetection、Detectron2这类训练框架YOLO TXT是ultralytics/YOLO系最顺手的形式。格式文件组织标注内容最常见配套VOC XML每张图一个同名.xml类别、xmin/ymin/xmax/ymax可视化工具、旧pipelineCOCO JSON全数据集一个annotations.json类别、像素坐标bbox可选分割Detectron2、mmdetectionYOLO TXT每张图一个同名.txt类别、归一化xywhultralytics、YOLOv8这三种格式互转是常规操作但坐标基准最容易出错。VOC和COCO用的是像素坐标YOLO用的是归一化坐标转格式时漏掉除以图像宽高这一步框就会整体漂移到角落。2.2 标签字段逐项拆解从一行文本到一张图的根数以YOLO TXT为例一个标注文件的内容长这样0 0.523437 0.817708 0.015625 0.014583 0 0.531250 0.833333 0.013672 0.012500每行有5个值空格分隔。第一个值是类别ID这里只有一个类别所以是0第二个值是框中心点的归一化x坐标等于像素x坐标除以图像宽度第三个值是中心点归一化y坐标等于像素y坐标除以图像高度第四、第五个值是框的归一化宽度和高度分别等于像素宽高除以图像宽高。举个例子假设原图是1920乘1080第一条标注的像素中心x大约是0.523437乘1920约等于1005像素框的像素宽度大约是0.015625乘1920等于30像素。30像素在整图里是个很小的目标这直接决定了后面训练时输入分辨率不能设太低。把每行读出来行数就是这个图的钢筋根数。统计全部标注文件的行数就得到整个数据集的标注总量。from pathlib import Path label_dir Path(labels/train) total_boxes 0 empty_files 0 for txt in label_dir.glob(*.txt): lines [line for line in txt.read_text().splitlines() if line.strip()] if not lines: empty_files 1 total_boxes len(lines) print(ftrain集标注框总数: {total_boxes}) print(f空标注文件数: {empty_files})这段脚本的逻辑很简单按行切分并去掉空行非空行的数量就是该图的检测框数量累加所有txt文件就得到数据集总量。参数注意两点。一是空txt文件要单独统计它表示“这张图里没有钢筋”这是合法的负样本不要直接删掉二是如果某行出现负数或者大于1的坐标值说明标注文件越界这种文件会在训练时报错需要提前清洗。2.3 可视化校验把标注文件画回图片上统计完数量只能证明“标注文件不空”不能证明“框标对了位置”。钢筋计数数据集最常见的标注问题不是漏标而是框的边界不统一有人在图上只框了钢筋截面的内圈有人框了外轮廓。只靠肉眼看txt文件完全发现不了必须把框画回图上。import cv2 from pathlib import Path img_path Path(images/train/0001.jpg) label_path Path(labels/train/0001.txt) img cv2.imread(str(img_path)) h, w img.shape[:2] for line in label_path.read_text().splitlines(): fields line.split() if len(fields) ! 5: continue cls, xc, yc, bw, bh map(float, fields) x1 int((xc - bw / 2) * w) y1 int((yc - bh / 2) * h) x2 int((xc bw / 2) * w) y2 int((yc bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.imwrite(check_0001.jpg, img)这里的核心是坐标还原YOLO格式存的是归一化值画图时必须乘以图像宽高并换算成整数像素坐标。画完之后逐个看框合格的框应该正好包住钢筋的圆形端面不能留出大片背景也不能只压住中心。这个检查建议在划分训练集之前做抽5到10张图看一遍就够了不需要全部画出来。密集钢筋图上框与框相互重叠是正常的人眼很难逐根核对这时候重点看边缘区域和远端小目标这些位置最容易出现“标偏了”的情况。提示如果标注文件是COCO JSON格式可视化时要同时读images数组和annotations数组用image_id关联bbox字段是[x, y, width, height]的像素坐标直接画不要再乘图像宽高。3. 用YOLOv8跑通钢筋计数数据集格式转换、训练命令与旋转框判断3.1 数据集划分与标准目录结构进入训练前先把数据集整理成YOLO系列能直接读的目录结构。虽然不同数据集的原始目录五花八门但最终都要统一成下面这个样子dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── rebar.yamlimages和labels目录下分别是训练集和验证集的图片与对应txt图片文件名和txt文件名完全一致只有扩展名不同。这是ultralytics默认的YOLO目录约定不要改。划分时建议按图片文件名配对不要按整个目录复制。下面是我常用的划分脚本把图片和同名txt一起按比例分到train和valimport random import shutil from pathlib import Path random.seed(42) images sorted(Path(images_src).glob(*.jpg)) random.shuffle(images) val_ratio 0.15 val_count int(len(images) * val_ratio) val_imgs set(images[:val_count]) for img in images: stem img.stem label_src Path(labels_src) / f{stem}.txt if not label_src.exists(): print(f缺少标注: {img.name}) continue subset val if img in val_imgs else train img_dst Path(dataset/images) / subset / img.name lbl_dst Path(dataset/labels) / subset / label_src.name img_dst.parent.mkdir(parentsTrue, exist_okTrue) shutil.copy(img, img_dst) shutil.copy(label_src, lbl_dst)这段脚本做了三件事固定随机种子打乱图片顺序按15%的比例挑出验证集按图片的stem把同名txt挪进对应子集。最关键的设计是label缺失时打印日志并跳过而不是直接报错。实际数据集里偶尔会有一两张图没有标注文件如果不处理训练时dataloader会直接报错那一下午就耗在找文件上了。目录准备好后写一个数据集配置文件rebar.yamlpath: /home/user/dataset train: images/train val: images/val nc: 1 names: 0: rebar_endpath建议写绝对路径相对路径在换了工作目录后经常找不到图片names里的rebar_end只是标签名显示用不影响训练。类和ID的对应必须和txt里的第一个数字一致这里只有0不需要改。3.2 训练命令与关键参数小目标场景下imgsz不能省用ultralytics训练最小可用的命令是这样yolo detect train \ datarebar.yaml \ modelyolov8s.pt \ epochs100 \ batch16 \ imgsz960 \ patience30逐个说参数。model选yolov8s而不是yolov8n是因为钢筋端面在画面里往往只有30到50像素n号模型的骨干网络太浅小目标特征提取不足密集场景误检会明显变多如果显存紧张再退回n并配合小batch。imgsz设960是我在钢筋端面场景的起点默认640会让小目标的特征在下采样后只剩几个像素漏检率肉眼可见地升高。batch受显存限制16G显存跑960分辨率配16基本是上限显存小就降到8。epochs设100但配patience30目的是让训练在验证集不再提升时提前停止不浪费时间。训练结束后用验证集里效果最好的权重做推理yolo detect predict \ modelruns/detect/train/weights/best.pt \ sourcetest_images \ save_txtTrue \ save_confTrue \ conf0.35save_txtTrue会在runs/detect/predict/labels目录下输出每张图的检测结果格式和数据集标注文件完全一致一行一个框。这样就能直接写脚本数出模型认为的钢筋根数再和标注文件里的真实根数对比。conf设0.35是密集场景的折中设到0.25会把钢管口、模板边缘误检成钢筋设到0.5又会把被遮挡的钢筋端面漏掉。这个值在验证集上多跑两轮再定不要照抄。3.3 判断数据集要不要旋转框先看每行字段数钢筋并不总是端面正对着镜头。侧面拍摄成捆钢筋时画面里是一根根斜放的长条水平检测框会框进大量背景两个相邻框的重叠度很高。遇到这种图普通YOLO的NMS容易把同一条钢筋吃掉计数偏少。这时候要考虑旋转框检测典型工具链是mmrotate训练DOTA格式的旋转框模型。但并不是所有钢筋计数数据集都需要旋转框判断方法很简单看标注文件每行有几个字段。from pathlib import Path sample sorted(Path(labels/train).glob(*.txt))[0] fields len(sample.read_text().splitlines()[0].split()) print(f每行字段数: {fields})如果每行是5个字段说明是水平框直接用YOLOv8这套流程如果一行有9个及以上的数字前8个是四个角点的x、y坐标后面是类别和难易标记那就是旋转框格式需要按DOTA的目录结构组织数据再用mmrotate训练。最怕的是数据集里混着两种格式训练时YOLOv8会把旋转框的9个字段当成异常输入直接报错。所以这个字段检查必须在数据预处理阶段完成一次就能决定后面的技术路线。注意旋转框数据集在mmrotate中的目录组织方式是images和annfiles平行存放annotations里是DOTA格式的txt不是YOLO的归一化txt。转换时如果只有水平框和角度字段要先把中心点宽高转成四角点坐标再按角度旋转角度缺失时用PCA主方向近似。4. 钢筋计数避坑指南小目标、密集遮挡、标注噪声与误报排查4.1 小目标漏检验证集指标好看总数少了几十根现象训练出来的模型在验证集上mAP50有0.9但把整张现场图跑一遍远的钢筋端面一个都没检测出来总根数比人工清点少了几十根。问题出在小目标上而不是模型能力上。原因钢筋端面在1920宽的原图里只有20到40像素默认imgsz640训练时网络下采样后这个目标在特征图上只有几个像素加上钢筋截面是深色实心圆边缘纹理少和水泥地面的灰度接近模型根本没学到可以区分的特征。解决第一优先级是把imgsz提到1280大部分漏检会立刻缓解。第二优先级是切片推理把大图切成512乘512带重叠的块钢筋正好落在切片边界时带重叠能避免被截断切块后小目标在块内的相对尺寸变大了模型更容易看到。第三优先级是训练时不要开mosaic增强钢筋图拼接后每块里的钢筋变得更小反而加剧小目标问题把mosaic关掉或系数降到0.5更稳。4.2 密集遮挡导致计数偏少NMS在帮你删“重复框”现象一整垛钢筋端面互相贴着模型其实每个端面都产生了候选框但后处理时两个重叠框的IOU超过0.7NMS把其中一个当成重复框删掉最后计数比真实少5%到8%。原因检测头的默认阈值针对通用目标调优钢筋端面这种“挤在一起”的密集目标真实框之间IOU本来就很高NMS分不清是重复检测还是两个不同目标。解决推理时把NMS的IOU阈值调低到0.3让后处理更保守不要随便删框。同时把max_det从默认的300往上调极端密集的图像里候选框数量可能超过默认上限。还有一个现场常用的土办法统计每张图的检测框面积直方图如果出现两个明显峰说明存在一整片端面没检出来按缺失峰的面积和平均框面积估算漏检根数然后人工复核那一片区域。这套经验在密集场景比单纯调阈值更实用。4.3 标注噪声同一个框两种标法现象打开可视化脚本后发现有的框把钢筋整个圆面包住了有的框只圈住中心圆孔还有个别框偏到旁边那根钢筋上。训练时loss一直降不下去推理出来的框忽大忽小。原因这是标注规范没有定死的典型症状。多人协作时有人按“外轮廓最大范围”标有人按“中心可见区域”标没有统一规则也没有抽检标注文件里的噪声成了训练时的错误监督信号。解决先定死规则标注框必须贴合钢筋端面外轮廓包含整个圆形截面。凡是不符合规则的框直接重画或删除。然后写脚本做兜底清洗先把明显异常剔除再交给人工避免人力处理几千个文件。下面这段清洗脚本是我最常用的from pathlib import Path def clean_label(txt_path, min_side_px3, img_w1920, img_h1080): lines [] for line in txt_path.read_text().splitlines(): fields line.split() if len(fields) ! 5: continue cls, xc, yc, bw, bh map(float, fields) pw, ph bw * img_w, bh * img_h if pw min_side_px or ph min_side_px: continue if not (0 xc 1 and 0 yc 1): continue lines.append(line) txt_path.write_text(\n.join(lines)) for txt in Path(labels/train).glob(*.txt): clean_label(txt)参数说明min_side_px3表示宽度或高度小于3像素的框直接丢掉这种框绝大多数是标注误操作中心点越界的检查针对的是坐标换算错误正常标注不可能出现中心点在图外。这两个规则不删正常框也不误伤密集区域的小目标是比较安全的清洗策略。清洗后最好重新跑一遍可视化确认没有把合法的小目标当成噪声误删。4.4 背景误报模型把钢管口、捆扎带当成了钢筋现象推理结果里出现一堆“假钢筋”位置对着钢筋捆扎带的金属扣、旁边的钢管口、模板上的圆孔。人眼一看就知道不是钢筋模型却非常坚定地给了高置信度。原因训练集里所有正样本都是圆形深色端面模型学到的本质是“圆的、深色的东西”。现场环境里圆形深色干扰太多而标注文件里完全没有这类负样本模型没有机会学习“看起来像圆但不是钢筋”的特征。解决从现场收集一批不包含钢筋的图或者包含干扰物的图生成对应的空标注txt文件混进训练集。负样本数量不用多占现场图的10%到20%就能显著压误检。注意空txt文件在上一节的统计脚本里会被单独记录训练时YOLOv8会把它们当成难负样本正常处理不需要额外配置。如果误报仍然集中在某个特定区域比如钢筋包装带就把这类图单独复制几份增强一下。5. 用标注文件验收钢筋计数模型从MAE到现场复核流程5.1 用计数误差验收而不是只看mAP训练结束后的常规操作是看mAP但对计数任务来说mAP不是最合适的验收口径。mAP对框位置的轻微偏移非常敏感一个框偏了5个像素mAP就会掉但对“数钢筋根数”这个任务毫无影响。现场要交付的是“这张图有多少根”不是“框画得多准”。正确的验收方式是直接对比预测框数和标注框数。写一个评估脚本把每张图的真实根数和预测根数拉出来算误差import json from pathlib import Path def count_pred(label_dir): counts {} for txt in Path(label_dir).glob(*.txt): n len([l for l in txt.read_text().splitlines() if l.strip()]) counts[txt.stem] n return counts def count_gt_from_coco(gt_json): with open(gt_json) as f: data json.load(f) id_to_count {} for ann in data[annotations]: img_id ann[image_id] id_to_count[img_id] id_to_count.get(img_id, 0) 1 id_to_name {img[id]: img[file_name] for img in data[images]} return {id_to_name[i][: -4]: c for i, c in id_to_count.items()} pred count_pred(runs/detect/predict/labels) gt count_gt_from_coco(annotations/instances_val.json) m sum(abs(pred[k] - gt[k]) for k in gt if k in pred) mae m / len(gt) mre mae / (sum(gt.values()) / len(gt)) print(fMAE: {mae:.2f}) print(fMRE: {mre:.2%})脚本里的关键点有两个。一是预测txt的文件名要和GT的图片名对齐我这里按stem处理把COCO的file_name截掉了扩展名二是GT的根数要从annotations数组里按image_id聚合而不是读某个单独字段。常见的数据集里有时会直接提供count.json这类汇总文件那就不用解析annotations逻辑更简单。真正要盯的指标只有两个MAE和MRE。指标公式我的验收建议MAE平均每张图预测数与真实数的差的绝对值单张图小于3根MREMAE除以平均真实根数5%以内可上线10%要回到训练调参还有一个常见误区用mAP评估时模型在密集区域多检一个框和少检一个框可能互相抵消mAP变化不大但计数误差完全暴露。这也是我后来坚持报告MAE和MRE的原因。实际做项目时甲方要求的是“差多少根”不是“交并比多少”验收口径必须跟着业务走。5.2 把计数结果接进钢筋清点流程模型单张推理速度在200毫秒左右和人工20分钟数一车相比是量级上的差距。但直接全自动数完不给人复核现场一定出问题。我一般把流程设计成四步拍照、AI计数、人工抽检、按车次汇总。拍照阶段要求钢筋摊开一层端面朝镜头不要从侧面拍一堆叠放的长条模型对侧面堆叠的计数误差会急剧上升。AI计数阶段输出每张图的根数和带框的预览图。人工抽检阶段只看预览图里边缘和密集区域把模型数错的地方记下来不回退整张图。按车次汇总时同一车次的照片如果覆盖了同一片钢筋的不同角度根数只能取交集不能简单相加否则同一捆钢筋会被重复计入。这个流程把AI的效率和人工的判断结合在了一起。人工不再需要逐根数但保留了发现模型系统性漏检的能力。和传统方式对比差异非常明显方式一车耗时主要误差来源双人逐根清点20到30分钟疲劳、数岔、两人口径不一AI计数加人工抽检2到3分钟小目标漏检、密集遮挡、背景误报5.3 用标注文件做模型更新闭环只标“错图”很多团队训练完就结束了这是最大的浪费。标注文件的价值在模型上线之后才真正发挥出来。现场人工抽检时凡是模型数错的图都单独放进一个hard_examples目录每周把这批图重新标注只标模型漏掉或误报的目标然后做增量训练。增量训练用原来的权重作为预训练数据集只含hard examples学习率降到正常训练的十分之一跑30到50个epoch。这个习惯能把标注成本从“每张都重新标”降到“只标模型错的图”而且错图会随着模型变强而越来越少整个闭环的维护成本是递减的。注意增量训练的验证集必须固定不变否则会因为模型见过hard examples而指标虚高失去对比意义。6. 批量推理与计数校正钢筋计数模型上线的三个具体技巧模型在验证集上MRE做到5%以内不等于现场能直接用。批量推理和计数校正这两件事必须写进工具链里。from ultralytics import YOLO from pathlib import Path import csv model YOLO(best.pt) imgs sorted(Path(incoming).glob(*.jpg)) with open(count_result.csv, w, newline) as f: writer csv.writer(f) writer.writerow([file, count]) for img in imgs: res model.predict(str(img), conf0.4, imgsz1280)[0] boxes res.boxes keep sum( 1 for box in boxes if box.xywh[0, 2].item() 5 and box.xywh[0, 3].item() 5 ) writer.writerow([img.name, keep])批量推理的窍门是把结果落到csv而不是终端后续按车次汇总、和人工抽检结果对比都方便。conf调到0.4比训练时略高批量阶段优先压误检少一根可以通过人工抽检补回来多一根反而浪费复核时间。过滤小于5像素的框是最便宜的计数校正这类框在1280分辨率下基本是背景噪声或边缘碎片。第二个技巧是看每张图的框面积直方图。把一张图上所有检测框的面积画出来正常钢筋端面会聚成一个主峰如果出现一个远离主峰的小峰那多半是误检直接截断过滤。这套做法在反光和阴影重的现场图里尤其管用。第三个技巧是我自己的血泪经验数据划分必须按拍摄批次分不能按文件名随机分。最早做钢筋计数时我把同一个钢筋堆的连续帧随机切到了训练和验证集验证指标好看到不行上线就翻车。后来改成按车次分组同一批拍摄的图全部进训练或全部进验证验证集才真实反映现场泛化能力。每次训练前我也会先跑5个epoch看loss曲线震荡太厉害就先查标注再调参别急着堆数据。希望帮到你。本文还有配套的精品资源点击获取