从零到一:构建专属实例分割数据集的实战指南

如果你正在踏入计算机视觉领域,尤其是目标检测和实例分割方向,那么你很快就会意识到一个残酷的现实:模型的好坏,很大程度上取决于你喂给它的数据质量。无论是学术界的研究,还是工业界的落地项目,一个高质量、标注精准的数据集往往是成功的一半。然而,对于许多初学者甚至是有一定经验的开发者来说,从环境搭建到数据标注,再到格式转换,这一系列流程中布满了大大小小的“坑”。今天,我们不谈空洞的理论,直接上手实战,用最清晰、最接地气的方式,带你完整走一遍创建实例分割数据集的全部流程。你会发现,只要工具选对、步骤清晰,这件事并没有想象中那么复杂。

我们将聚焦于两个核心工具:Anacondalabelme。Anaconda 能为我们提供一个干净、隔离的 Python 环境,避免各种依赖包冲突的噩梦;而 labelme 则是一个直观、强大的图形化标注工具,特别适合多边形标注任务。我们的目标不仅仅是完成标注,更要生成业界通用的 COCOVOC 格式数据集,为后续使用 PyTorch、TensorFlow 等主流框架训练模型铺平道路。无论你是时间紧迫的学生,还是需要快速验证想法的工程师,这篇指南都将为你节省大量摸索的时间。

1. 基石:快速搭建无污染的 Python 工作环境

在开始任何机器学习项目之前,建立一个独立、可控的 Python 环境是至关重要的第一步。这能确保项目依赖的库版本固定,不会与其他项目相互干扰,也便于复现和分享。Anaconda 正是管理环境的神器。

1.1 Anaconda 的安装与初识

如果你还没有安装 Anaconda,访问其官方网站下载对应操作系统的安装包即可。安装过程基本是“下一步”到底,这里不再赘述。安装成功后,你会在开始菜单或应用程序中找到 Anaconda Navigator(图形界面)和 Anaconda Prompt(命令行工具)。对于追求效率和明确性的开发者,我强烈推荐使用 Anaconda Prompt,它直接集成了 conda 命令的环境。

打开 Anaconda Prompt,你会看到一个类似命令行的窗口。首先,我们可以查看一下当前已有的环境:

conda env list

通常,会有一个名为 base 的根环境。我们所有的操作都尽量不要在 base 环境中进行,以免污染它。

1.2 创建专属的标注环境

接下来,我们为 labelme 标注任务创建一个全新的虚拟环境。假设我们使用 Python 3.8(这是一个兼容性较好的版本),环境命名为 labelme_env

conda create -n labelme_env python=3.8

执行命令后,conda 会解析并列出将要安装的包,输入 y 确认即可。这个过程会从镜像源下载必要的 Python 和基础包。

环境创建成功后,需要激活它才能使用:

conda activate labelme_env

激活后,命令行提示符的开头通常会从 (base) 变为 (labelme_env),这表示你已经进入了这个独立的环境。接下来所有包的安装都只会影响当前环境。

1.3 安装 labelme 及其核心依赖

labelme 的安装看似简单,但其中有一些依赖关系需要注意,否则可能会在后续使用或格式转换时报错。我们将采用分步安装,确保稳定性。

首先,安装图形界面框架 PyQt5 和图像处理库 Pillow:

conda install pyqt
conda install pillow

这两个是 labelme 运行时的核心依赖。安装过程中同样确认即可。

然后,通过 pip 安装 labelme 本身。这里建议使用国内镜像源以加速下载,例如清华源:

pip install labelme -i https://pypi.tuna.tsinghua.edu.cn/simple

安装完成后,可以输入 labelme 命令来验证是否成功。如果弹出一个图形界面窗口,恭喜你,环境搭建已经成功了!

注意:有时可能会遇到 pycocotools 安装失败的问题,这通常在后续转换 COCO 格式时需要。如果遇到,可以尝试 pip install pycocotools-windows(Windows)或根据系统搜索特定安装方法。

2. 核心操作:使用 labelme 进行高效图像标注

环境就绪,现在进入最关键的环节——标注。labelme 的界面简洁,但熟练掌握其技巧能极大提升标注效率。

2.1 启动与界面导览

在激活的 labelme_env 环境中,直接输入命令启动:

labelme

主界面主要分为几个区域:

  • 菜单栏和工具栏:提供文件、编辑、视图等操作。
  • 图像显示区:中央主要区域,用于显示和标注图片。
  • 文件列表区:左侧或右侧,显示已加载目录下的图片。
  • 形状/标签列表区:显示当前图片上已标注的所有多边形及其标签。

