news 2026/10/4 14:07:20

桥梁裂缝数据集处理:COCO转YOLO格式与训练避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
桥梁裂缝数据集处理:COCO转YOLO格式与训练避坑指南

简介:一份面向桥梁结构健康监测与计算机视觉研究者的图像分割数据集,采用COCO标注格式,聚焦混凝土桥梁表面裂缝缺陷的精准定位与分割。数据集包含约4500张训练图像和200张验证图像,覆盖不同光照、角度、拍摄距离及复杂背景下的裂缝形态,可为深度学习模型提供充足的正负样本,可直接用于训练或评估U-Net、DeepLab、Mask R-CNN等主流分割网络,有效解决桥梁巡检中裂缝样本稀缺、人工标注成本高等痛点。压缩包内共2000个文件,其中1998张jpg图像配合2个json标注文件,整体体积约557.74MB,标注文件采用标准COCO结构,包含categories、images、annotations等字段,便于用户借助现成工具链完成解析与训练。资源发布以来已有317人学习下载,适合高校实验室、工程检测单位及算法工程师复现实验、调整模型或开展桥梁病害自动化识别研究。

1. 桥梁裂缝缺陷数据集.zip:解压后先别急着训模型,标注格式决定后续一切

拿到一个“桥梁裂缝缺陷数据集.zip”,大多数人的第一反应是解压、看文件夹、把图片丢进训练脚本。我见过太多人在这一步翻车——花了半天把数据塞进YOLO,结果发现标注是COCO格式,类别id对不上、segmentation没处理、坐标归一化算错,训练出来的模型把整座桥都画满框。这个zip包背后是一套完整的COCO标注体系:images、annotations、categories三个字段把图像、裂缝轮廓和类别绑定在一起。下面按从解压到训练再到评估的顺序,把这条链路上的每一步拆开讲,适合做结构健康监测、无人机巡检和路面病害检测的工程师照着复现——尤其是那些刚拿到数据包、不知道从哪下手的团队。

2. COCO标注格式拆解:读懂json里的三个核心字段,才能谈转换

2.1 先看文件树和顶层字段,别急着跑代码

拿到一个裂缝数据集的zip文件,解压之后先别急着写训练脚本。我习惯先扫一眼目录结构,确认图像和标注文件的组织方式。一个规范的COCO格式裂缝数据集,目录结构通常长这样:

bridge_crack_dataset/ ├── images/ │ ├── train/ # 训练图像 │ └── val/ # 验证图像 └── annotations/ ├── instances_train.json └── instances_val.json

实际的目录结构可能千奇百怪,有些把全部图片放在一个目录里,json命名也不统一。真正重要的是json内部的数据结构。我习惯用几十行脚本先把标注文件读进来,打印顶层字段再决定后续操作。

import json with open('annotations/instances_train.json', 'r', encoding='utf-8') as f: coco = json.load(f) print('顶层字段:', list(coco.keys())) print('图像记录数:', len(coco['images'])) print('标注记录数:', len(coco['annotations'])) print('类别数:', len(coco['categories']))

COCO格式的json顶层固定有五个字段:info、licenses、images、annotations、categories。info和licenses是描述性元数据,跟训练没有直接关系,可以暂时不管。真正决定模型上限的是images、annotations和categories这三个字段。images里的每个元素是一张图的元信息,包括id、file_name、width、height;annotations里每个元素是一条目标标注,包含id、image_id、category_id、bbox、segmentation、area;categories就是类别表,桥梁裂缝数据集一般会定义“横向裂缝”“纵向裂缝”“网状裂缝”等细分类别,具体类别名取决于发布者的标注规范。

提示:先看categories里类别id是否从1开始连续编号。COCO官方约定类别id从1开始,但不少自建数据集从0开始,整个训练流程里所有类别映射都以这里的id为基准,一旦搞错,后续全部白训。

2.2 segmentation是裂缝检测的核心,bbox只是辅助

桥梁裂缝检测和普通目标检测有一个显著区别:裂缝是细长结构,轮廓在图像里占像素比值很小,一条裂缝可能跨越几十公分,但宽度只有几个像素。如果用bbox代表一条裂缝,框里既包含裂缝本身,也包含大量混凝土背景。模型学到的特征会被背景严重稀释,定位精度自然上不去。

