From 79d0ee07c97fc3cd6bb4b08db6036f57eaac3cd0 Mon Sep 17 00:00:00 2001 From: CoolCola <49013063+CoolKbh@users.noreply.github.com> Date: Wed, 8 Feb 2023 19:46:58 +0800 Subject: [PATCH] [DOC]Renew how to develop a new model file (#1204) * renew develop_a_new_model * renew doc * update readme file * fix review problem * fix review problem --------- Co-authored-by: WJJ1995 --- docs/cn/faq/develop_a_new_model.md | 284 ++++++++++++++++++++--------- 1 file changed, 201 insertions(+), 83 deletions(-) diff --git a/docs/cn/faq/develop_a_new_model.md b/docs/cn/faq/develop_a_new_model.md index 37a312f09..bb8b482e1 100644 --- a/docs/cn/faq/develop_a_new_model.md +++ b/docs/cn/faq/develop_a_new_model.md @@ -3,23 +3,22 @@ # FastDeploy集成新模型流程 -在FastDeploy里面新增一个模型,包括增加C++/Python的部署支持。 本文以torchvision v0.12.0中的ResNet50模型为例,介绍使用FastDeploy做外部[模型集成](#modelsupport),具体包括如下3步。 +在FastDeploy里面新增一个模型,包括增加C++/Python的部署支持。 本文以YOLOv7Face模型为例,介绍使用FastDeploy做外部[模型集成](#modelsupport),具体包括如下3步。 | 步骤 | 说明 | 创建或修改的文件 | |:------:|:-------------------------------------:|:---------------------------------------------:| -| [1](#step2) | 在fastdeploy/vision相应任务模块增加模型实现 | resnet.h、resnet.cc、vision.h | -| [2](#step4) | 通过pybind完成Python接口绑定 | resnet_pybind.cc、classification_pybind.cc | -| [3](#step5) | 实现Python相应调用接口 | resnet.py、\_\_init\_\_.py | +| [1](#step2) | 在fastdeploy/vision相应任务模块增加模型实现 | yolov7face.h、yolov7face.cc、preprocessor.h、preprocess.cc、postprocessor.h、postprocessor.cc、vision.h | +| [2](#step4) | 通过pybind完成Python接口绑定 | yolov7face_pybind.cc | +| [3](#step5) | 实现Python相应调用接口 | yolov7face.py、\_\_init\_\_.py | 在完成上述3步之后,一个外部模型就集成好了。
如果您想为FastDeploy贡献代码,还需要为新增模型添加测试代码、说明文档和代码注释,可在[测试](#test)中查看。 ## 模型集成 -### 模型准备 +## 1、模型准备 - -在集成外部模型之前,先要将训练好的模型(.pt,.pdparams 等)转换成FastDeploy支持部署的模型格式(.onnx,.pdmodel)。多数开源仓库会提供模型转换脚本,可以直接利用脚本做模型的转换。由于torchvision没有提供转换脚本,因此手动编写转换脚本,本文中将 `torchvison.models.resnet50` 转换为 `resnet50.onnx`, 参考代码如下: +在集成外部模型之前,先要将训练好的模型(.pt,.pdparams 等)转换成FastDeploy支持部署的模型格式(.onnx,.pdmodel)。多数开源仓库会提供模型转换脚本,可以直接利用脚本做模型的转换。例如yolov7face官方库提供的[export.py](https://github.com/derronqi/yolov7-face/blob/main/models/export.py)文件, 若官方库未提供转换导出文件,则需要手动编写转换脚本,如torchvision没有提供转换脚本,因此手动编写转换脚本,下文中将 `torchvison.models.resnet50` 转换为 `resnet50.onnx`,参考代码如下: ```python import torch @@ -41,57 +40,139 @@ torch.onnx.export(model, ``` 执行上述脚本将会得到 `resnet50.onnx` 文件。 -### C++部分 -* 创建`resnet.h`文件 +## 2、CPP代码实现 +### 2.1、前处理类实现 +* 创建`preprocessor.h`文件 * 创建位置 - * FastDeploy/fastdeploy/vision/classification/contrib/resnet.h (FastDeploy/C++代码存放位置/视觉模型/任务名称/外部模型/模型名.h) + * FastDeploy/fastdeploy/vision/facedet/contrib/yolov7face/preprocess.h (FastDeploy/C++代码存放位置/视觉模型/任务名称/外部模型/模型名/precessor.h) * 创建内容 - * 首先在resnet.h中创建 ResNet类并继承FastDeployModel父类,之后声明`Predict`、`Initialize`、`Preprocess`、`Postprocess`和`构造函数`,以及必要的变量,具体的代码细节请参考[resnet.h](https://github.com/PaddlePaddle/FastDeploy/pull/347/files#diff-69128489e918f305c208476ba793d8167e77de2aa7cadf5dcbac30da448bd28e)。 + * 首先在preprocess.h中创建 Yolov7FacePreprocess 类,之后声明`Run`、`preprocess`、`LetterBox`和`构造函数`,以及必要的变量及其`set`和`get`方法,具体的代码细节请参考[preprocess.h](https://github.com/PaddlePaddle/FastDeploy/tree/develop/fastdeploy/vision/facedet/contrib/yolov7face/preprocessor.h)。 ```C++ -class FASTDEPLOY_DECL ResNet : public FastDeployModel { +class FASTDEPLOY_DECL Yolov7FacePreprocessor { public: - ResNet(...); - virtual bool Predict(...); - private: - bool Initialize(); + Yolov7FacePreprocessor(...); + bool Run(...); + protected: bool Preprocess(...); - bool Postprocess(...); + void LetterBox(...); }; ``` -* 创建`resnet.cc`文件 +* 创建`preprocessor.cc`文件 * 创建位置 - * FastDeploy/fastdeploy/vision/classification/contrib/resnet.cc (FastDeploy/C++代码存放位置/视觉模型/任务名称/外部模型/模型名.cc) + * FastDeploy/fastdeploy/vision/facedet/contrib/yolov7face/preprocessor.cc (FastDeploy/C++代码存放位置/视觉模型/任务名称/外部模型/模型名/preprocessor.cc) * 创建内容 - * 在`resnet.cc`中实现`resnet.h`中声明函数的具体逻辑,其中`PreProcess` 和 `PostProcess`需要参考源官方库的前后处理逻辑复现,ResNet每个函数具体逻辑如下,具体的代码请参考[resnet.cc](https://github.com/PaddlePaddle/FastDeploy/pull/347/files#diff-d229d702de28345253a53f2a5839fd2c638f3d32fffa6a7d04d23db9da13a871)。 + * 在`preprocessor.cc`中实现`preprocessor.h`中声明函数的具体逻辑,其中`Preprocess`需要参考源官方库的前后处理逻辑复现,preprocessor每个函数具体逻辑如下,具体的代码请参考[preprocessor.cc](https://github.com/PaddlePaddle/FastDeploy/tree/develop/fastdeploy/vision/facedet/contrib/yolov7face/preprocessor.cc)。 ```C++ -ResNet::ResNet(...) { +Yolov7FacePreprocessor::Yolov7FacePreprocessor(...) { + // 构造函数逻辑 + // 全局变量赋值 +} +bool Yolov7FacePreprocessor::Run() { + // 执行前处理 + // 根据传入图片数量对每张图片进行处理,通过循环的方式将每张图片传入Preprocess函数进行预处理, + // 即Preprocess为处理单元,Run方法为每张图片调用处理单元处理 + return true; +} +bool Yolov7FacePreprocessor::Preprocess(FDMat* mat, FDTensor* output, + std::map>* im_info) { +// 前处理逻辑 +// 1. LetterBox 2. convert and permute 3. 处理结果存入 FDTensor类中 + return true; +} +void Yolov7FacePreprocessor::LetterBox(FDMat* mat) { + //LetterBox + return true; +} +``` + +### 2.2、后处理类实现 +* 创建`postprocessor.h`文件 + * 创建位置 + * FastDeploy/fastdeploy/vision/facedet/contrib/yolov7face/postprocessor.h (FastDeploy/C++代码存放位置/视觉模型/任务名称/外部模型/模型名/postprocessor.h) + * 创建内容 + * 首先在postprocess.h中创建 Yolov7FacePostprocess 类,之后声明`Run`和`构造函数`,以及必要的变量及其`set`和`get`方法,具体的代码细节请参考[postprocessor.h](https://github.com/PaddlePaddle/FastDeploy/tree/develop/fastdeploy/vision/facedet/contrib/yolov7face/postprocessor.h)。 + +```C++ +class FASTDEPLOY_DECL Yolov7FacePostprocessor { + public: + Yolov7FacePostprocessor(...); + bool Run(...); +}; +``` + +* 创建`postprocessor.cc`文件 + * 创建位置 + * FastDeploy/fastdeploy/vision/facedet/contrib/yolov7face/postprocessor.cc (FastDeploy/C++代码存放位置/视觉模型/任务名称/外部模型/模型名/postprocessor.cc) + * 创建内容 + * 在`postprocessor.cc`中实现`postprocessor.h`中声明函数的具体逻辑,其中`Postprocess`需要参考源官方库的前后处理逻辑复现,postprocessor每个函数具体逻辑如下,具体的代码请参考[postprocessor.cc](https://github.com/PaddlePaddle/FastDeploy/tree/develop/fastdeploy/vision/facedet/contrib/yolov7face/postprocessor.cc)。 + +```C++ +Yolov7FacePostprocessor::Yolov7FacePostprocessor(...) { + // 构造函数逻辑 + // 全局变量赋值 +} +bool Yolov7FacePostprocessor::Run() { + // 后处理逻辑 + // 1. Padding 2. Choose box by conf_threshold 3. NMS 4. 结果存入 FaceDetectionResult类 + return true; +} + +``` +### 2.3、YOLOv7Face实现 +* 创建`yolov7face.h`文件 + * 创建位置 + * FastDeploy/fastdeploy/vision/facedet/contrib/yolov7face/yolov7face.h (FastDeploy/C++代码存放位置/视觉模型/任务名称/外部模型/模型名/模型名.h) + * 创建内容 + * 首先在yolov7face.h中创建 YOLOv7Face 类并继承FastDeployModel父类,之后声明`Predict`、`BatchPredict`、`Initialize`和`构造函数`,以及必要的变量及其`get`方法,具体的代码细节请参考[yolov7face.h](https://github.com/PaddlePaddle/FastDeploy/tree/develop/fastdeploy/vision/facedet/contrib/yolov7face/yolov7face.h)。 + +```C++ +class FASTDEPLOY_DECL YOLOv7Face : public FastDeployModel { + public: + YOLOv7Face(...); + virtual bool Predict(...); + virtual bool BatchPredict(...); + protected: + bool Initialize(); + Yolov7FacePreprocessor preprocessor_; + Yolov7FacePostprocessor postprocessor_; +}; +``` + +* 创建`yolov7face.cc`文件 + * 创建位置 + * FastDeploy/fastdeploy/vision/facedet/contrib/yolov7face/yolov7face.cc (FastDeploy/C++代码存放位置/视觉模型/任务名称/外部模型/模型名/模型名.cc) + * 创建内容 + * 在`yolov7face.cc`中实现`yolov7face.h`中声明函数的具体逻辑,YOLOv7Face每个函数具体逻辑如下,具体的代码请参考[yolov7face.cc](https://github.com/PaddlePaddle/FastDeploy/tree/develop/fastdeploy/vision/facedet/contrib/yolov7face/yolov7face.cc)。 + +```C++ +YOLOv7Face::YOLOv7Face(...) { // 构造函数逻辑 // 1. 指定 Backend 2. 设置RuntimeOption 3. 调用Initialize()函数 } -bool ResNet::Initialize() { +bool YOLOv7Face::Initialize() { // 初始化逻辑 // 1. 全局变量赋值 2. 调用InitRuntime()函数 return true; } -bool ResNet::Preprocess(Mat* mat, FDTensor* output) { -// 前处理逻辑 -// 1. Resize 2. BGR2RGB 3. Normalize 4. HWC2CHW 5. 处理结果存入 FDTensor类中 +bool YOLOv7Face::Predict(const cv::Mat& im, FaceDetectionResult* result) { + std::vector results; + if (!BatchPredict({im}, &results)) { + return false; + } + *result = std::move(results[0]); return true; } -bool ResNet::Postprocess(FDTensor& infer_result, ClassifyResult* result, int topk) { - //后处理逻辑 - // 1. Softmax 2. Choose topk labels 3. 结果存入 ClassifyResult类 - return true; -} -bool ResNet::Predict(cv::Mat* im, ClassifyResult* result, int topk) { +// Predict是对单张图片进行预测,通过将含有一张图片的数组送入BatchPredict实现 +bool YOLOv7Face::BatchPredict(const std::vector& images, std::vector* result) { Preprocess(...) Infer(...) Postprocess(...) return true; } +// BatchPredict为对批量图片进行预测,接收一个含有若干张图片的动态数组vector ``` * 在`vision.h`文件中加入新增模型文件 @@ -101,77 +182,116 @@ bool ResNet::Predict(cv::Mat* im, ClassifyResult* result, int topk) { ```C++ #ifdef ENABLE_VISION -#include "fastdeploy/vision/classification/contrib/resnet.h" +#include "fastdeploy/vision/facedet/contrib/yolov7face.h" #endif ``` +## 3、Python接口封装 -### Pybind部分 +### 3.1、Pybind部分 * 创建Pybind文件 * 创建位置 - * FastDeploy/fastdeploy/vision/classification/contrib/resnet_pybind.cc (FastDeploy/C++代码存放位置/视觉模型/任务名称/外部模型/模型名_pybind.cc) + * FastDeploy/fastdeploy/vision/facedet/contrib/yolov7face/yolov7face_pybind.cc (FastDeploy/C++代码存放位置/视觉模型/任务名称/外部模型/模型名/模型名_pybind.cc) * 创建内容 - * 利用Pybind将C++中的函数变量绑定到Python中,具体代码请参考[resnet_pybind.cc](https://github.com/PaddlePaddle/FastDeploy/pull/347/files#diff-270af0d65720310e2cfbd5373c391b2110d65c0f4efa547f7b7eeffcb958bdec)。 + * 利用Pybind将C++中的函数变量绑定到Python中,具体代码请参考[yolov7face_pybind.cc](https://github.com/PaddlePaddle/FastDeploy/tree/develop/fastdeploy/vision/facedet/contrib/yolov7face/yolov7face_pybind.cc)。 ```C++ -void BindResNet(pybind11::module& m) { - pybind11::class_( - m, "ResNet") +void BindYOLOv7Face(pybind11::module& m) { + pybind11::class_( + m, "YOLOv7Face") .def(pybind11::init()) .def("predict", ...) - .def_readwrite("size", &vision::classification::ResNet::size) - .def_readwrite("mean_vals", &vision::classification::ResNet::mean_vals) - .def_readwrite("std_vals", &vision::classification::ResNet::std_vals); + .def("batch_predict", ...) + .def_property_readonly("preprocessor", ...) + .def_property_readonly("postprocessor", ...); + pybind11::class_( + m, "Yolov7FacePreprocessor") + .def(pybind11::init<>()) + .def("run", ...) + .def_property("size", ...) + .def_property("padding_color_value", ...) + .def_property("is_scale_up", ...); + pybind11::class_( + m, "Yolov7FacePostprocessor") + .def(pybind11::init<>()) + .def("run", ...) + .def_property("conf_threshold", ...) + .def_property("nms_threshold", ...); } ``` * 调用Pybind函数 * 修改位置 - * FastDeploy/fastdeploy/vision/classification/classification_pybind.cc (FastDeploy/C++代码存放位置/视觉模型/任务名称/任务名称}_pybind.cc) + * FastDeploy/fastdeploy/vision/facedet/facedet_pybind.cc (FastDeploy/C++代码存放位置/视觉模型/任务名称/任务名称}_pybind.cc) * 修改内容 ```C++ -void BindResNet(pybind11::module& m); -void BindClassification(pybind11::module& m) { - auto classification_module = - m.def_submodule("classification", "Image classification models."); - BindResNet(classification_module); +void BindYOLOv7Face(pybind11::module& m); +void BindFaceDet(pybind11::module& m) { + auto facedet_module = + m.def_submodule("facedet", "Face detection models."); + BindYOLOv7Face(facedet_module); } ``` - -### Python部分 - - -* 创建`resnet.py`文件 +### 3.2、python部分 +* 创建`yolov7face.py`文件 * 创建位置 - * FastDeploy/python/fastdeploy/vision/classification/contrib/resnet.py (FastDeploy/Python代码存放位置/fastdeploy/视觉模型/任务名称/外部模型/模型名.py) + * FastDeploy/python/fastdeploy/vision/facedet/contrib/yolov7face.py (FastDeploy/Python代码存放位置/fastdeploy/视觉模型/任务名称/外部模型/模型名.py) * 创建内容 - * 创建ResNet类继承自FastDeployModel,实现 `\_\_init\_\_`、Pybind绑定的函数(如`predict()`)、以及`对Pybind绑定的全局变量进行赋值和获取的函数`,具体代码请参考[resnet.py](https://github.com/PaddlePaddle/FastDeploy/pull/347/files#diff-a4dc5ec2d450e91f1c03819bf314c238b37ac678df56d7dea3aab7feac10a157)。 + * 创建YOLOv7Face类继承自FastDeployModel、preprocess以及postprocess类,实现 `\_\_init\_\_`、Pybind绑定的函数(如`predict()`)、以及`对Pybind绑定的全局变量进行赋值和获取的函数`,具体代码请参考[yolov7face.py](https://github.com/PaddlePaddle/FastDeploy/tree/develop/python/fastdeploy/vision/facedet/contrib/yolov7face.py)。 ```python -class ResNet(FastDeployModel): +class YOLOv7Face(FastDeployModel): def __init__(self, ...): - self._model = C.vision.classification.ResNet(...) - def predict(self, input_image, topk=1): - return self._model.predict(input_image, topk) + self._model = C.vision.facedet.YOLOv7Face(...) + def predict(self, input_image): + return self._model.predict(input_image) + def batch_predict(self, images): + return self._model.batch_predict(images) + @property + def preprocessor(self): + return self._model.preprocessor + @property + def postprocessor(self): + return self._model.postprocessor + +class Yolov7FacePreprocessor(): + def __init__(self, ...): + self._model = C.vision.facedet.Yolov7FacePreprocessor(...) + def run(self, input_ims): + return self._preprocessor.run(input_ims) @property def size(self): - return self._model.size - @size.setter - def size(self, wh): - ... + return self._preprocessor.size + @property + def padding_color_value(self): + return self._preprocessor.padding_color_value + ... + +class Yolov7FacePreprocessor(): + def __init__(self, ...): + self._model = C.vision.facedet.Yolov7FacePostprocessor(...) + def run(self, ...): + return self._postprocessor.run(...) + @property + def conf_threshold(self): + return self._postprocessor.conf_threshold + @property + def nms_threshold(self): + return self._postprocessor.nms_threshold + ... ``` -* 导入ResNet类 +* 导入YOLOv7Face、Yolov7FacePreprocessor、Yolov7facePostprocessor类 * 修改位置 - * FastDeploy/python/fastdeploy/vision/classification/\_\_init\_\_.py (FastDeploy/Python代码存放位置/fastdeploy/视觉模型/任务名称/\_\_init\_\_.py) + * FastDeploy/python/fastdeploy/vision/facedet/\_\_init\_\_.py (FastDeploy/Python代码存放位置/fastdeploy/视觉模型/任务名称/\_\_init\_\_.py) * 修改内容 ```Python -from .contrib.resnet import ResNet +from .contrib.yolov7face import * ``` -## 测试 +## 4、测试 ### 编译 * C++ * 位置:FastDeploy/ @@ -203,8 +323,8 @@ cd dist pip install fastdeploy_gpu_python-版本号-cpxx-cpxxm-系统架构.whl ``` -### 编写测试代码 - * 创建位置: FastDeploy/examples/vision/classification/resnet/ (FastDeploy/示例目录/视觉模型/任务名称/模型名/) +## 5、示例代码开发 + * 创建位置: FastDeploy/examples/vision/facedet/yolov7face/ (FastDeploy/示例目录/视觉模型/任务名称/模型名/) * 创建目录结构 ``` @@ -220,9 +340,9 @@ pip install fastdeploy_gpu_python-版本号-cpxx-cpxxm-系统架构.whl ``` * C++ - * 编写CmakeLists文件、C++ 代码以及 README.md 内容请参考[cpp/](https://github.com/PaddlePaddle/FastDeploy/pull/347/files#diff-afcbe607b796509581f89e38b84190717f1eeda2df0419a2ac9034197ead5f96)。 + * 编写CmakeLists文件、C++ 代码以及 README.md 内容请参考[cpp/](https://github.com/PaddlePaddle/FastDeploy/tree/develop/examples/vision/facedet/yolov7face/cpp)。 * 编译 infer.cc - * 位置:FastDeploy/examples/vision/classification/resnet/cpp/ + * 位置:FastDeploy/examples/vision/facedet/yolov7face/cpp/ ``` mkdir build & cd build @@ -231,38 +351,36 @@ make ``` * Python - * Python 代码以及 README.md 内容请参考[python/](https://github.com/PaddlePaddle/FastDeploy/pull/347/files#diff-5a0d6be8c603a8b81454ac14c17fb93555288d9adf92bbe40454449309700135)。 + * Python 代码以及 README.md 内容请参考[python/](https://github.com/PaddlePaddle/FastDeploy/tree/develop/examples/vision/facedet/yolov7face/python)。 ### 为代码添加注释 为了方便用户理解代码,我们需要为新增代码添加注释,添加注释方法可参考如下示例。 - C++ 代码 -您需要在resnet.h文件中为函数和变量增加注释,有如下三种注释方式,具体可参考[resnet.h](https://github.com/PaddlePaddle/FastDeploy/pull/347/files#diff-69128489e918f305c208476ba793d8167e77de2aa7cadf5dcbac30da448bd28e)。 +您需要在resnet.h文件中为函数和变量增加注释,有如下三种注释方式,具体可参考[yolov7face.h](https://github.com/PaddlePaddle/FastDeploy/tree/develop/fastdeploy/vision/facedet/contrib/yolov7face/yolov7face.h)。 ```C++ /** \brief Predict for the input "im", the result will be saved in "result". * * \param[in] im Input image for inference. * \param[in] result Saving the inference result. -* \param[in] topk The length of return values, e.g., if topk==2, the result will include the 2 most possible class label for input image. */ -virtual bool Predict(cv::Mat* im, ClassifyResult* result, int topk = 1); +virtual bool Predict(const cv::Mat& im, FaceDetectionResult* result); /// Tuple of (width, height) std::vector size; -/*! @brief Initialize for ResNet model, assign values to the global variables and call InitRuntime() +/*! @brief Initialize for YOLOv7Face model, assign values to the global variables and call InitRuntime() */ bool Initialize(); ``` - Python 代码 -你需要为resnet.py文件中的函数和变量增加适当的注释,示例如下,具体可参考[resnet.py](https://github.com/PaddlePaddle/FastDeploy/pull/347/files#diff-a4dc5ec2d450e91f1c03819bf314c238b37ac678df56d7dea3aab7feac10a157)。 +你需要为yolov7face.py文件中的函数和变量增加适当的注释,示例如下,具体可参考[yolov7face.py](https://github.com/PaddlePaddle/FastDeploy/tree/develop/python/fastdeploy/vision/facedet/contrib/yolov7face.py)。 ```python - def predict(self, input_image, topk=1): - """Classify an input image - :param input_image: (numpy.ndarray)The input image data, 3-D array with layout HWC, BGR format - :param topk: (int)The topk result by the classify confidence score, default 1 - :return: ClassifyResult - """ - return self._model.predict(input_image, topk) + def predict(self, input_image): + """Detect the location and key points of human faces from an input image + :param input_image: (numpy.ndarray)The input image data, 3-D array with layout HWC, BGR format + :return: FaceDetectionResult + """ + return self._model.predict(input_image) ``` 对于集成模型过程中的其他文件,您也可以对实现的细节添加适当的注释说明。