news 2026/8/31 14:18:19

石头检测+分割数据集实操:COCO格式解析与YOLO训练指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
石头检测+分割数据集实操:COCO格式解析与YOLO训练指南

简介:本资源是面向计算机视觉研究者与算法工程师的岩石目标检测与语义分割专用数据集,适用于地质勘探、环境监测及矿产资源智能识别等实际场景,尤其适合中高级CV学习者开展模型训练与泛化能力验证。压缩包共782个文件,含779张高质量岩石实景JPEG图像(涵盖多角度、多光照及复杂背景)和3个COCO格式JSON标注文件,完整提供实例分割掩码、边界框坐标及类别标签,支持目标检测、实例分割与语义分割三类任务联合训练。资源大小为30.68MB,结构精简高效,便于快速加载与本地调试。已有601人下载学习,数据集标注精细、场景多样,可直接用于YOLO系列、Mask R-CNN或SegFormer等主流模型的端到端训练与评估,显著降低岩石识别类项目的数据准备门槛。 直接拿到“石头rock检测+分割数据集.zip”这个压缩包的时候,我第一反应是:这名字取得挺直白,但里面能不能直接用,还得看两件事——一是COCO标注格式是否规范,二是检测框和分割掩码是否真的配套。这年头很多数据集标注文件是“看着像COCO,用起来全是坑”,尤其是石头这类目标,边界不规则、重叠严重、大小差异大,如果标注质量不过关,后面训练检测头和分割头都会很痛苦。所以这篇就围绕这个石头检测+分割数据集的实操,把COCO格式解析、检测与分割两个任务的协同、从解压到训练的完整过程,以及我踩过的那些坑,一次说清楚。适合正在做岩石识别、地质勘探、采矿监测、建筑工地碎石识别,或者单纯想拿真实数据集练手COCO格式转换的朋友。

1. 数据集整体设计与标注方案拆解

这个zip包的核心卖点其实不是一个“文件”,而是一整套“标注体系”。石头检测+分割,本质上是两个任务共用一份底层数据:目标检测告诉模型“哪里有石头、大概在什么范围”,语义分割告诉模型“石头的精确轮廓是什么”。两边用同一批图片、同一个COCO JSON,只是读取的字段不同。

1.1 为什么石头同时需要检测和分割

先说应用逻辑。如果只是做“石头区域圈定”,目标检测就够了,一个矩形框框住每块石头。但很多场景下矩形框不够用,比如:

  • 采矿皮带上的矿石粒度分析,需要知道每块矿石的精确面积和周长,矩形框会带来大量背景误差;
  • 地质露头照片里石头层层叠叠,矩形框互相重叠,模型没法判断哪块是前景;
  • 无人机航拍碎石堆,石头和土壤的边界是锯齿状的,只有像素级分割才能算出覆盖率。

所以很多实际项目都是“检测粗定位、分割精修”的双阶段方案。直接用一个模型同时出检测和分割结果也是可行路径,但这要求数据集必须同时提供bbox和polygon/mask标注,这正是这个zip的底气所在。

1.2 COCO格式如何同时装下两个任务

COCO标注格式最大的优点是它的annotation条目里同时存在bboxsegmentation字段。也就是说,每一个石头实例,既可以读出它的目标检测标注(矩形框坐标),也可以读出它的语义分割标注(多边形顶点或RLE编码),不需要准备两份JSON。

这也是我在实操中比较认可COCO的地方:它天然支持“一个数据集,两个任务”。相比之下,VOC格式的XML虽然也有bndbox和polygon,但工具链支持不如COCO广泛,尤其在训练分割模型时多数框架都默认转COCO或直接吃COCO。

1.3 数据集规模与标注难点

石头目标的标注难度,比常规物体(人、车、猫狗)要大不少。我拿到这类数据先做体检,发现最典型的三个坑:

  1. 石头轮廓不规则:多边形标注的顶点数量差异极大,有的几十个点,有的上百个点。标注工具导出时如果简化过多,轮廓会严重失真;
  2. 小石头占比高:很多小石头只有十几个像素宽,bbox标注尚可,但分割掩码稍不仔细就标成一条线;
  3. 背景混淆:石头和土壤、石头和碎石堆之间的颜色纹理接近,标注员容易把两块相邻的石头连成一个实例。