首次使用时,建议通过 File -> Open Dir 打开包含所有待标注图片的文件夹,这样可以在文件列表中快速切换图片,实现流水线作业。

2.2 实例分割标注实战技巧

实例分割要求我们不仅标出物体所在的区域,还要区分开同一个类别的不同个体。例如,图片中有三只猫,我们需要为每一只猫单独标出一个多边形。

标注步骤:

  1. 点击左侧工具栏的 Create Polygon(创建多边形)按钮,或按快捷键 Ctrl + P
  2. 在图像上,沿着目标的边缘依次点击,形成一系列的点。尽量让点贴近物体边缘,对于弯曲部分可以多点几个点以提高精度。
  3. 当点形成一个闭合区域时(通常最后一个点靠近第一个点),双击或按回车键完成当前多边形的绘制。
  4. 随即会弹出标签输入框。为这个实例输入一个类别名,例如 cat
  5. 重复步骤1-4,为图中的下一个实例(如另一只猫)进行标注,并使用相同的类别名 cat

提升效率与质量的要点:

  • 快捷键:熟练使用快捷键是提速的关键。
    • Ctrl + S:保存当前标注(生成同名的 .json 文件)。
    • Ctrl + Shift + S:保存并跳转到下一张。
    • D:下一张图片。
    • A:上一张图片。
    • Ctrl + Z:撤销上一个点或多边形。
    • Del:删除选中的多边形。
  • 标签统一:确保同一类物体的标签名称完全一致(大小写敏感),例如全部用 cat,而不是混用 catCatcat_1
  • 复杂物体处理:对于结构复杂的物体(如树枝交错的树木),可以将其分解为多个多边形进行标注,labelme 会自动将它们归属于同一个实例(通过相同的标签名和后续的组ID管理,但简单情况下,同标签即被视为同一实例需谨慎,最好通过“编辑形状”确保group_id一致以区分实例)。
  • 遮挡处理:物体被部分遮挡时,只需标注其可见部分。

完成一个文件夹内所有图片的标注后,你会得到一系列与图片同名的 .json 文件。每个 JSON 文件都包含了对应图片的路径、尺寸以及所有多边形顶点的坐标和标签信息。

3. 格式转换:从 labelme JSON 到标准数据集

原始的 .json 文件是 labelme 的专有格式,绝大多数训练框架无法直接使用。我们需要将其转换为通用的数据集格式,最主流的是 PASCAL VOCMS COCO

3.1 准备工作:整理文件结构

在开始转换前,建立一个清晰的项目目录至关重要。假设你的项目根目录为 my_seg_project,建议按如下方式组织:

my_seg_project/
├── data_annotated/          # 原始标注数据
│   ├── image1.jpg
│   ├── image1.json
│   ├── image2.jpg
│   └── image2.json
├── labels.txt              # 标签列表文件
├── labelme2voc.py          # 转换脚本 (VOC格式)
└── labelme2coco.py         # 转换脚本 (COCO格式)

labels.txt 文件的内容有严格格式要求:

__ignore__
_background_
cat
dog
person
  • 第一行必须是 __ignore__
  • 第二行必须是 _background_
  • 从第三行开始,每行一个你的实际类别名称,顺序将决定类别ID(从0开始,背景为0,你的第一个类别cat为1,以此类推)。

3.2 获取转换脚本

labelme 官方并未直接提供完整的转换脚本,但社区有成熟的方案。你可以直接使用我们下面提供的代码,将其保存为对应的 .py 文件。

labelme2voc.py 脚本:

#!/usr/bin/env python
"""
将 labelme 标注的 JSON 文件转换为 PASCAL VOC 格式的数据集。
"""
import argparse
import glob
import json
import os
import os.path as osp
import sys
import numpy as np
import labelme
import imgviz