COCO格式里segmentation字段弥补了这个短板,它保存裂缝的精确轮廓,主要有两种编码:polygon和RLE。桥梁裂缝数据集里绝大多数采用polygon,因为轮廓由人工标注或半自动标注工具生成;RLE多数用于分割标注图直接转换过来的数据。你可以先看任意一条标注的具体结构。

ann = coco['annotations'][0] print('标注字段:', list(ann.keys())) print('类别ID:', ann['category_id']) print('bbox:', ann['bbox']) print('segmentation类型:', type(ann['segmentation'])) print('area:', ann['area'])

polygon格式的segmentation是一个二维列表,每个子列表是一组坐标点的扁平数组,按[x1, y1, x2, y2, ...]排列。注意这是绝对像素坐标,不是归一化到[0,1]的比例值,转YOLO格式时一定要除以图像宽高。另外area字段在COCO定义里是轮廓包围的像素面积,不是bbox宽乘高,很多人在这里会混。对细长裂缝来说,bbox面积和真实面积可以差几十倍,如果转换脚本用bbox面积代替area做过滤条件,会误伤大量细长裂缝样本。所以用COCO数据前,先搞清area是怎么算出来的。

2.3 用可视化确认标注没有错位

在写任何转换代码之前,我强烈建议花几分钟把标注画出来看一眼。这一步能筛掉后面所有环节的连锁翻车。我见过数据集里有图像被EXIF旋转过,但标注坐标没有跟着旋转,直接训练出来的模型在推理阶段给出的裂缝框全部偏移到边缘,怎么看怎么别扭。可视化脚本不复杂:

import cv2 import numpy as np import matplotlib.pyplot as plt def draw_coco_annotation(img_path, ann, categories): img = cv2.imread(img_path) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 画bbox,COCO的bbox是[x, y, width, height],左上角加宽高 x, y, w, h = [int(v) for v in ann['bbox']] cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2) # 画segmentation多边形 for seg in ann['segmentation']: pts = np.array(seg, dtype=np.float32).reshape(-1, 2) cv2.polylines(img, [pts.astype(np.int32)], True, (0, 255, 0), 2) plt.figure(figsize=(12, 8)) plt.imshow(img) plt.title(f"Category: {categories.get(ann['category_id'], 'unknown')}") plt.axis('off') plt.show() img_info = coco['images'][ann['image_id']] img_path = f"images/train/{img_info['file_name']}" draw_coco_annotation(img_path, ann, {c['id']: c['name'] for c in coco['categories']})

segmentation的第一个子列表的坐标数量一定是偶数,reshape成(-1, 2)之后表示一组有序的轮廓点。传给cv2.polylines的第二个参数是点数组,第三个参数传True表示闭合多边形,对裂缝轮廓通常闭合。如果画出来绿色折线和裂缝实际位置对不上,最常见的两个原因:图像被EXIF方向信息旋转过,或者json里记录的width/height和实际图像尺寸不一致。这两个原因在第5章还会展开说。

2.4 图像id、标注id和类别id的映射关系

COCO格式里存在三套独立的id,很多人在这上面翻车。image_id是图像维度的编号,annotation的id是标注维度的编号,category_id是类别维度的编号。三者不在同一个命名空间,不能互换使用。例如不能拿annotation id去找图片,也不能拿category id直接当annotation id用。我在处理数据集时习惯先把三套映射关系构建清楚。

from collections import defaultdict image_id_to_info = {img['id']: img for img in coco['images']} category_id_to_name = {cat['id']: cat['name'] for cat in coco['categories']} anns_by_image = defaultdict(list) for ann in coco['annotations']: anns_by_image[ann['image_id']].append(ann) # 找到有标注的第一张图 first_img_id = next(iter(anns_by_image)) img = image_id_to_info[first_img_id] anns = anns_by_image[first_img_id] print(f'图像: {img["file_name"]}, 尺寸: {img["width"]}x{img["height"]}') print(f'标注数量: {len(anns)}') for a in anns: print(f' ann_id={a["id"]}, cat={category_id_to_name.get(a["category_id"], "?")}')

这里的原则是:用哪个id当字典key,就必须保证这个id在整份数据里指向同一个对象。image_id、annotation id、category_id分别在各自的空间里编顺序号,严格独立。如果某个category_id在category_id_to_name里查不到,说明categories表和annotations表对不上,要么是类别定义漏了,要么是标注时用了不存在的类别id。这类问题在自制数据集里比想象中常见,尽早发现比训练跑一半报错要好得多。