这些不是这个zip独有的问题,是所有岩石类数据集的共性。但好在COCO格式给了我们修正空间——所有标注都是文本化的JSON,脚本可以批量清洗,后面我会演示怎么检查。

2. COCO标注文件核心字段与读取方法

COCO标注格式是理解这个数据集的重中之重。很多朋友拿到zip后第一件事是解压,第二件事是看图片,第三件事才是看JSON。但我的习惯恰恰相反:先看JSON,再看图片。因为JSON的完整性决定数据集能不能训练。

2.1 COCO JSON骨架结构

一个标准的COCO标注文件,顶层是一个字典,包含五个关键字段:infolicensesimagesannotationscategories

其中images是图片列表,每张图片的字段有idfile_namewidthheightannotations是标注列表,每个标注包含idimage_idcategory_idbboxsegmentationareaiscrowdcategories是类别表,包含idname

我把读取COCO的脚本写成了一个通用函数,拿到任何COCO数据集都能直接复用:

import json from collections import defaultdict def load_coco_annotations(json_path): with open(json_path, 'r', encoding='utf-8') as f: coco = json.load(f) # 类别映射 categories = {} for cat in coco['categories']: categories[cat['id']] = cat['name'] # 图片id到文件名的映射 images = {} for img in coco['images']: images[img['id']] = img['file_name'] # 按图片聚合标注 img_anns = defaultdict(list) for ann in coco['annotations']: img_anns[ann['image_id']].append(ann) return coco, categories, images, img_anns coco, categories, images, img_anns = load_coco_annotations('annotations/train.json') print("类别:", categories) print("图片数量:", len(images)) print("标注数量:", len(coco['annotations']))

这段代码跑完,基本就知道数据集“值不值得继续折腾”。如果标注数量是0,或者类别映射是空的,那后面全是白费功夫。

2.2 bbox字段:检测任务的坐标系统

COCO的bbox字段格式是[x, y, width, height],注意这是绝对坐标,不是归一化坐标。xy是矩形框左上角的像素坐标,widthheight是框的宽高。

石头场景里这个字段有个经典坑:小石头的widthheight可能只有5到8个像素,如果在数据增强时随意resize,框很容易漂移或越界。我在训练检测头时一般会在数据加载环节加一个保护性clip,把超出图片边界的框拉回来。

2.3 segmentation字段:多边形与RLE两种形态

COCO的segmentation有两种写法:多边形顶点列表和RLE编码。

  • 多边形格式segmentation是一个扁平的坐标列表,[x1, y1, x2, y2, x3, y3, ...],每两个数组成一个顶点。这个格式适合单个实例的轮廓标注,石头标注重度依赖这个格式;
  • RLE格式:如果标注的iscrowd为1,或者分割对象非常复杂,COCO会采用RLE(Run-Length Encoding)压缩编码。RLE按行存储像素段的长度,解码后可以直接得到二进制掩码。

实操中遇到最多的场景,是模型训练库(如detectron2、MMDetection)会自动把polygon转成mask,不需要你手动处理。但如果自己写训练流程,这个转换要特别小心,因为COCO的polygon坐标是float类型,转成mask时要用pycocotools.maskUtils,否则可能存在1像素的偏移。

from pycocotools import mask as maskUtils import numpy as np def polygon_to_mask(polygon, height, width): rle = maskUtils.frPyObjects(polygon, height, width) mask = maskUtils.decode(rle) return mask.astype(np.uint8) * 255

2.4 area字段:看似没用其实很有用

COCO每个annotation还有一个area字段,表示实例的像素面积。很多教程都忽略它,但石头场景里它是宝贝——你可以直接用area做目标尺度的统计分析,看看自己的数据集到底是大石头多还是小石头多。

