写点什么

C++ 到 Python 全搞定,教你如何为 FastDeploy 贡献代码

  • 2023-02-10
    北京
  • 本文字数:7034 字

    阅读完需:约 23 分钟

C++到Python全搞定,教你如何为FastDeploy贡献代码

大家好!今天为大家带来的是一篇经验帖文。本次分享的主人公是黑客松比赛参赛者郑必城,他将为大家带来比赛项目“No.80 瑞芯微 RK3588:通过 Paddle2ONNX 打通 5 个飞桨模型的部署中如何为 FastDeploy”任务中的一些心得体会,快来看看他是如何为 FastDeploy 贡献代码的吧!


RKNPU2 是瑞芯微 Rockchip 推出的针对 RK356X/RK3588/RV1103/RV1106 的 C++推理工具。在参加黑客松比赛时,FastDeploy 仓库[1]还没有集成 RKNPU2 的引擎。开发者需要使用 RKNPU2 从头编写代码。在参加完黑客松之后,我为 FastDeploy 仓库贡献了 RKNPU2 的后端推理引擎的代码,现在能直接使用 FastDeploy 快速开发基于 RKNPU2 的代码。本次教程将以贡献 SCRFD 模型[2]为例,教你如何给 FastDeploy 贡献代码。


  • Zheng_Bicheng 主页

  • https://github.com/Zheng-Bicheng

  • No.80 瑞芯微 RK3588:通过 Paddle2ONNX 打通 5 个飞桨模型的部署链接

  • https://github.com/PaddlePaddle/Paddle/issues/44068

FastDeploy 简介

FastDeploy 是一款全场景、易用灵活、极致高效的 AI 推理部署工具,提供开箱即用的云边端部署体验,支持超过 150+文本、计算机视觉、语音和跨模态模型,并实现端到端的推理性能优化。其应用于图像分类、物体检测、图像分割、人脸检测、人脸识别、关键点检测、抠图、OCR、NLP、TTS 等任务,满足开发者多场景、多硬件、多平台的产业部署需求。同时,FastDeploy 集成了多种后端推理引擎,其中就包括 RKNPU2。开发者能够快速基于现有的模型以及后端来进行开发。


很多开发者可能会有疑惑,为什么 Rockchip 提供了 RKNPU2 rknn-toolkit2 这两个分别面向 C++和 Python 的推理引擎,我们还要使用 FastDeploy 进行开发呢?简单来说,RKNPU2 和 rknn-toolkit2 是推理引擎,它们侧重于推理;FastDeploy 是推理部署工具侧重于部署。给 RKNPU2 输入一张图片,会得到一串数字。给 FastDeploy 输入一张图片,会直接得到经过后处理后的图片。这样就能大大减少开发者在项目落地过程中的一些困难。


贡献步骤

给 FastDeploy 贡献代码,我一般按以下步骤进行,当然你可以根据自己的能力制定自己的开发步骤。



由上图所示,给 FastDeploy 贡献代码的步骤一般为编写 C++代码、编写 C++ example、编写 Python 代码、编写 Python example 代码、编写文档、提交 PR。

贡献代码指南

下面我以贡献 SCRFD 模型为例子,给大家详细介绍每个贡献环节中的注意事项。

转换模型

不管你是在 FastDeploy 上开发 C++还是 Python 的代码,转换模型都是你首先需要完成的任务。通常情况下,转换模型的工具一般使用 rknn-toolkit2,但是这个工具 API 比较多,用起来较为复杂。为了让大家能够更快速的转换模型,在 FastDeploy 中,我已经编写了转换模型的代码并且提供了详细的文档。详情请查看 FastDeploy RKNPU2 模型转换文档。这里为了缩短篇幅,直接给出模型转换的配置文件以及模型转换的文档。大家可以参考这几个文档转换自己的模型。


编写 C++代码

上文提到,SCRFD 的 C++代码需要在 fastdeploy/vision/facedet/contrib 这个目录下编写,因此我创建了 scrfd.h scrfd.cc 这两个文件,实现模型具体代码。这里要注意与常见的文件命名形式不同,scrfd.cc 这个 C++代码文件的后缀不是 .cpp 而是 .cc ,如果 scrfd.cc 改为 scrfd.cpp 将无法成功编译!


  • 编写 scrfd.h


