소스 검색

Merge pull request #194 from SunAhong1993/syf_docs

add jingjing docs and code
Jason 5 년 전
부모
커밋
ca89f2fe27
6개의 변경된 파일216개의 추가작업 그리고 13개의 파일을 삭제
  1. 5 4
      docs/data/annotation.md
  2. 48 0
      paddlex/command.py
  3. 1 0
      paddlex/tools/base.py
  4. 26 0
      paddlex/tools/convert.py
  5. 107 3
      paddlex/tools/x2coco.py
  6. 29 6
      paddlex/tools/x2imagenet.py

+ 5 - 4
docs/data/annotation.md

@@ -10,7 +10,7 @@ PaddleX支持图像分类、目标检测、实例分割和语义分割四大视
 | 标注工具    | 图像分类 | 目标检测 | 实例分割 | 语义分割 | 安装                                             |
 | :---------  | :------- | :------ | :------  | :------- | :----------------------------------------------- |
 | Labelme     | -        | √        | √        | √        | pip install labelme (本地数据标注)                              |
-| 精灵标注    | √        |         | √        | √        | [官网下载](http://www.jinglingbiaozhu.com/) (本地数据标注)     |
+| 精灵标注    | √        | -        | √        | √        | [官网下载](http://www.jinglingbiaozhu.com/) (本地数据标注)     |
 | EasyData    | √        | √        | √        | √        | [Web页面标注](https://ai.baidu.com/easydata/) (需上传数据进行标注)   |
 
 数据标注完成后,参照如下流程,将标注数据转为可用PaddleX模型训练的数据组织格式。
@@ -23,9 +23,10 @@ PaddleX支持图像分类、目标检测、实例分割和语义分割四大视
 > 2. 将所有的标注json文件放在同一个目录下,如`annotations`目录  
 > 3. 使用如下命令进行转换
 ```
-paddlex --data_conversion --from labelme --to PascalVOC --pics ./pics --annotations ./annotations --save_dir ./converted_dataset_dir
+paddlex --data_conversion --source labelme --to PascalVOC --pics ./pics --annotations ./annotations --save_dir ./converted_dataset_dir
 ```
-> `--from`表示数据标注来源,支持`labelme`、`jingling`和`easydata`(分别表示数据来源于LabelMe,精灵标注助手和EasyData)  
-> `--to`表示数据需要转换成为的格式,支持`PascalVOC`(目标检测),`MSCOCO`(实例分割,也可用于目标检测)和`SEG`(语义分割)  
+> `--source`表示数据标注来源,支持`labelme`、`jingling`和`easydata`(分别表示数据来源于LabelMe,精灵标注助手和EasyData)  
+> `--to`表示数据需要转换成为的格式,支持`ImageNet`(图像分类)、`PascalVOC`(目标检测),`MSCOCO`(实例分割,也可用于目标检测)和`SEG`(语义分割)  
 > `--pics`指定原图所在的目录路径  
 > `--annotations`指定标注文件所在的目录路径
+> 【备注】由于标注精灵可以标注PascalVOC格式的数据集,所以此处不再支持标注精灵到PascalVOC格式数据集的转换

+ 48 - 0
paddlex/command.py

@@ -51,6 +51,36 @@ def arg_parser():
         default=False,
         help="export onnx model for deployment")
     parser.add_argument(
+        "--data_conversion",
+        "-dc",
+        action="store_true",
+        default=False,
+        help="convert the dataset to the standard format")
+    parser.add_argument(
+        "--source",
+        "-se",
+        type=_text_type,
+        default=None,
+        help="define dataset format before the conversion")
+    parser.add_argument(
+        "--to",
+        "-to",
+        type=_text_type,
+        default=None,
+        help="define dataset format after the conversion")
+    parser.add_argument(
+        "--pics",
+        "-p",
+        type=_text_type,
+        default=None,
+        help="define pictures directory path")
+    parser.add_argument(
+        "--annotations",
+        "-a",
+        type=_text_type,
+        default=None,
+        help="define annotations directory path")
+    parser.add_argument(
         "--fixed_input_shape",
         "-fs",
         default=None,
@@ -105,6 +135,24 @@ def main():
                 "paddlex --export_inference --model_dir model_path --save_dir infer_model"
             )
         pdx.convertor.export_onnx_model(model, args.save_dir)
+        
+    if args.data_conversion:
+        assert args.source is not None, "--source should be defined while converting dataset"
+        assert args.to is not None, "--to should be defined to confirm the taregt dataset format"
+        assert args.pics is not None, "--pics should be defined to confirm the pictures path"
+        assert args.annotations is not None, "--annotations should be defined to confirm the annotations path"
+        assert args.save_dir is not None, "--save_dir should be defined to store taregt dataset"
+        if args.source == 'labelme' and args.to == 'ImageNet':
+            logging.error(
+                "The labelme dataset can not convert to the ImageNet dataset.",
+                exit=False)
+        if args.source == 'jingling' and args.to == 'PascalVOC':
+            logging.error(
+                "The jingling dataset can not convert to the PascalVOC dataset.",
+                exit=False)
+        pdx.tools.convert.dataset_conversion(args.source, args.to, 
+                                             args.pics, args.annotations, args.save_dir )
+        
 
 
 if __name__ == "__main__":

+ 1 - 0
paddlex/tools/base.py

@@ -40,4 +40,5 @@ def get_encoding(path):
     f = open(path, 'rb')
     data = f.read()
     file_encoding = chardet.detect(data).get('encoding')
+    f.close()
     return file_encoding

+ 26 - 0
paddlex/tools/convert.py

@@ -15,8 +15,10 @@
 # limitations under the License.
 
 from .x2imagenet import EasyData2ImageNet
+from .x2imagenet import JingLing2ImageNet
 from .x2coco import LabelMe2COCO
 from .x2coco import EasyData2COCO
+from .x2coco import JingLing2COCO
 from .x2voc import LabelMe2VOC
 from .x2voc import EasyData2VOC
 from .x2seg import JingLing2Seg
@@ -24,10 +26,34 @@ from .x2seg import LabelMe2Seg
 from .x2seg import EasyData2Seg
 
 easydata2imagenet = EasyData2ImageNet().convert
+jingling2imagenet = JingLing2ImageNet().convert
 labelme2coco = LabelMe2COCO().convert
 easydata2coco = EasyData2COCO().convert
+jingling2coco = JingLing2COCO().convert
 labelme2voc = LabelMe2VOC().convert
 easydata2voc = EasyData2VOC().convert
 jingling2seg = JingLing2Seg().convert
 labelme2seg = LabelMe2Seg().convert
 easydata2seg = EasyData2Seg().convert
+
+def dataset_conversion(source, to, pics, anns, save_dir):
+    if source == 'labelme' and to == 'PascalVOC':
+        labelme2voc(pics, anns, save_dir)
+    elif source == 'labelme' and to == 'MSCOCO':
+        labelme2coco(pics, anns, save_dir)
+    elif source == 'labelme' and to == 'SEG':
+        labelme2seg(pics, anns, save_dir)
+    elif source == 'jingling' and to == 'ImageNet':
+        jingling2imagenet(pics, anns, save_dir)
+    elif source == 'jingling' and to == 'MSCOCO':
+        jingling2coco(pics, anns, save_dir)
+    elif source == 'jingling' and to == 'SEG':
+        jingling2seg(pics, anns, save_dir)
+    elif source == 'easydata' and to == 'ImageNet':
+        easydata2imagenet(pics, anns, save_dir)
+    elif source == 'easydata' and to == 'PascalVOC':
+        easydata2voc(pics, anns, save_dir)
+    elif source == 'easydata' and to == 'MSCOCO':
+        easydata2coco(pics, anns, save_dir)
+    elif source == 'easydata' and to == 'SEG':
+        easydata2seg(pics, anns, save_dir)

+ 107 - 3
paddlex/tools/x2coco.py

@@ -100,7 +100,7 @@ class LabelMe2COCO(X2COCO):
         image["height"] = json_info["imageHeight"]
         image["width"] = json_info["imageWidth"]
         image["id"] = image_id + 1
-        image["file_name"] = json_info["imagePath"].split("/")[-1]
+        image["file_name"] = osp.split(json_info["imagePath"])[-1]
         return image
     
     def generate_polygon_anns_field(self, height, width, 
@@ -144,7 +144,7 @@ class LabelMe2COCO(X2COCO):
             img_name_part = osp.splitext(img_file)[0]
             json_file = osp.join(json_dir, img_name_part + ".json")
             if not osp.exists(json_file):
-                os.remove(os.remove(osp.join(image_dir, img_file)))
+                os.remove(osp.join(image_dir, img_file))
                 continue
             image_id = image_id + 1
             with open(json_file, mode='r', \
@@ -216,7 +216,7 @@ class EasyData2COCO(X2COCO):
             img_name_part = osp.splitext(img_file)[0]
             json_file = osp.join(json_dir, img_name_part + ".json")
             if not osp.exists(json_file):
-                os.remove(os.remove(osp.join(image_dir, img_file)))
+                os.remove(osp.join(image_dir, img_file))
                 continue
             image_id = image_id + 1
             with open(json_file, mode='r', \
@@ -255,3 +255,107 @@ class EasyData2COCO(X2COCO):
                         self.annotations_list.append(
                             self.generate_polygon_anns_field(points, segmentation, label, image_id, object_id,
                                                 label_to_num))
+                        
+
+class JingLing2COCO(X2COCO):
+    """将使用EasyData标注的检测或分割数据集转换为COCO数据集。
+    """
+    def __init__(self):
+        super(JingLing2COCO, self).__init__()
+        
+    def generate_images_field(self, json_info, image_id):
+        image = {}
+        image["height"] = json_info["size"]["height"]
+        image["width"] = json_info["size"]["width"]
+        image["id"] = image_id + 1
+        image["file_name"] = osp.split(json_info["path"])[-1]
+        return image
+    
+    def generate_polygon_anns_field(self, height, width, 
+                                    points, label, image_id, 
+                                    object_id, label_to_num):
+        annotation = {}
+        annotation["segmentation"] = [list(np.asarray(points).flatten())]
+        annotation["iscrowd"] = 0
+        annotation["image_id"] = image_id + 1
+        annotation["bbox"] = list(map(float, self.get_bbox(height, width, points)))
+        annotation["area"] = annotation["bbox"][2] * annotation["bbox"][3]
+        annotation["category_id"] = label_to_num[label]
+        annotation["id"] = object_id + 1
+        return annotation
+    
+    def get_bbox(self, height, width, points):
+        polygons = points
+        mask = np.zeros([height, width], dtype=np.uint8)
+        mask = PIL.Image.fromarray(mask)
+        xy = list(map(tuple, polygons))
+        PIL.ImageDraw.Draw(mask).polygon(xy=xy, outline=1, fill=1)
+        mask = np.array(mask, dtype=bool)
+        index = np.argwhere(mask == 1)
+        rows = index[:, 0]
+        clos = index[:, 1]
+        left_top_r = np.min(rows)
+        left_top_c = np.min(clos)
+        right_bottom_r = np.max(rows)
+        right_bottom_c = np.max(clos)
+        return [
+            left_top_c, left_top_r, right_bottom_c - left_top_c,
+            right_bottom_r - left_top_r
+        ]
+        
+    def parse_json(self, img_dir, json_dir):
+        image_id = -1
+        object_id = -1
+        labels_list = []
+        label_to_num = {}
+        for img_file in os.listdir(img_dir):
+            img_name_part = osp.splitext(img_file)[0]
+            json_file = osp.join(json_dir, img_name_part + ".json")
+            if not osp.exists(json_file):
+                os.remove(osp.join(image_dir, img_file))
+                continue
+            image_id = image_id + 1
+            with open(json_file, mode='r', \
+                              encoding=get_encoding(json_file)) as j:
+                json_info = json.load(j)
+                img_info = self.generate_images_field(json_info, image_id)
+                self.images_list.append(img_info)
+                anns_type = "bndbox"
+                for i, obj in enumerate(json_info["outputs"]["object"]):
+                    if i == 0:
+                        if "polygon" in obj:
+                            anns_type = "polygon" 
+                    else:
+                        if anns_type not in obj:
+                            continue
+                    object_id = object_id + 1
+                    label = obj["name"]
+                    if label not in labels_list:
+                        self.categories_list.append(\
+                            self.generate_categories_field(label, labels_list))
+                        labels_list.append(label)
+                        label_to_num[label] = len(labels_list)
+                    if anns_type == "polygon":
+                        points = []
+                        for j in range(int(len(obj["polygon"]) / 2.0)):
+                            points.append([obj["polygon"]["x" + str(j + 1)], 
+                                           obj["polygon"]["y" + str(j + 1)]])
+                        self.annotations_list.append(
+                            self.generate_polygon_anns_field(json_info["size"]["height"], 
+                                                             json_info["size"]["width"], 
+                                                             points, 
+                                                             label, 
+                                                             image_id,
+                                                             object_id, 
+                                                             label_to_num))
+                    if anns_type == "bndbox":
+                        points = []
+                        points.append([obj["bndbox"]["xmin"], obj["bndbox"]["ymin"]])
+                        points.append([obj["bndbox"]["xmax"], obj["bndbox"]["ymax"]])
+                        points.append([obj["bndbox"]["xmin"], obj["bndbox"]["ymax"]])
+                        points.append([obj["bndbox"]["xmax"], obj["bndbox"]["ymin"]])
+                        self.annotations_list.append(
+                            self.generate_rectangle_anns_field(points, label, image_id,
+                                                  object_id, label_to_num))
+                        
+                        

+ 29 - 6
paddlex/tools/x2imagenet.py

@@ -22,9 +22,8 @@ import shutil
 import numpy as np
 from .base import MyEncoder, is_pic, get_encoding
 
-class EasyData2ImageNet(object):
-    """将使用EasyData标注的分类数据集转换为COCO数据集。
-    """
+
+class X2ImageNet(object):
     def __init__(self):
         pass
     
@@ -46,8 +45,8 @@ class EasyData2ImageNet(object):
                 continue
             with open(json_file, mode="r", \
                               encoding=get_encoding(json_file)) as j:
-                json_info = json.load(j)
-                for output in json_info['labels']:
+                json_info = self.get_json_info(j)
+                for output in json_info:
                     cls_name = output['name']
                     new_image_dir = osp.join(dataset_save_dir, cls_name)
                     if not osp.exists(new_image_dir):
@@ -55,4 +54,28 @@ class EasyData2ImageNet(object):
                     if is_pic(img_name):
                         shutil.copyfile(
                                     osp.join(image_dir, img_name),
-                                    osp.join(new_image_dir, img_name))
+                                    osp.join(new_image_dir, img_name))
+    
+
+class EasyData2ImageNet(X2ImageNet):
+    """将使用EasyData标注的分类数据集转换为ImageNet数据集。
+    """
+    def __init__(self):
+        super(EasyData2ImageNet, self).__init__()
+    
+    def get_json_info(self, json_file):
+        json_info = json.load(json_file)
+        json_info = json_info['labels']
+        return json_info
+                        
+class JingLing2ImageNet(X2ImageNet):
+    """将使用标注精灵标注的分类数据集转换为ImageNet数据集。
+    """
+    def __init__(self):
+        super(X2ImageNet, self).__init__()
+    
+    def get_json_info(self, json_file):
+        json_info = json.load(json_file)
+        json_info = json_info['outputs']['object']
+        return json_info
+