[自动驾驶-深度学习] PPliteSeg—基础部署(TensorRT)
PaddleSeg使用介绍
- PaddleSeg 简介
- PPliteseg 模型
- 模型导出
- 部署代码
- 测试示例
- 参考文献
PaddleSeg 简介
PaddleSeg 是百度飞桨(PaddlePaddle)推出的开源图像分割工具库,专注于高效、灵活的语义分割、实例分割和全景分割任务。它提供丰富的预训练模型、模块化设计以及端到端的训练部署支持,适用于工业级和学术研究场景。
核心特性包括:
- 高性能模型:支持 DeepLabV3+、OCRNet、BiSeNet 等先进分割模型。
- 模块化设计:可灵活配置骨干网络、损失函数等组件。
- 轻量化部署:提供 Paddle Lite、Paddle Inference 等部署方案。
- 可视化工具:集成 LabelMe 标注工具和预测结果可视化功能。
具体介绍可以参考 PaddleSeg GitHub 、 PaddleSeg 文档 、 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: Datasetdataset_root: dataset/optic_disc_seg #评估集文件夹路径val_path: dataset/optic_disc_seg/val_list.txtnum_classes: 2mode: valtransforms:- 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 reduceif 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上(跨平台部署)
- 在完成ppliteseg的模型训练之后,我们若想要嵌入到其他平台系统,广泛适配的是onnx模型架构,由于均是百度平台出品,因此,paddle训练出的模型绝大多数均是可以兼容
paddle2onnx
的。或者可以采用转换文件进行onnx转换:
deploy/python/infer_onnx_trt.py
- 进一步使用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 datachar *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 enginenvinfer1::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 channelCHECK(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);}// normalizationprocessed.convertTo(processed, CV_32F, 1.0 / 255.0); processed = (processed - 0.5) / 0.5;// transformstd::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 outputProcessPredictImage(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 numberauto 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 imagecv::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
…