到这里先别急著作转换。先确认每张图平均有多少条标注、类别分布是否均衡。桥梁裂缝数据集的常见问题是横向裂缝远多于纵向裂缝,或者只有细裂缝没有宽裂缝,这两个分布偏差会在后面直接影响模型泛化和评价指标。

3. 解压与数据体检:zip完整性检查和标注一致性校验

3.1 zip解压不是双击就完事,先验EOCD

很多数据集zip包是从学术平台、网盘或者内部服务器下载的,文件在传输过程中被截断或损坏的情况不算罕见。如果你解压时遇到could not find EOCD或者invalid zip archive: could not find eocd这类报错,说明文件尾部的中央目录记录找不到了,常见原因是下载不完整,也有可能是服务器端文件本身已经损坏。我拿到zip包后的第一件事不是解压,而是做完整性校验,这一步能帮你省掉后面两小时的排错时间。

# Linux/macOS下校验zip完整性 unzip -t bridge_crack_dataset.zip # 或者用Python的zipfile模块 python -c "import zipfile; print(zipfile.ZipFile('bridge_crack_dataset.zip').testzip())"

unzip -t会对zip内每个文件计算CRC校验值,所有文件通过会输出ok,中途遇到损坏会直接报出文件名。ZipFile.testzip()返回第一个损坏文件的文件名,全部正常返回None。如果返回了具体的文件名,建议删掉重新下载,而不是指望某款修复工具能救回来。网络传输导致的数据损坏往往不是单个字节的问题,即便强制修复成功,后面读图时也会出现像素花屏这种更隐蔽的问题。另外要注意,如果zip包里面还嵌套了zip,Python的zipfile在解压嵌套压缩包时不会自动递归处理,需要先解压外层再单独处理内层;还有的发布者会把图像和标注放在两个zip里,这种必须保证两个包都完整,只校验其中一个是不行的。

3.2 遇到带密码的zip包怎么处理

部分桥梁裂缝数据集zip会设置密码,通常是发布者为了避免数据集被随意扩散。遇到这种情况,最直接也最正当的办法是联系原发布者获取密码。不要花时间在线搜所谓“密码移除”的第三方方案,我见过不少同行在这上面栽跟头——下载的破解工具本身就带病毒,还有的跑了一整夜也没拆开,最后还得回去找发布者。如果你已经知道密码,用Python处理非常简单:

import zipfile # 已知密码时读取zip内容 with zipfile.ZipFile('bridge_crack_dataset.zip') as zf: zf.extractall(path='./dataset', pwd=b'your_password')

注意pwd参数需要字节串而不是字符串,密码输错会抛出RuntimeError: Bad password for file。对于不知道密码的情况,联系发布者获取是唯一的正道,这个环节没有捷径可走。

3.3 图像和标注对账:数量不一致才是大问题

解压完成后,不要急着看图片,先把三件事对齐:图像文件数、json里images列表长度、annotations里image_id的去重数量。这三个数字如果对不上,说明数据集内部不一致,训练前不处理的话,会在训练过程中出现“找不到jpg”或者索引越界这类诡异报错。

import os from collections import Counter img_files = os.listdir('images/train') print('实际图像文件数:', len(img_files)) json_img_ids = set(img['id'] for img in coco['images']) print('json图像记录数:', len(json_img_ids)) ann_img_ids = set(ann['image_id'] for ann in coco['annotations']) print('有标注的图像数:', len(ann_img_ids))

三个数字对不上有两种情况。一种是图像文件数大于json记录数,说明部分图片根本没进json,这些图片可能是负样本,也可能是发布者后期加入但没来得及更新标注文件。另一种是json记录数大于实际文件数,说明zip包内文件缺失,或者file_name带子目录前缀但你漏做了路径拼接。关于file_name再多提醒一句:数据集里file_name经常带相对路径前缀,比如train/bridge_001.jpg。如果你先用os.path.join拼成images/train/bridge_001.jpg,能够正常读图;如果file_name只有bridge_001.jpg,而train和val的图片全在一个目录里,直接拼接就会把两边文件混在一起,导致后面找图时张冠李戴。先检查file_name是否带路径分隔符,再写拼接逻辑。

