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
字段中的id
;category_id
是类别索引,在上面的labels.txt文件中第一行的__ignore__
的索引为-1,依次递增;segmentation
字段是一个存放标注范围的列表,一般只有一项,如果一个目标分为多块,则会有多项,(参考下面第1点)每一项的点分别按照xyxyxy
排列;bbox
字段为xywh
categories
字段是类别名字与id
的对应关系,也就是annotations
字段中的category_id
其他
- 如果一个目标被其他目标遮挡或截断为多个部分,可以参考data_annotated/2011_000006.json中沙发的标注方式,为每个部分增加
group id
- 如果有需要忽略的,可以参考data_annotated/2011_000003.json中桌子的标注方式,将不要的目标标注为
__ignore__
类别
额外代码
这部分代码可能不需要使用,但是我实际应用时用到了,有需要可以参考
复制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
中
划分数据集
- 划分训练集和验证集
- 分别转换训练集和验证集
划分数据集
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