diff --git a/docs/data/transform/transform_cn.md b/docs/data/transform/transform_cn.md
index e5649ca5b0..ba8c7d8e5a 100644
--- a/docs/data/transform/transform_cn.md
+++ b/docs/data/transform/transform_cn.md
@@ -2,102 +2,111 @@
# LabelMe分割数据标注
-无论是语义分割,全景分割,还是实例分割,我们都需要充足的训练数据。
+本文档简要介绍使用LabelMe软件进行分割数据标注,并将标注数据转换为PaddleSeg和PaddleX支持的格式。
-本文档简要介绍使用LabelMe软件进行分割数据标注,并将标注数据转换为PaddleSeg支持的格式。
## 1. 安装LabelMe
-LabelMe支持在Windows/macOS/Linux三个系统上使用,且三个系统下的标注格式是一样。
+LabelMe支持在Windows/macOS/Linux三个系统上安装。
-LabelMe的安装流程请参见[官方安装指南](https://github.com/wkentaro/labelme)。
+在Python3环境下,执行如下命令,可以快速安装LabelMe。
+```
+pip install labelme
+```
+
+LabelMe详细的安装和使用流程,可以参照[官方指南](https://github.com/wkentaro/labelme)。
## 2. 使用LabelMe
-### 预览已标注图片
-打开终端输入`labelme`会出现LableMe的交互界面,可以先预览`LabelMe`给出的已标注好的图片。
+### 2.1 启动LabelMe
+在电脑终端输入`labelme`,稍等会出现LableMe的交互界面。
-
图1 LableMe交互界面的示意图
+
LableMe交互界面
-点击`OpenDir`打开`/examples/semantic_segmentation/data_annotated`,其中``为克隆下来的`labelme`的路径,打开后显示的是语义分割的真值标注。
-
+点击左上角`File`:
+* 勾选`Save Automatically`,设置软件自动保存标注json文件,避免需要手动保存
+* 取消勾选`Save With Image Data`,设置标注json文件中不保存data数据
-
-
图2 已标注图片的示意图
+
+
LableMe设置
-### 开始标注图片
+### 2.2 预览已标注图片(可选)
-(1) 点击`OpenDir`打开待标注图片所在目录,点击`Create Polygons`,沿着目标的边缘画多边形,完成后输入目标的类别。在标注过程中,如果某个点画错了,可以按撤销快捷键可撤销该点。Mac下的撤销快捷键为`command+Z`。
+执行如下命令,clone下载LabelMe的代码。
+```
+git clone https://github.com/wkentaro/labelme.git
+```
+
+在LabelMe交互界面上点击`OpenDir`,选择`/examples/semantic_segmentation/data_annotated`目录(``为clone下载的`labelme`的路径),打开后可以显示的是语义分割的真值标注。
-
-
图3 标注单个目标的示意图
+
+
已标注图片的示意图
-(2) 右击选择`Edit Polygons`可以整体移动多边形的位置,也可以移动某个点的位置;右击选择`Edit Label`可以修改每个目标的类别。请根据自己的需要执行这一步骤,若不需要修改,可跳过。
+### 2.3 标注图片
+将所有待标注图片保存在一个目录下,点击`OpenDir`打开待标注图片所在目录。
+
+点击`Create Polygons`,沿着前景目标的边缘画闭合的多边形,然后输入或者选择目标的类别。
-
-
图4 修改标注的示意图
+
+
标注单个目标的示意图
+通常情况下,大家只需要标注前景目标并设置标注类别,其他像素默认作为背景。如果大家需要手动标注背景区域,**类别必须设置为`_background_`**,否则格式转换会有问题。
-(3) 图片中所有目标的标注都完成后,点击`Save`保存json文件,**请将json文件和图片放在同一个文件夹里**,点击`Next Image`标注下一张图片。
+比如针对有空洞的目标,在标注完目标外轮廓后,再沿空洞边缘画多边形,并将空洞指定为特定类别,如果空洞是背景则指定为`_background_`,示例如下。
-LableMe产出的真值文件可参考我们给出的[文件夹](https://github.com/PaddlePaddle/PaddleSeg/blob/release/v0.8.0/docs/annotation/labelme_demo)。
+
+
+
带空洞目标的标注示意图
+
+如果在标注过程中某个点画错了,可以鼠标右键选择撤销该点;点击`Edit Polygons`可以移动多边形的位置,也可以移动某个点的位置;右击点击类别label,可以选择`Edit Label`修改类别名称。
-
-
图5 LableMe产出的真值文件的示意图
+
+
修改标注的示意图
-**Note:**
-
-对于中间有空洞的目标的标注方法:在标注完目标轮廓后,再沿空洞区域边缘画多边形,并将其指定为其他类别,如果是背景则指定为`_background_`。如下:
+图片中所有目标的标注都完成后,直接选择下一张图片进行标注。(由于勾选`Save Automatically`,不再需要手动点击`Save`保存json文件)
+检查标注json文件和图片**存放在同一个文件夹**,而且是一一对应关系,如下图所示。
-
-
图6 带空洞目标的标注示意图
+
+
LableMe产出的标注文件的示意图
## 3. 数据格式转换
-使用PaddleSeg提供的数据转换脚本,将LabelMe标注工具产出的数据格式转换为PaddleSeg所需的数据格式。
+使用PaddleSeg提供的数据转换脚本,将LabelMe标注工具产出的数据格式转换为PaddleSeg和PaddleX所需的数据格式。
-运行以下代码进行转换,其中``为图片以及LabelMe产出的json文件所在文件夹的目录,同时也是转换后的标注集所在文件夹的目录。
+运行以下代码进行转换,第一个`input_dir`参数是原始图像和json标注文件的保存目录,第二个`output_dir`参数是转换后数据集的保存目录。
```
-python tools/data/labelme2seg.py
+python tools/data/labelme2seg.py input_dir output_dir
```
-经过数据格式转换后的数据集目录结构如下:
+格式转换后的数据集目录结构如下:
```
-my_dataset # 根目录
-|-- annotations # 标注图像的目录
-| |-- xxx.png # 标注图像
+dataset_dir # 根目录
+|-- images # 原始图像的目录
+| |-- xxx.png(png or other) # 原始图像
| |...
-|-- class_names.txt # 数据集的类别名称
-|-- xxx.jpg(png or other) # 原图
-|-- ...
-|-- xxx.json # 标注json文件
-|-- ...
-
+|-- annotations # 标注图像的目录
+| |-- xxx.png # 标注图像
+| |...
+|-- class_names.txt # 数据集的类别名称,背景_background_的类别id是0,其他类别id依次递增
```
-
-
-
-
-
图7 格式转换后的数据集目录的结构示意图
-
diff --git a/tools/data/labelme2seg.py b/tools/data/labelme2seg.py
index 20bf7d55f5..554bb80e1d 100644
--- a/tools/data/labelme2seg.py
+++ b/tools/data/labelme2seg.py
@@ -21,102 +21,122 @@
import json
import os
import os.path as osp
+import shutil
+
import numpy as np
import PIL.Image
import PIL.ImageDraw
import cv2
-from gray2pseudo_color import get_color_map_list
-
def parse_args():
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('input_dir', help='input annotated directory')
+ parser.add_argument('output_dir', help='output annotated directory')
return parser.parse_args()
+def get_color_map_list(num_classes):
+ num_classes += 1
+ color_map = num_classes * [0, 0, 0]
+ for i in range(0, num_classes):
+ j = 0
+ lab = i
+ while lab:
+ color_map[i * 3] |= (((lab >> 0) & 1) << (7 - j))
+ color_map[i * 3 + 1] |= (((lab >> 1) & 1) << (7 - j))
+ color_map[i * 3 + 2] |= (((lab >> 2) & 1) << (7 - j))
+ j += 1
+ lab >>= 3
+ color_map = color_map[3:]
+ return color_map
+
+
+def shape2mask(img_size, points):
+ label_mask = PIL.Image.fromarray(np.zeros(img_size[:2], dtype=np.uint8))
+ image_draw = PIL.ImageDraw.Draw(label_mask)
+ points_list = [tuple(point) for point in points]
+ assert len(points_list) > 2, 'Polygon must have points more than 2'
+ image_draw.polygon(xy=points_list, outline=1, fill=1)
+ return np.array(label_mask, dtype=bool)
+
+
+def shape2label(img_size, shapes, class_name_mapping):
+ label = np.zeros(img_size[:2], dtype=np.int32)
+ for shape in shapes:
+ points = shape['points']
+ class_name = shape['label']
+ shape_type = shape.get('shape_type', None)
+ class_id = class_name_mapping[class_name]
+ label_mask = shape2mask(img_size[:2], points)
+ label[label_mask] = class_id
+ return label
+
+
def main(args):
- output_dir = osp.join(args.input_dir, 'annotations')
+ # prepare
+ output_dir = args.output_dir
+ output_img_dir = osp.join(args.output_dir, 'images')
+ output_annot_dir = osp.join(args.output_dir, 'annotations')
if not osp.exists(output_dir):
os.makedirs(output_dir)
- print('Creating annotations directory:', output_dir)
-
- # get the all class names for the given dataset
+ print('Creating directory:', output_dir)
+ if not osp.exists(output_img_dir):
+ os.makedirs(output_img_dir)
+ print('Creating directory:', output_img_dir)
+ if not osp.exists(output_annot_dir):
+ os.makedirs(output_annot_dir)
+ print('Creating directory:', output_annot_dir)
+
+ # collect and save class names
class_names = ['_background_']
for label_file in glob.glob(osp.join(args.input_dir, '*.json')):
with open(label_file) as f:
data = json.load(f)
for shape in data['shapes']:
- label = shape['label']
- cls_name = label
- if not cls_name in class_names:
+ cls_name = shape['label']
+ if cls_name not in class_names:
class_names.append(cls_name)
class_name_to_id = {}
for i, class_name in enumerate(class_names):
class_id = i # starts with 0
class_name_to_id[class_name] = class_id
- if class_id == 0:
- assert class_name == '_background_'
- class_names = tuple(class_names)
print('class_names:', class_names)
- out_class_names_file = osp.join(args.input_dir, 'class_names.txt')
+ out_class_names_file = osp.join(output_dir, 'class_names.txt')
with open(out_class_names_file, 'w') as f:
f.writelines('\n'.join(class_names))
print('Saved class_names:', out_class_names_file)
+ # create annotated images and copy origin images
color_map = get_color_map_list(256)
-
for label_file in glob.glob(osp.join(args.input_dir, '*.json')):
print('Generating dataset from:', label_file)
+ filename = osp.splitext(osp.basename(label_file))[0]
+ annotated_img_path = osp.join(output_annot_dir, filename + '.png')
with open(label_file) as f:
- base = osp.splitext(osp.basename(label_file))[0]
- out_png_file = osp.join(output_dir, base + '.png')
-
data = json.load(f)
+ img_path = osp.join(osp.dirname(label_file), data['imagePath'])
+ shutil.copy(img_path, output_img_dir)
- img_file = osp.join(osp.dirname(label_file), data['imagePath'])
- img = np.asarray(cv2.imread(img_file))
-
+ img = np.asarray(cv2.imread(img_path))
lbl = shape2label(
img_size=img.shape,
shapes=data['shapes'],
class_name_mapping=class_name_to_id, )
- if osp.splitext(out_png_file)[1] != '.png':
- out_png_file += '.png'
# Assume label ranges [0, 255] for uint8,
if lbl.min() >= 0 and lbl.max() <= 255:
lbl_pil = PIL.Image.fromarray(lbl.astype(np.uint8), mode='P')
lbl_pil.putpalette(color_map)
- lbl_pil.save(out_png_file)
+ lbl_pil.save(annotated_img_path)
else:
raise ValueError(
'[%s] Cannot save the pixel-wise class label as PNG. '
- 'Please consider using the .npy format.' % out_png_file)
-
-
-def shape2mask(img_size, points):
- label_mask = PIL.Image.fromarray(np.zeros(img_size[:2], dtype=np.uint8))
- image_draw = PIL.ImageDraw.Draw(label_mask)
- points_list = [tuple(point) for point in points]
- assert len(points_list) > 2, 'Polygon must have points more than 2'
- image_draw.polygon(xy=points_list, outline=1, fill=1)
- return np.array(label_mask, dtype=bool)
-
-
-def shape2label(img_size, shapes, class_name_mapping):
- label = np.zeros(img_size[:2], dtype=np.int32)
- for shape in shapes:
- points = shape['points']
- class_name = shape['label']
- shape_type = shape.get('shape_type', None)
- class_id = class_name_mapping[class_name]
- label_mask = shape2mask(img_size[:2], points)
- label[label_mask] = class_id
- return label
+ 'Please consider using the .npy format.' %
+ annotated_img_path)
if __name__ == '__main__':