def main():
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter
    )
    parser.add_argument("input_dir", help="输入标注目录(包含jpg和json)")
    parser.add_argument("output_dir", help="输出VOC格式数据集目录")
    parser.add_argument("--labels", help="标签文件路径", required=True)
    parser.add_argument("--noviz", help="不生成可视化预览图", action="store_true")
    args = parser.parse_args()

    # 创建输出目录结构
    os.makedirs(args.output_dir, exist_ok=True)
    os.makedirs(osp.join(args.output_dir, "JPEGImages"), exist_ok=True)
    os.makedirs(osp.join(args.output_dir, "SegmentationClass"), exist_ok=True)
    os.makedirs(osp.join(args.output_dir, "SegmentationClassPNG"), exist_ok=True)
    if not args.noviz:
        os.makedirs(osp.join(args.output_dir, "SegmentationClassVisualization"), exist_ok=True)
    os.makedirs(osp.join(args.output_dir, "SegmentationObject"), exist_ok=True)
    os.makedirs(osp.join(args.output_dir, "SegmentationObjectPNG"), exist_ok=True)
    if not args.noviz:
        os.makedirs(osp.join(args.output_dir, "SegmentationObjectVisualization"), exist_ok=True)

    # 读取标签
    class_names = []
    class_name_to_id = {}
    with open(args.labels, 'r') as f:
        for i, line in enumerate(f):
            class_id = i - 1
            class_name = line.strip()
            class_name_to_id[class_name] = class_id
            if class_id == -1:
                assert class_name == "__ignore__"
                continue
            elif class_id == 0:
                assert class_name == "_background_"
            class_names.append(class_name)

    print("Class names:", class_names)

    # 处理每个JSON文件
    for filename in glob.glob(osp.join(args.input_dir, "*.json")):
        print("Processing:", filename)
        label_file = labelme.LabelFile(filename=filename)
        base = osp.splitext(osp.basename(filename))[0]

        # 路径定义
        out_img_file = osp.join(args.output_dir, "JPEGImages", base + ".jpg")
        out_cls_file = osp.join(args.output_dir, "SegmentationClass", base + ".npy")
        out_clsp_file = osp.join(args.output_dir, "SegmentationClassPNG", base + ".png")
        if not args.noviz:
            out_clsv_file = osp.join(args.output_dir, "SegmentationClassVisualization", base + ".jpg")
        out_ins_file = osp.join(args.output_dir, "SegmentationObject", base + ".npy")
        out_insp_file = osp.join(args.output_dir, "SegmentationObjectPNG", base + ".png")
        if not args.noviz:
            out_insv_file = osp.join(args.output_dir, "SegmentationObjectVisualization", base + ".jpg")

        # 保存原图
        img = labelme.utils.img_data_to_arr(label_file.imageData)
        imgviz.io.imsave(out_img_file, img)

        # 将形状转换为标签矩阵
        cls, ins = labelme.utils.shapes_to_label(
            img_shape=img.shape,
            shapes=label_file.shapes,
            label_name_to_value=class_name_to_id,
        )
        ins[cls == -1] = 0  # 忽略区域处理

        # 保存语义分割标签(类别)
        labelme.utils.lblsave(out_clsp_file, cls)
        np.save(out_cls_file, cls)
        if not args.noviz:
            clsv = imgviz.label2rgb(
                cls, imgviz.rgb2gray(img), label_names=class_names, font_size=15, loc="rb"
            )
            imgviz.io.imsave(out_clsv_file, clsv)

        # 保存实例分割标签(个体)
        labelme.utils.lblsave(out_insp_file, ins)
        np.save(out_ins_file, ins)
        if not args.noviz:
            instance_ids = np.unique(ins)
            instance_names = [str(i) for i in range(max(instance_ids) + 1)]
            insv = imgviz.label2rgb(
                ins, imgviz.rgb2gray(img), label_names=instance_names, font_size=15, loc="rb"
            )
            imgviz.io.imsave(out_insv_file, insv)

    print("VOC format dataset created successfully at:", args.output_dir)

if __name__ == "__main__":
    main()

labelme2coco.py 脚本: (由于代码较长,原理是类似的,它会生成一个 annotations.json 文件,遵循 COCO 的格式规范,包含 images, annotations, categories 三个主要字段。你可以从 labelme 的 GitHub 仓库示例中找到完整可用的脚本,或者使用一些成熟的第三方封装库如 labelme2coco。)

3.3 执行转换命令

将两个脚本文件保存到你的项目根目录后,打开 Anaconda Prompt,激活你的 labelme_env 环境,并导航到项目目录。

转换为 VOC 格式:

python labelme2voc.py data_annotated data_dataset_voc --labels labels.txt
  • data_annotated: 你的原始标注数据文件夹。
  • data_dataset_voc: 输出的 VOC 格式数据集文件夹(会自动创建)。
  • --labels labels.txt: 指定标签文件。

转换为 COCO 格式:

python labelme2coco.py data_annotated data_dataset_coco --labels labels.txt
  • data_dataset_coco: 输出的 COCO 格式数据集文件夹。

