ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

PaddleOCR-v3模型ONNXRuntime部署实战:C++/Python跨平台推理优化

PaddleOCR-v3模型ONNXRuntime部署实战:C++/Python跨平台推理优化 简介ONNXOpen Neural Network Exchange作为一种开放的模型格式实现了不同深度学习框架间模型的互操作性。其核心原理在于定义了一套通用的计算图表示标准使得训练好的模型可以脱离原生框架在统一的运行时环境中进行高效推理。这一特性为模型部署带来了巨大的技术价值尤其在资源受限的边缘计算和需要高性能C集成的生产环境中能够显著降低依赖复杂性并提升执行效率。ONNXRuntime作为微软推出的高性能推理引擎对ONNX模型提供了优异的支持并具备多语言API和跨平台能力成为工业级部署的理想选择。在实际应用场景中例如光学字符识别OCR将业界知名的PaddleOCR-v3模型转换为ONNX格式再利用ONNXRuntime进行部署可以兼顾识别精度与推理速度有效解决Python方案在边缘设备或C主程序中集成困难的问题。本文即围绕这一实践详细阐述了从模型导出、环境配置到核心代码实现与性能优化的完整流程。1. 项目背景与核心价值最近在做一个需要离线OCR识别的项目要求是部署在边缘设备上对速度和稳定性都有比较高的要求。网上找了一圈PaddleOCR的识别精度和速度口碑一直不错但官方提供的Python推理方案在资源受限的环境下尤其是需要多线程并发或者集成到C主程序里时就显得有点力不从心了。直接调用Paddle Inference的C API虽然可行但依赖库的编译和部署又是一大堆麻烦事而且模型转换、前后处理都得自己从头搞非常折腾。这时候ONNXRuntime就进入了我的视野。它是一个高性能的推理引擎对ONNX格式的模型支持得非常好而且提供了C、Python、C#、Java等多种语言的API部署起来非常灵活。最关键的是它的C接口非常干净依赖少性能高特别适合集成到产品里。所以我的目标就很明确了把训练好的PaddleOCR-v3模型转换成ONNX格式然后用ONNXRuntime的C和Python接口分别进行部署实现一套代码、两种语言、随处可跑的效果。这个“ONNXRuntime部署PaddleOCR-v3”的项目包就是我这个折腾过程的完整总结。里面不仅包含了转换好的ONNX模型文件还有写好的C和Python推理源码以及详细的部署说明。无论你是想快速在Python环境里验证效果还是需要把OCR能力无缝嵌入到现有的C桌面应用或服务器后端这个项目都能给你提供一个扎实的起点省去你从零开始摸索模型转换、前后处理对齐、性能优化这些坑的时间。2. 环境准备与工具链搭建工欲善其事必先利其器。在开始代码之前我们需要把整个工具链搭建好。这个过程虽然有点繁琐但每一步都关系到后面能否顺利跑通。2.1 Python环境与PaddleOCR模型导出首先我们需要一个Python环境来运行PaddlePaddle以便导出原始模型。我强烈建议使用Anaconda或者Miniconda来管理环境这样可以避免包冲突。# 创建一个新的conda环境Python版本建议3.8或3.9兼容性最好 conda create -n paddle_onnx python3.9 conda activate paddle_onnx # 安装PaddlePaddle GPU版本如果你有NVIDIA显卡且配置了CUDA # 请根据你的CUDA版本去Paddle官网选择对应的安装命令例如CUDA 11.2 python -m pip install paddlepaddle-gpu2.5.2.post112 -f https://www.paddlepaddle.org.cn/whl/linux/mkl/avx/stable.html # 或者安装CPU版本 python -m pip install paddlepaddle2.5.2 -i https://mirror.baidu.com/pypi/simple # 安装PaddleOCR和paddle2onnx工具 pip install paddleocr2.7.3 pip install paddle2onnx1.0.8 pip install onnxruntime1.16.3 # 这是Python版的ONNXRuntime安装完成后我们可以先验证一下PaddleOCR是否能正常工作并用它提供的预训练模型进行导出。PaddleOCR-v3是一个统称它通常包含三个子模型文本检测Detection、方向分类Classification和文本识别Recognition。我们需要分别导出它们。这里有个关键点PaddleOCR的推理过程是串联的先检测出文本框再对每个框判断方向是否需要旋转最后进行识别。在部署时我们可以选择部署这三个独立的模型然后在代码里组织调用逻辑也可以尝试将它们组合成一个图。为了灵活性和可调试性我选择分别导出三个独立的ONNX模型。假设我们已经下载好了PaddleOCR的预训练模型文件.pdmodel和.pdiparams或者使用PaddleOCR代码中自动下载的模型。模型通常保存在~/.paddleocr/whl/目录下。导出脚本大致如下import paddle2onnx from paddle2onnx.command import program2onnx # 以文本检测模型ch_PP-OCRv3_det为例 # 你需要指定模型的模型文件、参数文件路径以及输入输出的名称和形状 det_model_dir “/path/to/ch_PP-OCRv3_det_infer” # 使用paddle2onnx命令行工具转换 # 在终端执行 # paddle2onnx --model_dir {det_model_dir} --model_filename inference.pdmodel --params_filename inference.pdiparams --save_file det_model.onnx --opset_version 12 --enable_onnx_checker True对于识别和分类模型操作类似。opset_version我选择了12这是一个在兼容性和性能上比较平衡的版本。导出的ONNX模型文件det_model.onnx,cls_model.onnx,rec_model.onnx就是后续推理的核心。2.2 C开发环境配置Visual Studio / VSCode对于C部署我们需要准备ONNXRuntime的C开发库。这里以Windows平台配合Visual Studio 2019/2022为例Linux下的CMake流程也类似。下载ONNXRuntime去ONNXRuntime的GitHub Release页面下载对应你平台的预编译包。对于Windows我们通常下载onnxruntime-win-x64-1.16.3.zipCPU版本或带gpu标签的版本。解压到一个合适的目录例如D:\Libs\onnxruntime。配置Visual Studio项目创建一个新的C控制台项目。右键项目 - 属性。C/C - 常规 - 附加包含目录添加ONNXRuntime解压目录下的include文件夹路径如D:\Libs\onnxruntime\include。链接器 - 常规 - 附加库目录添加ONNXRuntime的lib文件夹路径如D:\Libs\onnxruntime\lib。链接器 - 输入 - 附加依赖项添加onnxruntime.lib。调试环境为了运行时能找到DLL你需要将ONNXRuntime的bin目录包含onnxruntime.dll添加到系统的PATH环境变量或者更简单的方法是在Visual Studio的项目属性中“调试 - 环境”里添加一行例如PATHD:\Libs\onnxruntime\bin;%PATH%。如果你更喜欢使用VSCode配合CMake那么你需要编写一个CMakeLists.txt文件来定位ONNXRuntime的库。网络上有很多关于“vscode 配置c”和“vscode配置c/c环境”的教程核心就是配置好c_cpp_properties.json,tasks.json,launch.json这三个文件并在CMake中通过find_package或直接指定路径来链接ONNXRuntime。注意ONNXRuntime有多个构建版本如onnxruntime默认、onnxruntime_providers_cudaGPU支持等。如果你需要GPU推理务必下载并链接对应的版本并在代码中指定CUDA执行提供器。否则默认使用CPU执行提供器。2.3 Python推理环境Python环境就简单多了我们在第一步已经安装了onnxruntime包。如果你想用GPU加速需要安装onnxruntime-gpu。注意onnxruntime和onnxruntime-gpu不能同时安装且onnxruntime-gpu的版本必须与你系统的CUDA版本严格匹配。# 卸载CPU版本安装对应CUDA 11.8的GPU版本 pip uninstall onnxruntime pip install onnxruntime-gpu1.16.3安装后在Python代码中通过onnxruntime.InferenceSession加载模型时可以通过providers参数指定使用‘CUDAExecutionProvider’来启用GPU。3. 模型推理核心流程与代码实现有了模型和环境接下来就是最核心的部分编写推理代码。无论是C还是Python其核心逻辑都是一致的加载模型 - 预处理输入数据 - 运行会话Session - 后处理输出结果。PaddleOCR的三个模型需要按顺序调用。3.1 图像预处理与PaddleOCR对齐的关键这是最容易出错的一步。PaddleOCR在训练和原始推理时对输入图像有一系列特定的预处理操作包括归一化Normalize、通道顺序转换BGR-RGB、尺寸缩放Resize等。如果我们直接用原始图片喂给ONNX模型结果肯定不对。我们必须完全复现PaddleOCR推理脚本中的预处理逻辑。以检测模型为例PaddleOCR的输入通常是一个经过Normalize减均值、除方差、ToCHWHWC转CHW和Scale可能缩放处理的float32数组形状为[1, 3, H, W]其中H和W是固定的或者是动态的。我们需要仔细查看PaddleOCR源码中通常是tools/infer/utility.py和ppocr/data/imaug/operators.py关于DetResizeForTest,NormalizeImage,ToCHWImage等操作的具体参数。例如均值可能是[123.675, 116.28, 103.53]方差是[58.395, 57.12, 57.375]。在我的项目代码中我分别用C使用OpenCV和Python使用NumPy和cv2实现了完全一致的预处理函数。例如C的预处理片段cv::Mat preprocess_det(const cv::Mat src_img, int target_height, int target_width) { cv::Mat img_resized; cv::resize(src_img, img_resized, cv::Size(target_width, target_height)); // 转换颜色空间 BGR - RGB cv::cvtColor(img_resized, img_resized, cv::COLOR_BGR2RGB); // 转换为float并归一化 img_resized.convertTo(img_resized, CV_32FC3); // 手动进行减均值除方差 std::vectorfloat mean {123.675f, 116.28f, 103.53f}; std::vectorfloat std {58.395f, 57.12f, 57.375f}; // ... 对每个通道每个像素进行计算 ... // 转换为 CHW 格式: [H, W, C] - [C, H, W] std::vectorcv::Mat split_channels; cv::split(img_resized, split_channels); // 将三个通道的数据合并到一个一维vector中准备输入到模型 std::vectorfloat input_tensor_values; for (int c 0; c 3; c) { input_tensor_values.insert(input_tensor_values.end(), (float*)split_channels[c].data, (float*)split_channels[c].data target_height * target_width); } return input_tensor_values; // 实际上返回的是vectorfloat }3.2 C接口封装与推理在C中我们使用ONNXRuntime的C API。主要步骤是创建Ort::Env环境、Ort::Session会话然后准备输入输出。#include onnxruntime_cxx_api.h class PaddleOCROnnx { public: PaddleOCROnnx(const std::string det_model_path, const std::string rec_model_path, const std::string cls_model_path “”) { // 1. 初始化环境 env_ Ort::Env(ORT_LOGGING_LEVEL_WARNING, “PaddleOCR”); Ort::SessionOptions session_options; session_options.SetIntraOpNumThreads(1); // 设置线程数 // 如果需要GPU在这里添加CUDA提供器 // Ort::ThrowOnError(OrtSessionOptionsAppendExecutionProvider_CUDA(session_options, 0)); // 2. 创建会话 det_session_ Ort::Session(env_, det_model_path.c_str(), session_options); rec_session_ Ort::Session(env_, rec_model_path.c_str(), session_options); if (!cls_model_path.empty()) { cls_session_ Ort::Session(env_, cls_model_path.c_str(), session_options); } // 3. 获取输入输出信息名称、形状等这部分代码略长主要是调用session_.GetInputName/GetOutputName等 } std::vectorOCRRect detect(const cv::Mat image) { // 预处理图像得到符合检测模型输入的float数组 auto input_tensor_values preprocess_det(image); // 创建Ort::Value输入张量 std::vectorint64_t input_shape {1, 3, det_input_height_, det_input_width_}; auto memory_info Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault); Ort::Value input_tensor Ort::Value::CreateTensorfloat( memory_info, input_tensor_values.data(), input_tensor_values.size(), input_shape.data(), input_shape.size()); // 运行推理 auto output_tensors det_session_.Run( Ort::RunOptions{nullptr}, det_input_names_.data(), // 输入节点名数组 input_tensor, // 输入张量指针数组 1, det_output_names_.data(), // 输出节点名数组 det_output_names_.size()); // 后处理从output_tensors[0]可能是score map和output_tensors[1]可能是geometry中解析出文本框坐标 // 这里涉及到OpenCV的findContours、阈值化、多边形逼近等操作是检测模型后处理的核心代码较长。 return postprocess_det(output_tensors, image.size()); } // 识别和分类函数类似... private: Ort::Env env_; Ort::Session det_session_; Ort::Session rec_session_; Ort::Session cls_session_; // ... 其他成员变量如输入输出名称、尺寸等 };C代码的关键在于内存管理和数据转换。ONNXRuntime的Ort::Value封装了张量数据我们需要确保输入数据的形状、类型和内存布局完全正确。后处理部分特别是检测模型的后处理从热力图和几何图中提取文本框是算法核心也是性能瓶颈之一需要仔细优化。3.3 Python接口封装与推理Python接口就友好得多代码也更简洁。我们可以利用onnxruntime和numpy的便利性。import onnxruntime as ort import cv2 import numpy as np class PaddleOCROnnxPy: def __init__(self, det_model_path, rec_model_path, cls_model_pathNone): # 创建会话可以指定使用CPU或GPU providers [‘CPUExecutionProvider’] # 如果想用GPU改为 # providers [‘CUDAExecutionProvider’, ‘CPUExecutionProvider’] sess_options ort.SessionOptions() sess_options.intra_op_num_threads 4 # 设置线程数 self.det_session ort.InferenceSession(det_model_path, sess_optionssess_options, providersproviders) self.rec_session ort.InferenceSession(rec_model_path, sess_optionssess_options, providersproviders) if cls_model_path: self.cls_session ort.InferenceSession(cls_model_path, sess_optionssess_options, providersproviders) # 获取输入输出名 self.det_input_name self.det_session.get_inputs()[0].name # ... def detect(self, image): # 预处理 img_preprocessed, (ratio_h, ratio_w) self.preprocess_det(image) # 将numpy数组转换为模型输入需要的格式 det_inputs {self.det_input_name: img_preprocessed} # 运行推理 det_outputs self.det_session.run(None, det_inputs) # 后处理 boxes self.postprocess_det(det_outputs, (ratio_h, ratio_w), image.shape) return boxes def recognize(self, image_crop_list): 对裁剪出的每个文本框图像进行识别 rec_results [] for img_crop in image_crop_list: # 对每个小图进行预处理归一化、resize到rec模型输入尺寸等 rec_input self.preprocess_rec(img_crop) rec_outputs self.rec_session.run(None, {self.rec_input_name: rec_input}) # 后处理将输出向量解码为文本字符串这里通常需要一个CTC解码器和一个字典文件 text self.decode_rec_output(rec_outputs[0]) rec_results.append(text) return rec_resultsPython版本的后处理逻辑与C一致但实现起来更快捷。识别模型的后处理通常需要一个CTC解码算法如贪婪解码或束搜索和一个包含所有字符的字典文件ppocr_keys_v1.txt将模型输出的概率序列转换成最终的文本字符串。3.4 串联调用与结果整合最后我们需要将三个模型串联起来形成一个完整的OCR流水线。// C 示例 std::vectorOCRRect run_ocr(const cv::Mat img) { // 1. 文本检测 auto text_boxes ocr_engine.detect(img); std::vectorOCRRect results; for (const auto box : text_boxes) { // 2. 裁剪文本框图像 cv::Mat crop crop_rotate_image(img, box); // 3. 方向分类可选 float cls_score; bool need_rotate false; if (use_cls) { need_rotate ocr_engine.classify(crop, cls_score); if (need_rotate) { cv::rotate(crop, crop, cv::ROTATE_180); } } // 4. 文本识别 std::string text ocr_engine.recognize(crop); // 5. 保存结果 results.push_back({box, text, cls_score}); } // 6. 按位置排序可选例如从上到下从左到右 sort_text_boxes(results); return results; }Python版本的流程完全一样。这里的一个优化点是识别模型通常一次只能处理一张小图。为了提高吞吐量可以对多个裁剪出的文本框进行批处理Batch Inference。这需要我们将多个小图预处理后在批次维度batch dimension上堆叠成一个张量然后一次性输入给识别模型。这要求识别模型的ONNX导出时支持动态的批次维度-1并且在代码中做好批次的组装和结果的拆分。4. 性能优化与实战踩坑记录把流程跑通只是第一步要让它在实际项目中可用性能优化和避坑是必不可少的环节。下面分享几个我在实战中遇到的关键问题和解决方案。4.1 模型优化与动态输入问题最初导出的检测模型输入尺寸是固定的例如[1, 3, 960, 960]这意味着无论原图多大都会被强制缩放到这个尺寸。对于长宽比悬殊的图片缩放会导致变形影响检测精度。解决方案使用Paddle2ONNX导出时可以指定输入为动态形状。例如将高度和宽度维度设置为-1。paddle2onnx ... --input_shape_dict{‘x’:[-1,3,-1,-1]}这样导出的ONNX模型输入形状为[batch_size, 3, height, width]。在推理时我们可以根据原图尺寸动态计算预处理后的尺寸保持原比例缩放短边缩放到某个值长边按比例缩放并填充到32的倍数。这要求我们的预处理和后处理代码能适应可变的输入尺寸计算复杂度会稍微增加但精度提升是值得的。另一个优化点ONNXRuntime支持模型优化。我们可以使用onnxruntime_tools中的optimizer或者onnxruntime自带的GraphOptimizationLevel进行图优化。在创建Session时设置// C session_options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_EXTENDED);# Python sess_options.graph_optimization_level ort.GraphOptimizationLevel.ORT_ENABLE_EXTENDED这可以融合一些操作符提高推理速度。4.2 内存管理与多线程安全问题在C服务端部署时需要处理高并发请求。如果每个请求都创建和销毁ONNXRuntime Session开销巨大。同时多个线程同时调用同一个Session的Run方法是否安全解决方案Session复用在程序初始化时创建好OCR引擎包含所有Session并将其作为全局或单例对象。所有推理请求共享这个引擎。多线程安全根据ONNXRuntime官方文档一个Ort::Session对象的Run方法不是线程安全的。如果多个线程同时调用同一个Session的Run会导致未定义行为。方案A线程独占为每个处理线程创建独立的Session实例。这适用于线程数量固定且不多的场景如线程池。内存消耗会随线程数增加。方案BSession池维护一个Session对象池。当线程需要推理时从池中借用一个Session用完后归还。这需要自己实现池化和锁机制。方案C请求队列采用生产者-消费者模式。所有OCR请求放入一个队列由单个或少数几个专用的“推理线程”从队列中取出任务并执行然后将结果返回。这是最稳妥的方案能保证最高的吞吐量和稳定性我在生产环境中采用了这种模式。4.3 前后处理性能瓶颈问题 profiling性能分析后发现整个OCR流程中模型推理时间只占一小部分大量的时间花在了图像的预处理resize, normalize, color convert和后处理检测框的解析、NMS、多边形点排序、图像裁剪、识别结果解码上。解决方案使用高性能库确保使用OpenCV的优化版本如带IPP、OpenCL支持的。在C中避免在循环中频繁创建临时cv::Mat尽量复用内存。向量化操作将归一化等操作从逐像素循环改为对整个cv::Mat进行矩阵运算或者使用cv::convertTo配合cv::Scalar进行减均值除方差。优化后处理算法检测后处理中的findContours和approxPolyDP是瓶颈。可以尝试调整轮廓发现的模式如RETR_LIST和方法如CHAIN_APPROX_SIMPLE。非极大值抑制NMS可以尝试使用更快的实现或者调整阈值在精度和速度间权衡。识别解码部分如果使用束搜索Beam Search束宽beam width是影响速度的关键参数在保证准确率的前提下尽量调小。批处理如前所述对多个文本框的识别进行批处理能极大减少模型启动开销和GPU利用率低的问题。4.4 版本兼容性与依赖地狱问题项目在Windows上编译运行良好移植到Linux如CentOS 7或嵌入式设备如Jetson Nano上出现各种链接错误或运行时崩溃。解决方案静态链接对于C部署可以考虑将ONNXRuntime库静态链接到你的程序中。ONNXRuntime提供了静态库的构建方式。这样可以避免目标机器上缺少特定版本的动态库如libonnxruntime.so的问题。缺点是最终可执行文件体积会变大。交叉编译与依赖打包对于嵌入式设备最好在设备本身或相同架构的虚拟机上编译。将所有依赖的共享库如OpenCV, ONNXRuntime, protobuf等打包并通过LD_LIBRARY_PATH指定路径。Docker容器化这是解决环境问题最彻底的方法。创建一个包含所有依赖指定版本的系统库、OpenCV、ONNXRuntime的Docker镜像。无论在什么宿主机上只要运行这个容器环境就是一致的。这对于服务端部署尤其友好。谨慎选择第三方库版本OpenCV、Protobuf等库的版本与ONNXRuntime可能有兼容性要求。最好参考ONNXRuntime官方构建文档中推荐的依赖版本。例如某个版本的ONNXRuntime可能要求Protobuf的版本不低于3.xx。4.5 精度对齐与调试技巧问题用ONNXRuntime推理的结果与直接使用PaddlePaddle原版推理的结果有细微差异导致最终识别文本不同。解决方案确保预处理一致这是99%的问题来源。务必使用相同的归一化均值、方差相同的插值算法如cv::INTER_LINEAR相同的填充padding策略。将预处理后的第一个像素值打印出来和Paddle版本对比。检查模型导出使用netron工具打开导出的ONNX模型检查输入输出节点名称、数据类型float32、形状是否正确。确保导出时没有启用某些可能改变计算图的优化选项。中间结果对比分别用Paddle和ONNXRuntime运行同一张图片不仅对比最终文本更要比对三个模型各自的原始输出张量det的score map, rec的output。如果原始输出就不同那问题出在模型转换或推理引擎上。如果原始输出相同但后处理结果不同问题出在后处理代码。使用ONNXRuntime的Python API进行桥接调试先用Python版的ONNXRuntime跑通整个流程并和Paddle原版结果对齐。因为Python环境调试更方便。对齐后再将完全相同的预处理、后处理逻辑移植到C版本。这样可以隔离语言环境带来的问题。5. 项目源码结构与应用示例我提供的项目包ONNXRuntime部署PaddleOCR-v3包含C和Python源码模型说明.zip就是按照上述思路组织的。解压后你可能会看到类似如下的结构PaddleOCR-ONNX-Deploy/ ├── models/ # 存放ONNX模型文件 │ ├── ch_PP-OCRv3_det_infer.onnx │ ├── ch_PP-OCRv3_rec_infer.onnx │ └── ch_PP-OCRv3_cls_infer.onnx ├── cpp/ # C部署源码 │ ├── CMakeLists.txt │ ├── src/ │ │ ├── main.cpp # 示例主程序 │ │ ├── ocr_engine.cpp/.h # OCR引擎核心封装类 │ │ ├── preprocess.cpp/.h # 预处理函数 │ │ ├── postprocess.cpp/.h # 后处理函数 │ │ └── utils.cpp/.h │ └── third_party/ # 可能包含必要的第三方头文件 ├── python/ # Python部署源码 │ ├── paddleocr_onnx.py # 封装的OCR类 │ ├── demo.py # 使用示例 │ ├── tools/ │ │ └── model_export.py # 模型导出脚本 │ └── requirements.txt ├── docs/ # 详细说明文档 │ ├── build_windows.md │ ├── build_linux.md │ └── faq.md └── test_images/ # 测试图片C示例编译后运行程序指定图片路径即可。./ocr_system_demo --image ./test.jpg --det ./models/det.onnx --rec ./models/rec.onnx --cls ./models/cls.onnxPython示例更加简单直接。from paddleocr_onnx import PaddleOCROnnxPy ocr PaddleOCROnnxPy(det_model_path‘./models/det.onnx’, rec_model_path‘./models/rec.onnx’, cls_model_path‘./models/cls.onnx’) image cv2.imread(‘test.jpg’) results ocr.ocr(image) for box, text, confidence in results: print(f“Text: {text}, Box: {box}”) # 也可以在图片上画框 cv2.polylines(image, [box.astype(np.int32)], True, (0, 255, 0), 2)这个项目包的价值在于它提供了一个端到端、可编译、可运行的参考实现。你不需要再从零开始研究PaddleOCR的模型结构、前后处理细节也不需要手动去摸索ONNXRuntime的C API调用。你可以直接基于我的代码进行修改适配你的业务逻辑比如修改后处理的阈值、调整识别字典、增加批量处理逻辑或者将其集成到你的Qt、MFC桌面应用或者HTTP服务后端中。部署这条路坑多且杂但一旦走通收益也是巨大的。它意味着你的应用摆脱了对庞大Python环境和PaddlePaddle框架的依赖获得了更快的启动速度、更低的内存占用以及更灵活的集成能力。希望这个项目和你正在读的这篇总结能帮你少走些弯路。本文还有配套的精品资源点击获取
返回列表