3.4 统计裂缝宽高比分布,决定模型选型

桥梁裂缝数据集和通用目标检测数据集有个显著差异:裂缝的长宽比极其悬殊。我见过太多项目组,拿到数据集就往YOLO里塞,训练完看mAP还行,但部署时发现模型对又细又长的裂缝完全招架不住。原因很简单:没有先做数据分布统计,模型选型错了。bbox宽高比是个便宜好用的统计量:

import numpy as np widths, heights, ratios = [], [], [] for ann in coco['annotations']: _, _, w, h = ann['bbox'] if w > 0 and h > 0: widths.append(w) heights.append(h) ratios.append(w / h) widths = np.array(widths) ratios = np.array(ratios) print('bbox宽度分位数: 10%={:.0f}, 50%={:.0f}, 90%={:.0f}'.format( np.percentile(widths, 10), np.percentile(widths, 50), np.percentile(widths, 90))) print('宽高比 > 10 的标注占比: {:.1%}'.format((ratios > 10).mean()))

如果宽高比大于10的标注占比超过一半,说明数据集以横向宽幅裂缝为主,模型需要更强的长条特征表达能力。如果bbox宽度中位数在50像素以下,说明裂缝在整图里占比很小,属于小目标检测范畴,直接整图训练的效果通常很差。常见做法是先把大图裁剪成若干个patch,在patch上做检测或分割,推理时再把预测结果映射回原图坐标。这个统计结果也影响模型架构选择:YOLOv8这类anchor-free模型对极端宽高比有一定容忍度,但仍有上限;如果数据里存在大量接近整图宽度的横向裂缝,我会优先考虑RTMDet或者带注意力机制的变体。不要盲信某个backbone在COCO上刷了多高的分,桥梁裂缝和自然生活照片完全是两个世界。

4. 从COCO转到YOLO格式:类别映射、坐标归一化和数据集划分

4.1 为什么首选转YOLO而不是直接训MMDetection

桥梁裂缝落地项目里,用YOLO系的工程师最多,原因很直接:推理速度快,部署容易,同一套导出流程可以直接落到无人机边缘设备上。但裂缝本身的细长特征又决定了,带分割头的模型才能把COCO里的segmentation信息用足,YOLOv8-seg这类模型同时具备检测和分割输出,是首选。问题在于YOLO系列原生不支持COCO的json,必须先把标注转成它的格式。转换脚本里最大的坑有两个:类别id重新编号和坐标归一化。下面这个精简脚本处理了两者。

4.2 转换脚本:COCO转YOLO检测框的最简可用版

import json import os from collections import defaultdict def coco_to_yolo(coco_path, output_dir, categories_keep=None): with open(coco_path, 'r', encoding='utf-8') as f: coco = json.load(f) os.makedirs(output_dir, exist_ok=True) # 1. 构建旧类别id到新类别id的映射,YOLO类别id从0开始 cats = coco['categories'] id_to_name = {cat['id']: cat['name'] for cat in cats} if categories_keep is not None: filtered = [cat for cat in cats if cat['name'] in categories_keep] else: filtered = cats old_to_new = {cat['id']: idx for idx, cat in enumerate(filtered)} name_to_new = {cat['name']: idx for idx, cat in enumerate(filtered)} img_id_to_info = {img['id']: img for img in coco['images']} # 2. 按图像分组,只保留需要参与训练的类别 anns_by_image = defaultdict(list) for ann in coco['annotations']: if ann['category_id'] in old_to_new: anns_by_image[ann['image_id']].append(ann) # 3. 逐张图像生成txt标签 for img_id, anns in anns_by_image.items(): img = img_id_to_info[img_id] w, h = img['width'], img['height'] if w == 0 or h == 0: continue base = os.path.splitext(os.path.basename(img['file_name']))[0] txt_path = os.path.join(output_dir, base + '.txt') lines = [] for ann in anns: new_id = old_to_new[ann['category_id']] x, y, bw, bh = ann['bbox'] # YOLO格式:类别 + 归一化中心坐标和宽高,注意bbox是左上角+x宽高 cx = (x + bw / 2) / w cy = (y + bh / 2) / h nw = bw / w nh = bh / h # 越界保护,边缘标注常见 cx = min(max(cx, 0.0), 1.0) cy = min(max(cy, 0.0), 1.0) nw = min(max(nw, 0.0), 1.0) nh = min(max(nh, 0.0), 1.0) lines.append(f'{new_id} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}') with open(txt_path, 'w', encoding='utf-8') as f: f.write('\n'.join(lines) + '\n') # 4. 生成classes.txt,顺序必须和上面重新编号一致 with open(os.path.join(output_dir, 'classes.txt'), 'w', encoding='utf-8') as f: for cat in filtered: f.write(cat['name'] + '\n') print(f'完成: {len(anns_by_image)} 张图像,类别映射: {name_to_new}')

