[自动驾驶-深度学习] PPliteSeg—基础部署(TensorRT)

发布于:2025-06-27 ⋅ 阅读:(20) ⋅ 点赞:(0)

PaddleSeg 简介

PaddleSeg 是百度飞桨(PaddlePaddle)推出的开源图像分割工具库,专注于高效、灵活的语义分割、实例分割和全景分割任务。它提供丰富的预训练模型、模块化设计以及端到端的训练部署支持,适用于工业级和学术研究场景。
在这里插入图片描述

核心特性包括:

  • 高性能模型:支持 DeepLabV3+、OCRNet、BiSeNet 等先进分割模型。
  • 模块化设计:可灵活配置骨干网络、损失函数等组件。
  • 轻量化部署:提供 Paddle Lite、Paddle Inference 等部署方案。
  • 可视化工具:集成 LabelMe 标注工具和预测结果可视化功能。

具体介绍可以参考 PaddleSeg GitHubPaddleSeg 文档AI Studio 项目

注: 本系列目的将记录实现PaddleSeg相关流程(主要在于模型部署(Tensorrt版本)),以及解析基础流程代码,最后将分割网络修改成多任务网络模型等基础操作

PPliteseg 模型

本篇以实践为主,采用ppliteseg为基础实践。原理大家可以细细研读论文《PP-LiteSeg: A Superior Real-Time Semantic Segmentation Model》 里面各个模块作用及效果均介绍的非常清楚。
在这里插入图片描述
指的注意的,需要充分理解config评估策略的相关参数含义:
以configs/quick_start/pp_liteseg_optic_disc_512x512_1k.yml 文件为例。

val_dataset:
  type: Dataset
  dataset_root: dataset/optic_disc_seg #评估集文件夹路径
  val_path: dataset/optic_disc_seg/val_list.txt
  num_classes: 2
  mode: val
  transforms:
    - type: Normalize  # 图像预处理 正则化

从参数可以看出,在做评估推理时,图像会需要做一次预处理过程(正则化),其中正则化的方式写在代码中:

@manager.TRANSFORMS.add_component
class Normalize:
    """
    Normalize an image.

    Args:
        mean (list, optional): The mean value of a data set. Default: [0.5,].
        std (list, optional): The standard deviation of a data set. Default: [0.5,].

    Raises:
        ValueError: When mean/std is not list or any value in std is 0.
    """

    def __init__(self, mean=(0.5, ), std=(0.5, )):
        if not (isinstance(mean, (list, tuple)) and isinstance(std, (list, tuple))) \
            and (len(mean) not in [1, 3]) and (len(std) not in [1, 3]):
            raise ValueError(
                "{}: input type is invalid. It should be list or tuple with the lenght of 1 or 3".
                format(self))
        self.mean = np.array(mean)
        self.std = np.array(std)

        from functools import reduce
        if reduce(lambda x, y: x * y, self.std) == 0:
            raise ValueError('{}: std is invalid!'.format(self))

    def __call__(self, data):
        data['img'] = functional.normalize(data['img'], self.mean, self.std)
        return data

从上面可以看出,其中默认均值为0.5,标准差为0.5所计算归一化。在后续TensorRT做预处理时需要与此参数保持一致。

模型导出

官方运行训练的示例文档讲解的非常清楚了,本文就不班门弄斧了。
本文主要介绍在训练好模型之后,需要怎么搭建到TensorRT上(跨平台部署)

  1. 在完成ppliteseg的模型训练之后,我们若想要嵌入到其他平台系统,广泛适配的是onnx模型架构,由于均是百度平台出品,因此,paddle训练出的模型绝大多数均是可以兼容paddle2onnx的。或者可以采用转换文件进行onnx转换:
deploy/python/infer_onnx_trt.py
  1. 进一步使用onnxslim功能库可以简化训练后的onnx模型
onnxslim ./output/onnx_model/pp_liteseg_model.onnx ./output/onnx_model/pp_liteseg_s.onnx
  • 最后可通过TensorRT安装后执行文件来实现onnx转engine
trtexec --onnx=./output/onnx_model/pp_liteseg_s.onnx \
		--saveEngine=./output/onnx_model/pp_liteseg_s.engine  \
       	--fp16 \
       	--explicitBatch

部署代码

此时我们假设已有待分割图片与转换好的pp_liteseg_s.engine模型,此时需要编写基础版本的tensorrt部署代码实现推理。

  • 首先是需要加载对应的模型
bool PPliteseg::LoadTrtEngin(const std::string &engine_path) {
  std::ifstream file(engine_path, std::ios::binary);
  if (!file.is_open() || !file.good()) {
    return false;
  }

  file.seekg(0, file.end);
  size_t size = file.tellg();
  file.seekg(0, file.beg);
  if (size == 0) return false;

  // Dynamically allocate memory to store model data
  char *trt_model_stream = new char[size];
  if (!trt_model_stream) return false;

  file.read(trt_model_stream, size);
  file.close();

  nvinfer1::IRuntime *run_time = nvinfer1::createInferRuntime(gLogger);
  if (!run_time) {
    delete[] trt_model_stream;
    return false;
  }

  // Deserialize CUDA engine
  nvinfer1::ICudaEngine *engine =
      run_time->deserializeCudaEngine(trt_model_stream, size, nullptr);
  if (!engine) {
    delete[] trt_model_stream;
    run_time->destroy();
    return false;
  }


  context = engine->createExecutionContext();
  if (!context) {
    delete[] trt_model_stream;
    engine->destroy(); 
    return false;
  }
  delete[] trt_model_stream;
  return true;
}
  • 进一步则需要构建对应的模型写入GPU(cuda) 上