scrfd.h 里定义了 SCRFD 模型的一些基本参数以及需要重定义的函数。其中定义的 SCRFD 模型需要继承 FastDeployModel 这个公共的模型类,为的是继承 FastDeploy 的一些公共特性。如下面的代码所示,在头文件中,我们需要重写 FastDeployModel 中的以下几个函数,包括 Initialize、Preprocess、Postprocess、Predict、ModelName。分别对应初始化、预处理、后处理、预测、模型名称。如果你需要完整详细的代码,请访问下方链接。



#pragma once#include <unordered_map>#include "fastdeploy/fastdeploy_model.h"#include "fastdeploy/vision/common/processors/transform.h"#include "fastdeploy/vision/common/result.h"namespace fastdeploy {namespace vision {namespace facedet {class FASTDEPLOY_DECL SCRFD : public FastDeployModel { public:  SCRFD(const std::string& model_file, const std::string& params_file = "",        const RuntimeOption& custom_option = RuntimeOption(), const ModelFormat& model_format = ModelFormat::ONNX);  std::string ModelName() const { return "scrfd"; }  virtual bool Predict(cv::Mat* im, FaceDetectionResult* result, float conf_threshold = 0.25f, float nms_iou_threshold = 0.4f); private:  bool Initialize();  bool Preprocess(Mat* mat, FDTensor* output, std::map<std::string, std::array<float, 2>>* im_info);  bool Postprocess(std::vector<FDTensor>& infer_result, FaceDetectionResult* result, const std::map<std::string, std::array<float, 2>>& im_info, float conf_threshold, float nms_iou_threshold);};}  // namespace facedet}  // namespace vision}  // namespace fastdeploy
复制代码


  • 编写 scrfd.cc


scrfd.cc 负责对在 scrfd.h 中声明的函数进行了实现。在编写预处理的过程中要注意,RKNPU2 目前仅支持 NHWC 格式的输入数据,因此必须屏蔽 Permute 操作。我这里使用 disable_permute_ 变量控制 Permute 操作。此外由于 FastDeploy 采用的是 RKNPU2 的零拷贝流程来实现后端的处理和运算,因此可以考虑将 Normalize 操作放在 NPU 上来做,提升速度,我这里使用 disable_normalize_ 变量控制 Normalize 的开关。如果需要详细的代码,请访问以下链接。



#include "fastdeploy/vision/facedet/contrib/scrfd.h"#include "fastdeploy/utils/perf.h"#include "fastdeploy/vision/utils/utils.h"namespace fastdeploy {namespace vision {namespace facedet {bool SCRFD::Preprocess(Mat* mat, FDTensor* output, std::map<std::string, std::array<float, 2>>* im_info) {  return true;}
bool SCRFD::Postprocess(std::vector<FDTensor>& infer_result, FaceDetectionResult* result, const std::map<std::string, std::array<float, 2>>& im_info, float conf_threshold, float nms_iou_threshold) { return true;}
bool SCRFD::Predict(cv::Mat* im, FaceDetectionResult* result, float conf_threshold, float nms_iou_threshold) { return true;}} // namespace facedet} // namespace vision} // namespace fastdeploy
复制代码


  • 在 vision.h 中添加我们的模型


我们编写完 scrfd 的代码之后,我们还需要让 FastDeploy 知道我们已经编写了 scrfd 代码,因此我们需要在 fastdeploy/vision.h 文件中补充 scrfd.h 头文件的路径。

编译 FastDeploy C++ SDK

编写完 C++代码后,我们需要编译 C++版本的 FastDeploy。一是为了测试我们编写的代码是否有程序上的漏洞,二是为了后续编写 example 可以链接 FastDeploy 编译出来的动态库。编译的细节详情请参考 FastDeploy C++代码编译指南。



这里直接给出编译时的命令:


git clone https://github.com/PaddlePaddle/FastDeploy.gitcd FastDeploymkdir build && cd buildcmake ..  -DENABLE_ORT_BACKEND=ON \          -DENABLE_RKNPU2_BACKEND=ON \          -DENABLE_VISION=ON \          -DRKNN2_TARGET_SOC=RK3588 \          -DCMAKE_INSTALL_PREFIX=${PWD}/fastdeploy-0.0.3make -j8make install
复制代码

编写 C++ example 代码

为了调试我们已经完成的 C++代码,以及方便用户使用,在编写完上述代码之后,我们需要编写对应 example 的代码来验证我们的想法是否正确。在编写 C++ example 时,目录下的文件一般由 infer_model_name.cc 以及 CMakeLists.txt 组成。在 CMakeLists.txt 中需要对不同的 infer_model_name.cc 生成不同的 infer_model_name 程序。


  • 编写 infer.cc