这段脚本有几个关键点。第一,类别id必须重新编号成从0开始的连续整数,直接沿用COCO的id会错位或者多出空类别。第二,归一化必须除以json里记录的width和height,而不是运行时读图得到的尺寸,两者在旋转图像场景下可能不一致。第三,bbox坐标在COCO里是左上角x、y加宽高,YOLO需要的是中心点坐标加宽高,转换时记得加bw / 2。我没有在脚本里写太多防御分支,因为裂缝标注常见贴边的情况,越界保护那四行min-max大概率会用到。

如果你要转的是YOLOv8-seg,每行标签的格式是“类别id + 归一化后的多边形顶点”:

def coco_seg_to_yolo_seg(seg, img_w, img_h): """把COCO polygon转成YOLO分割标签格式""" coords = [] for polygon in seg: # polygon是扁平坐标列表 for i in range(0, len(polygon), 2): coords.append(polygon[i] / img_w) coords.append(polygon[i + 1] / img_h) return coords

注意转seg时要保留polygon的顶点顺序和闭合性,不要自己做过多的坐标平滑。聚类算法在超点上有时会丢顶点导致轮廓变形,裂缝的锯齿边缘是重要特征,保持原样最稳妥。

4.3 数据集划分:train、val、test别混在一起

很多桥梁裂缝数据集的zip包已经划分好了train和val,但test很少见。我习惯从val里再切出一小部分作为最终评估的hold-out集合,因为验证集在训练过程中被反复用来挑模型,存在选型偏差,用同一批数据评估最终模型会高估精度。切分时按图像id而不是按标注id,这是底线。

import random import shutil import os random.seed(42) # 从val集中随机抽20%作为test val_anns = json.load(open('annotations/instances_val.json', encoding='utf-8')) val_img_ids = [img['id'] for img in val_anns['images']] random.shuffle(val_img_ids) test_count = int(len(val_img_ids) * 0.2) test_ids = set(val_img_ids[:test_count]) # 移动对应图像到test目录 for img in val_anns['images']: if img['id'] in test_ids: src = os.path.join('images/val', img['file_name']) dst = os.path.join('images/test', img['file_name']) os.makedirs(os.path.dirname(dst), exist_ok=True) shutil.move(src, dst)

如果原数据集没有划分,我通常按0.7、0.2、0.1分配train、val、test。关键点是同一张图的多个裂缝标注必须整体属于同一个集合,按image_id划分能天然保证这一点。另外别忽略test里也要有对应的标注json,很多人在切分时只移动了图像,忘掉同步更新标注文件,导致评估时找不到ground truth。

4.4 针对裂缝的增强策略:旋转要克制

标准数据增强对裂缝数据集有一定效果,但参数需要调整。桥梁裂缝对旋转扰动非常敏感,因为裂缝在图像里的朝向本身承载类别信息——横向裂缝和纵向裂缝是不同类别。如果标注做90度旋转,横向裂缝就变成了纵向,类别标签却没变,模型会学到混乱的特征,训练损失可能一直下不去。我一般把旋转限制在±15度以内,尽量避免裂缝方向发生本质改变。马赛克增强(mosaic)也要把关,拼接后图像分辨率骤变,裂缝这类细长目标容易被切碎,尺度分布会被打乱。实际项目里我用得比较稳的增强组合是:轻度旋转、亮度扰动、对比度扰动和随机裁剪,裁剪区域尽量保留完整裂缝。如果你用的是带分割头的模型,还可以对segmentation mask做同样的仿射变换,保证输入图和mask始终对齐,这一步很多框架自带,但手工实现时别漏了。

5. 桥梁裂缝训练的避坑清单:从zip解压到训练的五条踩坑记录

