Channingss 5 éve
szülő
commit
c3b50efe43

+ 1 - 1
README.md

@@ -17,7 +17,7 @@ PaddleX是基于飞桨技术生态的全流程深度学习模型开发工具。
 - [10分钟快速上手PaddleX模型训练](docs/quick_start.md)
 - [PaddleX使用教程](docs/tutorials)
 - [PaddleX模型库](docs/model_zoo.md)
-- [导出模型部署](docs/deploy.md)
+- [导出模型部署](docs/deploy/deploy.md)
 
 
 ## 反馈

+ 10 - 5
deploy/cpp/CMakeLists.txt

@@ -3,9 +3,10 @@ project(PaddleX CXX C)
 
 option(WITH_MKL        "Compile demo with MKL/OpenBlas support,defaultuseMKL."          ON)
 option(WITH_GPU        "Compile demo with GPU/CPU, default use CPU."                    ON)
-option(WITH_STATIC_LIB "Compile demo with static/shared library, default use static."   ON)
+option(WITH_STATIC_LIB "Compile demo with static/shared library, default use static."   OFF)
 option(WITH_TENSORRT "Compile demo with TensorRT."   OFF)
 
+SET(TENSORRT_DIR "" CACHE PATH "Compile demo with TensorRT")
 SET(PADDLE_DIR "" CACHE PATH "Location of libraries")
 SET(OPENCV_DIR "" CACHE PATH "Location of libraries")
 SET(CUDA_LIB "" CACHE PATH "Location of libraries")
@@ -111,8 +112,10 @@ endif()
 
 if (NOT WIN32)
   if (WITH_TENSORRT AND WITH_GPU)
-      include_directories("${PADDLE_DIR}/third_party/install/tensorrt/include")
-      link_directories("${PADDLE_DIR}/third_party/install/tensorrt/lib")
+      include_directories("${TENSORRT_DIR}/include")
+      link_directories("${TENSORRT_DIR}/lib")
+      #include_directories("${PADDLE_DIR}/third_party/install/tensorrt/include")
+      #link_directories("${PADDLE_DIR}/third_party/install/tensorrt/lib")
   endif()
 endif(NOT WIN32)
 
@@ -194,8 +197,10 @@ endif(NOT WIN32)
 if(WITH_GPU)
   if(NOT WIN32)
     if (WITH_TENSORRT)
-      set(DEPS ${DEPS} ${PADDLE_DIR}/third_party/install/tensorrt/lib/libnvinfer${CMAKE_STATIC_LIBRARY_SUFFIX})
-      set(DEPS ${DEPS} ${PADDLE_DIR}/third_party/install/tensorrt/lib/libnvinfer_plugin${CMAKE_STATIC_LIBRARY_SUFFIX})
+      set(DEPS ${DEPS} ${TENSORRT_DIR}/lib/libnvinfer${CMAKE_SHARED_LIBRARY_SUFFIX})
+      set(DEPS ${DEPS} ${TENSORRT_DIR}/lib/libnvinfer_plugin${CMAKE_SHARED_LIBRARY_SUFFIX})
+      #set(DEPS ${DEPS} ${PADDLE_DIR}/third_party/install/tensorrt/lib/libnvinfer${CMAKE_STATIC_LIBRARY_SUFFIX})
+      #set(DEPS ${DEPS} ${PADDLE_DIR}/third_party/install/tensorrt/lib/libnvinfer_plugin${CMAKE_STATIC_LIBRARY_SUFFIX})
     endif()
     set(DEPS ${DEPS} ${CUDA_LIB}/libcudart${CMAKE_SHARED_LIBRARY_SUFFIX})
     set(DEPS ${DEPS} ${CUDNN_LIB}/libcudnn${CMAKE_SHARED_LIBRARY_SUFFIX})

+ 3 - 1
deploy/cpp/include/paddlex/paddlex.h