infer.cc 主要负责调用 FastDeploy 的 C++代码来对 SCRFD 进行测试。在上文中,我们提到 vision.h 可以让 fastdeploy 知道我们已经编写了 SCRFD 模型。因此在编写 example 时,我们只需要包含 vision.h,即可让程序知道,我们已经声明了 FastDeploy 所有已经实现的视觉模型。针对 RKNPU 的测试,其流程一般为初始化模型,然后根据转换模型时的配置决定是否需要 disable_normalize 和 disable_permute,随后输入测试图片,调用 Predict 函数进行处理,最后使用对应的可视化函数进行可视化。


#include <iostream>#include <string>#include "fastdeploy/vision.h"void RKNPU2Infer(const std::string& model_dir, const std::string& image_file) {  auto model = fastdeploy::vision::facedet::SCRFD(model_file, params_file, option, format);  model.Initialized();  model.DisableNormalize();  model.DisablePermute();  auto im = cv::imread(image_file);  fastdeploy::vision::FaceDetectionResult res;  model.Predict(&im, &res)  auto vis_im = fastdeploy::vision::VisFaceDetection(im, res);  cv::imwrite("infer_rknn.jpg", vis_im);  std::cout << "Visualized result saved in ./infer_rknn.jpg" << std::endl;}
int main(int argc, char* argv[]) { if (argc < 3) { std::cout << "Usage: infer_demo path/to/model_dir path/to/image run_option, " "e.g ./infer_model ./picodet_model_dir ./test.jpeg" << std::endl; return -1; }
RKNPU2Infer(argv[1], argv[2]); return 0;}
复制代码


  • 编写 CMakeLists.txt


编写完 C++ example 的代码后,我们还需要编写 CMakeLists.txtCMakeLists.txt 相当于编译时的配置文件,负责链接 infer_model_name.cc 和 FastDeploy 的动态库,并且把模型推理需要用到的东西集成在 install 目录下。


CMAKE_MINIMUM_REQUIRED(VERSION 3.10)project(rknpu_test)set(CMAKE_CXX_STANDARD 14)# 指定下载解压后的fastdeploy库路径set(FASTDEPLOY_INSTALL_DIR "thirdpartys/fastdeploy-0.7.0")include(${FASTDEPLOY_INSTALL_DIR}/FastDeployConfig.cmake)include_directories(${FastDeploy_INCLUDE_DIRS})add_executable(rknpu_test infer.cc)target_link_libraries(rknpu_test ${FastDeploy_LIBS})
复制代码

编写 Python 代码

Python 代码的编写主要包括 pybind 文件的编写以及 py 本体文件的编写。上文提到,在 FastDeploy 中,python 代码通过调用 pybind 暴露出的 C++ API 来进行工作,因此我们首先需要编写 pybind.cc。


  • 编写 scrfd_pybind.cc


pybind.cc 主要负责提供可用的 API 给 Python 调用。scrfd_pybind.cc 中对 SCRFD C++的代码进行了暴露,代码如下:


#include "fastdeploy/pybind/main.h"namespace fastdeploy {void BindSCRFD(pybind11::module& m) {  // Bind SCRFD  pybind11::class_<vision::facedet::SCRFD, FastDeployModel>(m, "SCRFD")      .def(pybind11::init<std::string, std::string, RuntimeOption,                          ModelFormat>())      .def("predict",           [](vision::facedet::SCRFD& self, pybind11::array& data,              float conf_threshold, float nms_iou_threshold) {             auto mat = PyArrayToCvMat(data);             vision::FaceDetectionResult res;             self.Predict(&mat, &res, conf_threshold, nms_iou_threshold);             return res;           })      .def("disable_normalize",&vision::facedet::SCRFD::DisableNormalize)      .def("disable_permute",&vision::facedet::SCRFD::DisablePermute);}}  // namespace fastdeploy
复制代码


  • 在 facedet_pybind.cc 中添加声明


和在 vision.h 文件中添加声明一样,在编写完 pybind 代码之后,我们还需要在 fastdeploy/vision/facedet/facedet_pybind.cc 中添加声明。目的是告诉编译器我们已经编写了 pybind 的代码,并且在编译 Python 时请把我们的代码加上。核心代码如下:


#include "fastdeploy/pybind/main.h"namespace fastdeploy {void BindSCRFD(pybind11::module& m);void BindFaceDet(pybind11::module& m) {  auto facedet_module = m.def_submodule("facedet", "Face detection models.");  BindSCRFD(facedet_module);}}
复制代码


  • 编写 scrfd.py