5.1 现象:解压报错could not find EOCD,数据集看似到手实际是坏的

拿到数据包,执行unzip -t,直接报could not find EOCD,第一反应是文件坏了。原因是下载不完整最常见,其次是FTP传输时用了文本模式,导致二进制数据被篡改。解决:先看发布页面有没有给MD5校验值,给了就本地算一下对比,不一致立刻删掉重下,别指望修复工具。没给MD5的话,重新下载一次并换个下载源,比如回原始服务器而不是云盘转存,很多时候转存环节就已经坏了。这个坑最浪费时间的点在于:你选的别的环节排查半天,最后发现文件从源头就不完整。

5.2 现象:训练出的模型把每个裂缝都框成一个大方块

只用了bbox信息,没有用segmentation,模型输出的检测框整体偏大,一条裂缝甚至框了半个桥面。原因:桥梁裂缝本就细长,bbox宽高比动辄几十,目标相对于输入图像尺寸极小,模型在多次下采样后丢失了细节。解决:优先换YOLOv8-seg或Mask R-CNN这类有分割输出的模型,让模型直接回归裂缝轮廓。如果坚持用不带分割头的YOLO,先做切图,把大图裁成512×512的patch再训练,推理时把patch的预测框映射回原图坐标,效果远比整图训练好。这个问题容易和mAP不高混在一起,实际是两个不同的毛病:一个是框不紧,一个是压根没检出。

5.3 现象:loss不降或者看着在降,mAP始终为零

桥梁裂缝的数据里,没有裂缝的桥面图像往往占多数,正样本偏少,模型很容易把全部预测偏向背景类,loss曲线还在缓慢下降,但mAP一直为零。原因是类别不均衡,正负样本比例悬殊。解决:给正样本增加损失权重,或者把无裂缝图像单独提出来,在训练中作为负样本的hard example补充,不能直接丢弃。Focal Loss比普通交叉熵在这种数据上更稳,YOLOv8里可以调整类别损失权重;如果细分类别之间样本量差异也大,建议先合并成“裂缝”一个类训练一个基线模型,再用细分类进一步微调。先用二分类痛点最小。

5.4 现象:图像显示方向正常,标注画上去整体偏移

手机或无人机拍摄的照片可能带EXIF方向信息,图像查看器会自动旋转显示,但标注坐标是标注工具在原始像素坐标系里打的,于是挪位。原因是两套坐标系不一致。解决:在数据预处理阶段统一处理好方向,先把所有图像转正并同步更新json里的width、height和所有标注坐标,后面才做转换和训练。另一个容易混淆的原因是json里记录的尺寸和实际图像尺寸不匹配,比如原图是1920×1080,json里误写成1080×1920,归一化时坐标就会整体错位。可视化验证那一步如果跳过了,这类问题通常要到训练收敛后或部署阶段才暴露,排查成本高得多。

5.5 现象:导出ONNX部署时类别数量对不上,推理直接崩溃

训练时用了3个类别,部署配置里写的是4个或者2个,模型输出维度对不上,推理端直接报错或输出无意义结果。原因是配置一致性没管好,类别映射文件没有跟着模型一起走。解决:COCO转换时生成的classes.txt,要作为模型包的组成部分一并保存和传递。我在这上面栽过跟头:训练时忘了把自定义类别文件拷到部署端,结果无人机上跑推理输出头对不上,白白浪费了一天排查。建议在训练脚本一开始打印类别数量和类别名,部署脚本开头也同样打印一次,两边逐项对照没问题再往下走。这类问题属于典型的看代码看不出错,看配置才知道差在哪。

6. 用COCO评估脚本验证模型:从mAP到裂缝宽度的工程化度量

6.1 用pycocotools跑标准mAP

训练完模型,需要一个不掺杂感情的评估标准。pycocotools里的COCOeval能直接读原格式标注json,输出mAP、mAR等标准指标。第一步是用模型对验证集做推理,把预测结果写成一个和COCO annotations同结构的json。

from pycocotools.coco import COCO from pycocotools.cocoeval import COCOeval import json # 加载ground truth标注和模型预测 coco_gt = COCO('annotations/instances_val.json') coco_dt = coco_gt.loadRes('model_predictions.json') coco_eval = COCOeval(coco_gt, coco_dt, 'bbox') coco_eval.evaluate() coco_eval.accumulate() coco_eval.summarize() # 常用指标:IoU=0.50的mAP和IoU=0.75的mAP mAP_50 = coco_eval.stats[0] mAP_75 = coco_eval.stats[5]