@@ -38,12 +38,14 @@ class Model {
  public:
   void Init(const std::string& model_dir,
             bool use_gpu = false,
+            bool use_trt = false,
             int gpu_id = 0) {
-    create_predictor(model_dir, use_gpu, gpu_id);
+    create_predictor(model_dir, use_gpu, use_trt, gpu_id);
   }
 
   void create_predictor(const std::string& model_dir,
                         bool use_gpu = false,
+                        bool use_trt = false,
                         int gpu_id = 0);
 
   bool load_config(const std::string& model_dir);

+ 3 - 6
deploy/cpp/include/paddlex/transforms.h

@@ -35,10 +35,8 @@ class ImageBlob {
   std::vector<int> ori_im_size_ = std::vector<int>(2);
   // Newest image height and width after process
   std::vector<int> new_im_size_ = std::vector<int>(2);
-  // Image height and width before padding
-  std::vector<int> im_size_before_padding_ = std::vector<int>(2);
   // Image height and width before resize
-  std::vector<int> im_size_before_resize_ = std::vector<int>(2);
+  std::vector<std::vector<int>> im_size_before_resize_;
   // Reshape order
   std::vector<std::string> reshape_order_;
   // Resize scale
@@ -49,7 +47,6 @@ class ImageBlob {
   void clear() {
     ori_im_size_.clear();
     new_im_size_.clear();
-    im_size_before_padding_.clear();
     im_size_before_resize_.clear();
     reshape_order_.clear();
     im_data_.clear();
@@ -165,8 +162,8 @@ class Padding : public Transform {
         width_ = item["target_size"].as<int>();
         height_ = item["target_size"].as<int>();
       } else if (item["target_size"].IsSequence()) {
-        width_ = item["target_size"].as<std::vector<int>>()[0];
-        height_ = item["target_size"].as<std::vector<int>>()[1];
+        width_ = item["target_size"].as<std::vector<int>>()[1];
+        height_ = item["target_size"].as<std::vector<int>>()[0];
       }
     }
     if (item["im_padding_value"].IsDefined()) {

+ 4 - 1
deploy/cpp/scripts/build.sh

@@ -1,7 +1,9 @@
 # 是否使用GPU(即是否使用 CUDA)
-WITH_GPU=ON
+WITH_GPU=OFF
 # 是否集成 TensorRT(仅WITH_GPU=ON 有效)
 WITH_TENSORRT=OFF
+# TensorRT 的lib路径
+TENSORRT_DIR=/path/to/TensorRT/
 # Paddle 预测库路径
 PADDLE_DIR=/path/to/fluid_inference/
 # CUDA 的 lib 路径
@@ -20,6 +22,7 @@ cd build
 cmake .. \
     -DWITH_GPU=${WITH_GPU} \
     -DWITH_TENSORRT=${WITH_TENSORRT} \
+    -DTENSORRT_DIR=${TENSORRT_DIR} \
     -DPADDLE_DIR=${PADDLE_DIR} \
     -DCUDA_LIB=${CUDA_LIB} \
     -DCUDNN_LIB=${CUDNN_LIB} \

+ 2 - 1
deploy/cpp/src/classifier.cpp

@@ -23,6 +23,7 @@
 
 DEFINE_string(model_dir, "", "Path of inference model");
 DEFINE_bool(use_gpu, false, "Infering with GPU or CPU");
+DEFINE_bool(use_trt, false, "Infering with TensorRT");
 DEFINE_int32(gpu_id, 0, "GPU card id");
 DEFINE_string(image, "", "Path of test image file");
 DEFINE_string(image_list, "", "Path of test image list file");
@@ -42,7 +43,7 @@ int main(int argc, char** argv) {
 
   // 加载模型
   PaddleX::Model model;
-  model.Init(FLAGS_model_dir, FLAGS_use_gpu, FLAGS_gpu_id);
+  model.Init(FLAGS_model_dir, FLAGS_use_gpu, FLAGS_use_trt, FLAGS_gpu_id);
 
   // 进行预测
   if (FLAGS_image_list != "") {

+ 2 - 1
deploy/cpp/src/detector.cpp

@@ -24,6 +24,7 @@
 
 DEFINE_string(model_dir, "", "Path of inference model");
 DEFINE_bool(use_gpu, false, "Infering with GPU or CPU");
+DEFINE_bool(use_trt, false, "Infering with TensorRT");
 DEFINE_int32(gpu_id, 0, "GPU card id");
 DEFINE_string(image, "", "Path of test image file");
 DEFINE_string(image_list, "", "Path of test image list file");
@@ -44,7 +45,7 @@ int main(int argc, char** argv) {
 
   // 加载模型
   PaddleX::Model model;
-  model.Init(FLAGS_model_dir, FLAGS_use_gpu, FLAGS_gpu_id);
+  model.Init(FLAGS_model_dir, FLAGS_use_gpu, FLAGS_use_trt, FLAGS_gpu_id);
 
   auto colormap = PaddleX::GenerateColorMap(model.labels.size());
   std::string save_dir = "output";

+ 21 - 7
deploy/cpp/src/paddlex.cpp

@@ -18,6 +18,7 @@ namespace PaddleX {
 
 void Model::create_predictor(const std::string& model_dir,
                              bool use_gpu,
+                             bool use_trt,
                              int gpu_id) {
   // 读取配置文件
   if (!load_config(model_dir)) {
@@ -37,6 +38,14 @@ void Model::create_predictor(const std::string& model_dir,
   config.SwitchSpecifyInputNames(true);
   // 开启内存优化
   config.EnableMemoryOptim();
+  if (use_trt){
+    config.EnableTensorRtEngine(1 << 20      /* workspace_size*/,  
+                        32        /* max_batch_size*/,  
+                        20                 /* min_subgraph_size*/,
+                        paddle::AnalysisConfig::Precision::kFloat32 /* precision*/,
+                        false             /* use_static*/,
+                        false             /* use_calib_mode*/);
+  }
   predictor_ = std::move(CreatePaddlePredictor(config));
 }
 
@@ -286,19 +295,23 @@ bool Model::predict(const cv::Mat& im, SegResult* result) {
                      result->score_map.shape[3],
                      CV_32FC1,
                      result->score_map.data.data());
-
+  int idx=1;
+  int len_postprocess = inputs_.im_size_before_resize_.size();
   for (std::vector<std::string>::reverse_iterator iter =
            inputs_.reshape_order_.rbegin();
-       iter != inputs_.reshape_order_.rend();
-       ++iter) {
+       iter != inputs_.reshape_order_.rend(); ++iter) {
     if (*iter == "padding") {
-      auto padding_w = inputs_.im_size_before_padding_[0];
-      auto padding_h = inputs_.im_size_before_padding_[1];
+      auto before_shape = inputs_.im_size_before_resize_[len_postprocess-idx];
+      inputs_.im_size_before_resize_.pop_back();
+      auto padding_w = before_shape[0];
+      auto padding_h = before_shape[1];
       mask_label = mask_label(cv::Rect(0, 0, padding_w, padding_h));
       mask_score = mask_score(cv::Rect(0, 0, padding_w, padding_h));
     } else if (*iter == "resize") {
-      auto resize_w = inputs_.im_size_before_resize_[0];
-      auto resize_h = inputs_.im_size_before_resize_[1];
+      auto before_shape = inputs_.im_size_before_resize_[len_postprocess-idx];
+      inputs_.im_size_before_resize_.pop_back();
+      auto resize_w = before_shape[0];
+      auto resize_h = before_shape[1];
       cv::resize(mask_label,
                  mask_label,
                  cv::Size(resize_h, resize_w),
@@ -312,6 +325,7 @@ bool Model::predict(const cv::Mat& im, SegResult* result) {
                  0,
                  cv::INTER_NEAREST);
     }
+    ++idx;
   }
   result->label_map.data.assign(mask_label.begin<uint8_t>(),
                                 mask_label.end<uint8_t>());

+ 3 - 1
deploy/cpp/src/segmenter.cpp

@@ -24,6 +24,7 @@
 
 DEFINE_string(model_dir, "", "Path of inference model");
 DEFINE_bool(use_gpu, false, "Infering with GPU or CPU");
+DEFINE_bool(use_trt, false, "Infering with TensorRT");
 DEFINE_int32(gpu_id, 0, "GPU card id");
 DEFINE_string(image, "", "Path of test image file");
 DEFINE_string(image_list, "", "Path of test image list file");
@@ -44,7 +45,8 @@ int main(int argc, char** argv) {
 
   // 加载模型
   PaddleX::Model model;
-  model.Init(FLAGS_model_dir, FLAGS_use_gpu, FLAGS_gpu_id);
+  model.Init(FLAGS_model_dir, FLAGS_use_gpu, FLAGS_use_trt, FLAGS_gpu_id);
+
   auto colormap = PaddleX::GenerateColorMap(model.labels.size());
   // 进行预测
   if (FLAGS_image_list != "") {

+ 4 - 8
deploy/cpp/src/transforms.cpp

@@ -56,8 +56,7 @@ float ResizeByShort::GenerateScale(const cv::Mat& im) {
 }
 
 bool ResizeByShort::Run(cv::Mat* im, ImageBlob* data) {
-  data->im_size_before_resize_[0] = im->rows;
-  data->im_size_before_resize_[1] = im->cols;
+  data->im_size_before_resize_.push_back({im->rows,im->cols});
   data->reshape_order_.push_back("resize");
 
   float scale = GenerateScale(*im);
@@ -88,8 +87,7 @@ bool CenterCrop::Run(cv::Mat* im, ImageBlob* data) {
 }
 
 bool Padding::Run(cv::Mat* im, ImageBlob* data) {
-  data->im_size_before_padding_[0] = im->rows;
-  data->im_size_before_padding_[1] = im->cols;
+  data->im_size_before_resize_.push_back({im->rows,im->cols});
   data->reshape_order_.push_back("padding");
 
   int padding_w = 0;
@@ -122,8 +120,7 @@ bool ResizeByLong::Run(cv::Mat* im, ImageBlob* data) {
               << std::endl;
     return false;
   }
-  data->im_size_before_resize_[0] = im->rows;
-  data->im_size_before_resize_[1] = im->cols;
+  data->im_size_before_resize_.push_back({im->rows,im->cols});
   data->reshape_order_.push_back("resize");
   int origin_w = im->cols;
   int origin_h = im->rows;
@@ -149,8 +146,7 @@ bool Resize::Run(cv::Mat* im, ImageBlob* data) {
               << std::endl;
     return false;
   }
-  data->im_size_before_resize_[0] = im->rows;
-  data->im_size_before_resize_[1] = im->cols;
+  data->im_size_before_resize_.push_back({im->rows,im->cols});
   data->reshape_order_.push_back("resize");
 
   cv::resize(

+ 10 - 3
paddlex/command.py

@@ -29,7 +29,11 @@ def arg_parser():
         action="store_true",
         default=False,
         help="export inference model for C++/Python deployment")
-
+    parser.add_argument(
+        "--fixed_input_shape",
+        "-fs",
+        default=None,
+        help="export inference model with fixed input shape(TensorRT need)")
     return parser
 
 
@@ -53,8 +57,11 @@ def main():
     if args.export_inference:
         assert args.model_dir is not None, "--model_dir should be defined while exporting inference model"
         assert args.save_dir is not None, "--save_dir should be defined to save inference model"
-        model = pdx.load_model(args.model_dir)
-        model.export_inference_model(args.save_dir)
+        fixed_input_shape = eval(args.fixed_input_shape)
+        assert len(fixed_input_shape) == 2, "len of fixed input shape must == 2"
+
+        model = pdx.load_model(args.model_dir, fixed_input_shape)
+        model.export_inference_model(args.save_dir, fixed_input_shape)
 
 
 if __name__ == "__main__":

+ 21 - 2
paddlex/cv/models/base.py

@@ -283,7 +283,7 @@ class BaseAPI:
         open(osp.join(save_dir, '.success'), 'w').close()
         logging.info("Model saved in {}.".format(save_dir))
 
-    def export_inference_model(self, save_dir):
+    def export_inference_model(self, save_dir, fixed_input_shape=None):
         test_input_names = [
             var.name for var in list(self.test_inputs.values())
         ]
@@ -316,11 +316,30 @@ class BaseAPI:
         model_info['_ModelInputsOutputs']['test_outputs'] = [
             [k, v.name] for k, v in self.test_outputs.items()
         ]
-
+        resize = {'ResizeByShort': {}}
+        padding = {'Padding':{}}
+
+        if model_info['_Attributes']['model_type'] == 'classifier':
+            crop_size = 0
+            for transform in model_info['Transforms']:
+                if 'CenterCrop' in transform:
+                    crop_size = transform['CenterCrop']['crop_size']
+                    break
+            assert crop_size == fixed_input_shape[0], "fixed_input_shape must == CenterCrop:crop_size:{}".format(crop_size)
+            assert crop_size == fixed_input_shape[1], "fixed_input_shape must == CenterCrop:crop_size:{}".format(crop_size)
+            if crop_size == 0:
+                logging.warning("fixed_input_shape must == input shape when trainning")
+        else:
+            resize['ResizeByShort']['short_size'] = min(fixed_input_shape)
+            resize['ResizeByShort']['max_size'] = max(fixed_input_shape)
+            padding['Padding']['target_size'] = list(fixed_input_shape)
+            model_info['Transforms'].append(resize)
+            model_info['Transforms'].append(padding)
         with open(
                 osp.join(save_dir, 'model.yml'), encoding='utf-8',
                 mode='w') as f:
             yaml.dump(model_info, f)
+
         # 模型保存成功的标志
         open(osp.join(save_dir, '.success'), 'w').close()
         logging.info(

+ 10 - 3
paddlex/cv/models/classifier.py

@@ -35,9 +35,10 @@ class BaseClassifier(BaseAPI):
                           'MobileNetV1', 'MobileNetV2', 'Xception41',
                           'Xception65', 'Xception71']。默认为'ResNet50'。
         num_classes (int): 类别数。默认为1000。
+        fixed_input_shape (list): 长度为2,维度为1的list,如:[640,720],用来固定模型输入:'image'的shape,默认为None。
     """
 
-    def __init__(self, model_name='ResNet50', num_classes=1000):
+    def __init__(self, model_name='ResNet50', num_classes=1000, fixed_input_shape=None):
         self.init_params = locals()
         super(BaseClassifier, self).__init__('classifier')
         if not hasattr(paddlex.cv.nets, str.lower(model_name)):
@@ -46,10 +47,16 @@ class BaseClassifier(BaseAPI):
         self.model_name = model_name
         self.labels = None
         self.num_classes = num_classes
+        self.fixed_input_shape = fixed_input_shape
 
     def build_net(self, mode='train'):
-        image = fluid.data(
-            dtype='float32', shape=[None, 3, None, None], name='image')
+        if self.fixed_input_shape is not None:
+            input_shape =[None, 3, self.fixed_input_shape[0], self.fixed_input_shape[1]]
+            image = fluid.data(
+                dtype='float32', shape=input_shape, name='image')
+        else:
+            image = fluid.data(
+                dtype='float32', shape=[None, 3, None, None], name='image')
         if mode != 'test':
             label = fluid.data(dtype='int64', shape=[None, 1], name='label')
         model = getattr(paddlex.cv.nets, str.lower(self.model_name))

+ 6 - 3
paddlex/cv/models/deeplabv3p.py

@@ -48,7 +48,7 @@ class DeepLabv3p(BaseAPI):
             自行计算相应的权重,每一类的权重为:每类的比例 * num_classes。class_weight取默认值None时,各类的权重1,
             即平时使用的交叉熵损失函数。
         ignore_index (int): label上忽略的值,label为ignore_index的像素不参与损失函数的计算。默认255。
-
+        fixed_input_shape (list): 长度为2,维度为1的list,如:[640,720],用来固定模型输入:'image'的shape,默认为None。
     Raises:
         ValueError: use_bce_loss或use_dice_loss为真且num_calsses > 2。
         ValueError: backbone取值不在['Xception65', 'Xception41', 'MobileNetV2_x0.25',
@@ -69,7 +69,8 @@ class DeepLabv3p(BaseAPI):
                  use_bce_loss=False,
                  use_dice_loss=False,
                  class_weight=None,
-                 ignore_index=255):
+                 ignore_index=255,
+                 fixed_input_shape=None):
         self.init_params = locals()
         super(DeepLabv3p, self).__init__('segmenter')
         # dice_loss或bce_loss只适用两类分割中
@@ -118,6 +119,7 @@ class DeepLabv3p(BaseAPI):
         self.enable_decoder = enable_decoder
         self.labels = None
         self.sync_bn = True
+        self.fixed_input_shape = fixed_input_shape
 
     def _get_backbone(self, backbone):
         def mobilenetv2(backbone):
@@ -182,7 +184,8 @@ class DeepLabv3p(BaseAPI):
             use_bce_loss=self.use_bce_loss,
             use_dice_loss=self.use_dice_loss,
             class_weight=self.class_weight,
-            ignore_index=self.ignore_index)
+            ignore_index=self.ignore_index,
+            fixed_input_shape = self.fixed_input_shape)
         inputs = model.generate_inputs()
         model_out = model.build_net(inputs)
         outputs = OrderedDict()

+ 6 - 2
paddlex/cv/models/faster_rcnn.py

@@ -36,6 +36,7 @@ class FasterRCNN(BaseAPI):
         with_fpn (bool): 是否使用FPN结构。默认为True。
         aspect_ratios (list): 生成anchor高宽比的可选值。默认为[0.5, 1.0, 2.0]。
         anchor_sizes (list): 生成anchor大小的可选值。默认为[32, 64, 128, 256, 512]。
+        fixed_input_shape (list): 长度为2,维度为1的list,如:[640,720],用来固定模型输入:'image'的shape,默认为None。
     """
 
     def __init__(self,
@@ -43,7 +44,8 @@ class FasterRCNN(BaseAPI):
                  backbone='ResNet50',
                  with_fpn=True,
                  aspect_ratios=[0.5, 1.0, 2.0],
-                 anchor_sizes=[32, 64, 128, 256, 512]):
+                 anchor_sizes=[32, 64, 128, 256, 512],
+                fixed_input_shape=None):
         self.init_params = locals()
         super(FasterRCNN, self).__init__('detector')
         backbones = [
@@ -57,6 +59,7 @@ class FasterRCNN(BaseAPI):
         self.aspect_ratios = aspect_ratios
         self.anchor_sizes = anchor_sizes
         self.labels = None
+        self.fixed_input_shape = fixed_input_shape
 
     def _get_backbone(self, backbone_name):
         norm_type = None
@@ -109,7 +112,8 @@ class FasterRCNN(BaseAPI):
             aspect_ratios=self.aspect_ratios,
             anchor_sizes=self.anchor_sizes,
             train_pre_nms_top_n=train_pre_nms_top_n,
-            test_pre_nms_top_n=test_pre_nms_top_n)
+            test_pre_nms_top_n=test_pre_nms_top_n,
+            fixed_input_shape = self.fixed_input_shape)
         inputs = model.generate_inputs()
         if mode == 'train':
             model_out = model.build_net(inputs)

+ 3 - 1
paddlex/cv/models/load_model.py

@@ -23,7 +23,7 @@ import paddlex
 import paddlex.utils.logging as logging
 
 
-def load_model(model_dir):
+def load_model(model_dir, fixed_input_shape=None):
     if not osp.exists(osp.join(model_dir, "model.yml")):
         raise Exception("There's not model.yml in {}".format(model_dir))
     with open(osp.join(model_dir, "model.yml")) as f:
@@ -39,6 +39,8 @@ def load_model(model_dir):
         raise Exception("There's no attribute {} in paddlex.cv.models".format(
             info['Model']))
 
+    info['_init_params']['fixed_input_shape'] = fixed_input_shape
+
     if info['_Attributes']['model_type'] == 'classifier':
         model = paddlex.cv.models.BaseClassifier(**info['_init_params'])
     else:

+ 6 - 2
paddlex/cv/models/mask_rcnn.py

@@ -36,6 +36,7 @@ class MaskRCNN(FasterRCNN):
         with_fpn (bool): 是否使用FPN结构。默认为True。
         aspect_ratios (list): 生成anchor高宽比的可选值。默认为[0.5, 1.0, 2.0]。
         anchor_sizes (list): 生成anchor大小的可选值。默认为[32, 64, 128, 256, 512]。
+        fixed_input_shape (list): 长度为2,维度为1的list,如:[640,720],用来固定模型输入:'image'的shape,默认为None。
     """
 
     def __init__(self,
@@ -43,7 +44,8 @@ class MaskRCNN(FasterRCNN):
                  backbone='ResNet50',
                  with_fpn=True,
                  aspect_ratios=[0.5, 1.0, 2.0],
-                 anchor_sizes=[32, 64, 128, 256, 512]):
+                 anchor_sizes=[32, 64, 128, 256, 512],
+                 fixed_input_shape=None):
         self.init_params = locals()
         backbones = [
             'ResNet18', 'ResNet50', 'ResNet50vd', 'ResNet101', 'ResNet101vd'
@@ -60,6 +62,7 @@ class MaskRCNN(FasterRCNN):
             self.mask_head_resolution = 28
         else:
             self.mask_head_resolution = 14
+        self.fixed_input_shape = fixed_input_shape
 
     def build_net(self, mode='train'):
         train_pre_nms_top_n = 2000 if self.with_fpn else 12000
@@ -73,7 +76,8 @@ class MaskRCNN(FasterRCNN):
             train_pre_nms_top_n=train_pre_nms_top_n,
             test_pre_nms_top_n=test_pre_nms_top_n,
             num_convs=num_convs,
-            mask_head_resolution=self.mask_head_resolution)
+            mask_head_resolution=self.mask_head_resolution,
+            fixed_input_shape = self.fixed_input_shape)
         inputs = model.generate_inputs()
         if mode == 'train':
             model_out = model.build_net(inputs)

+ 6 - 2
paddlex/cv/models/unet.py

@@ -33,6 +33,7 @@ class UNet(DeepLabv3p):
             自行计算相应的权重,每一类的权重为:每类的比例 * num_classes。class_weight取默认值None是,各类的权重1,
             即平时使用的交叉熵损失函数。
         ignore_index (int): label上忽略的值,label为ignore_index的像素不参与损失函数的计算。默认255。
+        fixed_input_shape (list): 长度为2,维度为1的list,如:[640,720],用来固定模型输入:'image'的shape,默认为None。
 
     Raises:
         ValueError: use_bce_loss或use_dice_loss为真且num_calsses > 2。
@@ -47,7 +48,8 @@ class UNet(DeepLabv3p):
                  use_bce_loss=False,
                  use_dice_loss=False,
                  class_weight=None,
-                 ignore_index=255):
+                 ignore_index=255,
+                 fixed_input_shape=None):
         self.init_params = locals()
         super(DeepLabv3p, self).__init__('segmenter')
         # dice_loss或bce_loss只适用两类分割中
@@ -77,6 +79,7 @@ class UNet(DeepLabv3p):
         self.class_weight = class_weight
         self.ignore_index = ignore_index
         self.labels = None
+        self.fixed_input_shape = fixed_input_shape
 
     def build_net(self, mode='train'):
         model = paddlex.cv.nets.segmentation.UNet(
@@ -86,7 +89,8 @@ class UNet(DeepLabv3p):
             use_bce_loss=self.use_bce_loss,
             use_dice_loss=self.use_dice_loss,
             class_weight=self.class_weight,
-            ignore_index=self.ignore_index)
+            ignore_index=self.ignore_index,
+            fixed_input_shape = self.fixed_input_shape)
         inputs = model.generate_inputs()
         model_out = model.build_net(inputs)
         outputs = OrderedDict()

+ 5 - 2
paddlex/cv/models/yolo_v3.py

@@ -60,7 +60,8 @@ class YOLOv3(BaseAPI):
                  label_smooth=False,
                  train_random_shapes=[
                      320, 352, 384, 416, 448, 480, 512, 544, 576, 608
-                 ]):
+                 ],
+                 fixed_input_shape=None):
         self.init_params = locals()
         super(YOLOv3, self).__init__('detector')
         backbones = [
@@ -80,6 +81,7 @@ class YOLOv3(BaseAPI):
         self.label_smooth = label_smooth
         self.sync_bn = True
         self.train_random_shapes = train_random_shapes
+        self.fixed_input_shape = fixed_input_shape
 
     def _get_backbone(self, backbone_name):
         if backbone_name == 'DarkNet53':
@@ -113,7 +115,8 @@ class YOLOv3(BaseAPI):
             nms_topk=self.nms_topk,
             nms_keep_topk=self.nms_keep_topk,
             nms_iou_threshold=self.nms_iou_threshold,
-            train_random_shapes=self.train_random_shapes)
+            train_random_shapes=self.train_random_shapes,
+            fixed_input_shape = self.fixed_input_shape)
         inputs = model.generate_inputs()
         model_out = model.build_net(inputs)
         outputs = OrderedDict([('bbox', model_out)])

+ 11 - 3
paddlex/cv/nets/detection/faster_rcnn.py

@@ -76,7 +76,8 @@ class FasterRCNN(object):
             fg_thresh=.5,
             bg_thresh_hi=.5,
             bg_thresh_lo=0.,
-            bbox_reg_weights=[0.1, 0.1, 0.2, 0.2]):
+            bbox_reg_weights=[0.1, 0.1, 0.2, 0.2],
+            fixed_input_shape=None):
         super(FasterRCNN, self).__init__()
         self.backbone = backbone
         self.mode = mode
@@ -148,6 +149,7 @@ class FasterRCNN(object):
         self.bg_thresh_lo = bg_thresh_lo
         self.bbox_reg_weights = bbox_reg_weights
         self.rpn_only = rpn_only
+        self.fixed_input_shape = fixed_input_shape
 
     def build_net(self, inputs):
         im = inputs['image']
@@ -219,8 +221,14 @@ class FasterRCNN(object):
 
     def generate_inputs(self):
         inputs = OrderedDict()
-        inputs['image'] = fluid.data(
-            dtype='float32', shape=[None, 3, None, None], name='image')
+
+        if self.fixed_input_shape is not None:
+            input_shape =[None, 3, self.fixed_input_shape[0], self.fixed_input_shape[1]]
+            inputs['image'] = fluid.data(
+                dtype='float32', shape=input_shape, name='image')
+        else:
+            inputs['image'] = fluid.data(
+                dtype='float32', shape=[None, 3, None, None], name='image')
         if self.mode == 'train':
             inputs['im_info'] = fluid.data(
                 dtype='float32', shape=[None, 3], name='im_info')

+ 11 - 3
paddlex/cv/nets/detection/mask_rcnn.py

@@ -86,7 +86,8 @@ class MaskRCNN(object):
             fg_thresh=.5,
             bg_thresh_hi=.5,
             bg_thresh_lo=0.,
-            bbox_reg_weights=[0.1, 0.1, 0.2, 0.2]):
+            bbox_reg_weights=[0.1, 0.1, 0.2, 0.2],
+            fixed_input_shape=None):
         super(MaskRCNN, self).__init__()
         self.backbone = backbone
         self.mode = mode
@@ -167,6 +168,7 @@ class MaskRCNN(object):
         self.bg_thresh_lo = bg_thresh_lo
         self.bbox_reg_weights = bbox_reg_weights
         self.rpn_only = rpn_only
+        self.fixed_input_shape = fixed_input_shape
 
     def build_net(self, inputs):
         im = inputs['image']
@@ -306,8 +308,14 @@ class MaskRCNN(object):
 
     def generate_inputs(self):
         inputs = OrderedDict()
-        inputs['image'] = fluid.data(
-            dtype='float32', shape=[None, 3, None, None], name='image')
+
+        if self.fixed_input_shape is not None:
+            input_shape =[None, 3, self.fixed_input_shape[0], self.fixed_input_shape[1]]
+            inputs['image'] = fluid.data(
+                dtype='float32', shape=input_shape, name='image')
+        else:
+            inputs['image'] = fluid.data(
+                dtype='float32', shape=[None, 3, None, None], name='image')
         if self.mode == 'train':
             inputs['im_info'] = fluid.data(
                 dtype='float32', shape=[None, 3], name='im_info')

+ 10 - 3
paddlex/cv/nets/detection/yolo_v3.py

@@ -33,7 +33,8 @@ class YOLOv3:
                  nms_iou_threshold=0.45,
                  train_random_shapes=[
                      320, 352, 384, 416, 448, 480, 512, 544, 576, 608
-                 ]):
+                 ],
+                 fixed_input_shape=None):
         if anchors is None:
             anchors = [[10, 13], [16, 30], [33, 23], [30, 61], [62, 45],
                        [59, 119], [116, 90], [156, 198], [373, 326]]
@@ -54,6 +55,7 @@ class YOLOv3:
         self.norm_decay = 0.0
         self.prefix_name = ''
         self.train_random_shapes = train_random_shapes
+        self.fixed_input_shape = fixed_input_shape
 
     def _head(self, feats):
         outputs = []
@@ -247,8 +249,13 @@ class YOLOv3:
 
     def generate_inputs(self):
         inputs = OrderedDict()
-        inputs['image'] = fluid.data(
-            dtype='float32', shape=[None, 3, None, None], name='image')
+        if self.fixed_input_shape is not None:
+            input_shape =[None, 3, self.fixed_input_shape[0], self.fixed_input_shape[1]]
+            inputs['image'] = fluid.data(
+                dtype='float32', shape=input_shape, name='image')
+        else:
+            inputs['image'] = fluid.data(
+                dtype='float32', shape=[None, 3, None, None], name='image')
         if self.mode == 'train':
             inputs['gt_box'] = fluid.data(
                 dtype='float32', shape=[None, None, 4], name='gt_box')

+ 12 - 3
paddlex/cv/nets/segmentation/deeplabv3p.py

@@ -61,6 +61,7 @@ class DeepLabv3p(object):
             自行计算相应的权重,每一类的权重为:每类的比例 * num_classes。class_weight取默认值None是,各类的权重1,
             即平时使用的交叉熵损失函数。
         ignore_index (int): label上忽略的值,label为ignore_index的像素不参与损失函数的计算。
+        fixed_input_shape (list): 长度为2,维度为1的list,如:[640,720],用来固定模型输入:'image'的shape,默认为None。
 
     Raises:
         ValueError: use_bce_loss或use_dice_loss为真且num_calsses > 2。
@@ -81,7 +82,8 @@ class DeepLabv3p(object):
                  use_bce_loss=False,
                  use_dice_loss=False,
                  class_weight=None,
-                 ignore_index=255):
+                 ignore_index=255,
+                 fixed_input_shape=None):
         # dice_loss或bce_loss只适用两类分割中
         if num_classes > 2 and (use_bce_loss or use_dice_loss):
             raise ValueError(
@@ -115,6 +117,7 @@ class DeepLabv3p(object):
         self.decoder_use_sep_conv = decoder_use_sep_conv
         self.encoder_with_aspp = encoder_with_aspp
         self.enable_decoder = enable_decoder
+        self.fixed_input_shape = fixed_input_shape
 
     def _encoder(self, input):
         # 编码器配置,采用ASPP架构,pooling + 1x1_conv + 三个不同尺度的空洞卷积并行, concat后1x1conv
@@ -310,8 +313,14 @@ class DeepLabv3p(object):
 
     def generate_inputs(self):
         inputs = OrderedDict()
-        inputs['image'] = fluid.data(
-            dtype='float32', shape=[None, 3, None, None], name='image')
+
+        if self.fixed_input_shape is not None:
+            input_shape =[None, 3, self.fixed_input_shape[0], self.fixed_input_shape[1]]
+            inputs['image'] = fluid.data(
+                dtype='float32', shape=input_shape, name='image')
+        else:
+            inputs['image'] = fluid.data(
+                dtype='float32', shape=[None, 3, None, None], name='image')
         if self.mode == 'train':
             inputs['label'] = fluid.data(
                 dtype='int32', shape=[None, 1, None, None], name='label')

+ 12 - 3
paddlex/cv/nets/segmentation/unet.py

@@ -54,6 +54,7 @@ class UNet(object):
                 自行计算相应的权重,每一类的权重为:每类的比例 * num_classes。class_weight取默认值None是,各类的权重1,
                 即平时使用的交叉熵损失函数。
             ignore_index (int): label上忽略的值,label为ignore_index的像素不参与损失函数的计算。
+            fixed_input_shape (list): 长度为2,维度为1的list,如:[640,720],用来固定模型输入:'image'的shape,默认为None。
 
         Raises:
             ValueError: use_bce_loss或use_dice_loss为真且num_calsses > 2。
@@ -69,7 +70,8 @@ class UNet(object):
                  use_bce_loss=False,
                  use_dice_loss=False,
                  class_weight=None,
-                 ignore_index=255):
+                 ignore_index=255,
+                 fixed_input_shape=None):
         # dice_loss或bce_loss只适用两类分割中
         if num_classes > 2 and (use_bce_loss or use_dice_loss):
             raise Exception(
@@ -97,6 +99,7 @@ class UNet(object):
         self.use_dice_loss = use_dice_loss
         self.class_weight = class_weight
         self.ignore_index = ignore_index
+        self.fixed_input_shape = fixed_input_shape
 
     def _double_conv(self, data, out_ch):
         param_attr = fluid.ParamAttr(
@@ -226,8 +229,14 @@ class UNet(object):
 
     def generate_inputs(self):
         inputs = OrderedDict()
-        inputs['image'] = fluid.data(
-            dtype='float32', shape=[None, 3, None, None], name='image')
+
+        if self.fixed_input_shape is not None:
+            input_shape =[None, 3, self.fixed_input_shape[0], self.fixed_input_shape[1]]
+            inputs['image'] = fluid.data(
+                dtype='float32', shape=input_shape, name='image')
+        else:
+            inputs['image'] = fluid.data(
+                dtype='float32', shape=[None, 3, None, None], name='image')
         if self.mode == 'train':
             inputs['label'] = fluid.data(
                 dtype='int32', shape=[None, 1, None, None], name='label')

+ 19 - 2
paddlex/cv/transforms/det_transforms.py

@@ -201,10 +201,12 @@ class Padding:
 
     Args:
         coarsest_stride (int): 填充后的图像长、宽为该参数的倍数,默认为1。
+        target_size (int|list): 填充后的图像长、宽,默认为1。
     """
 
-    def __init__(self, coarsest_stride=1):
+    def __init__(self, coarsest_stride=1, target_size=None):
         self.coarsest_stride = coarsest_stride
+        self.target_size = target_size
 
     def __call__(self, im, im_info=None, label_info=None):
         """
@@ -221,9 +223,10 @@ class Padding:
         Raises:
             TypeError: 形参数据类型不满足需求。
             ValueError: 数据长度不匹配。
+            ValueError: target_size小于原图的大小。
         """
 
-        if self.coarsest_stride == 1:
+        if self.coarsest_stride == 1 and self.target_size is None:
             if label_info is None:
                 return (im, im_info)
             else:
@@ -240,6 +243,20 @@ class Padding:
                 np.ceil(im_h / self.coarsest_stride) * self.coarsest_stride)
             padding_im_w = int(
                 np.ceil(im_w / self.coarsest_stride) * self.coarsest_stride)
+        if self.target_size is not None:
+            if isinstance(self.target_size, int):
+                padding_im_h = self.target_size
+                padding_im_w = self.target_size
+            else:
+                padding_im_h = self.target_size[0]
+                padding_im_w = self.target_size[1]
+            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))
         padding_im = np.zeros((padding_im_h, padding_im_w, im_c),
                               dtype=np.float32)
         padding_im[:im_h, :im_w, :] = im

+ 70 - 0
paddlex/cv/transforms/seg_transforms.py

@@ -287,6 +287,76 @@ class ResizeByLong:
         else:
             return (im, im_info, label)
 
+class ResizeByShort:
+    """根据图像的短边调整图像大小(resize)。
+
+    1. 获取图像的长边和短边长度。
+    2. 根据短边与short_size的比例,计算长边的目标长度,
+       此时高、宽的resize比例为short_size/原图短边长度。
+    3. 如果max_size>0,调整resize比例:
+       如果长边的目标长度>max_size,则高、宽的resize比例为max_size/原图长边长度。
+    4. 根据调整大小的比例对图像进行resize。
+
+    Args:
+        target_size (int): 短边目标长度。默认为800。
+        max_size (int): 长边目标长度的最大限制。默认为1333。
+
+     Raises:
+        TypeError: 形参数据类型不满足需求。
+    """
+
+    def __init__(self, short_size=800, max_size=1333):
+        self.max_size = int(max_size)
+        if not isinstance(short_size, int):
+            raise TypeError(
+                "Type of short_size is invalid. Must be Integer, now is {}".
+                format(type(short_size)))
+        self.short_size = short_size
+        if not (isinstance(self.max_size, int)):
+            raise TypeError("max_size: input type is invalid.")
+
+    def __call__(self, im, im_info=None, label_info=None):
+        """
+        Args:
+            im (numnp.ndarraypy): 图像np.ndarray数据。
+            im_info (dict, 可选): 存储与图像相关的信息。
+            label_info (dict, 可选): 存储与标注框相关的信息。
+
+        Returns:
+            tuple: 当label_info为空时,返回的tuple为(im, im_info),分别对应图像np.ndarray数据、存储与图像相关信息的字典;
+                   当label_info不为空时,返回的tuple为(im, im_info, label_info),分别对应图像np.ndarray数据、
+                   存储与标注框相关信息的字典。
+                   其中,im_info更新字段为:
+                       - im_resize_info (np.ndarray): resize后的图像高、resize后的图像宽、resize后的图像相对原始图的缩放比例
+                                                 三者组成的np.ndarray,形状为(3,)。
+
+        Raises:
+            TypeError: 形参数据类型不满足需求。
+            ValueError: 数据长度不匹配。
+        """
+        if im_info is None:
+            im_info = dict()
+        if not isinstance(im, np.ndarray):
+            raise TypeError("ResizeByShort: image type is not numpy.")
+        if len(im.shape) != 3:
+            raise ValueError('ResizeByShort: image is not 3-dimensional.')
+        im_short_size = min(im.shape[0], im.shape[1])
+        im_long_size = max(im.shape[0], im.shape[1])
+        scale = float(self.short_size) / im_short_size
+        if self.max_size > 0 and np.round(
+                scale * im_long_size) > self.max_size:
+            scale = float(self.max_size) / float(im_long_size)
+        resized_width = int(round(im.shape[1] * scale))
+        resized_height = int(round(im.shape[0] * scale))
+        im_resize_info = [resized_height, resized_width, scale]
+        im = cv2.resize(
+            im, (resized_width, resized_height),
+            interpolation=cv2.INTER_LINEAR)
+        im_info['im_resize_info'] = np.array(im_resize_info).astype(np.float32)
+        if label_info is None:
+            return (im, im_info)
+        else:
+            return (im, im_info, label_info)
 
 class ResizeRangeScaling:
     """对图像长边随机resize到指定范围内,短边按比例进行缩放。当存在标注图像时,则同步进行处理。