编写完 pybind.cc 后,我们还需要编写对应的 py 文件调用 pybind 暴露出来的 C++ API。代码如下


from __future__ import absolute_importimport loggingfrom .... import FastDeployModel, ModelFormatfrom .... import c_lib_wrap as Cclass SCRFD(FastDeployModel):    def __init__(self,                 model_file,                 params_file="",                 runtime_option=None,                 model_format=ModelFormat.ONNX):        super(SCRFD, self).__init__(runtime_option)
self._model = C.vision.facedet.SCRFD(model_file, params_file, self._runtime_option, model_format) assert self.initialized, "SCRFD initialize failed."
def predict(self, input_image, conf_threshold=0.7, nms_iou_threshold=0.3): return self._model.predict(input_image, conf_threshold, nms_iou_threshold)
复制代码

编译 FastDeploy Python SDK

编写 example 之前我们肯定需要编译 Python 版本的 FastDeploy 代码,请参考 FastDeploy RKNPU2 编译指南编译 Python 版本的 FastDeploy。



这里给出我经常使用的编译命令:


cd FastDeploycd pythonexport ENABLE_ORT_BACKEND=ONexport ENABLE_RKNPU2_BACKEND=ONexport ENABLE_VISION=ONexport RKNN2_TARGET_SOC=RK3588python3 setup.py buildpython3 setup.py bdist_wheelcd distpip3 install fastdeploy_python-0.0.0-cp39-cp39-linux_aarch64.whl
复制代码

编写 Python example 代码

为了调试我们已经完成的 Python 代码,以及方便用户使用,在编写完上述 scrfd 代码之后,我们需要编写对应 example 的代码来验证我们的想法是否正确。在编写 Python example 时,目录下的文件一般由 infer_model_name.py 组成。


  • 编写 infer.py


infer.py 主要负责调用 FastDeploy 的 Python 代码来对 SCRFD 的测试。与 C++ example 相似,针对 RKNPU 的测试,其流程一般为初始化模型,然后根据转换模型时的配置决定是否需要 disable_normalize 和 disable_permute,随后输入测试图片,调用 Predict 函数进行处理,最后使用对应的可视化函数进行可视化。


import fastdeploy as fdimport cv2import osdef parse_arguments():    import argparse    import ast    parser = argparse.ArgumentParser()    parser.add_argument("--model_file", required=True, help="Path of FaceDet model.")    parser.add_argument("--image", type=str, required=True, help="Path of test image file.")    return parser.parse_args()def build_option(args):    option = fd.RuntimeOption()    option.use_rknpu2()    return optionargs = parse_arguments()# 配置runtime,加载模型runtime_option = build_option(args)model_file = args.model_fileparams_file = ""model = fd.vision.facedet.SCRFD(model_file, params_file, runtime_option=runtime_option, model_format=fd.ModelFormat.RKNN)model.disable_normalize()model.disable_permute()# 预测图片分割结果im = cv2.imread(args.image)result = model.predict(im)print(result)# 可视化结果vis_im = fd.vision.vis_face_detection(im, result)cv2.imwrite("visualized_result.jpg", vis_im)print("Visualized result save in ./visualized_result.jpg")
复制代码

编写文档以及提交 pr

请参考 SCRFD example 编写模型的转换文档、模型的 cpp example 运行文档、模型的 python 运行文档共三份文档,然后向 FastDeploy 的 Github 仓库提交 PR。待审核过后,你的贡献就会被记录啦。


总结

在飞桨做开源贡献的体验是无与伦比的,首先能够快速实现编程能力提升,在贡献代码的过程中,你会更加深刻的理解书本上的内容,掌握行业前沿的代码逻辑和编程规范。同时在开发过程中,你还会认识飞桨研发团队的同学以及很多志同道合的好友,与他们共同创造一些有趣的成果,在修复 bug 的过程中体验成就感。欢迎和我一起加入贡献代码的行列。


参考文献


[1]https://github.com/PaddlePaddle/FastDeploy


[2]Guo J , Deng J , Lattas A , et al. Sample and Computation Redistribution for Efficient Face Detection[J]. 2021.

用户头像

还未添加个人签名 2022-12-26 加入

还未添加个人简介

评论

发布
暂无评论
C++到Python全搞定,教你如何为FastDeploy贡献代码_c++_飞桨PaddlePaddle_InfoQ写作社区