瀏覽代碼

Merge pull request #785 from will-jl944/develop_jf

Compatible with 1.x APIs
FlyingQianMM 4 年之前
父節點
當前提交
4abce927f2

+ 1 - 1
README.md

@@ -16,7 +16,7 @@
  ![QQGroup](https://img.shields.io/badge/QQ_Group-1045148026-52B6EF?style=social&logo=tencent-qq&logoColor=000&logoWidth=20)
 
 
-## PaddleX dygraph mode is ready! Static mode is set by default and dygraph graph code base is in [dygraph](https://github.com/PaddlePaddle/PaddleX/tree/develop/dygraph). If you want to use static mode, the version 1.3.10 can be installed by pip. The version 2.0.0rc0 corresponds to the dygraph mode.
+## PaddleX dygraph mode is ready! Static mode is set by default and dynamic graph code base is in [dygraph](https://github.com/PaddlePaddle/PaddleX/tree/develop/dygraph). If you want to use static mode, the version 1.3.10 can be installed by pip. The version 2.0.0rc0 corresponds to the dygraph mode.
 
 
 :hugs:  PaddleX integrated the abilities of **Image classification**, **Object detection**, **Semantic segmentation**, and **Instance segmentation** in the Paddle CV toolkits, and get through the whole-process development from **Data preparation** and **Model training and optimization** to **Multi-end deployment**. At the same time, PaddleX provides **Succinct APIs** and a **Graphical User Interface**. Developers can quickly complete the end-to-end process development of the Paddle in a form of **low-code**  without installing different libraries.

+ 1 - 1
dygraph/PaddleClas

@@ -1 +1 @@
-Subproject commit b5de5322b9f40449db4d55078044a1e1b44c0644
+Subproject commit c471929c18969273936c8b8893e1a4d0645fed7b

+ 209 - 9
dygraph/paddlex/cls.py

@@ -12,15 +12,215 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import sys
-message = 'Your running script needs PaddleX<2.0.0, please refer to {} to solve this issue.'.format(
-    'https://github.com/PaddlePaddle/PaddleX/tree/release/2.0-rc/tutorials/train#%E7%89%88%E6%9C%AC%E5%8D%87%E7%BA%A7'
-)
+from . import cv
+from paddlex.cv.transforms import cls_transforms
+import paddlex.utils.logging as logging
 
+transforms = cls_transforms
 
-def __getattr__(attr):
-    if attr == 'transforms':
 
-        print("\033[1;31;40m{}\033[0m".format(message).encode("utf-8")
-              .decode("latin1"))
-        sys.exit(-1)
+class ResNet18(cv.models.ResNet18):
+    def __init__(self, num_classes=1000, input_channel=None):
+        if input_channel is not None:
+            logging.warning(
+                "`input_channel` is deprecated in PaddleX 2.0 and won't take effect. Defaults to 3."
+            )
+        super(ResNet18, self).__init__(num_classes=num_classes)
+
+
+class ResNet34(cv.models.ResNet34):
+    def __init__(self, num_classes=1000, input_channel=None):
+        if input_channel is not None:
+            logging.warning(
+                "`input_channel` is deprecated in PaddleX 2.0 and won't take effect. Defaults to 3."
+            )
+        super(ResNet34, self).__init__(num_classes=num_classes)
+
+
+class ResNet50(cv.models.ResNet50):
+    def __init__(self, num_classes=1000, input_channel=None):
+        if input_channel is not None:
+            logging.warning(
+                "`input_channel` is deprecated in PaddleX 2.0 and won't take effect. Defaults to 3."
+            )
+        super(ResNet50, self).__init__(num_classes=num_classes)
+
+
+class ResNet101(cv.models.ResNet101):
+    def __init__(self, num_classes=1000, input_channel=None):
+        if input_channel is not None:
+            logging.warning(
+                "`input_channel` is deprecated in PaddleX 2.0 and won't take effect. Defaults to 3."
+            )
+        super(ResNet101, self).__init__(num_classes=num_classes)
+
+
+class ResNet50_vd(cv.models.ResNet50_vd):
+    def __init__(self, num_classes=1000, input_channel=None):
+        if input_channel is not None:
+            logging.warning(
+                "`input_channel` is deprecated in PaddleX 2.0 and won't take effect. Defaults to 3."
+            )
+        super(ResNet50_vd, self).__init__(num_classes=num_classes)
+
+
+class ResNet101_vd(cv.models.ResNet101_vd):
+    def __init__(self, num_classes=1000, input_channel=None):
+        if input_channel is not None:
+            logging.warning(
+                "`input_channel` is deprecated in PaddleX 2.0 and won't take effect. Defaults to 3."
+            )
+        super(ResNet101_vd, self).__init__(num_classes=num_classes)
+
+
+class ResNet50_vd_ssld(cv.models.ResNet50_vd_ssld):
+    def __init__(self, num_classes=1000, input_channel=None):
+        if input_channel is not None:
+            logging.warning(
+                "`input_channel` is deprecated in PaddleX 2.0 and won't take effect. Defaults to 3."
+            )
+        super(ResNet50_vd_ssld, self).__init__(num_classes=num_classes)
+
+
+class ResNet101_vd_ssld(cv.models.ResNet101_vd_ssld):
+    def __init__(self, num_classes=1000, input_channel=None):
+        if input_channel is not None:
+            logging.warning(
+                "`input_channel` is deprecated in PaddleX 2.0 and won't take effect. Defaults to 3."
+            )
+        super(ResNet101_vd_ssld, self).__init__(num_classes=num_classes)
+
+
+class DarkNet53(cv.models.DarkNet53):
+    def __init__(self, num_classes=1000, input_channel=None):
+        if input_channel is not None:
+            logging.warning(
+                "`input_channel` is deprecated in PaddleX 2.0 and won't take effect. Defaults to 3."
+            )
+        super(DarkNet53, self).__init__(num_classes=num_classes)
+
+
+class MobileNetV1(cv.models.MobileNetV1):
+    def __init__(self, num_classes=1000, input_channel=None):
+        if input_channel is not None:
+            logging.warning(
+                "`input_channel` is deprecated in PaddleX 2.0 and won't take effect. Defaults to 3."
+            )
+        super(MobileNetV1, self).__init__(num_classes=num_classes)
+
+
+class MobileNetV2(cv.models.MobileNetV2):
+    def __init__(self, num_classes=1000, input_channel=None):
+        if input_channel is not None:
+            logging.warning(
+                "`input_channel` is deprecated in PaddleX 2.0 and won't take effect. Defaults to 3."
+            )
+        super(MobileNetV2, self).__init__(num_classes=num_classes)
+
+
+class MobileNetV3_small(cv.models.MobileNetV3_small):
+    def __init__(self, num_classes=1000, input_channel=None):
+        if input_channel is not None:
+            logging.warning(
+                "`input_channel` is deprecated in PaddleX 2.0 and won't take effect. Defaults to 3."
+            )
+        super(MobileNetV3_small, self).__init__(num_classes=num_classes)
+
+
+class MobileNetV3_large(cv.models.MobileNetV3_large):
+    def __init__(self, num_classes=1000, input_channel=None):
+        if input_channel is not None:
+            logging.warning(
+                "`input_channel` is deprecated in PaddleX 2.0 and won't take effect. Defaults to 3."
+            )
+        super(MobileNetV3_large, self).__init__(num_classes=num_classes)
+
+
+class MobileNetV3_small_ssld(cv.models.MobileNetV3_small_ssld):
+    def __init__(self, num_classes=1000, input_channel=None):
+        if input_channel is not None:
+            logging.warning(
+                "`input_channel` is deprecated in PaddleX 2.0 and won't take effect. Defaults to 3."
+            )
+        super(MobileNetV3_small_ssld, self).__init__(num_classes=num_classes)
+
+
+class MobileNetV3_large_ssld(cv.models.MobileNetV3_large_ssld):
+    def __init__(self, num_classes=1000, input_channel=None):
+        if input_channel is not None:
+            logging.warning(
+                "`input_channel` is deprecated in PaddleX 2.0 and won't take effect. Defaults to 3."
+            )
+        super(MobileNetV3_large_ssld, self).__init__(num_classes=num_classes)
+
+
+class Xception41(cv.models.Xception41):
+    def __init__(self, num_classes=1000, input_channel=None):
+        if input_channel is not None:
+            logging.warning(
+                "`input_channel` is deprecated in PaddleX 2.0 and won't take effect. Defaults to 3."
+            )
+        super(Xception41, self).__init__(num_classes=num_classes)
+
+
+class Xception65(cv.models.Xception65):
+    def __init__(self, num_classes=1000, input_channel=None):
+        if input_channel is not None:
+            logging.warning(
+                "`input_channel` is deprecated in PaddleX 2.0 and won't take effect. Defaults to 3."
+            )
+        super(Xception65, self).__init__(num_classes=num_classes)
+
+
+class DenseNet121(cv.models.DenseNet121):
+    def __init__(self, num_classes=1000, input_channel=None):
+        if input_channel is not None:
+            logging.warning(
+                "`input_channel` is deprecated in PaddleX 2.0 and won't take effect. Defaults to 3."
+            )
+        super(DenseNet121, self).__init__(num_classes=num_classes)
+
+
+class DenseNet161(cv.models.DenseNet161):
+    def __init__(self, num_classes=1000, input_channel=None):
+        if input_channel is not None:
+            logging.warning(
+                "`input_channel` is deprecated in PaddleX 2.0 and won't take effect. Defaults to 3."
+            )
+        super(DenseNet161, self).__init__(num_classes=num_classes)
+
+
+class DenseNet201(cv.models.DenseNet201):
+    def __init__(self, num_classes=1000, input_channel=None):
+        if input_channel is not None:
+            logging.warning(
+                "`input_channel` is deprecated in PaddleX 2.0 and won't take effect. Defaults to 3."
+            )
+        super(DenseNet201, self).__init__(num_classes=num_classes)
+
+
+class ShuffleNetV2(cv.models.ShuffleNetV2):
+    def __init__(self, num_classes=1000, input_channel=None):
+        if input_channel is not None:
+            logging.warning(
+                "`input_channel` is deprecated in PaddleX 2.0 and won't take effect. Defaults to 3."
+            )
+        super(ShuffleNetV2, self).__init__(num_classes=num_classes)
+
+
+class HRNet_W18(cv.models.HRNet_W18_C):
+    def __init__(self, num_classes=1000, input_channel=None):
+        if input_channel is not None:
+            logging.warning(
+                "`input_channel` is deprecated in PaddleX 2.0 and won't take effect. Defaults to 3."
+            )
+        super(HRNet_W18, self).__init__(num_classes=num_classes)
+
+
+class AlexNet(cv.models.AlexNet):
+    def __init__(self, num_classes=1000, input_channel=None):
+        if input_channel is not None:
+            logging.warning(
+                "`input_channel` is deprecated in PaddleX 2.0 and won't take effect. Defaults to 3."
+            )
+        super(AlexNet, self).__init__(num_classes=num_classes)

+ 6 - 2
dygraph/paddlex/cv/models/base.py

@@ -204,13 +204,17 @@ class BaseModel:
         nranks = paddle.distributed.get_world_size()
         local_rank = paddle.distributed.get_rank()
         if nranks > 1:
+            find_unused_parameters = getattr(self, 'find_unused_parameters',
+                                             False)
             # Initialize parallel environment if not done.
             if not paddle.distributed.parallel.parallel_helper._is_parallel_ctx_initialized(
             ):
                 paddle.distributed.init_parallel_env()
-                ddp_net = paddle.DataParallel(self.net)
+                ddp_net = paddle.DataParallel(
+                    self.net, find_unused_parameters=find_unused_parameters)
             else:
-                ddp_net = paddle.DataParallel(self.net)
+                ddp_net = paddle.DataParallel(
+                    self.net, find_unused_parameters=find_unused_parameters)
 
         if use_vdl:
             from visualdl import LogWriter

+ 30 - 5
dygraph/paddlex/cv/models/classifier.py

@@ -34,10 +34,11 @@ __all__ = [
     "ResNet18_vd", "ResNet34_vd", "ResNet50_vd", "ResNet50_vd_ssld",
     "ResNet101_vd", "ResNet101_vd_ssld", "ResNet152_vd", "ResNet200_vd",
     "AlexNet", "DarkNet53", "MobileNetV1", "MobileNetV2", "MobileNetV3_small",
-    "MobileNetV3_large", "DenseNet121", "DenseNet161", "DenseNet169",
-    "DenseNet201", "DenseNet264", "HRNet_W18_C", "HRNet_W30_C", "HRNet_W32_C",
-    "HRNet_W40_C", "HRNet_W44_C", "HRNet_W48_C", "HRNet_W64_C", "Xception41",
-    "Xception65", "Xception71", "ShuffleNetV2", "ShuffleNetV2_swish"
+    "MobileNetV3_small_ssld", "MobileNetV3_large", "MobileNetV3_large_ssld",
+    "DenseNet121", "DenseNet161", "DenseNet169", "DenseNet201", "DenseNet264",
+    "HRNet_W18_C", "HRNet_W30_C", "HRNet_W32_C", "HRNet_W40_C", "HRNet_W44_C",
+    "HRNet_W48_C", "HRNet_W64_C", "Xception41", "Xception65", "Xception71",
+    "ShuffleNetV2", "ShuffleNetV2_swish"
 ]
 
 
@@ -421,7 +422,7 @@ class ResNet101_vd(BaseClassifier):
 class ResNet101_vd_ssld(BaseClassifier):
     def __init__(self, num_classes=1000):
         super(ResNet101_vd_ssld, self).__init__(
-            model_name='ResNet101_vd_ssld',
+            model_name='ResNet101_vd',
             num_classes=num_classes,
             lr_mult_list=[.1, .1, .2, .2, .3])
         self.model_name = 'ResNet101_vd_ssld'
@@ -505,7 +506,24 @@ class MobileNetV3_small(BaseClassifier):
         model_name = 'MobileNetV3_small_x' + str(float(scale)).replace('.',
                                                                        '_')
         super(MobileNetV3_small, self).__init__(
+            model_name=model_name,
+            num_classes=num_classes,
+            lr_mult_list=[.1, .1, .2, .2, .3])
+
+
+class MobileNetV3_small_ssld(BaseClassifier):
+    def __init__(self, num_classes=1000, scale=1.0):
+        supported_scale = [.35, 1.0]
+        if scale not in supported_scale:
+            logging.warning(
+                "scale={} is not supported by MobileNetV3_small_ssld, "
+                "scale is forcibly set to 1.0".format(scale))
+            scale = 1.0
+        model_name = 'MobileNetV3_small_x' + str(float(scale)).replace('.',
+                                                                       '_')
+        super(MobileNetV3_small_ssld, self).__init__(
             model_name=model_name, num_classes=num_classes)
+        self.model_name = model_name + '_ssld'
 
 
 class MobileNetV3_large(BaseClassifier):
@@ -521,6 +539,13 @@ class MobileNetV3_large(BaseClassifier):
             model_name=model_name, num_classes=num_classes)
 
 