预测json里每条记录至少要包含image_id、category_id、bbox、score四个字段,bbox格式仍然要写成[x, y, w, h]的绝对像素坐标。COCOeval会按score降序排列预测结果,计算不同IoU阈值下的精确率和召回率。pycocotools的坑在于对输入格式极其敏感,比如score字段必须是浮点数,bbox的每一项必须是可转成float的数值,字符串之类会给莫名其妙的错误。

6.2 裂缝场景的特殊验证:像素级IoU和宽度量化

mAP在裂缝检测项目里只能反映“框准不准”,而工程上更关心两个指标:裂缝像素级分割的IoU,以及裂缝宽度的测量误差。如果最终部署的是分割模型,可以用预测mask对比ground truth的segmentation,按像素算IoU。

import numpy as np from pycocotools import mask as coco_mask def polygon_to_mask(seg, height, width): rles = coco_mask.frPyObjects(seg, height, width) rle = coco_mask.merge(rles) return coco_mask.decode(rle).astype(bool) def compute_mask_iou(pred_mask, gt_mask): intersection = np.logical_and(pred_mask, gt_mask).sum() union = np.logical_or(pred_mask, gt_mask).sum() return intersection / union if union > 0 else 0.0

frPyObjects把COCO polygon格式转成RLE,merge合并多个孤立的轮廓,decode再解码成bool mask。如果项目要求测量裂缝宽度,比如何时灌浆、何时修补,会涉及一个后处理流程:沿分割轮廓的中线做宽度采样,统计中位数和方差。这个指标COCOeval算不出来,要自己写,但它才是最终客户真正关心的物理量。训练时mAP也许中规中矩,但宽度误差能控制在一定范围内,这个模型在业务上才算站得住。

6.3 数据质量决定模型天花板

从我自己的经验看,桥梁裂缝数据集的坑往往不在模型,而在数据这个黑匣子里。COCO格式看起来只是一个json,但每个字段背后都是业务语义。解压、对账、统计分布、可视化确认、转换格式、验证结果,每一步都值得留出时间。我曾经拿过一个标注质量还不错的裂缝数据集,跳过可视化直接训,折腾了三周才发现是类别id从0开始导致预测整体错位,而从打印类别表到肉眼发现问题其实只需要十分钟。这个教训让我养成了固定习惯:数据集到手,先写一个data_validation.py,一口气做完整性检查、对账、统计和可视化,全部通过再进训练迭代。你的数据包不一定叫这个名字,但这个流程值得复制。希望这些经验能帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/4 14:02:09

MR25H40CDF与PIC18F65K40的工业不掉电存储实现

工业现场做数据存储,最难的不是“怎么存”,而是“存了之后能不能靠得住”。很多设备跑到一半掉电、主板被电机干扰、温度升高之后数据丢了一截,这些问题比代码本身更折磨人。这篇内容我会从一次实际项目出发,讲清楚用 Everspin 的…

作者头像 李华
网站建设 2026/10/4 14:01:35

有店才敢做线上:实体店如何成为电商底气

实体店关店潮和“必须做电商”的声音喊了好几年,身边不少朋友都问我同一个问题:现在不开网店是不是就活不下去了?我的答案正好相反——这几年我观察下来,真正在线上做出成绩的,几乎都是手里先有店的人。“不开网店活不…

作者头像 李华
网站建设 2026/10/4 14:01:32

failed to load plugins web boot 报错排查:插件加载与激活机制全解

插件这个词,可能是软件世界里被问得最多的一个词。我最近在后台看到的搜索记录里,密密麻麻全是跟 plugins 相关的:有人问 "iar plugins 是干什么的",有人贴出 "failed to load plugins web boot: 2 entries did no…

作者头像 李华
网站建设 2026/10/4 14:01:25

基于Spring Boot的学科竞赛管理系统:从报名到评审的完整实现

学科竞赛管理这件事,表面上是"发通知、收报名、交作品、打分数",真做起来却是一地鸡毛。我在开发这套基于Spring Boot的学科竞赛管理系统时,最深的感受是:业务本身不复杂,复杂的是把多角色、多流程、多状态的…

作者头像 李华