void PPliteseg::BuildTrtEngin() {
  CHECK(cudaStreamCreate(&stream));
  const nvinfer1::ICudaEngine &engine = context->getEngine();
  input_index = engine.getBindingIndex(INPUT_BLOB_NAME);
  output_seg_index = engine.getBindingIndex(OUTPUT_SEG_BLOB_NAME);

  // Output input C channels, output 1 channel
  CHECK(cudaMalloc(&device_buffer[input_index], INPUT_SIZE * sizeof(float)));
  CHECK(cudaMalloc(&device_buffer[output_seg_index],
                   OUTPUT_SIZE * sizeof(float)));
};
  • 接下来则是推理部分,首先需要把图片转成指定通用数组形式
float* PPliteseg::ProcessSegImage(
    const cv::Mat &image, int target_height, int target_width) const {
  if (image.empty() || image.channels() != INPUT_C) {
    throw std::runtime_error("invalid image");
  }
  const int target_size = target_height * target_width;
  if (target_size == 0) {
    throw std::runtime_error("invalid target dimension");
  }

  cv::Mat processed = image.clone();

  if (image.rows != target_height || image.cols != target_width) {
    cv::resize(processed, processed, cv::Size(target_width, target_height), 0,
               0, cv::INTER_CUBIC);
  }

  if (INPUT_C == 3) {
    cv::cvtColor(processed, processed, cv::COLOR_BGR2RGB);
  }
  
  // normalization
  processed.convertTo(processed, CV_32F, 1.0 / 255.0);  
  processed = (processed - 0.5) / 0.5;
  
  // transform
  std::vector<float> data(INPUT_C * target_size);
  for (int i = 0; i < target_height; ++i) {
    const float *row = processed.ptr<float>(i);
    const int row_offset = i * target_width;
    for (int j = 0; j < target_width; ++j) {
      const float *pixel = row + j * INPUT_C;
      for (int c = 0; c < INPUT_C; ++c) {
        data[c * target_size + row_offset + j] = pixel[c];
      }
    }
  }
  float *input_buffer = new float[INPUT_SIZE];
  memcpy(input_buffer, data.data(), data.size() * sizeof(float));
  return std::move(input_buffer);
}

  • 再者就是实现GPU推理流程

void PPliteseg::DoInference(const cv::Mat &input_image, cv::Mat &output_image) {
  if (input_image.empty()) return;
  const nvinfer1::ICudaEngine &engine = context->getEngine();
  assert(engine.getNbBindings() == 3);
  float *input_buffer = ProcessSegImage(input_image);

  // cudaStream_t stream;
  float *output_seg_buffer = new float[OUTPUT_SIZE];
  cudaMemcpyAsync(device_buffer[input_index], input_buffer,
                  INPUT_SIZE * sizeof(float), cudaMemcpyHostToDevice, stream);
  CHECK(cudaDeviceSynchronize());
  context->enqueueV2(device_buffer, stream, nullptr);
  CHECK(cudaDeviceSynchronize());
  cudaMemcpyAsync(output_seg_buffer, device_buffer[output_seg_index],
                  OUTPUT_SIZE * sizeof(float), cudaMemcpyDeviceToHost, stream);
  CHECK(cudaDeviceSynchronize());
  cudaStreamSynchronize(stream);
  
  // transform output
  ProcessPredictImage(output_seg_buffer, output_image);

  delete[] output_seg_buffer;
  delete[] input_buffer;
  return;
};
  • 最后处理推理预测标签图
void PPliteseg::ProcessPredictImage(float *output_seg_buffer,
                                    cv::Mat &output_image) {
  const int map_shape = INPUT_H * INPUT_W;
  cv::Mat seg_planes[OUTPUT_C];

  for (int i = 0; i < OUTPUT_C; ++i) {
    seg_planes[i] =
        cv::Mat(INPUT_H, INPUT_W, CV_32FC1, output_seg_buffer + map_shape * i);
  }

  // Take the highest confidence index number
  auto softmax = [&](const int &y, const int &x) {
    float max_value = -std::numeric_limits<float>::infinity();
    int max_index = 0;
    for (int c = 0; c < OUTPUT_C; ++c) {
      float value = seg_planes[c].at<float>(y, x);
      if (value > max_value) {
        max_value = value;
        max_index = c;
      }
    }
    return max_index;
  };
  
  // Convert to label grayscale image
  cv::Mat seg_softmax = cv::Mat(INPUT_H, INPUT_W, CV_8UC1);
  for (int y = 0; y < INPUT_H; ++y) {
    for (int x = 0; x < INPUT_W; ++x) {
      seg_softmax.at<uchar>(y, x) = static_cast<uchar>(softmax(y, x));
    }
  }

  output_image = std::move(seg_softmax);
  return;
}
  • 至此即完成主要推理部分代码,还有些细节处理地方需要注意,如需要释放GPU内存、无效数组指针等相关操作
void PPliteseg::Destroy() {
  CHECK(cudaFree(device_buffer[input_index]));
  CHECK(cudaFree(device_buffer[output_seg_index]));
  context->destroy();
  cudaStreamDestroy(stream);
}

测试示例

在这里插入图片描述
最后附上相关C++ tensorRT部署代码地址,大家可自行参考学习。有采用的地方请标注好出处,谢谢。

参考文献

[1] Peng J , Liu Y , Tang S ,et al.PP-LiteSeg: A Superior Real-Time Semantic Segmentation Model[J]. 2022.DOI:10.48550/arXiv.2204.02681.

[2] ronghuaiyang —— PP-LiteSeg: 来自baidu的实时语义分割模型.

[3] 万里鹏程转瞬至 —— 论文解读:PP-LiteSeg: A Superior Real-Time Semantic Segmentation Model


网站公告

今日签到

点亮在社区的每一天
去签到