运行命令后,终端会滚动显示处理过程。完成后,你会在项目目录下看到新生成的 data_dataset_vocdata_dataset_coco 文件夹。

4. 成果验收与后续应用指南

转换成功后,让我们看看生成了什么,以及如何将这些数据用于接下来的模型训练。

4.1 生成的数据集结构解析

VOC 格式输出 (data_dataset_voc):

data_dataset_voc/
├── JPEGImages/                 # 复制过来的原图
├── SegmentationClass/          # 语义分割标签 (.npy格式)
├── SegmentationClassPNG/       # 语义分割标签 (.png格式,可视化用)
├── SegmentationClassVisualization/ # 语义分割叠加原图的可视化
├── SegmentationObject/         # 实例分割标签 (.npy格式)
├── SegmentationObjectPNG/      # 实例分割标签 (.png格式)
└── SegmentationObjectVisualization/ # 实例分割叠加原图的可视化

VOC 格式将类别(Semantic)和实例(Object)分开存储,.npy 是 NumPy 数组格式,供程序读取;.png 是图像格式,方便人工检查。

COCO 格式输出 (data_dataset_coco):

data_dataset_coco/
├── annotations.json            # 核心:包含所有图像信息、标注信息、类别信息的JSON文件
├── JPEGImages/                 # 复制过来的原图
└── Visualization/              # (可选)标注可视化图片

COCO 格式将所有标注信息集中在一个 annotations.json 文件中,结构清晰,是当前很多研究论文和竞赛的首选格式。

4.2 在主流框架中加载使用

现在,你的数据集已经准备好了。以下是如何在常见框架中加载它们:

使用 PyTorch 和 torchvision 加载 COCO 格式:

from torchvision.datasets import CocoDetection
import torchvision.transforms as transforms

# 定义转换
transform = transforms.Compose([
    transforms.ToTensor(),
])

# 创建数据集实例
coco_dataset = CocoDetection(
    root='./data_dataset_coco/JPEGImages',
    annFile='./data_dataset_coco/annotations.json',
    transform=transform
)

# 获取第一张图片和标注
img, target = coco_dataset[0]
print(f"Image size: {img.shape}")
print(f"Number of annotations: {len(target)}")
for ann in target:
    print(f"  Category ID: {ann['category_id']}, BBox: {ann['bbox']}")

使用 TensorFlow 加载 VOC 格式(语义分割示例): 需要先将 PNG 标签文件处理成模型需要的格式,通常涉及读取图像和建立映射表。

4.3 常见问题排查与优化建议

在整套流程中,你可能会遇到一些典型问题:

  • ModuleNotFoundError: No module named 'pycocotools' 在运行 COCO 转换脚本时出现。解决方法:pip install pycocotools。在 Windows 上如果失败,可以尝试 pip install pycocotools-windows

  • 标注时点错了怎么办? 在 labelme 中,你可以用鼠标点击选中已画好的多边形,然后拖动其顶点进行修正,或按 Del 键删除整个多边形。

  • 转换后的可视化图全是黑色或颜色奇怪? 检查 labels.txt 文件格式是否正确,特别是前两行。可视化图的颜色是自动分配的,黑色可能表示所有像素都被归为背景(_background_,ID=0)。

  • 如何划分训练集和验证集? 在转换之前,将 data_annotated 文件夹下的图片和 JSON 文件按比例(如 8:2)移动到 trainval 子文件夹中。然后分别对这两个文件夹运行转换脚本,生成对应的 trainval 数据集。最后,在 COCO 的 annotations.json 中,需要手动或在脚本中为每个 image 对象添加 subset 字段(如 "subset": "train"),或者更常见的做法是生成两个独立的 annotations_train.jsonannotations_val.json 文件。

  • 标注效率太低? 考虑使用预训练模型进行“自动标注”初筛。例如,使用 GroundingDINO + SAM 等模型,先对图片生成大致的分割掩码,然后在 labelme 中导入这些掩码进行微调和修正,可以节省大量初始标注时间。但这需要额外的模型部署知识。

整个过程走下来,你可能已经花了不止“5分钟”,但获得的是一套完整、可控、可复现的数据生产流水线。这套方法的价值在于其通用性和可靠性,它适用于从小型实验到中型项目的各种场景。我自己的经验是,在环境配置和脚本调试上投入的这几个小时,会在后续无数次的标注-训练迭代中百倍地回报你。记住,清晰的数据是模型成功的基石,而一个稳定的工具链则是你高效构建这块基石的保障。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