我拿到石头数据后会做这样一个统计:

areas = [ann['area'] for ann in coco['annotations']] areas.sort() print("面积最小值:", areas[0]) print("面积中位数:", areas[len(areas) // 2]) print("面积最大值:", areas[-1])

如果中位数面积只有几十像素,说明这数据集几乎全是小目标,训练时必须优先考虑小目标检测的策略,比如提高输入分辨率、使用高分辨率特征图、或者做过采样。这些参数后面训练时都会用到。

3. 实操第一步:安全解压与数据体检

打开zip之前,先做好三件事:确认压缩包完整性、确认磁盘空间、确认没有中文路径。这三点我通通踩过,尤其是“file is not a zip file”和“invalid zip archive: could not find eocd”这类报错,十有八九是压缩包下载不完整或文件损坏。

3.1 zip解压与常见报错处理

Linux环境下我习惯用unzip命令,Windows下用解压软件。但不管哪个平台,第一步永远是先测试z压缩包完整性:

unzip -t 石头rock检测+分割数据集.zip

如果输出No errors detected in compressed data,再正式解压:

unzip 石头rock检测+分割数据集.zip -d stone_dataset

如果在下载或传输过程中导致zip文件损坏,会遇到两类经典报错:

  • file is not a zip file:说明文件头不是合法的ZIP头,可能是下载成了HTML页面,也可能是文件被截断;
  • invalid zip archive: could not find eocd:EOCD(End of Central Directory Record)是zip文件结尾的目录记录,找不到它基本是文件没下载完整。

遇到这两种情况,我的建议是重新下载,而不是想办法修复。网上有些zip修复工具能强行解出部分文件,但数据集这种需要完整目录结构的场景,修复出来的文件往往缺少关键的JSON文件,得不偿失。

Windows下如果解压出来的文件名带中文乱码,一般是编码问题。zip本身不强制编码,很多国产压缩软件用GBK,而Linux的unzip默认用UTF-8,需要手动指定编码:

unzip -O gbk 石头rock检测+分割数据集.zip -d stone_dataset

3.2 图片与标注匹配性校验

解压之后,下一步是核对图片和标注是否对得上。我写了一个快速校验脚本,检查三件事:每张图片在JSON里有没有对应条目、图片是否存在、图片尺寸和JSON里记录的尺寸是否一致。

import os import json from PIL import Image json_path = 'stone_dataset/annotations/train.json' img_dir = 'stone_dataset/images' with open(json_path, 'r') as f: coco = json.load(f) mismatch = [] for img in coco['images']: file_name = img['file_name'] file_path = os.path.join(img_dir, file_name) if not os.path.exists(file_path): mismatch.append((file_name, '文件不存在')) continue w, h = Image.open(file_path).size if w != img['width'] or h != img['height']: mismatch.append((file_name, f'尺寸不一致: JSON={img["width"]}x{img["height"]}, 实际={w}x{h}')) print("检查完毕,异常数量:", len(mismatch)) for item in mismatch[:20]: print(item)

这一步能筛掉很多“阴间数据”。我遇到过有的数据集JSON里图片尺寸和实际尺寸差几个像素,平时看没啥,但训练Mask R-CNN时roi align的grid会因此产生细微的坐标偏移,导致mask边界始终差一圈。

3.3 标注质量可视化

看JSON数据只能知道“结构完整”,但标注质量必须可视化。我强烈建议每拿一个数据集,先随机抽20张图,把bbox和mask叠在原图上检查。

可视化代码用OpenCV写很简单:

import cv2 import random from pycocotools import mask as maskUtils import numpy as np random.seed(42) sample_ids = random.sample(list(img_anns.keys()), 5) for img_id in sample_ids: img_path = os.path.join(img_dir, images[img_id]) img = cv2.imread(img_path) h, w = img.shape[:2] for ann in img_anns[img_id]: # 画检测框 x, y, bw, bh = ann['bbox'] x, y, bw, bh = int(x), int(y), int(bw), int(bh) cv2.rectangle(img, (x, y), (x+bw, y+bh), (0, 255, 0), 2) # 画分割掩码 if isinstance(ann['segmentation'], dict): mask = maskUtils.decode(ann['segmentation']) else: rle = maskUtils.frPyObjects(ann['segmentation'], h, w) mask = maskUtils.decode(rle) color = np.random.randint(0, 255, 3) img[mask > 0] = img[mask > 0] * 0.5 + color * 0.5 cv2.imwrite(f'check_{img_id}.jpg', img)

这一步能很快看出标注人员是否认真:石头之间有没有粘连、mask边界是否贴合轮廓、bbox是否统一包含整个石头。如果发现大面积质量问题,可以考虑联系数据来源方,或者自己用标注工具修一部分,否则模型训练效果上限会被标注质量锁死。

4. 数据转换:COCO转YOLO格式跑检测

虽然COCO格式很通用,但直接用COCO训YOLOv8其实不是最高效的路径。YOLO生态更习惯自己的txt标注格式,每行一个目标,格式是class_id x_center y_center width height,且坐标全部归一化到0到1之间。

4.1 COCO转YOLO的完整脚本

转换的核心逻辑:从COCO JSON里读出每张图的bbox和category_id,然后归一化写入txt。下面这份脚本我实测可以直接用:

import os import json def coco_to_yolo(coco_json, output_dir, img_dir): with open(coco_json, 'r') as f: coco = json.load(f) # 建立category id映射 cat_id_map = {} for idx, cat in enumerate(coco['categories']): cat_id_map[cat['id']] = idx # YOLO从0开始 # 图片id -> 文件名 img_id_to_name = {img['id']: img['file_name'] for img in coco['images']} # 图片id -> 宽高 img_id_to_size = {img['id']: (img['width'], img['height']) for img in coco['images']} os.makedirs(output_dir, exist_ok=True) # 每张图一个txt img_anns = {} for ann in coco['annotations']: img_id = ann['image_id'] if img_id not in img_anns: img_anns[img_id] = [] img_anns[img_id].append(ann) for img_id, anns in img_anns.items(): img_name = img_id_to_name[img_id] txt_name = os.path.splitext(img_name)[0] + '.txt' txt_path = os.path.join(output_dir, txt_name) width, height = img_id_to_size[img_id] w = ann['bbox'][2] h = ann['bbox'][3] x_center = (x + w / 2) / width y_center = (y + h / 2) / height w_norm = w / width h_norm = h / height cls_id = cat_id_map[ann['category_id']] line = f"{cls_id} {x_center:.6f} {y_center:.6f} {w_norm:.6f} {h_norm:.6f}\n" with open(txt_path, 'a') as f: f.write(line) # 写类别文件 with open(os.path.join(output_dir, 'classes.txt'), 'w') as f: for cat in coco['categories']: f.write(cat['name'] + '\n') coco_to_yolo('stone_dataset/annotations/train.json', 'stone_dataset/yolo_labels', 'stone_dataset/images')

注意:YOLO格式要求归一化坐标落在0到1之间,如果bbox越界(比如石头紧贴图片边缘,标注框略微超出边界),需要在转换时做clip。上面脚本看起来没有clip,实际用的时候我在x_centery_centerw_normh_norm计算后加了一行np.clip(..., 0.0, 1.0),避免出现负数或大于1的数值导致训练报错。

4.2 划分训练集和验证集

YOLO训练需要两个目录:训练图片和对应标签、验证图片和对应标签。常规划分比例是8:1:1或9:1,石头数据集目标密集,验证集不需要太大,但也不能太小,我建议至少留10%。

划分脚本思路很简单:遍历所有图片,按比例随机分到train和val两个目录,然后把对应的txt标签也复制过去。

import os import random import shutil random.seed(0) img_dir = 'stone_dataset/images' label_dir = 'stone_dataset/yolo_labels' train_img_dir = 'stone_dataset/train/images' val_img_dir = 'stone_dataset/val/images' train_label_dir = 'stone_dataset/train/labels' val_label_dir = 'stone_dataset/val/labels' os.makedirs(train_img_dir, exist_ok=True) os.makedirs(val_img_dir, exist_ok=True) os.makedirs(train_label_dir, exist_ok=True) os.makedirs(val_label_dir, exist_ok=True) imgs = [f for f in os.listdir(img_dir) if f.endswith(('.jpg', '.png', '.jpeg'))] random.shuffle(imgs) val_count = int(len(imgs) * 0.15) val_imgs = imgs[:val_count] train_imgs = imgs[val_count:] for img in train_imgs: shutil.copy(os.path.join(img_dir, img), train_img_dir) label_name = os.path.splitext(img)[0] + '.txt' label_path = os.path.join(label_dir, label_name) if os.path.exists(label_path): shutil.copy(label_path, train_label_dir) for img in val_imgs: shutil.copy(os.path.join(img_dir, img), val_img_dir) label_name = os.path.splitext(img)[0] + '.txt' label_path = os.path.join(label_dir, label_name) if os.path.exists(label_path): shutil.copy(label_path, val_label_dir) print("训练集图片数:", len(train_imgs)) print("验证集图片数:", len(val_imgs))

4.3 YOLOv8训练命令与参数经验

用Ultralytics YOLOv8训练时,先写一个data.yaml:

path: stone_dataset train: train/images val: val/images names: 0: rock

然后启动训练:

yolo detect train data=stone_dataset/data.yaml model=yolov8s.pt epochs=100 imgsz=640 batch=16 device=0

石头数据集有几个特别值得调的点:

  1. imgsz不要设太小。石头普遍是小目标,640起步,如果硬件允许可以试1280。我用640和1280对比过,小石头的recall差距能到10个点以上。当然显存不够的话,batch可以降到8,但imgsz别降;
  2. epochs不要照搬默认。石头目标形状单一,模型收敛很快,50到100个epoch基本就够。我见过有人直接跑300个epoch,结果后面全是过拟合,验证集mAP反而往下掉;
  3. augmentation要适度。YOLOv8默认的增强里,hsv_hhsv_shsv_v对石头这种颜色单一的目标影响不大,但degrees(旋转)和shear(错切)要小心,因为石头在自然场景里朝向是任意的,过强的旋转增强会让小石头变成斜条,反而误导模型。

5. 分割模型训练:从polygon到mask

检测模型搞定后,再来看语义分割任务。这里要注意,COCO的annotation是“实例分割”格式,也就是说一个石头一个标注对象。如果不做处理直接拿去训“语义分割”模型,通常会得到每个石头单独的mask,而不是统一的前景/背景图。两种方案:

  • 方案A:把COCO实例标注合并成单通道语义标签,每个像素取值0(背景)或1(石头),适合二分类语义分割;
  • 方案B:保留每个实例的独立mask,直接训实例分割模型(如Mask R-CNN、SOLO),输出每个石头的单独mask。

这个zip叫“语义分割+目标检测标注”,大概率是想跑方案A,用COCO的polygon生成语义分割标签。下面重点讲方案A。

5.1 从COCO polygon生成语义分割mask

把polygon转成语义分割标签的核心操作分成三步:创建全零的单通道图、把该图里所有实例的多边形内部填充为类别ID、保存为PNG或npy文件。

import os import json import numpy as np from PIL import Image, ImageDraw from pycocotools import mask as maskUtils json_path = 'stone_dataset/annotations/train.json' mask_dir = 'stone_dataset/masks' os.makedirs(mask_dir, exist_ok=True) with open(json_path, 'r') as f: coco = json.load(f) img_id_to_info = {img['id']: img for img in coco['images']} anns_by_img = {} for ann in coco['annotations']: img_id = ann['image_id'] if img_id not in anns_by_img: anns_by_img[img_id] = [] anns_by_img[img_id].append(ann) for img_id, anns in anns_by_img.items(): img_info = img_id_to_info[img_id] h, w = img_info['height'], img_info['width'] mask = np.zeros((h, w), dtype=np.uint8) for ann in anns: cat_id = ann['category_id'] # polygon转mask seg = ann['segmentation'] if isinstance(seg, dict): # RLE格式先decode m = maskUtils.decode(seg) else: rle = maskUtils.frPyObjects(seg, h, w) m = maskUtils.decode(rle) mask[m > 0] = cat_id # 同一区域被多个实例覆盖时,后面的会覆盖前面的 out_path = os.path.join(mask_dir, os.path.splitext(img_info['file_name'])[0] + '.png') Image.fromarray(mask).save(out_path) print("mask生成完成")

这个脚本有个细节:当两个石头实例的polygon重叠时,后面的标注会覆盖前面的。这在COCO里不算合理,但实际标注中确实可能出现。如果你想避免覆盖,可以在循环里用mask[m > 0]判断是否已填充。

实际使用中,我更推荐用np.max叠加而不是直接赋值:

overlay = np.zeros((h, w), dtype=np.uint8) for ann in anns: m = decode_polygon(ann) overlay = np.maximum(overlay, m * cat_id)

这样即使有重叠,也不会漏掉任何类别。

5.2 用segmentation-models-pytorch训练语义分割

生成mask后,就可以训练语义分割模型了。我最常用的是segmentation-models-pytorch,它封装了Unet、FPN、DeepLabV3等主流架构,配合torchvision的数据流很顺手。

训练脚本的核心分为几个部分:数据加载、模型定义、损失函数、训练循环。这里给出一个精简版,重点是能跑通:

import torch import torch.nn as nn from torch.utils.data import Dataset, DataLoader import segmentation_models_pytorch as smp from PIL import Image import numpy as np import os class StoneDataset(Dataset): def __init__(self, img_dir, mask_dir, img_size=(512, 512)): self.img_dir = img_dir self.mask_dir = mask_dir self.img_size = img_size self.images = [f for f in os.listdir(img_dir) if f.endswith(('.jpg', '.png', '.jpeg'))] def __len__(self): return len(self.images) def __getitem__(self, idx): img_name = self.images[idx] img_path = os.path.join(self.img_dir, img_name) mask_path = os.path.join(self.mask_dir, os.path.splitext(img_name)[0] + '.png') img = Image.open(img_path).convert('RGB').resize(self.img_size) mask = Image.open(mask_path).resize(self.img_size).convert('L') img = np.array(img).astype(np.float32) / 255.0 mask = np.array(mask).astype(np.int64) # 类别标签 # 转成CHW + tensor img = torch.from_numpy(img).permute(2, 0, 1) mask = torch.from_numpy(mask) return img, mask train_dataset = StoneDataset('stone_dataset/train/images', 'stone_dataset/masks_train') train_loader = DataLoader(train_dataset, batch_size=8, shuffle=True, num_workers=4) model = smp.Unet( encoder_name='resnet34', encoder_weights='imagenet', in_channels=3, classes=2 # 背景 + 石头 ) loss_fn = nn.CrossEntropyLoss() optimizer = torch.optim.Adam(model.parameters(), lr=1e-4) for epoch in range(30): model.train() total_loss = 0 for img, mask in train_loader: optimizer.zero_grad() output = model(img) loss = loss_fn(output, mask) loss.backward() optimizer.step() total_loss += loss.item() print(f'Epoch {epoch+1}, Loss: {total_loss/len(train_loader):.4f}') torch.save(model.state_dict(), 'rock_seg_unet.pth')

5.3 分割模型的关键指标与评估

分割模型的评估指标不是accuracy,而是IoU(交并比)和mIoU。对于二分类石头分割,重点是IoU和Dice系数。石头占画面比例通常不大,如果只看accuracy,模型全部预测背景也能做到90%以上的accuracy,但没有任何实际意义。

评估时我会用torchmetrics里的JaccardIndex

from torchmetrics import JaccardIndex jaccard = JaccardIndex(task='multiclass', num_classes=2) model.eval() ious = [] with torch.no_grad(): for img, mask in val_loader: output = model(img) pred = output.argmax(dim=1) ious.append(jaccard(pred, mask)) print("mIoU:", torch.mean(torch.tensor(ious)).item())

一般来说,石头数据集的mIoU能到0.75以上就算不错。如果低于0.6,多半是标注问题,或者训练集太小。

6. 常见问题与排查技巧实录

训练数据集的过程远不是“解压—转换—训练”这么顺利。下面这些问题,是我在不同石头数据集上反复遇到的,单独拎出来分享一下。

6.1 解压与文件损坏问题速查

报错信息原因解决办法
file is not a zip file文件头损坏或下载错误删除后重新下载,不要用修复工具
invalid zip archive: could not find eocd压缩包下载不完整检查下载大小,重新下载完整压缩包
中文乱码zip编码与系统不匹配Linux解压加-O gbk,或用Windows解压软件
解压后缺少JSON文件压缩包本身缺失或被杀毒软件隔离检查压缩包内的完整目录,必要时联系数据源

6.2 标注类别不平衡:石头少、土壤多

语义分割任务里最常见的坑是类别不平衡。石头区域可能只占整张图的5%到10%,而背景占了90%以上。直接训练CrossEntropyLoss,模型很容易倾向于全预测背景。

我的解决办法有三个:

  1. 使用加权损失nn.CrossEntropyLoss(weight=torch.tensor([0.3, 1.0])),把前景权重调高;
  2. 用Dice Loss或Focal Loss:Dice Loss天然缓解类别不平衡,smp.losses.DiceLoss可以直接用;
  3. 裁剪采样:只裁剪包含石头的区域作为训练样本,背景比例自然下降。

6.3 小目标与石头重叠问题

石头数据集里,大量目标是小于32x32像素的小目标,还有不少石头互相堆叠,边界模糊。我在检测和分割模型上都遇到了这个问题。

处理小目标比较有效的策略:

  • 检测头:把训练分辨率提到1280,P2特征层(80x80)对小石头响应更强;
  • 分割头:使用带空洞卷积的DeepLabV3,或多尺度特征融合结构,对小目标更友好;
  • 数据增强:随机crop成512x512后再训练,变相放大石头在画面中的占比。

6.4 COCO转YOLO时标签错位

这种问题最隐蔽。转换脚本写完跑完,classes.txt的顺序和cat_id_map的顺序不一致,模型训练时就会把所有类别错位分类。石头数据集如果只有一个类别(rock),这个问题不明显;一旦有多个类别(比如rock、crack、water),就会非常致命。

我的习惯是每次转完,随机抽一张图做可视化,把txt里的bbox框在原图上,人工确认框和类别是否对应。只有人工看过图才放心交给训练脚本。

7. 基于石头数据集的一些后续扩展思路

这个zip数据集跑通检测和分割之后,其实可以发散出很多衍生价值。我把自己做过的几个方向列出来,供参考。

7.1 用SAM大模型辅助标注和精修

现在做石头分割,很多团队已经不再纯手工标注了。SAM(Segment Anything Model)这类大模型可以先出初步mask,再人工修正。如果这个数据集中有标注不准确的轮廓,可以先用石头区域的边界框作为prompt,让SAM生成精细的石头轮廓,再结合人工检查,能大幅提升标注效率。

但需要注意,SAM有存在“对粘连物体过度分割不足”的问题,石头的密集场景中,SAM经常会生成“伪分割”,把相邻的两块石头合并、或者把阴影当作石头。所以它只能作为辅助工具,不能直接拿走做最终标注。

7.2 迁移到航拍或无人机场景

岩石检测方案在无人机巡检、矿山边坡监测、地质灾害评估中很常见。如果后续拿到无人机航拍的石头/碎石堆图像,可以用这套数据集的预训练权重做fine-tune,比从零开始训收敛快很多。

实操流程很简单:先用本数据集的权重初始化模型,再加载少量航拍数据,设置较低的学习率(如1e-5)继续训练10到20个epoch。石头纹理在图像尺度上有较大差异,但边缘结构等基础特征是可迁移的。

7.3 从岩石分割到粒度分析

严格来说,这个数据集本身是“图像识别”项目,但在工程应用中,它真正服务的是“岩石粒度分析”——通过分割结果的每个连通域,统计每块石头的像素面积,转换成长度尺寸,进而得到粒度分布曲线。

具体做法是:模型分割输出后,用OpenCV的连通域分析提取每个石头的轮廓和面积,再结合图片中已知的参考物尺寸(比如标尺)换算成现实尺寸。这个方向本质上就是把“分割结果”往“量化分析”推进了一步,价值比单单做一个识别模型大很多。

最后说点实在的

每次拿到COCO标注的数据集,我最深的体会是:标注格式只是数据集的皮,标注质量才是数据集的魂。这个石头rock检测+分割数据集zip,从文件名看是标准的COCO格式,检测和分割标注都齐备,已经能直接支撑两个任务的训练。但真正用起来的顺畅程度,完全取决于前期有没有把数据体检做扎实。我在解压后花了整整半天做可视化检查,把不合理的标注样本全部清理掉,后面训练省下来的时间远不止半天。

另外想提醒一句:如果你是在Windows下解压再拷贝到Linux服务器训练,尽量保持目录结构一致,不要在中间环节手动改名,否则图片路径和JSON里的file_name一旦对不上,报错排查起来相当糟心。希望这篇能帮你把石头数据集的坑提前填平。

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

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

世界基础模型与后训练:从VLM推理到合成数据生成实战

在物理 AI 概念逐渐落地的过程中,大家讨论最多的不再是“能不能训一个大模型”,而是“怎么让大模型真正理解物理世界、服务具体业务”。NVIDIA 的 Cosmos 3 正是围绕这个需求推出的世界基础模型(World Foundation Model,WFM&#…

作者头像 李华
网站建设 2026/8/31 14:16:26

Python OpenCV照片转卡通:双边滤波+自适应阈值+K-means全解析

简介:本资源是一套基于Python实现的photo-to-cartoon卡通化图像转换系统源码,面向图像处理初学者、计算机视觉爱好者及轻量级艺术创作需求者,解决真人照片自动转为卡通风格图像的技术落地问题,适用于社交头像生成、游戏原画预处理…

作者头像 李华
网站建设 2026/8/31 14:15:44

小红书校招算法笔试解析:从KMP到聚类与推荐系统

小红书2020校招算法笔试题卷三,算是一套在社区里流传比较广的题目。前阵子有学弟准备秋招,翻出这套题来问我哪些知识点必须吃透,我又把它整体过了一遍。说实话,这套卷子的风格很典型:不考偏门怪题,而是把数…

作者头像 李华
网站建设 2026/8/31 14:12:42

基于AI Agent的个性化信息流系统:原理与实战

最近总能在各种技术群里看到类似“算法推荐把我困在信息茧房里了”的吐槽。刷 B 站全是重复的影视解说,打开小红书全是广告软文,油管和推特更是被同质化内容塞满。平台推荐算法的核心目标并不是“让你看到你真正想看的”,而是“让你停留更久”…

作者头像 李华
网站建设 2026/8/31 14:09:23

AI Agent控制硬件设备:用Plumbing Spec构建标准化管道层

在实验室里让 AI 直接操作设备,最让人头疼的往往不是模型选型,而是设备之间五花八门的通信协议。你在 Agent 侧设计得很漂亮,结果到了设备端,有的走 Modbus,有的走 HTTP,有的只能通过串口读数据&#xff0c…

作者头像 李华
网站建设 2026/8/31 14:03:31

Java实现六爻起卦排盘小程序:从随机算法到规则引擎实战

简介:本资源是一套基于Java实现的六爻起卦排盘小程序完整源码,面向对周易文化与编程实践交叉领域感兴趣的开发者、传统文化爱好者及高校计算机专业学生,旨在解决传统六爻占卜手工起卦繁琐、解读门槛高、缺乏数字化工具支持等问题。压缩包共17…

作者头像 李华