简介:石头岩石检测与分割数据集面向计算机视觉研究者和工程师,收录700余张真实场景岩石图片,配套COCO格式标注,可同时支撑目标检测、实例分割与语义分割等任务。压缩包共782个文件,包括779张jpg图像与3个json标注文件,整体体积约30.68MB,便于快速下载与本地解压;json中提供了边界框、类别及像素级掩码信息,能灵活适配主流训练框架,减少数据预处理成本。数据集覆盖不同光照、视角和背景下的岩石样本,有助于增强模型泛化能力,适合地质勘探、环境监测与矿物资源分析等应用方向。目前已有604人浏览学习,对于正在入门或进阶目标检测/分割算法的开发者,可作为高质量基准数据,无需额外整理即可直接用于模型训练与效果验证。
1. 石头rock检测+分割数据集:COCO 双标注,检测和分割一次配齐
“石头rock检测+分割数据集”,从名字就能看出它的价值:同一批岩石图片,既给目标检测用的 bbox,又给语义分割用的 mask,还统一成 COCO 标注格式。这种双标注组合在地质、矿业、建筑工地的视觉项目里并不多见——多数公开数据集要么只有检测框,要么只有分割掩膜,想对比两种任务效果得自己补一套标注,费时还有标错的风险。这个包把两件事一次配齐,意味着训练管线可以共用同一批图像,做矿岩识别、碎石分选、边坡监测的团队能直接拿去跑。
这份资源适合三类人:需要快速验证岩石检测模型能不能落地的一线工程师;做语义分割、想用同一份数据对比 mask 与 bbox 效果的算法同学;以及刚接触 COCO 格式、想拿真实双标注数据练手的新手。下载之后先别急着进训练,按下面顺序把格式、统计、可视化都过一遍再动手,能省掉后面好几个小时的排错时间。
2. 看懂 COCO 双标注:目录结构、JSON 字段与两种分割编码
2.1 目录结构拆解:图片与标注文件怎么配对
解压后典型的目录组织方式是这样的:
rock_dataset/ ├── annotations/ │ ├── instances_train.json │ ├── instances_val.json │ └── instances_test.json ├── train/ ├── val/ └── test/annotations目录下放的是三个 JSON 标注文件,分别对应训练、验证、测试三个划分;train/、val/、test/目录下放对应的图片。这里有一个容易搞混的点:JSON 里的图片路径不一定和目录结构一致。有的数据集在 JSON 的file_name字段里只写文件名不带子目录,有的写着train/rock_001.jpg这种带前缀的相对路径,还有的干脆用train2017/这种 COCO 原版命名。
我拿到这一类数据集,第一步永远是打开 JSON 看file_name长什么样,而不是凭目录名猜。你可以在解压目录下直接执行:
python -c "import json; d=json.load(open('annotations/instances_train.json')); print(d['images'][0]['file_name'])"如果输出只有文件名,说明图片路径要靠代码拼接:os.path.join('train', file_name);如果输出带了子目录,拼接时就不要重复加。这里建议以 JSON 的file_name为唯一事实来源,不要用自己扫目录得到的文件名去反查,因为扫描结果通常带索引后缀或者编码问题,容易错位。
2.2 核心 JSON 字段:images、annotations、categories 三者的关系
COCO 格式的 JSON 顶层有三个数组:images、annotations、categories。images里每个元素是图片信息,关键字段是id、file_name、width、height;annotations里每个元素是一条目标标注,关键字段是id、image_id、category_id、bbox、segmentation、area、iscrowd;categories里是类别定义,通常只有id和name两个字段。
对于这份岩石数据集来说,几个字段需要重点理解:
| 字段 | 类型 | 对检测的意义 | 对分割的意义 |
|---|---|---|---|
bbox | [x, y, width, height] | 左上角坐标 + 宽高,像素单位 | 一般不用,但可用于面积比对 |
segmentation | list或dict | 检测训练忽略 | polygon 坐标列表或 RLE 编码 |
area | 浮点数 | 评估时算 mAP 权重 | 算 mask 面积,可和 bbox 面积互相印证 |
iscrowd | 0 或 1 | 标记粘连目标是否参与训练 | 同样影响训练样本是否被过滤 |
segmentation字段有两种编码方式,这是最容易踩坑的地方。如果它是list类型,比如[[x1, y1, x2, y2, ...]],这是 polygon 格式,每个子列表是一串扁平坐标,表示一个多边形轮廓;如果它是dict类型,里面带counts和size两个键,这是 RLE 格式,是像素级的压缩编码。用图形标注工具比如 LabelMe 导出的一般是 polygon,用像素级 mask 转换工具生成的多是 RLE。两者在后续转 YOLO 格式时的处理方式完全不同,下一步验证时先只判断类型,不做转换。
2.3 先用 pycocotools 做标注体检:能读是前提,读对是目标
拿到数据集先别急着写转换脚本,用 COCO 官方提供的pycocotools库把 JSON 读一遍,能发现一大批隐藏问题。这个库是 COCO 数据集的官方解析工具,用pip install pycocotools安装,Windows 上如果编译报错就装pycocotools-windows。
from pycocotools.coco import COCO # 加载训练集标注 coco = COCO('annotations/instances_train.json') # 类别信息 cats = coco.loadCats(coco.getCatIds()) print('categories:', cats) # 图片数量和标注数量 img_ids = coco.getImgIds() print('train images:', len(img_ids)) ann_ids = coco.getAnnIds() print('total anns:', len(ann_ids)) # 看第一张图的标注长什么样 img = coco.loadImgs(img_ids[0])[0] print('first image:', img['file_name'], img['width'], img['height']) first_anns = coco.loadAnns(coco.getAnnIds(imgIds=img['id'])) for ann in first_anns[:3]: print('bbox:', ann['bbox'], 'seg_type:', type(ann['segmentation']), 'crowd:', ann['iscrowd'])这段代码的逻辑是先用COCO(json_path)把整个标注文件加载进内存,然后通过getCatIds、getImgIds、getAnnIds三个接口拿索引,再按索引取具体内容。type(ann['segmentation'])这一行很关键,它直接告诉你该数据集的 mask 是 polygon 还是 RLE,后面写转换脚本时按这个类型走分支。
参数说明:getAnnIds()不传参数时返回全部标注的 ID;传imgIds=[...]按图片过滤;loadAnns接收 ID 列表返回标注字典。加载阶段要确认三件事:第一,categories里是否只有 rock 一个类别;第二,是否有图片存在但没有任何标注(这种图训练时会当背景,数量过多会拉偏 loss);第三,segmentation类型是否统一。如果同一份 JSON 里既有 polygon 又有 RLE,说明数据集可能是多个人分批标注后合并且没统一格式,必须先统一才能转 YOLO。
3. 转成 YOLO 格式:检测与分割两套转换脚本
3.1 为什么要转:YOLO 训练器读的是 txt,不是 json
COCO JSON 是集中式标注:一张图片的所有标注存在同一个文件里,按image_id索引。而 YOLO 系列的数据格式是分散式的:每张图片对应一个同名.txt文件,文件里每行是一条目标标注。Ultralytics YOLO 虽然提供了 COCO 格式转 YOLO 格式的脚本,但它默认针对 COCO 官方数据集的目录命名,放到这份岩石数据集上往往对不上路径,自己动手写转换脚本反而更可控。
转换的另一个原因是训练习惯问题。我接触的大多数做岩石视觉的团队,检测和分割都在 Ultralytics YOLO 生态里跑,因为这个框架把两种任务的训练命令收敛得几乎一致,模型权重也互相兼容。用同一份 COCO 数据集分别转成检测 txt 和分割 txt,对应训练yolov8s.pt和yolov8s-seg.pt,两条线共用同一套图片,只差标注文件,对比实验非常干净。
下面两个脚本我拆开写,方便在同一个数据集上分别生成检测标注和分割标注。
3.2 检测标注转换:COCO bbox 归一化成 YOLO 中心点格式
import json from pathlib import Path def coco_to_yolo_det(json_path, out_dir): with open(json_path, 'r', encoding='utf-8') as f: coco = json.load(f) # 把 COCO 的 category_id 映射到 0 开始的连续索引 cat_map = {c['id']: idx for idx, c in enumerate(coco['categories'])} images = {img['id']: img for img in coco['images']} anns_by_img = {} for ann in coco['annotations']: anns_by_img.setdefault(ann['image_id'], []).append(ann) out_dir = Path(out_dir) out_dir.mkdir(parents=True, exist_ok=True) for img_id, anns in anns_by_img.items(): img = images[img_id] w, h = img['width'], img['height'] lines = [] for ann in anns: cls = cat_map[ann['category_id']] x, y, bw, bh = ann['bbox'] # COCO bbox 是左上角+宽高,YOLO 需要中心点+宽高 cx = (x + bw / 2) / w cy = (y + bh / 2) / h nw = bw / w nh = bh / h # 边界裁剪,防止越界值导致训练报错 cx = min(1.0, max(0.0, cx)) cy = min(1.0, max(0.0, cy)) nw = min(1.0, max(0.0, nw)) nh = min(1.0, max(0.0, nh)) lines.append(f"{cls} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}") txt_path = out_dir / (Path(img['file_name']).stem + '.txt') txt_path.write_text('\n'.join(lines), encoding='utf-8') coco_to_yolo_det('annotations/instances_train.json', 'labels_det/train') coco_to_yolo_det('annotations/instances_val.json', 'labels_det/val')逻辑说明:脚本先把它 COCO 的category_id通过enumerate(coco['categories'])映射成从 0 开始的连续索引,再按image_id把标注分组,逐图写 txt。转换公式上,COCO 的bbox是左上角坐标加宽高,YOLO 需要的是归一化后的中心点坐标加归一化宽高,所以cx = x + bw / 2后除以图片宽度。
参数说明:cat_map的构建方式决定了类别不会错位,enumerate按 JSON 里categories数组的顺序生成 0、1、2,这要求 JSON 里的categories顺序本身是有意义的,如果原文件里类别 ID 不连续,这个映射依然可靠,因为它只依赖数组顺序。边界裁剪这段别删,YOLO 训练时如果读到超出 [0,1] 的归一化坐标,会直接报 "all indices must be within the range" 之类错误,裁掉后最多损失几个像素精度,不影响整体训练。
3.3 分割标注转换:polygon 坐标归一化与多段多边形处理
import json from pathlib import Path def coco_to_yolo_seg(json_path, out_dir): with open(json_path, 'r', encoding='utf-8') as f: coco = json.load(f) cat_map = {c['id']: idx for idx, c in enumerate(coco['categories'])} images = {img['id']: img for img in coco['images']} anns_by_img = {} for ann in coco['annotations']: anns_by_img.setdefault(ann['image_id'], []).append(ann) out_dir = Path(out_dir) out_dir.mkdir(parents=True, exist_ok=True) for img_id, anns in anns_by_img.items(): img = images[img_id] w, h = img['width'], img['height'] lines = [] for ann in anns: seg = ann['segmentation'] cls = cat_map[ann['category_id']] if isinstance(seg, list): # polygon 格式:取点数最多的多边形作为该目标的轮廓 best_poly = max(seg, key=len) if len(seg) > 0 else [] norm_coords = [] for i in range(0, len(best_poly), 2): px = min(w, max(0.0, best_poly[i])) / w py = min(h, max(0.0, best_poly[i + 1])) / h norm_coords.append(f'{px:.6f}') norm_coords.append(f'{py:.6f}') lines.append(f'{cls} ' + ' '.join(norm_coords)) elif isinstance(seg, dict): # RLE 格式:先用 pycocotools 还原 mask,再取轮廓 from pycocotools import mask as mask_utils import numpy as np mask = mask_utils.decode(seg) # 转 polygon 需要 cv2 或 skimage,这里给一个走轮廓的常见做法 try: import cv2 contours, _ = cv2.findContours( mask.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if len(contours) == 0: continue contour = max(contours, key=lambda c: cv2.contourArea(c)) pts = contour.flatten().tolist() norm_coords = [] for i in range(0, len(pts), 2): norm_coords.append(f'{pts[i] / w:.6f}') norm_coords.append(f'{pts[i + 1] / h:.6f}') lines.append(f'{cls} ' + ' '.join(norm_coords)) except ImportError: print('RLE 转换需要 opencv-python,请先安装') continue txt_path = out_dir / (Path(img['file_name']).stem + '.txt') txt_path.write_text('\n'.join(lines), encoding='utf-8') coco_to_yolo_seg('annotations/instances_train.json', 'labels_seg/train') coco_to_yolo_seg('annotations/instances_val.json', 'labels_seg/val')逻辑说明:这个脚本比检测转换复杂在两点。第一,polygon 格式下,一个segmentation的 list 里可能包含多个子多边形,表示同一个目标被分成几段轮廓,YOLO-seg 对多段多边形的支持并不好,常见做法是取点数最多的那个子多边形,这样至少保住目标的主体轮廓,而不是把不相连的轮廓强拼成一个。第二,RLE 格式没法直接写坐标,需要先用mask_utils.decode还原成二值掩膜,再用 OpenCV 的findContours提取外轮廓,最后做归一化。
参数说明:max_poly = max(seg, key=len)取的是点数最多的子多边形;mask_utils.decode(seg)是 pycocotools 官方接口,返回一个 H×W 的 0/1 数组;cv2.RETR_EXTERNAL表示只提取最外层轮廓,cv2.CHAIN_APPROX_SIMPLE压缩轮廓点数量,减少 txt 体积。注意这里用try接住了cv2未安装的情况,实际使用建议先把 opencv-python 装好再跑,我因为漏装在这卡过半小时。
3.4 转换后的文件自检
转完之后不要直接开训,先做三个快速检查。
第一,统计生成的 txt 数量是否与图片数量一致。如果不一致,通常是有些图片没有标注、被漏写,或者是file_name的 stem 重复导致文件覆盖。第二,抽查几个 txt 文件,检测格式每行应该是 5 列,分割格式每行应该是「类别 + 偶数个坐标」,列数不对基本就是 polygon 解析出了问题。第三,看数值范围是否全部在 [0,1] 区间,有大于 1 的值说明边界裁剪或归一化逻辑有漏洞。
# 统计 txt 数量 find labels_det/train -name '*.txt' | wc -l # 抽查行数列数 head -n 3 labels_det/train/rock_001.txt head -n 3 labels_seg/train/rock_001.txt这一步做扎实了,后面训练时的数据加载阶段基本不会因为标注格式翻车。
4. 训练配置:YOLOv8 检测与分割两条线怎么设
4.1 数据 YAML:类别映射和数据集路径
Ultralytics YOLO 训练前需要一份 YAML 文件描述数据集的绝对路径、类别数量和类别名称。注意这个文件里写的train、val是路径前缀,和标注 txt 的存放位置对应。
# rock.yaml path: /data/rock_dataset train: images/train val: images/val test: images/test nc: 1 names: 0: rock这段配置说明了三件事:path是整个数据集挂载的根目录,训练时会自动和train、val拼接;nc是类别数量,这份数据集只有 rock 一类,写 1;names里的 0 必须和转换脚本里的索引对齐,因为 YOLO 读取 txt 时第一列就是类别索引,它不关心你在 JSON 里的原始类别 id 是多少。
注意一个细节:YOLO 默认找标注的路径是「图片路径同级下的labels目录」。也就是说,如果train写的是images/train,训练器会去找labels/train下的同名 txt。我看到不少人在这一步不理解,把train直接写成labels_det/train,导致训练器找不到标注。正确做法是图片保持images/train,标注放在labels_det/train,然后在命令里用--label-dir或者直接把转换后的目录命名为labels/train。为避免混淆,我更推荐把转换输出目录直接命名为labels,这样 YAML 不用额外参数。
4.2 检测线:yolo detect train 参数与调法
yolo task=detect mode=train \ model=yolov8s.pt \ data=rock.yaml \ epochs=100 \ imgsz=640 \ batch=16 \ device=0 \ project=runs_det参数说明:model=yolov8s.pt是预训练权重,s 版本在岩石检测这种相对单一的场景里性价比最高,显存有限就换yolov8n.pt,想要更高精度上yolov8m.pt;epochs=100对于单类别的岩石检测通常够用,前提是你没有在 60 轮左右看到 val loss 明显回升;imgsz=640是训练输入尺寸,这份数据集如果图片分辨率普遍在 2000 以上,建议先用第 6 章的统计脚本看目标占比分布,如果目标较小再考虑imgsz=1280,但显存占用会翻到 2 到 4 倍。
有一个点容易被忽略:YOLOv8 的batch指单卡批次大小,不设--batch时默认是 16,如果你的显卡是 8GB 显存,训练imgsz=640时 16 可能直接 OOM,把batch=8甚至batch=4。还有随机种子,多跑几次对比实验时加上--seed 0防止随机性干扰结论。
4.3 分割线:yolo segment train 参数与调法
yolo task=segment mode=train \ model=yolov8s-seg.pt \ data=rock.yaml \ epochs=100 \ imgsz=640 \ batch=16 \ device=0 \ project=runs_seg分割训练和检测训练唯一的硬性区别是task=segment和model=yolov8s-seg.pt,数据 YAML 可以共用同一份。yolov8s-seg.pt是带分割头的预训练权重,它的后四行网络结构与检测版本不同,不能用yolov8s.pt替代。如果显存吃紧,分割任务比检测任务更耗内存,因为中间要多算一层 mask 分支,8GB 显卡建议直接batch=8。
这里要提醒一个问题:旧版 YOLOv5 的分割模式不支持rect批处理,但 YOLOv8 的 segment 默认也不会开 rect,所以不要手动加--rect。如果你加了,训练不会报错,但会显著降低分割精度,因为 mask 的标注在矩形批次裁剪时可能出现对齐问题。我实际测下来,分割任务老老实实让训练器自己缩放是最稳的。
4.4 训练中看哪些指标:loss 曲线和 mAP 的关系
训练过程中终端会持续打印指标,检测线重点看mAP50、mAP50-95、precision、recall,分割线额外看mask_mAP50、mask_mAP50-95。loss 曲线方面,train/box_loss、train/cls_loss、train/dfl_loss是检测的三个主要项,分割线还有train/seg_loss。
我的判断习惯是:前 30 轮如果mAP50还停留在个位数,先怀疑是标注问题而不是模型问题,回到第 3 章的自检步骤,抽 10 张图确认 mask 是否贴合岩石边缘;如果val/box_loss在第 70 轮后开始回升而train/box_loss还在下降,这是过拟合信号,处理方案是提前epochs=70或者加weight_decay=0.0005。岩石类目标纹理单一,类别间差异小,过拟合比一般场景来得早,这是这个数据集比较特殊的地方。
5. 避坑指南:COCO 标注石头数据集的五个常见翻车点
5.1 现象一:segmentation 坐标与图片尺寸对不上
训练时画的 mask 总是整体偏移,或者面积明显大于真实岩石区域。转换脚本算出来的归一化坐标看起来也在 [0,1] 内,但就是不对。
原因:最常见的是转换脚本里用了PIL读出的图片实际尺寸,而标注坐标是以 JSON 里images字段的width、height为基准生成的。两边一旦不一致,比如图片经过 EXIF 旋转、或者数据集发布前做过缩放但 JSON 没更新,polygon 坐标就会整体错位。另一个次常见原因是分割标注和检测标注来自两条标注流水线,本来就不对齐。
解决:转换和验证一律以 JSON 里的width、height为唯一基准,不要用PIL重新读图。在第 2 章的验证脚本里顺手把 JSON 尺寸和实际图片尺寸做一次全量比对,不一致的直接挑出来单独处理。从那以后我拿到 COCO 数据集的第一步必做这个尺寸核对,救过好几回。
5.2 现象二:类别 id 从 0 还是从 1 开始
检测训练完成后,预测结果里类别名对不上,rock 变成了其他类,或者 loss 一直不收敛。
原因:COCO 的category_id习惯从 1 开始,而 YOLO 的类别索引从 0 开始。如果转换脚本里直接拿ann['category_id']当 YOLO 类别索引写进 txt,所有类别会整体偏一位。对单类数据集来说,表现为类别索引变成 1 而不是 0,nc=1时训练器会直接忽略这条标注,等于所有目标都没参与训练。
解决:用enumerate(coco['categories'])构建映射字典,让 COCO id 落到连续索引上。转换之后抽查一个 txt 文件,确认第一列是 0 而不是 1。单类别数据最容易在这个问题上翻车,因为现象很不直观。
5.3 现象三:RLE 标注解析失败
加载 JSON 时报错,报错信息指向segmentation的counts字段,或者自己写的 RLE 解析代码输出乱码。
原因:RLE 分为两种,一种是 unencoded 的数组形式,一种是 compressed RLE 的 dict 形式。COCO 官方接口只认后者。有些数据导出工具会把像素级 mask 序列化成自定义的 base64 或二进制字符串,直接塞进counts,pycocotools 解析就会报错或算错。
解决:不要自己写 RLE 解码,直接用pycocotools.mask的decode、toBbox、frPyObjects这几个标准接口。如果annToMask报错,先用mask_utils.frPyObjects(ann['segmentation'], h, w)做一次对象规范化,再交给decode。RLE 解析是分割任务里最不建议手搓的部分,官方接口经过大量数据集验证,比自己 debug 省力得多。
5.4 现象四:iscrowd=1 的粘连物体被当成背景
训练完成后的 mAP 不差,但可视化时发现很多粘连在一起的岩石目标完全没有被预测出来。
原因:iscrowd=1是 COCO 里专门标记「人群/密集粘连区域」的字段,语义是这些区域里的单个目标难以分离,因此不会单独为它们计算评估指标。很多数据集的标注工具会自动把难以区分的粘连块标成iscrowd=1。岩石场景里碎石头相互堆叠,正是这类标注的高发区。如果它们在 JSON 里占比很大,等于这些样本根本没参与训练和评估。
解决:先统计iscrowd=1的占比。如果比例低于 5%,忽略即可;如果超过 10%,说明这批粘连样本本来就代表你的真实部署场景,不能丢。处理办法是把所有ann['iscrowd']强制改成 0,然后重新导出训练集。注意这种处理会让原本被屏蔽的粘连目标进入训练,初期 mAP 反而会下降,这是正常现象,等模型学到了堆叠特征会回升。
5.5 现象五:mask 面积与 bbox 面积比例异常
训练前可视化时发现,有的 mask 面积比 bbox 面积还大,有的 mask 面积接近为零而 bbox 正常。
原因:segmentation和bbox是两套独立标注时容易出现这种不一致。正常情况下 mask 面积应该小于等于 bbox 面积,且二者比值不会低于 0.3;比值接近 1 说明目标形状接近矩形;比值小于 0.1 说明 mask 标注严重缩水,多半是标注工具导出时某个环节丢了轮廓点。
解决:用第 6 章的统计脚本筛出面积比异常的样本,逐张复查。如果异常样本集中在某几张图,通常是标注质量问题,建议人工修正或删除;如果分散在全数据集,更可能是转换脚本的缩放逻辑出了问题。
6. 验证标注质量:可视化与统计体检脚本
6.1 画图:bbox 与 mask 叠加到原图
from pycocotools.coco import COCO import random import numpy as np from PIL import Image, ImageDraw coco = COCO('annotations/instances_train.json') img_ids = coco.getImgIds() sample_ids = random.sample(img_ids, 8) for img_id in sample_ids: img_info = coco.loadImgs(img_id)[0] img = Image.open(img_info['file_name']).convert('RGB') ann_ids = coco.getAnnIds(imgIds=img_id) anns = coco.loadAnns(ann_ids) overlay = img.copy() draw = ImageDraw.Draw(img, 'RGBA') for ann in anns: x, y, w, h = ann['bbox'] draw.rectangle([x, y, x + w, y + h], outline=(255, 0, 0, 255), width=3) mask = coco.annToMask(ann) color_mask = Image.new('RGBA', img.size, (0, 200, 0, 140)) mask_img = Image.fromarray((mask * 255).astype('uint8')).convert('L') overlay.paste(Image.composite(color_mask, overlay, mask_img), (0, 0), mask_img) preview = Image.blend(img, overlay, 0.4) preview.save(f'check_{img_id}.jpg')逻辑说明:coco.annToMask(ann)是官方最可靠的 mask 还原接口,不管是 polygon 还是 RLE 都会转成 H×W 的二值数组。把 bbox 画成红色框,mask 画成半透明绿色遮罩,保存后逐张看。随机抽样覆盖整个数据集而不是只看前几张,避免目录顺序带来的偏差。参数说明:random.sample(img_ids, 8)里 8 可改,第一次检查建议 20 张以上;Image.blend的 0.4 决定透明程度,数值越大遮罩越实。
6.2 统计:类别分布和面积分布
from pycocotools.coco import COCO from collections import Counter import numpy as np coco = COCO('annotations/instances_train.json') anns = coco.dataset['annotations'] cat_names = {c['id']: c['name'] for c in coco.dataset['categories']} cat_count = Counter(cat_names[ann['category_id']] for ann in anns) print('类别分布:', dict(cat_count)) widths, heights = [], [] for img in coco.dataset['images']: widths.append(img['width']) heights.append(img['height']) print('图片尺寸均值:', int(np.mean(widths)), int(np.mean(heights))) area_ratios = [] for ann in anns: img_info = coco.loadImgs(ann['image_id'])[0] img_area = img_info['width'] * img_info['height'] area_ratios.append(ann['area'] / img_area) print('目标面积占比 中位数:', np.median(area_ratios)) mask_bbox_ratios = [] for ann in anns: mask_area = coco.annToArea(ann) x, y, w, h = ann['bbox'] bbox_area = w * h if bbox_area > 0: mask_bbox_ratios.append(mask_area / bbox_area) print('mask/bbox 面积比在 [0.4, 1.0] 内的占比:', np.mean([0.4 <= r <= 1.0 for r in mask_bbox_ratios]))逻辑说明:四个统计量分别回答四个问题——类别是否均衡、整体图片分辨率、目标在画面中的尺度、mask 与 bbox 的一致性。参数说明:coco.annToArea(ann)计算的是分割面积,官方接口不区分 polygon 和 RLE;mask/bbox 面积比在 0.4 到 1.0 之间属于正常范围,低于 0.4 的样本要重点复查。面积占比中位数如果小于 0.05,说明这张数据集里目标偏小,训练时imgsz建议往 1280 靠,而不是盲目用 640。
这份体检脚本我每个 COCO 数据集都会跑一遍,它不解决训练问题,但它能把标注里的地雷提前排掉。最典型的一次是某个数据集的验证集标注文件实际是空的,训练时 mAP 一直在 0 附近,我当时第一反应是模型问题,调了两天参才发现是验证集标准错乱。从那以后我要求自己:任何标注数据进训练管线之前,先跑一遍可视化和统计体检,再谈模型、损失函数这些东西。流程虽然多花二十分钟,但能把后面几天的排错时间都省回来,希望这套检查流程也能帮到你。
本文还有配套的精品资源,点击获取