+class MobileNetV3_large_ssld(BaseClassifier):
+    def __init__(self, num_classes=1000):
+        super(MobileNetV3_large_ssld, self).__init__(
+            model_name='MobileNetV3_large_x1_0', num_classes=num_classes)
+        self.model_name = 'MobileNetV3_large_x1_0_ssld'
+
+
 class DenseNet121(BaseClassifier):
     def __init__(self, num_classes=1000):
         super(DenseNet121, self).__init__(

+ 1 - 0
dygraph/paddlex/cv/models/segmenter.py

@@ -49,6 +49,7 @@ class BaseSegmenter(BaseModel):
         self.losses = None
         self.labels = None
         self.net = self.build_net(**params)
+        self.find_unused_parameters = True
 
     def build_net(self, **params):
         # TODO: when using paddle.utils.unique_name.guard,

+ 158 - 0
dygraph/paddlex/cv/transforms/cls_transforms.py

@@ -0,0 +1,158 @@
+# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""
+function:
+    transforms for classification in PaddleX<2.0
+"""
+
+import math
+import numpy as np
+import cv2
+from PIL import Image
+from .operators import Transform, Compose, RandomHorizontalFlip, RandomVerticalFlip, Normalize, \
+    ResizeByShort, CenterCrop, RandomDistort, ArrangeClassifier
+
+
+class RandomCrop(Transform):
+    """对图像进行随机剪裁,模型训练时的数据增强操作。
+    1. 根据lower_scale、lower_ratio、upper_ratio计算随机剪裁的高、宽。
+    2. 根据随机剪裁的高、宽随机选取剪裁的起始点。
+    3. 剪裁图像。
+    4. 调整剪裁后的图像的大小到crop_size*crop_size。
+    Args:
+        crop_size (int): 随机裁剪后重新调整的目标边长。默认为224。
+        lower_scale (float): 裁剪面积相对原面积比例的最小限制。默认为0.08。
+        lower_ratio (float): 宽变换比例的最小限制。默认为3. / 4。
+        upper_ratio (float): 宽变换比例的最大限制。默认为4. / 3。
+    """
+
+    def __init__(self,
+                 crop_size=224,
+                 lower_scale=0.08,
+                 lower_ratio=3. / 4,
+                 upper_ratio=4. / 3):
+        super(RandomCrop, self).__init__()
+        self.crop_size = crop_size
+        self.lower_scale = lower_scale
+        self.lower_ratio = lower_ratio
+        self.upper_ratio = upper_ratio
+
+    def apply_im(self, image):
+        scale = [self.lower_scale, 1.0]
+        ratio = [self.lower_ratio, self.upper_ratio]
+        aspect_ratio = math.sqrt(np.random.uniform(*ratio))
+        w = 1. * aspect_ratio
+        h = 1. / aspect_ratio
+        bound = min((float(image.shape[0]) / image.shape[1]) / (h**2),
+                    (float(image.shape[1]) / image.shape[0]) / (w**2))
+        scale_max = min(scale[1], bound)
+        scale_min = min(scale[0], bound)
+        target_area = image.shape[0] * image.shape[1] * np.random.uniform(
+            scale_min, scale_max)
+        target_size = math.sqrt(target_area)
+        w = int(target_size * w)
+        h = int(target_size * h)
+        i = np.random.randint(0, image.shape[0] - h + 1)
+        j = np.random.randint(0, image.shape[1] - w + 1)
+        image = image[i:i + h, j:j + w, :]
+        image = cv2.resize(image, (self.crop_size, self.crop_size))
+        return image
+
+    def apply(self, sample):
+        sample['image'] = self.apply_im(sample['image'])
+        return sample
+
+
+class RandomRotate(Transform):
+    def __init__(self, rotate_range=30, prob=.5):
+        """
+        Randomly rotate image(s) by an arbitrary angle between -rotate_range and rotate_range.
+        Args:
+            rotate_range(int, optional): Range of the rotation angle. Defaults to 30.
+            prob(float, optional): Probability of operating rotation. Defaults to .5.
+        """
+        self.rotate_range = rotate_range
+        self.prob = prob
+
+    def apply_im(self, image, angle):
+        image = image.astype('uint8')
+        image = Image.fromarray(image)
+        image = image.rotate(angle)
+        image = np.asarray(image).astype('float32')
+        return image
+
+    def apply(self, sample):
+        rotate_lower = -self.rotate_range
+        rotate_upper = self.rotate_range
+
+        if np.random.uniform(0, 1) < self.prob:
+            angle = np.random.uniform(rotate_lower, rotate_upper)
+            sample['image'] = self.apply_im(sample['image'], angle)
+
+        return sample
+
+
+class ComposedClsTransforms(Compose):
+    """ 分类模型的基础Transforms流程,具体如下
+        训练阶段:
+        1. 随机从图像中crop一块子图,并resize成crop_size大小
+        2. 将1的输出按0.5的概率随机进行水平翻转
+        3. 将图像进行归一化
+        验证/预测阶段:
+        1. 将图像按比例Resize,使得最小边长度为crop_size[0] * 1.14
+        2. 从图像中心crop出一个大小为crop_size的图像
+        3. 将图像进行归一化
+        Args:
+            mode(str): 图像处理流程所处阶段,训练/验证/预测,分别对应'train', 'eval', 'test'
+            crop_size(int|list): 输入模型里的图像大小
+            mean(list): 图像均值
+            std(list): 图像方差
+            random_horizontal_flip(bool): 是否以0.5的概率使用随机水平翻转增强,该仅在mode为`train`时生效,默认为True
+    """
+
+    def __init__(self,
+                 mode,
+                 crop_size=[224, 224],
+                 mean=[0.485, 0.456, 0.406],
+                 std=[0.229, 0.224, 0.225],
+                 random_horizontal_flip=True):
+        width = crop_size
+        if isinstance(crop_size, list):
+            if crop_size[0] != crop_size[1]:
+                raise Exception(
+                    "In classifier model, width and height should be equal, please modify your parameter `crop_size`"
+                )
+            width = crop_size[0]
+        if width % 32 != 0:
+            raise Exception(
+                "In classifier model, width and height should be multiple of 32, e.g 224、256、320...., please modify your parameter `crop_size`"
+            )
+
+        if mode == 'train':
+            # 训练时的transforms,包含数据增强
+            transforms = [
+                RandomCrop(crop_size=width), Normalize(
+                    mean=mean, std=std)
+            ]
+            if random_horizontal_flip:
+                transforms.insert(0, RandomHorizontalFlip())
+        else:
+            # 验证/预测时的transforms
+            transforms = [
+                ResizeByShort(short_size=int(width * 1.14)),
+                CenterCrop(crop_size=width), Normalize(
+                    mean=mean, std=std)
+            ]
+
+        super(ComposedClsTransforms, self).__init__(transforms)

+ 147 - 0
dygraph/paddlex/cv/transforms/det_transforms.py

@@ -0,0 +1,147 @@
+# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""
+function:
+    transforms for detection in PaddleX<2.0
+"""
+
+import numpy as np
+from .operators import Transform, Compose, ResizeByShort, Resize, RandomHorizontalFlip, Normalize
+from .operators import RandomExpand as dy_RandomExpand
+from .operators import RandomCrop as dy_RandomCrop
+from .functions import is_poly, expand_poly, expand_rle
+
+
+class Padding(Transform):
+    """1.将图像的长和宽padding至coarsest_stride的倍数。如输入图像为[300, 640],
+       `coarest_stride`为32,则由于300不为32的倍数,因此在图像最右和最下使用0值
+       进行padding,最终输出图像为[320, 640]。
+       2.或者,将图像的长和宽padding到target_size指定的shape,如输入的图像为[300,640],
+         a. `target_size` = 960,在图像最右和最下使用0值进行padding,最终输出
+            图像为[960, 960]。
+         b. `target_size` = [640, 960],在图像最右和最下使用0值进行padding,最终
+            输出图像为[640, 960]。
+    1. 如果coarsest_stride为1,target_size为None则直接返回。
+    2. 获取图像的高H、宽W。
+    3. 计算填充后图像的高H_new、宽W_new。
+    4. 构建大小为(H_new, W_new, 3)像素值为0的np.ndarray,
+       并将原图的np.ndarray粘贴于左上角。
+    Args:
+        coarsest_stride (int): 填充后的图像长、宽为该参数的倍数,默认为1。
+        target_size (int|list|tuple): 填充后的图像长、宽,默认为None,coarset_stride优先级更高。
+    Raises:
+        TypeError: 形参`target_size`数据类型不满足需求。
+        ValueError: 形参`target_size`为(list|tuple)时,长度不满足需求。
+    """
+
+    def __init__(self, coarsest_stride=1, target_size=None):
+        if target_size is not None:
+            if not isinstance(target_size, int):
+                if not isinstance(target_size, tuple) and not isinstance(
+                        target_size, list):
+                    raise TypeError(
+                        "Padding: Type of target_size must in (int|list|tuple)."
+                    )
+                elif len(target_size) != 2:
+                    raise ValueError(
+                        "Padding: Length of target_size must equal 2.")
+        super(Padding, self).__init__()
+        self.coarsest_stride = coarsest_stride
+        self.target_size = target_size
+
+    def apply_im(self, image, padding_im_h, padding_im_w):
+        im_h, im_w, im_c = image.shape
+        padding_im = np.zeros(
+            (padding_im_h, padding_im_w, im_c), dtype=np.float32)
+        padding_im[:im_h, :im_w, :] = image
+        return padding_im
+
+    def apply_bbox(self, bbox):
+        return bbox
+
+    def apply_segm(self, segms, im_h, im_w, padding_im_h, padding_im_w):
+        expanded_segms = []
+        for segm in segms:
+            if is_poly(segm):
+                # Polygon format
+                expanded_segms.append(
+                    [expand_poly(poly, 0, 0) for poly in segm])
+            else:
+                # RLE format
+                expanded_segms.append(
+                    expand_rle(segm, 0, 0, im_h, im_w, padding_im_h,
+                               padding_im_w))
+        return expanded_segms
+
+    def apply(self, sample):
+        im_h, im_w, im_c = sample['image'].shape[:]
+
+        if isinstance(self.target_size, int):
+            padding_im_h = self.target_size
+            padding_im_w = self.target_size
+        elif isinstance(self.target_size, list) or isinstance(self.target_size,
+                                                              tuple):
+            padding_im_w = self.target_size[0]
+            padding_im_h = self.target_size[1]
+        elif self.coarsest_stride > 0:
+            padding_im_h = int(
+                np.ceil(im_h / self.coarsest_stride) * self.coarsest_stride)
+            padding_im_w = int(
+                np.ceil(im_w / self.coarsest_stride) * self.coarsest_stride)
+        else:
+            raise ValueError(
+                "coarsest_stridei(>1) or target_size(list|int) need setting in Padding transform"
+            )
+        pad_height = padding_im_h - im_h
+        pad_width = padding_im_w - im_w
+        if pad_height < 0 or pad_width < 0:
+            raise ValueError(
+                'the size of image should be less than target_size, but the size of image ({}, {}), is larger than target_size ({}, {})'
+                .format(im_w, im_h, padding_im_w, padding_im_h))
+        sample['image'] = self.apply_im(sample['image'], padding_im_h,
+                                        padding_im_w)
+        if 'gt_bbox' in sample and len(sample['gt_bbox']) > 0:
+            sample['gt_bbox'] = self.apply_bbox(sample['gt_bbox'])
+        if 'gt_poly' in sample and len(sample['gt_poly']) > 0:
+            sample['gt_poly'] = self.apply_segm(sample['gt_poly'], im_h, im_w,
+                                                padding_im_h, padding_im_w)
+
+        return sample
+
+
+class RandomExpand(dy_RandomExpand):
+    def __init__(self,
+                 ratio=4.,
+                 prob=0.5,
+                 fill_value=[123.675, 116.28, 103.53]):
+        super(RandomExpand, self).__init__(
+            upper_ratio=ratio, prob=prob, im_padding_value=fill_value)
+
+
+class RandomCrop(dy_RandomCrop):
+    def __init__(self,
+                 aspect_ratio=[.5, 2.],
+                 thresholds=[.0, .1, .3, .5, .7, .9],
+                 scaling=[.3, 1.],
+                 num_attempts=50,
+                 allow_no_crop=True,
+                 cover_all_box=False):
+        super(RandomCrop, self).__init__(
+            crop_size=None,
+            aspect_ratio=aspect_ratio,
+            thresholds=thresholds,
+            scaling=scaling,
+            num_attempts=num_attempts,
+            allow_no_crop=allow_no_crop,
+            cover_all_box=cover_all_box)

+ 528 - 0
dygraph/paddlex/cv/transforms/seg_transforms.py

@@ -0,0 +1,528 @@
+# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""
+function:
+    transforms for segmentation in PaddleX<2.0
+"""
+
+import numpy as np
+import cv2
+import copy
+from .operators import Transform, Compose, RandomHorizontalFlip, RandomVerticalFlip, Resize, \
+    ResizeByShort, Normalize, RandomDistort, ArrangeSegmenter
+from .operators import Padding as dy_Padding
+
+
+class ResizeByLong(Transform):
+    """对图像长边resize到固定值,短边按比例进行缩放。当存在标注图像时,则同步进行处理。
+    Args:
+        long_size (int): resize后图像的长边大小。
+    """
+
+    def __init__(self, long_size=256):
+        super(ResizeByLong, self).__init__()
+        self.long_size = long_size
+
+    def apply_im(self, image):
+        image = _resize_long(image, long_size=self.long_size)
+        return image
+
+    def apply_mask(self, mask):
+        mask = _resize_long(
+            mask, long_size=self.long_size, interpolation=cv2.INTER_NEAREST)
+        return mask
+
+    def apply(self, sample):
+        sample['image'] = self.apply_im(sample['image'])
+        if 'mask' in sample:
+            sample['mask'] = self.apply_mask(sample['mask'])
+
+        return sample
+
+
+class ResizeRangeScaling(Transform):
+    """对图像长边随机resize到指定范围内,短边按比例进行缩放。当存在标注图像时,则同步进行处理。
+    Args:
+        min_value (int): 图像长边resize后的最小值。默认值400。
+        max_value (int): 图像长边resize后的最大值。默认值600。
+    Raises:
+        ValueError: min_value大于max_value
+    """
+
+    def __init__(self, min_value=400, max_value=600):
+        super(ResizeRangeScaling, self).__init__()
+        if min_value > max_value:
+            raise ValueError('min_value must be less than max_value, '
+                             'but they are {} and {}.'.format(min_value,
+                                                              max_value))
+        self.min_value = min_value
+        self.max_value = max_value
+
+    def apply_im(self, image, random_size):
+        image = _resize_long(image, long_size=random_size)
+        return image
+
+    def apply_mask(self, mask, random_size):
+        mask = _resize_long(
+            mask, long_size=random_size, interpolation=cv2.INTER_NEAREST)
+        return mask
+
+    def apply(self, sample):
+        if self.min_value == self.max_value:
+            random_size = self.max_value
+        else:
+            random_size = int(
+                np.random.uniform(self.min_value, self.max_value) + 0.5)
+        sample['image'] = self.apply_im(sample['image'], random_size)
+        if 'mask' in sample:
+            sample['mask'] = self.apply_mask(sample['mask'], random_size)
+
+        return sample
+
+
+class ResizeStepScaling(Transform):
+    """对图像按照某一个比例resize,这个比例以scale_step_size为步长
+    在[min_scale_factor, max_scale_factor]随机变动。当存在标注图像时,则同步进行处理。
+    Args:
+        min_scale_factor(float), resize最小尺度。默认值0.75。
+        max_scale_factor (float), resize最大尺度。默认值1.25。
+        scale_step_size (float), resize尺度范围间隔。默认值0.25。
+    Raises:
+        ValueError: min_scale_factor大于max_scale_factor
+    """
+
+    def __init__(self,
+                 min_scale_factor=0.75,
+                 max_scale_factor=1.25,
+                 scale_step_size=0.25):
+        if min_scale_factor > max_scale_factor:
+            raise ValueError(
+                'min_scale_factor must be less than max_scale_factor, '
+                'but they are {} and {}.'.format(min_scale_factor,
+                                                 max_scale_factor))
+        super(ResizeStepScaling, self).__init__()
+        self.min_scale_factor = min_scale_factor
+        self.max_scale_factor = max_scale_factor
+        self.scale_step_size = scale_step_size
+
+    def apply_im(self, image, scale_factor):
+        image = cv2.resize(
+            image, (0, 0),
+            fx=scale_factor,
+            fy=scale_factor,
+            interpolation=cv2.INTER_LINEAR)
+        if image.ndim < 3:
+            image = np.expand_dims(image, axis=-1)
+        return image
+
+    def apply_mask(self, mask, scale_factor):
+        mask = cv2.resize(
+            mask, (0, 0),
+            fx=scale_factor,
+            fy=scale_factor,
+            interpolation=cv2.INTER_NEAREST)
+        return mask
+
+    def apply(self, sample):
+        if self.min_scale_factor == self.max_scale_factor:
+            scale_factor = self.min_scale_factor
+
+        elif self.scale_step_size == 0:
+            scale_factor = np.random.uniform(self.min_scale_factor,
+                                             self.max_scale_factor)
+
+        else:
+            num_steps = int((self.max_scale_factor - self.min_scale_factor) /
+                            self.scale_step_size + 1)
+            scale_factors = np.linspace(self.min_scale_factor,
+                                        self.max_scale_factor,
+                                        num_steps).tolist()
+            np.random.shuffle(scale_factors)
+            scale_factor = scale_factors[0]
+
+        sample['image'] = self.apply_im(sample['image'], scale_factor)
+        if 'mask' in sample:
+            sample['mask'] = self.apply_mask(sample['mask'], scale_factor)
+
+        return sample
+
+
+class Padding(dy_Padding):
+    """对图像或标注图像进行padding,padding方向为右和下。
+    根据提供的值对图像或标注图像进行padding操作。
+    Args:
+        target_size (int|list|tuple): padding后图像的大小。
+        im_padding_value (list): 图像padding的值。默认为[127.5, 127.5, 127.5]。
+        label_padding_value (int): 标注图像padding的值。默认值为255。
+    Raises:
+        TypeError: target_size不是int|list|tuple。
+        ValueError:  target_size为list|tuple时元素个数不等于2。
+    """
+
+    def __init__(self,
+                 target_size,
+                 im_padding_value=[127.5, 127.5, 127.5],
+                 label_padding_value=255):
+        super(Padding, self).__init__(
+            target_size=target_size,
+            pad_mode=0,
+            offsets=None,
+            im_padding_value=im_padding_value,
+            label_padding_value=label_padding_value)
+
+
+class RandomPaddingCrop(Transform):
+    """对图像和标注图进行随机裁剪,当所需要的裁剪尺寸大于原图时,则进行padding操作。
+    Args:
+        crop_size (int|list|tuple): 裁剪图像大小。默认为512。
+        im_padding_value (list): 图像padding的值。默认为[127.5, 127.5, 127.5]。
+        label_padding_value (int): 标注图像padding的值。默认值为255。
+    Raises:
+        TypeError: crop_size不是int/list/tuple。
+        ValueError:  target_size为list/tuple时元素个数不等于2。
+    """
+
+    def __init__(self,
+                 crop_size=512,
+                 im_padding_value=[127.5, 127.5, 127.5],
+                 label_padding_value=255):
+        if isinstance(crop_size, list) or isinstance(crop_size, tuple):
+            if len(crop_size) != 2:
+                raise ValueError(
+                    'when crop_size is list or tuple, it should include 2 elements, but it is {}'
+                    .format(crop_size))
+        elif not isinstance(crop_size, int):
+            raise TypeError(
+                "Type of crop_size is invalid. Must be Integer or List or tuple, now is {}"
+                .format(type(crop_size)))
+        super(RandomPaddingCrop, self).__init__()
+        self.crop_size = crop_size
+        self.im_padding_value = im_padding_value
+        self.label_padding_value = label_padding_value
+
+    def apply_im(self, image, pad_h, pad_w):
+        im_h, im_w, im_c = image.shape
+        orig_im = copy.deepcopy(image)
+        image = np.zeros(
+            (im_h + pad_h, im_w + pad_w, im_c)).astype(orig_im.dtype)
+        for i in range(im_c):
+            image[:, :, i] = np.pad(orig_im[:, :, i],
+                                    pad_width=((0, pad_h), (0, pad_w)),
+                                    mode='constant',
+                                    constant_values=(self.im_padding_value[i],
+                                                     self.im_padding_value[i]))
+        return image
+
+    def apply_mask(self, mask, pad_h, pad_w):
+        mask = np.pad(mask,
+                      pad_width=((0, pad_h), (0, pad_w)),
+                      mode='constant',
+                      constant_values=(self.label_padding_value,
+                                       self.label_padding_value))
+        return mask
+
+    def apply(self, sample):
+        """
+        Args:
+            im (np.ndarray): 图像np.ndarray数据。
+            im_info (list): 存储图像reisze或padding前的shape信息,如
+                [('resize', [200, 300]), ('padding', [400, 600])]表示
+                图像在过resize前shape为(200, 300), 过padding前shape为
+                (400, 600)
+            label (np.ndarray): 标注图像np.ndarray数据。
+         Returns:
+            tuple: 当label为空时,返回的tuple为(im, im_info),分别对应图像np.ndarray数据、存储与图像相关信息的字典;
+                当label不为空时,返回的tuple为(im, im_info, label),分别对应图像np.ndarray数据、
+                存储与图像相关信息的字典和标注图像np.ndarray数据。
+        """
+        if isinstance(self.crop_size, int):
+            crop_width = self.crop_size
+            crop_height = self.crop_size
+        else:
+            crop_width = self.crop_size[0]
+            crop_height = self.crop_size[1]
+
+        im_h, im_w, im_c = sample['image'].shape
+
+        if im_h == crop_height and im_w == crop_width:
+            return sample
+        else:
+            pad_height = max(crop_height - im_h, 0)
+            pad_width = max(crop_width - im_w, 0)
+            if pad_height > 0 or pad_width > 0:
+                sample['image'] = self.apply_im(sample['image'], pad_height,
+                                                pad_width)
+
+                if 'mask' in sample:
+                    sample['mask'] = self.apply_mask(sample['mask'],
+                                                     pad_height, pad_width)
+
+                im_h = sample['image'].shape[0]
+                im_w = sample['image'].shape[1]
+
+            if crop_height > 0 and crop_width > 0:
+                h_off = np.random.randint(im_h - crop_height + 1)
+                w_off = np.random.randint(im_w - crop_width + 1)
+
+                sample['image'] = sample['image'][h_off:(
+                    crop_height + h_off), w_off:(w_off + crop_width), :]
+                if 'mask' in sample:
+                    sample['mask'] = sample['mask'][h_off:(
+                        crop_height + h_off), w_off:(w_off + crop_width)]
+        return sample
+
+
+class RandomBlur(Transform):
+    """以一定的概率对图像进行高斯模糊。
+    Args:
+        prob (float): 图像模糊概率。默认为0.1。
+    """
+
+    def __init__(self, prob=0.1):
+        super(RandomBlur, self).__init__()
+        self.prob = prob
+
+    def apply_im(self, image, radius):
+        image = cv2.GaussianBlur(image, (radius, radius), 0, 0)
+        return image
+
+    def apply(self, sample):
+        if self.prob <= 0:
+            n = 0
+        elif self.prob >= 1:
+            n = 1
+        else:
+            n = int(1.0 / self.prob)
+        if n > 0:
+            if np.random.randint(0, n) == 0:
+                radius = np.random.randint(3, 10)
+                if radius % 2 != 1:
+                    radius = radius + 1
+                if radius > 9:
+                    radius = 9
+                sample['image'] = self.apply_im(sample['image'], radius)
+
+        return sample
+
+
+class RandomRotate(Transform):
+    """对图像进行随机旋转, 模型训练时的数据增强操作。
+    在旋转区间[-rotate_range, rotate_range]内,对图像进行随机旋转,当存在标注图像时,同步进行,
+    并对旋转后的图像和标注图像进行相应的padding。
+    Args:
+        rotate_range (float): 最大旋转角度。默认为15度。
+        im_padding_value (list): 图像padding的值。默认为[127.5, 127.5, 127.5]。
+        label_padding_value (int): 标注图像padding的值。默认为255。
+    """
+
+    def __init__(self,
+                 rotate_range=15,
+                 im_padding_value=[127.5, 127.5, 127.5],
+                 label_padding_value=255):
+        super(RandomRotate, self).__init__()
+        self.rotate_range = rotate_range
+        self.im_padding_value = im_padding_value
+        self.label_padding_value = label_padding_value
+
+    def apply(self, sample):
+        if self.rotate_range > 0:
+            h, w, c = sample['image'].shape
+            do_rotation = np.random.uniform(-self.rotate_range,
+                                            self.rotate_range)
+            pc = (w // 2, h // 2)
+            r = cv2.getRotationMatrix2D(pc, do_rotation, 1.0)
+            cos = np.abs(r[0, 0])
+            sin = np.abs(r[0, 1])
+
+            nw = int((h * sin) + (w * cos))
+            nh = int((h * cos) + (w * sin))
+
+            (cx, cy) = pc
+            r[0, 2] += (nw / 2) - cx
+            r[1, 2] += (nh / 2) - cy
+            dsize = (nw, nh)
+            rot_ims = list()
+            for i in range(0, c, 3):
+                ori_im = sample['image'][:, :, i:i + 3]
+                rot_im = cv2.warpAffine(
+                    ori_im,
+                    r,
+                    dsize=dsize,
+                    flags=cv2.INTER_LINEAR,
+                    borderMode=cv2.BORDER_CONSTANT,
+                    borderValue=self.im_padding_value[i:i + 3])
+                rot_ims.append(rot_im)
+            sample['image'] = np.concatenate(rot_ims, axis=-1)
+            if 'mask' in sample:
+                sample['mask'] = cv2.warpAffine(
+                    sample['mask'],
+                    r,
+                    dsize=dsize,
+                    flags=cv2.INTER_NEAREST,
+                    borderMode=cv2.BORDER_CONSTANT,
+                    borderValue=self.label_padding_value)
+
+        return sample
+
+
+class RandomScaleAspect(Transform):
+    """裁剪并resize回原始尺寸的图像和标注图像。
+    按照一定的面积比和宽高比对图像进行裁剪,并reszie回原始图像的图像,当存在标注图时,同步进行。
+    Args:
+        min_scale (float):裁取图像占原始图像的面积比,取值[0,1],为0时则返回原图。默认为0.5。
+        aspect_ratio (float): 裁取图像的宽高比范围,非负值,为0时返回原图。默认为0.33。
+    """
+
+    def __init__(self, min_scale=0.5, aspect_ratio=0.33):
+        super(RandomScaleAspect, self).__init__()
+        self.min_scale = min_scale
+        self.aspect_ratio = aspect_ratio
+
+    def apply(self, sample):
+        if self.min_scale != 0 and self.aspect_ratio != 0:
+            img_height = sample['image'].shape[0]
+            img_width = sample['image'].shape[1]
+            for i in range(0, 10):
+                area = img_height * img_width
+                target_area = area * np.random.uniform(self.min_scale, 1.0)
+                aspectRatio = np.random.uniform(self.aspect_ratio,
+                                                1.0 / self.aspect_ratio)
+
+                dw = int(np.sqrt(target_area * 1.0 * aspectRatio))
+                dh = int(np.sqrt(target_area * 1.0 / aspectRatio))
+                if (np.random.randint(10) < 5):
+                    tmp = dw
+                    dw = dh
+                    dh = tmp
+
+                if (dh < img_height and dw < img_width):
+                    h1 = np.random.randint(0, img_height - dh)
+                    w1 = np.random.randint(0, img_width - dw)
+
+                    sample['image'] = sample['image'][h1:(h1 + dh), w1:(w1 + dw
+                                                                        ), :]
+                    sample['image'] = cv2.resize(
+                        sample['image'], (img_width, img_height),
+                        interpolation=cv2.INTER_LINEAR)
+                    if sample['image'].ndim < 3:
+                        sample['image'] = np.expand_dims(
+                            sample['image'], axis=-1)
+
+                    if 'mask' in sample:
+                        sample['mask'] = sample['mask'][h1:(h1 + dh), w1:(w1 +
+                                                                          dw)]
+                        sample['mask'] = cv2.resize(
+                            sample['mask'], (img_width, img_height),
+                            interpolation=cv2.INTER_NEAREST)
+                    break
+        return sample
+
+
+class Clip(Transform):
+    """
+    对图像上超出一定范围的数据进行截断。
+    Args:
+        min_val (list): 裁剪的下限,小于min_val的数值均设为min_val. 默认值0.
+        max_val (list): 裁剪的上限,大于max_val的数值均设为max_val. 默认值255.0.
+    """
+
+    def __init__(self, min_val=[0, 0, 0], max_val=[255.0, 255.0, 255.0]):
+        if not (isinstance(min_val, list) and isinstance(max_val, list)):
+            raise ValueError("{}: input type is invalid.".format(self))
+        super(Clip, self).__init__()
+        self.min_val = min_val
+        self.max_val = max_val
+
+    def apply_im(self, image):
+        for k in range(image.shape[2]):
+            np.clip(
+                image[:, :, k],
+                self.min_val[k],
+                self.max_val[k],
+                out=image[:, :, k])
+        return image
+
+    def apply(self, sample):
+        sample['image'] = self.apply_im(sample['image'])
+        return sample
+
+
+class ComposedSegTransforms(Compose):
+    """ 语义分割模型(UNet/DeepLabv3p)的图像处理流程,具体如下
+        训练阶段:
+        1. 随机对图像以0.5的概率水平翻转,若random_horizontal_flip为False,则跳过此步骤
+        2. 按不同的比例随机Resize原图, 处理方式参考[paddlex.seg.transforms.ResizeRangeScaling](#resizerangescaling)。若min_max_size为None,则跳过此步骤
+        3. 从原图中随机crop出大小为train_crop_size大小的子图,如若crop出来的图小于train_crop_size,则会将图padding到对应大小
+        4. 图像归一化
+       预测阶段:
+        1. 将图像的最长边resize至(min_max_size[0] + min_max_size[1])//2, 短边按比例resize。若min_max_size为None,则跳过此步骤
+        2. 图像归一化
+        Args:
+            mode(str): Transforms所处的阶段,包括`train', 'eval'或'test'
+            min_max_size(list): 用于对图像进行resize,具体作用参见上述步骤。
+            train_crop_size(list): 训练过程中随机裁剪原图用于训练,具体作用参见上述步骤。此参数仅在mode为`train`时生效。
+            mean(list): 图像均值, 默认为[0.485, 0.456, 0.406]。
+            std(list): 图像方差,默认为[0.229, 0.224, 0.225]。
+            random_horizontal_flip(bool): 数据增强,是否随机水平翻转图像,此参数仅在mode为`train`时生效。
+    """
+
+    def __init__(self,
+                 mode,
+                 min_max_size=[400, 600],
+                 train_crop_size=[512, 512],
+                 mean=[0.5, 0.5, 0.5],
+                 std=[0.5, 0.5, 0.5],
+                 random_horizontal_flip=True):
+        if mode == 'train':
+            # 训练时的transforms,包含数据增强
+            if min_max_size is None:
+                transforms = [
+                    RandomPaddingCrop(crop_size=train_crop_size), Normalize(
+                        mean=mean, std=std)
+                ]
+            else:
+                transforms = [
+                    ResizeRangeScaling(
+                        min_value=min(min_max_size),
+                        max_value=max(min_max_size)),
+                    RandomPaddingCrop(crop_size=train_crop_size), Normalize(
+                        mean=mean, std=std)
+                ]
+            if random_horizontal_flip:
+                transforms.insert(0, RandomHorizontalFlip())
+        else:
+            # 验证/预测时的transforms
+            if min_max_size is None:
+                transforms = [Normalize(mean=mean, std=std)]
+            else:
+                long_size = (min(min_max_size) + max(min_max_size)) // 2
+                transforms = [
+                    ResizeByLong(long_size=long_size), Normalize(
+                        mean=mean, std=std)
+                ]
+        super(ComposedSegTransforms, self).__init__(transforms)
+
+
+def _resize_long(im, long_size=224, interpolation=cv2.INTER_LINEAR):
+    value = max(im.shape[0], im.shape[1])
+    scale = float(long_size) / float(value)
+    resized_width = int(round(im.shape[1] * scale))
+    resized_height = int(round(im.shape[0] * scale))
+
+    im_dims = im.ndim
+    im = cv2.resize(
+        im, (resized_width, resized_height), interpolation=interpolation)
+    if im_dims >= 3 and im.ndim < 3:
+        im = np.expand_dims(im, axis=-1)
+    return im

+ 175 - 9
dygraph/paddlex/det.py

@@ -11,16 +11,182 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+import logging
 
-import sys
-message = 'Your running script needs PaddleX<2.0.0, please refer to {} to solve this issue.'.format(
-    'https://github.com/PaddlePaddle/PaddleX/tree/release/2.0-rc/tutorials/train#%E7%89%88%E6%9C%AC%E5%8D%87%E7%BA%A7'
-)
+from . import cv
+from .cv.models.utils.visualize import visualize_detection, draw_pr_curve
+from paddlex.cv.transforms import det_transforms
 
+transforms = det_transforms
 
-def __getattr__(attr):
-    if attr == 'transforms':
+visualize = visualize_detection
+draw_pr_curve = draw_pr_curve
 
-        print("\033[1;31;40m{}\033[0m".format(message).encode("utf-8")
-              .decode("latin1"))
-        sys.exit(-1)
+
+class FasterRCNN(cv.models.FasterRCNN):
+    def __init__(self,
+                 num_classes=81,
+                 backbone='ResNet50',
+                 with_fpn=True,
+                 aspect_ratios=[0.5, 1.0, 2.0],
+                 anchor_sizes=[32, 64, 128, 256, 512],
+                 with_dcn=None,
+                 rpn_cls_loss=None,
+                 rpn_focal_loss_alpha=None,
+                 rpn_focal_loss_gamma=None,
+                 rcnn_bbox_loss=None,
+                 rcnn_nms=None,
+                 keep_top_k=100,
+                 nms_threshold=0.5,
+                 score_threshold=0.05,
+                 softnms_sigma=None,
+                 bbox_assigner=None,
+                 fpn_num_channels=256,
+                 input_channel=None,
+                 rpn_batch_size_per_im=256,
+                 rpn_fg_fraction=0.5,
+                 test_pre_nms_top_n=None,
+                 test_post_nms_top_n=1000):
+        if with_dcn is not None:
+            logging.warning(
+                "`with_dcn` is deprecated in PaddleX 2.0 and won't take effect. Defaults to False."
+            )
+        if rpn_cls_loss is not None:
+            logging.warning(
+                "`rpn_cls_loss` is deprecated in PaddleX 2.0 and won't take effect. "
+                "Defaults to 'SigmoidCrossEntropy'.")
+        if rpn_focal_loss_alpha is not None or rpn_focal_loss_gamma is not None:
+            logging.warning(
+                "Focal loss is deprecated in PaddleX 2.0."
+                " `rpn_focal_loss_alpha` and `rpn_focal_loss_gamma` won't take effect."
+            )
+        if rcnn_bbox_loss is not None:
+            logging.warning(
+                "`rcnn_bbox_loss` is deprecated in PaddleX 2.0 and won't take effect. "
+                "Defaults to 'SmoothL1Loss'")
+        if rcnn_nms is not None:
+            logging.warning(
+                "MultiClassSoftNMS is deprecated in PaddleX 2.0. "
+                "`rcnn_nms` and `softnms_sigma` won't take effect. MultiClassNMS will be used by default"
+            )
+        if bbox_assigner is not None:
+            logging.warning(
+                "`bbox_assigner` is deprecated in PaddleX 2.0 and won't take effect. "
+                "Defaults to 'BBoxAssigner'")
+        if input_channel is not None:
+            logging.warning(
+                "`input_channel` is deprecated in PaddleX 2.0 and won't take effect. Defaults to 3."
+            )
+        super(FasterRCNN, self).__init__(
+            num_classes=num_classes - 1,
+            backbone=backbone,
+            with_fpn=with_fpn,
+            aspect_ratios=aspect_ratios,
+            anchor_sizes=anchor_sizes,
+            keep_top_k=keep_top_k,
+            nms_threshold=nms_threshold,
+            score_threshold=score_threshold,
+            fpn_num_channels=fpn_num_channels,
+            rpn_batch_size_per_im=rpn_batch_size_per_im,
+            rpn_fg_fraction=rpn_fg_fraction,
+            test_pre_nms_top_n=test_pre_nms_top_n,
+            test_post_nms_top_n=test_post_nms_top_n)
+
+
+class YOLOv3(cv.models.YOLOv3):
+    def __init__(self,
+                 num_classes=80,
+                 backbone='MobileNetV1',
+                 anchors=None,
+                 anchor_masks=None,
+                 ignore_threshold=0.7,
+                 nms_score_threshold=0.01,
+                 nms_topk=1000,
+                 nms_keep_topk=100,
+                 nms_iou_threshold=0.45,
+                 label_smooth=False,
+                 train_random_shapes=None,
+                 input_channel=None):
+        if train_random_shapes is not None:
+            logging.warning(
+                "`train_random_shapes` is deprecated in PaddleX 2.0 and won't take effect. "
+                "To apply multi_scale training, please refer to paddlex.transforms.BatchRandomResize: "
+                "'https://github.com/PaddlePaddle/PaddleX/blob/develop/dygraph/paddlex/cv/transforms/batch_operators.py#L53'"
+            )
+        if input_channel is not None:
+            logging.warning(
+                "`input_channel` is deprecated in PaddleX 2.0 and won't take effect. Defaults to 3."
+            )
+        super(YOLOv3, self).__init__(
+            num_classes=num_classes,
+            backbone=backbone,
+            anchors=anchors,
+            anchor_masks=anchor_masks,
+            ignore_threshold=ignore_threshold,
+            nms_score_threshold=nms_score_threshold,
+            nms_topk=nms_topk,
+            nms_keep_topk=nms_keep_topk,
+            nms_iou_threshold=nms_iou_threshold,
+            label_smooth=label_smooth)
+
+
+class PPYOLO(cv.models.PPYOLO):
+    def __init__(
+            self,
+            num_classes=80,
+            backbone='ResNet50_vd_ssld',
+            with_dcn_v2=None,
+            # YOLO Head
+            anchors=None,
+            anchor_masks=None,
+            use_coord_conv=True,
+            use_iou_aware=True,
+            use_spp=True,
+            use_drop_block=True,
+            scale_x_y=1.05,
+            # PPYOLO Loss
+            ignore_threshold=0.7,
+            label_smooth=False,
+            use_iou_loss=True,
+            # NMS
+            use_matrix_nms=True,
+            nms_score_threshold=0.01,
+            nms_topk=1000,
+            nms_keep_topk=100,
+            nms_iou_threshold=0.45,
+            train_random_shapes=None,
+            input_channel=None):
+        if with_dcn_v2 is not None:
+            logging.warning(
+                "`with_dcn_v2` is deprecated in PaddleX 2.0 and will not take effect. "
+                "To use backbone with deformable convolutional networks, "
+                "please specify in `backbone_name`. "
+                "Currently the only backbone with dcn is 'ResNet50_vd_dcn'.")
+        if train_random_shapes is not None:
+            logging.warning(
+                "`train_random_shapes` is deprecated in PaddleX 2.0 and won't take effect. "
+                "To apply multi_scale training, please refer to paddlex.transforms.BatchRandomResize: "
+                "'https://github.com/PaddlePaddle/PaddleX/blob/develop/dygraph/paddlex/cv/transforms/batch_operators.py#L53'"
+            )
+        if input_channel is not None:
+            logging.warning(
+                "`input_channel` is deprecated in PaddleX 2.0 and won't take effect. Defaults to 3."
+            )
+        super(PPYOLO, self).__init__(
+            num_classes=num_classes,
+            backbone=backbone,
+            anchors=anchors,
+            anchor_masks=anchor_masks,
+            use_coord_conv=use_coord_conv,
+            use_iou_aware=use_iou_aware,
+            use_spp=use_spp,
+            use_drop_block=use_drop_block,
+            scale_x_y=scale_x_y,
+            ignore_threshold=ignore_threshold,
+            label_smooth=label_smooth,
+            use_iou_loss=use_iou_loss,
+            use_matrix_nms=use_matrix_nms,
+            nms_score_threshold=nms_score_threshold,
+            nms_topk=nms_topk,
+            nms_keep_topk=nms_keep_topk,
+            nms_iou_threshold=nms_iou_threshold)

+ 214 - 9
dygraph/paddlex/seg.py

@@ -11,16 +11,221 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+import logging
 
-import sys
-message = 'Your running script needs PaddleX<2.0.0, please refer to {} to solve this issue.'.format(
-    'https://github.com/PaddlePaddle/PaddleX/tree/release/2.0-rc/tutorials/train#%E7%89%88%E6%9C%AC%E5%8D%87%E7%BA%A7'
-)
+from . import cv
+from .cv.models.utils.visualize import visualize_segmentation
+from paddlex.cv.transforms import seg_transforms
 
+transforms = seg_transforms
 
-def __getattr__(attr):
-    if attr == 'transforms':
+visualize = visualize_segmentation
 
-        print("\033[1;31;40m{}\033[0m".format(message).encode("utf-8")
-              .decode("latin1"))
-        sys.exit(-1)
+
+class UNet(cv.models.UNet):
+    def __init__(self,
+                 num_classes=2,
+                 upsample_mode='bilinear',
+                 use_bce_loss=False,
+                 use_dice_loss=False,
+                 class_weight=None,
+                 ignore_index=None,
+                 input_channel=None):
+        if num_classes > 2 and (use_bce_loss or use_dice_loss):
+            raise ValueError(
+                "dice loss and bce loss is only applicable to binary classification"
+            )
+        elif num_classes == 2:
+            if use_bce_loss and use_dice_loss:
+                use_mixed_loss = [('CrossEntropyLoss', 1), ('DiceLoss', 1)]
+            elif use_bce_loss:
+                use_mixed_loss = [('CrossEntropyLoss', 1)]
+            elif use_dice_loss:
+                use_mixed_loss = [('DiceLoss', 1)]
+            else:
+                use_mixed_loss = False
+        else:
+            use_mixed_loss = False
+
+        if class_weight is not None:
+            logging.warning(
+                "`class_weight` is not supported in PaddleX 2.0 currently and is forcibly set to None."
+            )
+        if ignore_index is not None:
+            logging.warning(
+                "`ignore_index` is deprecated in PaddleX 2.0 and won't take effect. Defaults to 255."
+            )
+        if input_channel is not None:
+            logging.warning(
+                "`input_channel` is deprecated in PaddleX 2.0 and won't take effect. Defaults to 3."
+            )
+
+        if upsample_mode == 'bilinear':
+            use_deconv = False
+        else:
+            use_deconv = True
+        super(UNet, self).__init__(
+            num_classes=num_classes,
+            use_mixed_loss=use_mixed_loss,
+            use_deconv=use_deconv)
+
+
+class DeepLabv3p(cv.models.DeepLabV3P):
+    def __init__(self,
+                 num_classes=2,
+                 backbone='ResNet50_vd',
+                 output_stride=8,
+                 aspp_with_sep_conv=None,
+                 decoder_use_sep_conv=None,
+                 encoder_with_aspp=None,
+                 enable_decoder=None,
+                 use_bce_loss=False,
+                 use_dice_loss=False,
+                 class_weight=None,
+                 ignore_index=None,
+                 pooling_crop_size=None,
+                 input_channel=None):
+        if num_classes > 2 and (use_bce_loss or use_dice_loss):
+            raise ValueError(
+                "dice loss and bce loss is only applicable to binary classification"
+            )
+        elif num_classes == 2:
+            if use_bce_loss and use_dice_loss:
+                use_mixed_loss = [('CrossEntropyLoss', 1), ('DiceLoss', 1)]
+            elif use_bce_loss:
+                use_mixed_loss = [('CrossEntropyLoss', 1)]
+            elif use_dice_loss:
+                use_mixed_loss = [('DiceLoss', 1)]
+            else:
+                use_mixed_loss = False
+        else:
+            use_mixed_loss = False
+
+        if aspp_with_sep_conv is not None:
+            logging.warning(
+                "`aspp_with_sep_conv` is deprecated in PaddleX 2.0 and will not take effect. "
+                "Defaults to True")
+        if decoder_use_sep_conv is not None:
+            logging.warning(
+                "`decoder_use_sep_conv` is deprecated in PaddleX 2.0 and will not take effect. "
+                "Defaults to True")
+        if encoder_with_aspp is not None:
+            logging.warning(
+                "`encoder_with_aspp` is deprecated in PaddleX 2.0 and will not take effect. "
+                "Defaults to True")
+        if enable_decoder is not None:
+            logging.warning(
+                "`enable_decoder` is deprecated in PaddleX 2.0 and will not take effect. "
+                "Defaults to True")
+        if class_weight is not None:
+            logging.warning(
+                "`class_weight` is not supported in PaddleX 2.0 currently and is forcibly set to None."
+            )
+        if ignore_index is not None:
+            logging.warning(
+                "`ignore_index` is deprecated in PaddleX 2.0 and won't take effect. Defaults to 255."
+            )
+        if pooling_crop_size is not None:
+            logging.warning(
+                "Backbone 'MobileNetV3_large_x1_0_ssld' is currently not supported in PaddleX 2.0. "
+                "`pooling_crop_size` will not take effect. Defaults to None")
+        if input_channel is not None:
+            logging.warning(
+                "`input_channel` is deprecated in PaddleX 2.0 and won't take effect. Defaults to 3."
+            )
+
+        super(DeepLabv3p, self).__init__(
+            num_classes=num_classes,
+            backbone=backbone,
+            use_mixed_loss=use_mixed_loss,
+            output_stride=output_stride)
+
+
+class HRNet(cv.models.HRNet):
+    def __init__(self,
+                 num_classes=2,
+                 width=18,
+                 use_bce_loss=False,
+                 use_dice_loss=False,
+                 class_weight=None,
+                 ignore_index=None,
+                 input_channel=None):
+        if num_classes > 2 and (use_bce_loss or use_dice_loss):
+            raise ValueError(
+                "dice loss and bce loss is only applicable to binary classification"
+            )
+        elif num_classes == 2:
+            if use_bce_loss and use_dice_loss:
+                use_mixed_loss = [('CrossEntropyLoss', 1), ('DiceLoss', 1)]
+            elif use_bce_loss:
+                use_mixed_loss = [('CrossEntropyLoss', 1)]
+            elif use_dice_loss:
+                use_mixed_loss = [('DiceLoss', 1)]
+            else:
+                use_mixed_loss = False
+        else:
+            use_mixed_loss = False
+
+        if class_weight is not None:
+            logging.warning(
+                "`class_weight` is not supported in PaddleX 2.0 currently and is forcibly set to None."
+            )
+        if ignore_index is not None:
+            logging.warning(
+                "`ignore_index` is deprecated in PaddleX 2.0 and won't take effect. Defaults to 255."
+            )
+        if input_channel is not None:
+            logging.warning(
+                "`input_channel` is deprecated in PaddleX 2.0 and won't take effect. Defaults to 3."
+            )
+
+        super(HRNet, self).__init__(
+            num_classes=num_classes,
+            width=width,
+            use_mixed_loss=use_mixed_loss)
+
+
+class FastSCNN(cv.models.FastSCNN):
+    def __init__(self,
+                 num_classes=2,
+                 use_bce_loss=False,
+                 use_dice_loss=False,
+                 class_weight=None,
+                 ignore_index=255,
+                 multi_loss_weight=None,
+                 input_channel=3):
+        if num_classes > 2 and (use_bce_loss or use_dice_loss):
+            raise ValueError(
+                "dice loss and bce loss is only applicable to binary classification"
+            )
+        elif num_classes == 2:
+            if use_bce_loss and use_dice_loss:
+                use_mixed_loss = [('CrossEntropyLoss', 1), ('DiceLoss', 1)]
+            elif use_bce_loss:
+                use_mixed_loss = [('CrossEntropyLoss', 1)]
+            elif use_dice_loss:
+                use_mixed_loss = [('DiceLoss', 1)]
+            else:
+                use_mixed_loss = False
+        else:
+            use_mixed_loss = False
+
+        if class_weight is not None:
+            logging.warning(
+                "`class_weight` is not supported in PaddleX 2.0 currently and is forcibly set to None."
+            )
+        if ignore_index is not None:
+            logging.warning(
+                "`ignore_index` is deprecated in PaddleX 2.0 and won't take effect. Defaults to 255."
+            )
+        if multi_loss_weight is not None:
+            logging.warning(
+                "`multi_loss_weight` is deprecated in PaddleX 2.0 and will not take effect. "
+                "Defaults to [1.0, 0.4]")
+        if input_channel is not None:
+            logging.warning(
+                "`input_channel` is deprecated in PaddleX 2.0 and won't take effect. Defaults to 3."
+            )
+
+        super(FastSCNN, self).__init__(
+            num_classes=num_classes, use_mixed_loss=use_mixed_loss)

+ 6 - 0
dygraph/paddlex/utils/checkpoint.py

@@ -124,12 +124,16 @@ imagenet_weights = {
     'https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/MobileNetV2_x2_0_pretrained.pdparams',
     'MobileNetV3_small_x0_35_IMAGENET':
     'https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/MobileNetV3_small_x0_35_pretrained.pdparams',
+    'MobileNetV3_small_x0_35_ssld_IMAGENET':
+    'https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/MobileNetV3_small_x0_35_ssld_pretrained.pdparams',
     'MobileNetV3_small_x0_5_IMAGENET':
     'https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/MobileNetV3_small_x0_5_pretrained.pdparams',
     'MobileNetV3_small_x0_75_IMAGENET':
     'https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/MobileNetV3_small_x0_75_pretrained.pdparams',
     'MobileNetV3_small_x1_0_IMAGENET':
     'https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/MobileNetV3_small_x1_0_pretrained.pdparams',
+    'MobileNetV3_small_x1_0_ssld_IMAGENET':
+    'https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/MobileNetV3_small_x1_0_ssld_pretrained.pdparams',
     'MobileNetV3_small_x1_25_IMAGENET':
     'https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/MobileNetV3_small_x1_25_pretrained.pdparams',
     'MobileNetV3_large_x0_35_IMAGENET':
@@ -142,6 +146,8 @@ imagenet_weights = {
     'https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/MobileNetV3_large_x1_0_pretrained.pdparams',
     'MobileNetV3_large_x1_25_IMAGENET':
     'https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/MobileNetV3_large_x1_25_pretrained.pdparams',
+    'MobileNetV3_large_x1_0_ssld':
+    'https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/MobileNetV3_large_x1_0_ssld_pretrained.pdparams',
     'AlexNet_IMAGENET':
     'https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/AlexNet_pretrained.pdparams',
     'DarkNet53_IMAGENET':

+ 5 - 5
dygraph/requirements.txt

@@ -1,3 +1,8 @@
+-r PaddleClas/requirements.txt
+-r ./PaddleSeg/requirements.txt
+./PaddleSeg
+-r ./PaddleDetection/requirements.txt
+./PaddleDetection
 tqdm
 scipy
 colorama
@@ -8,8 +13,3 @@ paddleslim == 2.1.0
 shapely
 paddlepaddle-gpu==2.1.0
 opencv-python
--r PaddleClas/requirements.txt
--r ./PaddleSeg/requirements.txt
-./PaddleSeg
--r ./PaddleDetection/requirements.txt
-./PaddleDetection

+ 0 - 1
dygraph/tutorials/train/object_detection/ppyolo.py

@@ -55,6 +55,5 @@ model.train(
     warmup_start_lr=0.0,
     save_interval_epochs=5,
     lr_decay_epochs=[243, 324],
-    use_ema=True,
     save_dir='output/ppyolo_r50vd_dcn',
     use_vdl=True)

+ 0 - 1
dygraph/tutorials/train/object_detection/ppyolotiny.py

@@ -54,7 +54,6 @@ model.train(
     warmup_steps=1000,
     warmup_start_lr=0.0,
     lr_decay_epochs=[430, 540, 610],
-    use_ema=True,
     save_interval_epochs=5,
     save_dir='output/ppyolotiny',
     use_vdl=True)

+ 0 - 1
dygraph/tutorials/train/object_detection/ppyolov2.py

@@ -58,6 +58,5 @@ model.train(
     warmup_steps=1000,
     warmup_start_lr=0.0,
     lr_decay_epochs=[243],
-    use_ema=True,
     save_interval_epochs=5,
     save_dir='output/ppyolov2_r50vd_dcn')