labelme标注实例实例分割数据并转为COCO格式/VOC格式

  • 准备数据
  • 下载并安装labelme
  • 创建一个labels.txt
  • 标注数据
  • 转换格式
  • 其他
  • 额外代码
  • 复制json文件到单独文件夹
  • 划分数据集


准备数据

将需要标注的数据放到同一个文件夹中

下载并安装labelme

安装labelme,用于对数据进行标注

pip install labelme

下载labelme,转换格式时需要使用到其中的文件:

git clone https://github.com/wkentaro/labelme.git

转COCO格式需要用到这个文件:labelme2coco.py

转VOC格式需要用到这个文件:labelme2voc.py

本篇以COCO格式为例,VOC同理

创建一个labels.txt

参考labelme实例分割的labels.txt创建一个labels.txt文件

__ignore__
_background_
类别1
类别2

前面两行即使没有标注,也是需要的

注意:这里的类别1类别2在实际应用的时候最好不要使用中文,否则可能会出现转换失败的情况

标注数据

使用安装好的labelme进行标注,为每个图片创建一个json文件

labelme --labels=labels.txt

这个命令会启动labelme并使用指定的labels.txt文件

File >> Open Dir找到前面存放图片的文件夹并打开,接着可以开始进行标注

标注的原则是对感兴趣的目标根据实际应用的需求尽可能地准确

转换格式

python ./labelme2coco.py <labelled_data_folder> <out_folder> --labels labels.txt

<labelled_data_folder>就是存放标注数据json文件的文件夹
<out_folder>就是输出的路径,不存在的话会自动创建,存在的话会强制对出程序

因为标注数据时labelme已经将图片数据转为字节流数据保存到json文件中imageData字段,在转换时,会将字节流数据保存为jpg放到<out_folder>/JPEGImages下,会将标注数据可视化保存到<out_folder>/Visualization下,转换后的COCO格式的数据在<out_folder>/annotations.json

训练时,需要用到<out_folder>/JPEGImages<out_folder>/annotations.json

如果不需要可视化结果可以在上面的命令后面添加--noviz


标注的json的格式:

名字和图片名字相同,后缀不同

文件中的内容简化后:

{
  "shapes": [
    {
      "label": "类别1",
      "points": [
        [
          217.57142857142858,
          621.6190476190477
        ],
        [
          479.4761904761905,
          621.6190476190477
        ],
        [
          460.42857142857144,
          802.5714285714287
        ],
        [
          203.2857142857143,
          788.2857142857144
        ]
      ],
      "group_id": null,
      "shape_type": "polygon",
      "flags": {}
    },
    {
      "label": "类别1",
      "points": [...],
      "group_id": null,
      "shape_type": "polygon",
      "flags": {}
    },
    {
      "label": "类别2",
      "points": [...],
      "group_id": null,
      "shape_type": "polygon",
      "flags": {}
    },
  ],
  "imagePath": "000593-1-202102020641225190-0000000.jpg",
  "imageData": "",
  "imageHeight": 2048,
  "imageWidth": 2448
}

转换之后的json的内容是:

{
    "info": {
        "description": null,
        "url": null,
        "version": null,
        "year": 2021,
        "contributor": null,
        "date_created": "2021-10-09 14:34:09.838583"
    },
    "licenses": [
        {
            "url": null,
            "id": 0,
            "name": null
        }
    ],
    "images": [
        {
            "license": 0,
            "url": null,
            "file_name": "JPEGImages\\000593-1-202102020641225190-0000000.jpg",
            "height": 2048,
            "width": 2448,
            "date_captured": null,
            "id": 0
        },
        {...
        }
    ],
    "type": "instances",
    "annotations": [
        {
            "id": 0,
            "image_id": 0,
            "category_id": 1,
            "segmentation": [
                [
                    384.23809523809524,
                    759.7142857142858,
                    684.2380952380953,
                    721.6190476190477,
                    569.952380952381,
                    935.9047619047619,
                    398.5238095238095,
                    1059.7142857142858,
                    236.61904761904762,
                    997.8095238095239,
                    327.09523809523813,
                    764.4761904761906
                ]
            ],
            "area": 87050.0,
            "bbox": [
                236.0,
                721.0,
                449.0,
                339.0
            ],
            "iscrowd": 0
        },
        {...
        },
        {...
        }
    ],
    "categories": [
        {
            "supercategory": null,
            "id": 0,
            "name": "_background_"
        },
        {
            "supercategory": null,
            "id": 1,
            "name": "classname1"
        },
        {
            "supercategory": null,
            "id": 2,
            "name": "classname2"
        }
    ]
}

