Repository: PaddlePaddle/PaddleSeg
Pull request: fix label tool bugs
1 标注格式转换脚本使用自定义api，去除labelme依赖，避免外部依赖更新导致的bug
2 修复潜在问题，如果图片自带旋转信息，例如来自移动设备，PIL读图结果将不符合预期。已用opencv读图解决
close #263

Problem statement:
LabelMe标注好图片之后，使用labelme2seg无法生成真值图片
我使用LabelMe标注好图片之后，使用labelme2seg生成真值图片，运行labelme2seg的时候报错，报错信息如下：
class_names: ('_background_', 'steel')
Saved class_names: D:\2\class_names.txt
Generating dataset from: D:\2\2020_5_15_13_19_46_995.json
Traceback (most recent call last):
  File "D:\PaddleSeg\pdseg\tools\labelme2seg.py", line 90, in <module>
    main(args)
  File "D:\PaddleSeg\pdseg\tools\labelme2seg.py", line 78, in main
    if lbl.min() >= 0 and lbl.max() <= 255:
AttributeError: 'tuple' object has no attribute 'min'

Hints from issue discussion:

Commit message: fix label tool bug

Patch to review:
diff --git a/docs/annotation/labelme_demo/annotations/2011_000025.png b/docs/annotation/labelme_demo/annotations/2011_000025.png
deleted file mode 100644
index 0b5a56dda1..0000000000
Binary files a/docs/annotation/labelme_demo/annotations/2011_000025.png and /dev/null differ
diff --git a/pdseg/tools/jingling2seg.py b/pdseg/tools/jingling2seg.py
index 3f1e245d19..563f1eff75 100644
--- a/pdseg/tools/jingling2seg.py
+++ b/pdseg/tools/jingling2seg.py
@@ -20,12 +20,11 @@
 import json
 import os
 import os.path as osp
-
 import numpy as np
 import PIL.Image
-import labelme
 
-from gray2pseudo_color import get_color_map_list
+from .gray2pseudo_color import get_color_map_list
+from .labelme2seg import graphs2label
 
 
 def parse_args():
@@ -102,10 +101,10 @@ def main(args):
             img_shape = (data_size['height'], data_size['width'],
                          data_size['depth'])
 
-            lbl = labelme.utils.shapes_to_label(
-                img_shape=img_shape,
-                shapes=data_shapes,
-                label_name_to_value=class_name_to_id,
+            lbl = graphs2label(
+                img_size=img_shape,
+                graphs=data_shapes,
+                class_name_mapping=class_name_to_id,
             )
 
             if osp.splitext(out_png_file)[1] != '.png':
diff --git a/pdseg/tools/labelme2seg.py b/pdseg/tools/labelme2seg.py
index 9a3ec662f1..9a7bacd083 100755
--- a/pdseg/tools/labelme2seg.py
+++ b/pdseg/tools/labelme2seg.py
@@ -20,12 +20,13 @@
 import json
 import os
 import os.path as osp
-
 import numpy as np
 import PIL.Image
-import labelme
+import PIL.ImageDraw
+import cv2
+import math
 
-from gray2pseudo_color import get_color_map_list
+from .gray2pseudo_color import get_color_map_list
 
 
 def parse_args():
@@ -77,12 +78,12 @@ def main(args):
             data = json.load(f)
 
             img_file = osp.join(osp.dirname(label_file), data['imagePath'])
-            img = np.asarray(PIL.Image.open(img_file))
+            img = np.asarray(cv2.imread(img_file))
 
-            lbl = labelme.utils.shapes_to_label(
-                img_shape=img.shape,
-                shapes=data['shapes'],
-                label_name_to_value=class_name_to_id,
+            lbl = graphs2label(
+                img_size=img.shape,
+                graphs=data['shapes'],
+                class_name_mapping=class_name_to_id,
             )
 
             if osp.splitext(out_png_file)[1] != '.png':
@@ -98,6 +99,52 @@ def main(args):
                     'Please consider using the .npy format.' % out_png_file)
 
 
+def graph2mask(img_size, points, graph_type=None, point_size=6, line_width=9):
+    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]
+    if graph_type == 'rectangle':
+        assert len(points_list
+                   ) == 2, 'Shape of graph_type=rectangle must have 2 points'
+        image_draw.rectangle(points_list, outline=1, fill=1)
+    elif graph_type == 'line':
+        assert len(
+            points_list) == 2, 'Shape of graph_type=line must have 2 points'
+        image_draw.line(xy=points_list, fill=1, width=line_width)
+    elif graph_type == 'linestrip':
+        image_draw.line(xy=points_list, fill=1, width=line_width)
+    elif graph_type == 'circle':
+        assert len(
+            points_list) == 2, 'Shape of graph_type=circle must have 2 points'
+        (cx, cy), (px, py) = points_list
+        d = math.sqrt((cx - px)**2 + (cy - py)**2)
+        image_draw.ellipse([cx - d, cy - d, cx + d, cy + d], outline=1, fill=1)
+    elif graph_type == 'point':
+        assert len(
+            points_list) == 1, 'Shape of graph_type=point must have 1 points'
+        cx, cy = points_list[0]
+        r = point_size
+        image_draw.ellipse([cx - r, cy - r, cx + r, cy + r], outline=1, fill=1)
+    else:
+        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 graphs2label(img_size, graphs, class_name_mapping):
+    label = np.zeros(img_size[:2], dtype=np.int32)
+    for graph in graphs:
+        points = graph['points']
+        class_name = graph['label']
+        graph_type = graph.get('shape_type', None)
+
+        class_id = class_name_mapping[class_name]
+
+        label_mask = graph2mask(img_size[:2], points, graph_type)
+        label[label_mask] = class_id
+    return label
+
+
 if __name__ == '__main__':
     args = parse_args()
     main(args)


Code review comments:
diff_hunk: @@ -20,10 +20,11 @@
 import json
 import os
 import os.path as osp
-
 import numpy as np
 import PIL.Image
-import labelme
+import PIL.ImageDraw
+import cv2
+import math
path: pdseg/tools/labelme2seg.py
text: @user1:
math应该放到上面