images字段是一个存放每个图片的基本信息的列表,每个图片为列表的一项,其中的id是图片索引;

annotations字段是一个存放目标的列表,每个目标为列表的一项,其中的id是目标索引;因为一张图片可能有多个目标,所以通过image_id来对应images字段中的idcategory_id是类别索引,在上面的labels.txt文件中第一行的__ignore__的索引为-1,依次递增;segmentation字段是一个存放标注范围的列表,一般只有一项,如果一个目标分为多块,则会有多项,(参考下面第1点)每一项的点分别按照xyxyxy排列;bbox字段为xywh

categories字段是类别名字与id的对应关系,也就是annotations字段中的category_id

其他

  1. 如果一个目标被其他目标遮挡或截断为多个部分,可以参考data_annotated/2011_000006.json中沙发的标注方式,为每个部分增加group id

labelme可以不用手动批量语义分割吗_COCO格式

  1. 如果有需要忽略的,可以参考data_annotated/2011_000003.json中桌子的标注方式,将不要的目标标注为__ignore__类别

labelme可以不用手动批量语义分割吗_转换_02

额外代码

这部分代码可能不需要使用,但是我实际应用时用到了,有需要可以参考

复制json文件到单独文件夹

如果标注数据时,数据分散在目录下的不同子目录,无法一次性将所有文件全部转换,可以在标注之后将json复制到一个文件夹下,再进行转换,可以参考代码:

以下代码在jupyter中运行

遍历

import os, shutil

BASE_DIR = "标注数据的父目录"
DEST_DIR = "要存放的位置"
    
images = []
annas = []
anna_names = []
for root, dirs, files in os.walk(BASE_DIR, topdown=False):
    for name in files:
        print(os.path.join(root, name))
        if os.path.splitext(name)[1] == '.jpg':  
            images.append(os.path.join(root, name))
        if os.path.splitext(name)[1] == '.json':  
            annas.append(os.path.join(root, name))
            anna_names.append(name)

复制

if os.path.exists(DEST_DIR):
    print("Output directory already exists:", DEST_DIR)
    assert False, "Output directory already exists: %s" % DEST_DIR
os.makedirs(DEST_DIR)

for path, name in zip(annas, anna_names):
    shutil.copyfile(path, os.path.join(DEST_DIR, name))

print("结束")

这样就可以将所有json复制到DEST_DIR

划分数据集

  1. 划分训练集和验证集
  2. 分别转换训练集和验证集

划分数据集

import os, shutil
from sklearn.model_selection import train_test_split

BASE_DIR = r"json存放的文件夹路径"

json_names = os.listdir(BASE_DIR)
json_names = list([name for name in json_names if name.endswith('.json')])
# 分割数据集
X_train, X_test = train_test_split(json_names, test_size=0.3, random_state=42)
# 分别移动到train和val文件夹
for dest_fold in ['train', 'val']:
    DEST_DIR = os.path.join(BASE_DIR, dest_fold)
    if os.path.exists(DEST_DIR):
        print("文件夹已存在:", DEST_DIR)
        assert False, "文件夹已存在:%s" % DEST_DIR
    os.makedirs(DEST_DIR)
    
    if dest_fold == 'train':
        names = X_train
    elif dest_fold == 'val':
        names = X_test
    else:
        assert False, dest_fold
        
    for name in names:
        src = os.path.join(BASE_DIR, name)
        dest = os.path.join(DEST_DIR, name)
        shutil.move(src, dest)
    print("结束")

分别转换训练集和验证集

python .\examples\instance_segmentation\labelme2coco.py \path\to\train \path\to\train --labels \path\to\labels.txt

python .\examples\instance_segmentation\labelme2coco.py \path\to\val \path\to\val --labels \path\to\labels.txt