init
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
[English](README.md) | 简体中文
|
||||
# PaddleOCR 使用 FastDeploy 服务化部署PP-OCR系列模型
|
||||
## 1. FastDeploy 服务化部署介绍
|
||||
在线推理作为企业或个人线上部署模型的最后一环,是工业界必不可少的环节,其中最重要的就是服务化推理框架。FastDeploy 目前提供两种服务化部署方式:simple_serving和fastdeploy_serving
|
||||
- simple_serving:适用于只需要通过http等调用AI推理任务,没有高并发需求的场景。simple_serving基于Flask框架具有简单高效的特点,可以快速验证线上部署模型的可行性
|
||||
- fastdeploy_serving:适用于高并发、高吞吐量请求的场景。基于Triton Inference Server框架,是一套可用于实际生产的完备且性能卓越的服务化部署框架
|
||||
|
||||
## 2. 支持的PaddleOCR推理模型
|
||||
|
||||
下表中的推理模型为FastDeploy测试过的模型, 下载链接由PaddleOCR模型库提供,
|
||||
更多的模型, 详见[PP-OCR系列模型列表](https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.6/doc/doc_ch/models_list.md), 欢迎用户尝试.
|
||||
|
||||
| PaddleOCR版本 | 文本框检测 | 方向分类模型 | 文字识别 |字典文件| 说明 |
|
||||
|:----|:----|:----|:----|:----|:--------|
|
||||
| ch_PP-OCRv3[推荐] |[ch_PP-OCRv3_det](https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_det_infer.tar) | [ch_ppocr_mobile_v2.0_cls](https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_cls_infer.tar) | [ch_PP-OCRv3_rec](https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_rec_infer.tar) | [ppocr_keys_v1.txt](https://bj.bcebos.com/paddlehub/fastdeploy/ppocr_keys_v1.txt) | OCRv3系列原始超轻量模型,支持中英文、多语种文本检测 |
|
||||
| en_PP-OCRv3[推荐] |[en_PP-OCRv3_det](https://paddleocr.bj.bcebos.com/PP-OCRv3/english/en_PP-OCRv3_det_infer.tar) | [ch_ppocr_mobile_v2.0_cls](https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_cls_infer.tar) | [en_PP-OCRv3_rec](https://paddleocr.bj.bcebos.com/PP-OCRv3/english/en_PP-OCRv3_rec_infer.tar) | [en_dict.txt](https://bj.bcebos.com/paddlehub/fastdeploy/en_dict.txt) | OCRv3系列原始超轻量模型,支持英文与数字识别,除检测模型和识别模型的训练数据与中文模型不同以外,无其他区别 |
|
||||
| ch_PP-OCRv2 |[ch_PP-OCRv2_det](https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_det_infer.tar) | [ch_ppocr_mobile_v2.0_cls](https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_cls_infer.tar) | [ch_PP-OCRv2_rec](https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_rec_infer.tar) | [ppocr_keys_v1.txt](https://bj.bcebos.com/paddlehub/fastdeploy/ppocr_keys_v1.txt) | OCRv2系列原始超轻量模型,支持中英文、多语种文本检测 |
|
||||
| ch_PP-OCRv2_mobile |[ch_ppocr_mobile_v2.0_det](https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_det_infer.tar) | [ch_ppocr_mobile_v2.0_cls](https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_cls_infer.tar) | [ch_ppocr_mobile_v2.0_rec](https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_rec_infer.tar) | [ppocr_keys_v1.txt](https://bj.bcebos.com/paddlehub/fastdeploy/ppocr_keys_v1.txt) | OCRv2系列原始超轻量模型,支持中英文、多语种文本检测,比PPOCRv2更加轻量 |
|
||||
| ch_PP-OCRv2_server |[ch_ppocr_server_v2.0_det](https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_server_v2.0_det_infer.tar) | [ch_ppocr_mobile_v2.0_cls](https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_cls_infer.tar) | [ch_ppocr_server_v2.0_rec](https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_server_v2.0_rec_infer.tar) |[ppocr_keys_v1.txt](https://bj.bcebos.com/paddlehub/fastdeploy/ppocr_keys_v1.txt) | OCRv2服务器系列模型, 支持中英文、多语种文本检测,比超轻量模型更大,但效果更好|
|
||||
|
||||
## 3. 详细的部署示例
|
||||
|
||||
- [fastdeploy serving](fastdeploy_serving)
|
||||
- [simple serving](simple_serving)
|
||||
@@ -0,0 +1,127 @@
|
||||
[English](README.md) | 简体中文
|
||||
# PaddleOCR服务化部署示例
|
||||
|
||||
PaddleOCR 服务化部署示例是利用FastDeploy Serving搭建的服务化部署示例。FastDeploy Serving是基于Triton Inference Server框架封装的适用于高并发、高吞吐量请求的服务化部署框架,是一套可用于实际生产的完备且性能卓越的服务化部署框架。如没有高并发,高吞吐场景的需求,只想快速检验模型线上部署的可行性,请参考[simple_serving](../simple_serving/)
|
||||
|
||||
## 1. 部署环境准备
|
||||
在服务化部署前,需确认服务化镜像的软硬件环境要求和镜像拉取命令,请参考[FastDeploy服务化部署](https://github.com/PaddlePaddle/FastDeploy/blob/develop/serving/README_CN.md)
|
||||
|
||||
## 2. PP-OCRv3服务化部署介绍
|
||||
本文介绍了使用FastDeploy搭建PP-OCRv3模型服务的方法.
|
||||
服务端必须在docker内启动,而客户端不是必须在docker容器内.
|
||||
|
||||
**本文所在路径($PWD)下的models里包含模型的配置和代码(服务端会加载模型和代码以启动服务), 需要将其映射到docker中使用.**
|
||||
|
||||
PP-OCRv3由det(检测)、cls(分类)和rec(识别)三个模型组成.
|
||||
|
||||
服务化部署串联的示意图如下图所示,其中`pp_ocr`串联了`det_preprocess`、`det_runtime`和`det_postprocess`,`cls_pp`串联了`cls_runtime`和`cls_postprocess`,`rec_pp`串联了`rec_runtime`和`rec_postprocess`.
|
||||
|
||||
特别的是,在`det_postprocess`中会多次调用`cls_pp`和`rec_pp`服务,来实现对检测结果(多个框)进行分类和识别,,最后返回给用户最终的识别结果。
|
||||
|
||||
<p align="center">
|
||||
<br>
|
||||
<img src='./ppocr.png'">
|
||||
<br>
|
||||
<p>
|
||||
|
||||
|
||||
## 3. 服务端的使用
|
||||
|
||||
### 3.1 下载模型并使用服务化Docker
|
||||
```bash
|
||||
# 下载仓库代码
|
||||
# 下载部署示例代码
|
||||
git clone https://github.com/PaddlePaddle/FastDeploy.git
|
||||
cd FastDeploy/examples/vision/ocr/PP-OCR/serving/fastdeploy_serving
|
||||
|
||||
# 如果您希望从PaddleOCR下载示例代码,请运行
|
||||
git clone https://github.com/PaddlePaddle/PaddleOCR.git
|
||||
# 注意:如果当前分支找不到下面的fastdeploy测试代码,请切换到dygraph分支
|
||||
git checkout dygraph
|
||||
cd PaddleOCR/deploy/fastdeploy/serving/fastdeploy_serving
|
||||
|
||||
# 下载模型,图片和字典文件
|
||||
wget https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_det_infer.tar
|
||||
tar xvf ch_PP-OCRv3_det_infer.tar && mv ch_PP-OCRv3_det_infer 1
|
||||
mv 1/inference.pdiparams 1/model.pdiparams && mv 1/inference.pdmodel 1/model.pdmodel
|
||||
mv 1 models/det_runtime/ && rm -rf ch_PP-OCRv3_det_infer.tar
|
||||
|
||||
wget https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_cls_infer.tar
|
||||
tar xvf ch_ppocr_mobile_v2.0_cls_infer.tar && mv ch_ppocr_mobile_v2.0_cls_infer 1
|
||||
mv 1/inference.pdiparams 1/model.pdiparams && mv 1/inference.pdmodel 1/model.pdmodel
|
||||
mv 1 models/cls_runtime/ && rm -rf ch_ppocr_mobile_v2.0_cls_infer.tar
|
||||
|
||||
wget https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_rec_infer.tar
|
||||
tar xvf ch_PP-OCRv3_rec_infer.tar && mv ch_PP-OCRv3_rec_infer 1
|
||||
mv 1/inference.pdiparams 1/model.pdiparams && mv 1/inference.pdmodel 1/model.pdmodel
|
||||
mv 1 models/rec_runtime/ && rm -rf ch_PP-OCRv3_rec_infer.tar
|
||||
|
||||
mkdir models/pp_ocr/1 && mkdir models/rec_pp/1 && mkdir models/cls_pp/1
|
||||
|
||||
wget https://gitee.com/paddlepaddle/PaddleOCR/raw/release/2.6/ppocr/utils/ppocr_keys_v1.txt
|
||||
mv ppocr_keys_v1.txt models/rec_postprocess/1/
|
||||
|
||||
wget https://gitee.com/paddlepaddle/PaddleOCR/raw/release/2.6/doc/imgs/12.jpg
|
||||
|
||||
# x.y.z为镜像版本号,需参照serving文档替换为数字
|
||||
docker pull registry.baidubce.com/paddlepaddle/fastdeploy:x.y.z-gpu-cuda11.4-trt8.4-21.10
|
||||
docker run -dit --net=host --name fastdeploy --shm-size="1g" -v $PWD:/ocr_serving registry.baidubce.com/paddlepaddle/fastdeploy:x.y.z-gpu-cuda11.4-trt8.4-21.10 bash
|
||||
docker exec -it -u root fastdeploy bash
|
||||
```
|
||||
|
||||
### 3.2 安装(在docker内)
|
||||
```bash
|
||||
ldconfig
|
||||
apt-get install libgl1
|
||||
```
|
||||
|
||||
#### 3.3 启动服务端(在docker内)
|
||||
```bash
|
||||
fastdeployserver --model-repository=/ocr_serving/models
|
||||
```
|
||||
|
||||
参数:
|
||||
- `model-repository`(required): 整套模型streaming_pp_tts存放的路径.
|
||||
- `http-port`(optional): HTTP服务的端口号. 默认: `8000`. 本示例中未使用该端口.
|
||||
- `grpc-port`(optional): GRPC服务的端口号. 默认: `8001`.
|
||||
- `metrics-port`(optional): 服务端指标的端口号. 默认: `8002`. 本示例中未使用该端口.
|
||||
|
||||
|
||||
## 4. 客户端的使用
|
||||
### 4.1 安装
|
||||
```bash
|
||||
pip3 install tritonclient[all]
|
||||
```
|
||||
|
||||
### 4.2 发送请求
|
||||
```bash
|
||||
python3 client.py
|
||||
```
|
||||
|
||||
## 5.配置修改
|
||||
当前默认配置在GPU上运行, 如果要在CPU或其他推理引擎上运行。 需要修改`models/runtime/config.pbtxt`中配置,详情请参考[配置文档](../../../../../serving/docs/zh_CN/model_configuration.md)
|
||||
|
||||
## 6. 其他指南
|
||||
|
||||
- 使用PP-OCRv2进行服务化部署, 除了自行准备PP-OCRv2模型之外, 只需手动添加一行代码即可.
|
||||
在[model.py](./models/det_postprocess/1/model.py#L109)文件**109行添加以下代码**:
|
||||
```
|
||||
self.rec_preprocessor.cls_image_shape[1] = 32
|
||||
```
|
||||
|
||||
- [使用 VisualDL 进行 Serving 可视化部署](https://github.com/PaddlePaddle/FastDeploy/blob/develop/serving/docs/zh_CN/vdl_management.md)
|
||||
通过VisualDL的可视化界面对PP-OCRv3进行服务化部署只需要如下三步:
|
||||
```text
|
||||
1. 载入模型库:./vision/ocr/PP-OCRv3/serving
|
||||
2. 下载模型资源文件:点击det_runtime模型,点击版本号1添加预训练模型,选择文字识别模型ch_PP-OCRv3_det进行下载。点击cls_runtime模型,点击版本号1添加预训练模型,选择文字识别模型ch_ppocr_mobile_v2.0_cls进行下载。点击rec_runtime模型,点击版本号1添加预训练模型,选择文字识别模型ch_PP-OCRv3_rec进行下载。点击rec_postprocess模型,点击版本号1添加预训练模型,选择文字识别模型ch_PP-OCRv3_rec进行下载。
|
||||
3. 启动服务:点击启动服务按钮,输入启动参数。
|
||||
```
|
||||
<p align="center">
|
||||
<img src="https://user-images.githubusercontent.com/22424850/211709324-b07bb303-ced2-4137-9df7-0d2574ba84c8.gif" width="100%"/>
|
||||
</p>
|
||||
|
||||
## 7. 常见问题
|
||||
- [如何编写客户端 HTTP/GRPC 请求](https://github.com/PaddlePaddle/FastDeploy/blob/develop/serving/docs/zh_CN/client.md)
|
||||
- [如何编译服务化部署镜像](https://github.com/PaddlePaddle/FastDeploy/blob/develop/serving/docs/zh_CN/compile.md)
|
||||
- [服务化部署原理及动态Batch介绍](https://github.com/PaddlePaddle/FastDeploy/blob/develop/serving/docs/zh_CN/demo.md)
|
||||
- [模型仓库介绍](https://github.com/PaddlePaddle/FastDeploy/blob/develop/serving/docs/zh_CN/model_repository.md)
|
||||
@@ -0,0 +1,109 @@
|
||||
import logging
|
||||
import numpy as np
|
||||
import time
|
||||
from typing import Optional
|
||||
import cv2
|
||||
import json
|
||||
|
||||
from tritonclient import utils as client_utils
|
||||
from tritonclient.grpc import InferenceServerClient, InferInput, InferRequestedOutput, service_pb2_grpc, service_pb2
|
||||
|
||||
LOGGER = logging.getLogger("run_inference_on_triton")
|
||||
|
||||
|
||||
class SyncGRPCTritonRunner:
|
||||
DEFAULT_MAX_RESP_WAIT_S = 120
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
server_url: str,
|
||||
model_name: str,
|
||||
model_version: str,
|
||||
*,
|
||||
verbose=False,
|
||||
resp_wait_s: Optional[float]=None, ):
|
||||
self._server_url = server_url
|
||||
self._model_name = model_name
|
||||
self._model_version = model_version
|
||||
self._verbose = verbose
|
||||
self._response_wait_t = self.DEFAULT_MAX_RESP_WAIT_S if resp_wait_s is None else resp_wait_s
|
||||
|
||||
self._client = InferenceServerClient(
|
||||
self._server_url, verbose=self._verbose)
|
||||
error = self._verify_triton_state(self._client)
|
||||
if error:
|
||||
raise RuntimeError(
|
||||
f"Could not communicate to Triton Server: {error}")
|
||||
|
||||
LOGGER.debug(
|
||||
f"Triton server {self._server_url} and model {self._model_name}:{self._model_version} "
|
||||
f"are up and ready!")
|
||||
|
||||
model_config = self._client.get_model_config(self._model_name,
|
||||
self._model_version)
|
||||
model_metadata = self._client.get_model_metadata(self._model_name,
|
||||
self._model_version)
|
||||
LOGGER.info(f"Model config {model_config}")
|
||||
LOGGER.info(f"Model metadata {model_metadata}")
|
||||
|
||||
self._inputs = {tm.name: tm for tm in model_metadata.inputs}
|
||||
self._input_names = list(self._inputs)
|
||||
self._outputs = {tm.name: tm for tm in model_metadata.outputs}
|
||||
self._output_names = list(self._outputs)
|
||||
self._outputs_req = [
|
||||
InferRequestedOutput(name) for name in self._outputs
|
||||
]
|
||||
|
||||
def Run(self, inputs):
|
||||
"""
|
||||
Args:
|
||||
inputs: list, Each value corresponds to an input name of self._input_names
|
||||
Returns:
|
||||
results: dict, {name : numpy.array}
|
||||
"""
|
||||
infer_inputs = []
|
||||
for idx, data in enumerate(inputs):
|
||||
infer_input = InferInput(self._input_names[idx], data.shape,
|
||||
"UINT8")
|
||||
infer_input.set_data_from_numpy(data)
|
||||
infer_inputs.append(infer_input)
|
||||
|
||||
results = self._client.infer(
|
||||
model_name=self._model_name,
|
||||
model_version=self._model_version,
|
||||
inputs=infer_inputs,
|
||||
outputs=self._outputs_req,
|
||||
client_timeout=self._response_wait_t, )
|
||||
results = {name: results.as_numpy(name) for name in self._output_names}
|
||||
return results
|
||||
|
||||
def _verify_triton_state(self, triton_client):
|
||||
if not triton_client.is_server_live():
|
||||
return f"Triton server {self._server_url} is not live"
|
||||
elif not triton_client.is_server_ready():
|
||||
return f"Triton server {self._server_url} is not ready"
|
||||
elif not triton_client.is_model_ready(self._model_name,
|
||||
self._model_version):
|
||||
return f"Model {self._model_name}:{self._model_version} is not ready"
|
||||
return None
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
model_name = "pp_ocr"
|
||||
model_version = "1"
|
||||
url = "localhost:8001"
|
||||
runner = SyncGRPCTritonRunner(url, model_name, model_version)
|
||||
im = cv2.imread("12.jpg")
|
||||
im = np.array([im, ])
|
||||
for i in range(1):
|
||||
result = runner.Run([im, ])
|
||||
batch_texts = result['rec_texts']
|
||||
batch_scores = result['rec_scores']
|
||||
batch_bboxes = result['det_bboxes']
|
||||
for i_batch in range(len(batch_texts)):
|
||||
texts = batch_texts[i_batch]
|
||||
scores = batch_scores[i_batch]
|
||||
bboxes = batch_bboxes[i_batch]
|
||||
for i_box in range(len(texts)):
|
||||
print('text=', texts[i_box].decode('utf-8'), ' score=',
|
||||
scores[i_box], ' bbox=', bboxes[i_box])
|
||||
@@ -0,0 +1,105 @@
|
||||
# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
import numpy as np
|
||||
import time
|
||||
|
||||
import fastdeploy as fd
|
||||
|
||||
# triton_python_backend_utils is available in every Triton Python model. You
|
||||
# need to use this module to create inference requests and responses. It also
|
||||
# contains some utility functions for extracting information from model_config
|
||||
# and converting Triton input/output types to numpy types.
|
||||
import triton_python_backend_utils as pb_utils
|
||||
|
||||
|
||||
class TritonPythonModel:
|
||||
"""Your Python model must use the same class name. Every Python model
|
||||
that is created must have "TritonPythonModel" as the class name.
|
||||
"""
|
||||
|
||||
def initialize(self, args):
|
||||
"""`initialize` is called only once when the model is being loaded.
|
||||
Implementing `initialize` function is optional. This function allows
|
||||
the model to intialize any state associated with this model.
|
||||
Parameters
|
||||
----------
|
||||
args : dict
|
||||
Both keys and values are strings. The dictionary keys and values are:
|
||||
* model_config: A JSON string containing the model configuration
|
||||
* model_instance_kind: A string containing model instance kind
|
||||
* model_instance_device_id: A string containing model instance device ID
|
||||
* model_repository: Model repository path
|
||||
* model_version: Model version
|
||||
* model_name: Model name
|
||||
"""
|
||||
# You must parse model_config. JSON string is not parsed here
|
||||
self.model_config = json.loads(args['model_config'])
|
||||
print("model_config:", self.model_config)
|
||||
|
||||
self.input_names = []
|
||||
for input_config in self.model_config["input"]:
|
||||
self.input_names.append(input_config["name"])
|
||||
print("postprocess input names:", self.input_names)
|
||||
|
||||
self.output_names = []
|
||||
self.output_dtype = []
|
||||
for output_config in self.model_config["output"]:
|
||||
self.output_names.append(output_config["name"])
|
||||
dtype = pb_utils.triton_string_to_numpy(output_config["data_type"])
|
||||
self.output_dtype.append(dtype)
|
||||
print("postprocess output names:", self.output_names)
|
||||
self.postprocessor = fd.vision.ocr.ClassifierPostprocessor()
|
||||
|
||||
def execute(self, requests):
|
||||
"""`execute` must be implemented in every Python model. `execute`
|
||||
function receives a list of pb_utils.InferenceRequest as the only
|
||||
argument. This function is called when an inference is requested
|
||||
for this model. Depending on the batching configuration (e.g. Dynamic
|
||||
Batching) used, `requests` may contain multiple requests. Every
|
||||
Python model, must create one pb_utils.InferenceResponse for every
|
||||
pb_utils.InferenceRequest in `requests`. If there is an error, you can
|
||||
set the error argument when creating a pb_utils.InferenceResponse.
|
||||
Parameters
|
||||
----------
|
||||
requests : list
|
||||
A list of pb_utils.InferenceRequest
|
||||
Returns
|
||||
-------
|
||||
list
|
||||
A list of pb_utils.InferenceResponse. The length of this list must
|
||||
be the same as `requests`
|
||||
"""
|
||||
responses = []
|
||||
for request in requests:
|
||||
infer_outputs = pb_utils.get_input_tensor_by_name(
|
||||
request, self.input_names[0])
|
||||
infer_outputs = infer_outputs.as_numpy()
|
||||
results = self.postprocessor.run([infer_outputs])
|
||||
out_tensor_0 = pb_utils.Tensor(self.output_names[0],
|
||||
np.array(results[0]))
|
||||
out_tensor_1 = pb_utils.Tensor(self.output_names[1],
|
||||
np.array(results[1]))
|
||||
inference_response = pb_utils.InferenceResponse(
|
||||
output_tensors=[out_tensor_0, out_tensor_1])
|
||||
responses.append(inference_response)
|
||||
return responses
|
||||
|
||||
def finalize(self):
|
||||
"""`finalize` is called only once when the model is being unloaded.
|
||||
Implementing `finalize` function is optional. This function allows
|
||||
the model to perform any necessary clean ups before exit.
|
||||
"""
|
||||
print('Cleaning up...')
|
||||
@@ -0,0 +1,30 @@
|
||||
name: "cls_postprocess"
|
||||
backend: "python"
|
||||
max_batch_size: 128
|
||||
input [
|
||||
{
|
||||
name: "POST_INPUT_0"
|
||||
data_type: TYPE_FP32
|
||||
dims: [ 2 ]
|
||||
}
|
||||
]
|
||||
|
||||
output [
|
||||
{
|
||||
name: "POST_OUTPUT_0"
|
||||
data_type: TYPE_INT32
|
||||
dims: [ 1 ]
|
||||
},
|
||||
{
|
||||
name: "POST_OUTPUT_1"
|
||||
data_type: TYPE_FP32
|
||||
dims: [ 1 ]
|
||||
}
|
||||
]
|
||||
|
||||
instance_group [
|
||||
{
|
||||
count: 1
|
||||
kind: KIND_CPU
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,54 @@
|
||||
name: "cls_pp"
|
||||
platform: "ensemble"
|
||||
max_batch_size: 128
|
||||
input [
|
||||
{
|
||||
name: "x"
|
||||
data_type: TYPE_FP32
|
||||
dims: [ 3, -1, -1 ]
|
||||
}
|
||||
]
|
||||
output [
|
||||
{
|
||||
name: "cls_labels"
|
||||
data_type: TYPE_INT32
|
||||
dims: [ 1 ]
|
||||
},
|
||||
{
|
||||
name: "cls_scores"
|
||||
data_type: TYPE_FP32
|
||||
dims: [ 1 ]
|
||||
}
|
||||
]
|
||||
ensemble_scheduling {
|
||||
step [
|
||||
{
|
||||
model_name: "cls_runtime"
|
||||
model_version: 1
|
||||
input_map {
|
||||
key: "x"
|
||||
value: "x"
|
||||
}
|
||||
output_map {
|
||||
key: "softmax_0.tmp_0"
|
||||
value: "infer_output"
|
||||
}
|
||||
},
|
||||
{
|
||||
model_name: "cls_postprocess"
|
||||
model_version: 1
|
||||
input_map {
|
||||
key: "POST_INPUT_0"
|
||||
value: "infer_output"
|
||||
}
|
||||
output_map {
|
||||
key: "POST_OUTPUT_0"
|
||||
value: "cls_labels"
|
||||
}
|
||||
output_map {
|
||||
key: "POST_OUTPUT_1"
|
||||
value: "cls_scores"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
# optional, If name is specified it must match the name of the model repository directory containing the model.
|
||||
name: "cls_runtime"
|
||||
backend: "fastdeploy"
|
||||
max_batch_size: 128
|
||||
|
||||
# Input configuration of the model
|
||||
input [
|
||||
{
|
||||
# input name
|
||||
name: "x"
|
||||
# input type such as TYPE_FP32、TYPE_UINT8、TYPE_INT8、TYPE_INT16、TYPE_INT32、TYPE_INT64、TYPE_FP16、TYPE_STRING
|
||||
data_type: TYPE_FP32
|
||||
# input shape, The batch dimension is omitted and the actual shape is [batch, c, h, w]
|
||||
dims: [ 3, -1, -1 ]
|
||||
}
|
||||
]
|
||||
|
||||
# The output of the model is configured in the same format as the input
|
||||
output [
|
||||
{
|
||||
name: "softmax_0.tmp_0"
|
||||
data_type: TYPE_FP32
|
||||
dims: [ 2 ]
|
||||
}
|
||||
]
|
||||
|
||||
# Number of instances of the model
|
||||
instance_group [
|
||||
{
|
||||
# The number of instances is 1
|
||||
count: 1
|
||||
# Use GPU, CPU inference option is:KIND_CPU
|
||||
kind: KIND_GPU
|
||||
# The instance is deployed on the 0th GPU card
|
||||
gpus: [0]
|
||||
}
|
||||
]
|
||||
|
||||
optimization {
|
||||
execution_accelerators {
|
||||
# GPU推理配置, 配合KIND_GPU使用
|
||||
gpu_execution_accelerator : [
|
||||
{
|
||||
name : "paddle"
|
||||
# 设置推理并行计算线程数为4
|
||||
parameters { key: "cpu_threads" value: "4" }
|
||||
# 开启mkldnn加速,设置为0关闭mkldnn
|
||||
parameters { key: "use_mkldnn" value: "1" }
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
import numpy as np
|
||||
import time
|
||||
import math
|
||||
import cv2
|
||||
import fastdeploy as fd
|
||||
|
||||
# triton_python_backend_utils is available in every Triton Python model. You
|
||||
# need to use this module to create inference requests and responses. It also
|
||||
# contains some utility functions for extracting information from model_config
|
||||
# and converting Triton input/output types to numpy types.
|
||||
import triton_python_backend_utils as pb_utils
|
||||
|
||||
|
||||
def get_rotate_crop_image(img, box):
|
||||
'''
|
||||
img_height, img_width = img.shape[0:2]
|
||||
left = int(np.min(points[:, 0]))
|
||||
right = int(np.max(points[:, 0]))
|
||||
top = int(np.min(points[:, 1]))
|
||||
bottom = int(np.max(points[:, 1]))
|
||||
img_crop = img[top:bottom, left:right, :].copy()
|
||||
points[:, 0] = points[:, 0] - left
|
||||
points[:, 1] = points[:, 1] - top
|
||||
'''
|
||||
points = []
|
||||
for i in range(4):
|
||||
points.append([box[2 * i], box[2 * i + 1]])
|
||||
points = np.array(points, dtype=np.float32)
|
||||
img = img.astype(np.float32)
|
||||
assert len(points) == 4, "shape of points must be 4*2"
|
||||
img_crop_width = int(
|
||||
max(
|
||||
np.linalg.norm(points[0] - points[1]),
|
||||
np.linalg.norm(points[2] - points[3])))
|
||||
img_crop_height = int(
|
||||
max(
|
||||
np.linalg.norm(points[0] - points[3]),
|
||||
np.linalg.norm(points[1] - points[2])))
|
||||
pts_std = np.float32([[0, 0], [img_crop_width, 0],
|
||||
[img_crop_width, img_crop_height],
|
||||
[0, img_crop_height]])
|
||||
M = cv2.getPerspectiveTransform(points, pts_std)
|
||||
dst_img = cv2.warpPerspective(
|
||||
img,
|
||||
M, (img_crop_width, img_crop_height),
|
||||
borderMode=cv2.BORDER_REPLICATE,
|
||||
flags=cv2.INTER_CUBIC)
|
||||
dst_img_height, dst_img_width = dst_img.shape[0:2]
|
||||
if dst_img_height * 1.0 / dst_img_width >= 1.5:
|
||||
dst_img = np.rot90(dst_img)
|
||||
return dst_img
|
||||
|
||||
|
||||
class TritonPythonModel:
|
||||
"""Your Python model must use the same class name. Every Python model
|
||||
that is created must have "TritonPythonModel" as the class name.
|
||||
"""
|
||||
|
||||
def initialize(self, args):
|
||||
"""`initialize` is called only once when the model is being loaded.
|
||||
Implementing `initialize` function is optional. This function allows
|
||||
the model to intialize any state associated with this model.
|
||||
Parameters
|
||||
----------
|
||||
args : dict
|
||||
Both keys and values are strings. The dictionary keys and values are:
|
||||
* model_config: A JSON string containing the model configuration
|
||||
* model_instance_kind: A string containing model instance kind
|
||||
* model_instance_device_id: A string containing model instance device ID
|
||||
* model_repository: Model repository path
|
||||
* model_version: Model version
|
||||
* model_name: Model name
|
||||
"""
|
||||
# You must parse model_config. JSON string is not parsed here
|
||||
self.model_config = json.loads(args['model_config'])
|
||||
print("model_config:", self.model_config)
|
||||
|
||||
self.input_names = []
|
||||
for input_config in self.model_config["input"]:
|
||||
self.input_names.append(input_config["name"])
|
||||
print("postprocess input names:", self.input_names)
|
||||
|
||||
self.output_names = []
|
||||
self.output_dtype = []
|
||||
for output_config in self.model_config["output"]:
|
||||
self.output_names.append(output_config["name"])
|
||||
dtype = pb_utils.triton_string_to_numpy(output_config["data_type"])
|
||||
self.output_dtype.append(dtype)
|
||||
print("postprocess output names:", self.output_names)
|
||||
self.postprocessor = fd.vision.ocr.DBDetectorPostprocessor()
|
||||
self.cls_preprocessor = fd.vision.ocr.ClassifierPreprocessor()
|
||||
self.rec_preprocessor = fd.vision.ocr.RecognizerPreprocessor()
|
||||
self.cls_threshold = 0.9
|
||||
|
||||
def execute(self, requests):
|
||||
"""`execute` must be implemented in every Python model. `execute`
|
||||
function receives a list of pb_utils.InferenceRequest as the only
|
||||
argument. This function is called when an inference is requested
|
||||
for this model. Depending on the batching configuration (e.g. Dynamic
|
||||
Batching) used, `requests` may contain multiple requests. Every
|
||||
Python model, must create one pb_utils.InferenceResponse for every
|
||||
pb_utils.InferenceRequest in `requests`. If there is an error, you can
|
||||
set the error argument when creating a pb_utils.InferenceResponse.
|
||||
Parameters
|
||||
----------
|
||||
requests : list
|
||||
A list of pb_utils.InferenceRequest
|
||||
Returns
|
||||
-------
|
||||
list
|
||||
A list of pb_utils.InferenceResponse. The length of this list must
|
||||
be the same as `requests`
|
||||
"""
|
||||
responses = []
|
||||
for request in requests:
|
||||
infer_outputs = pb_utils.get_input_tensor_by_name(
|
||||
request, self.input_names[0])
|
||||
im_infos = pb_utils.get_input_tensor_by_name(request,
|
||||
self.input_names[1])
|
||||
ori_imgs = pb_utils.get_input_tensor_by_name(request,
|
||||
self.input_names[2])
|
||||
|
||||
infer_outputs = infer_outputs.as_numpy()
|
||||
im_infos = im_infos.as_numpy()
|
||||
ori_imgs = ori_imgs.as_numpy()
|
||||
|
||||
results = self.postprocessor.run([infer_outputs], im_infos)
|
||||
batch_rec_texts = []
|
||||
batch_rec_scores = []
|
||||
batch_box_list = []
|
||||
for i_batch in range(len(results)):
|
||||
|
||||
cls_labels = []
|
||||
cls_scores = []
|
||||
rec_texts = []
|
||||
rec_scores = []
|
||||
|
||||
box_list = fd.vision.ocr.sort_boxes(results[i_batch])
|
||||
image_list = []
|
||||
if len(box_list) == 0:
|
||||
image_list.append(ori_imgs[i_batch])
|
||||
else:
|
||||
for box in box_list:
|
||||
crop_img = get_rotate_crop_image(ori_imgs[i_batch], box)
|
||||
image_list.append(crop_img)
|
||||
|
||||
batch_box_list.append(box_list)
|
||||
|
||||
cls_pre_tensors = self.cls_preprocessor.run(image_list)
|
||||
cls_dlpack_tensor = cls_pre_tensors[0].to_dlpack()
|
||||
cls_input_tensor = pb_utils.Tensor.from_dlpack(
|
||||
"x", cls_dlpack_tensor)
|
||||
|
||||
inference_request = pb_utils.InferenceRequest(
|
||||
model_name='cls_pp',
|
||||
requested_output_names=['cls_labels', 'cls_scores'],
|
||||
inputs=[cls_input_tensor])
|
||||
inference_response = inference_request.exec()
|
||||
if inference_response.has_error():
|
||||
raise pb_utils.TritonModelException(
|
||||
inference_response.error().message())
|
||||
else:
|
||||
# Extract the output tensors from the inference response.
|
||||
cls_labels = pb_utils.get_output_tensor_by_name(
|
||||
inference_response, 'cls_labels')
|
||||
cls_labels = cls_labels.as_numpy()
|
||||
|
||||
cls_scores = pb_utils.get_output_tensor_by_name(
|
||||
inference_response, 'cls_scores')
|
||||
cls_scores = cls_scores.as_numpy()
|
||||
|
||||
for index in range(len(image_list)):
|
||||
if cls_labels[index] == 1 and cls_scores[
|
||||
index] > self.cls_threshold:
|
||||
image_list[index] = cv2.rotate(
|
||||
image_list[index].astype(np.float32), 1)
|
||||
image_list[index] = np.astype(np.uint8)
|
||||
|
||||
rec_pre_tensors = self.rec_preprocessor.run(image_list)
|
||||
rec_dlpack_tensor = rec_pre_tensors[0].to_dlpack()
|
||||
rec_input_tensor = pb_utils.Tensor.from_dlpack(
|
||||
"x", rec_dlpack_tensor)
|
||||
|
||||
inference_request = pb_utils.InferenceRequest(
|
||||
model_name='rec_pp',
|
||||
requested_output_names=['rec_texts', 'rec_scores'],
|
||||
inputs=[rec_input_tensor])
|
||||
inference_response = inference_request.exec()
|
||||
if inference_response.has_error():
|
||||
raise pb_utils.TritonModelException(
|
||||
inference_response.error().message())
|
||||
else:
|
||||
# Extract the output tensors from the inference response.
|
||||
rec_texts = pb_utils.get_output_tensor_by_name(
|
||||
inference_response, 'rec_texts')
|
||||
rec_texts = rec_texts.as_numpy()
|
||||
|
||||
rec_scores = pb_utils.get_output_tensor_by_name(
|
||||
inference_response, 'rec_scores')
|
||||
rec_scores = rec_scores.as_numpy()
|
||||
|
||||
batch_rec_texts.append(rec_texts)
|
||||
batch_rec_scores.append(rec_scores)
|
||||
|
||||
out_tensor_0 = pb_utils.Tensor(
|
||||
self.output_names[0],
|
||||
np.array(
|
||||
batch_rec_texts, dtype=np.object_))
|
||||
out_tensor_1 = pb_utils.Tensor(self.output_names[1],
|
||||
np.array(batch_rec_scores))
|
||||
out_tensor_2 = pb_utils.Tensor(self.output_names[2],
|
||||
np.array(batch_box_list))
|
||||
inference_response = pb_utils.InferenceResponse(
|
||||
output_tensors=[out_tensor_0, out_tensor_1, out_tensor_2])
|
||||
responses.append(inference_response)
|
||||
return responses
|
||||
|
||||
def finalize(self):
|
||||
"""`finalize` is called only once when the model is being unloaded.
|
||||
Implementing `finalize` function is optional. This function allows
|
||||
the model to perform any necessary clean ups before exit.
|
||||
"""
|
||||
print('Cleaning up...')
|
||||
@@ -0,0 +1,45 @@
|
||||
name: "det_postprocess"
|
||||
backend: "python"
|
||||
max_batch_size: 128
|
||||
input [
|
||||
{
|
||||
name: "POST_INPUT_0"
|
||||
data_type: TYPE_FP32
|
||||
dims: [ 1, -1, -1]
|
||||
},
|
||||
{
|
||||
name: "POST_INPUT_1"
|
||||
data_type: TYPE_INT32
|
||||
dims: [ 4 ]
|
||||
},
|
||||
{
|
||||
name: "ORI_IMG"
|
||||
data_type: TYPE_UINT8
|
||||
dims: [ -1, -1, 3 ]
|
||||
}
|
||||
]
|
||||
|
||||
output [
|
||||
{
|
||||
name: "POST_OUTPUT_0"
|
||||
data_type: TYPE_STRING
|
||||
dims: [ -1, 1 ]
|
||||
},
|
||||
{
|
||||
name: "POST_OUTPUT_1"
|
||||
data_type: TYPE_FP32
|
||||
dims: [ -1, 1 ]
|
||||
},
|
||||
{
|
||||
name: "POST_OUTPUT_2"
|
||||
data_type: TYPE_FP32
|
||||
dims: [ -1, -1, 1 ]
|
||||
}
|
||||
]
|
||||
|
||||
instance_group [
|
||||
{
|
||||
count: 1
|
||||
kind: KIND_CPU
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,107 @@
|
||||
# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
import numpy as np
|
||||
import time
|
||||
|
||||
import fastdeploy as fd
|
||||
|
||||
# triton_python_backend_utils is available in every Triton Python model. You
|
||||
# need to use this module to create inference requests and responses. It also
|
||||
# contains some utility functions for extracting information from model_config
|
||||
# and converting Triton input/output types to numpy types.
|
||||
import triton_python_backend_utils as pb_utils
|
||||
|
||||
|
||||
class TritonPythonModel:
|
||||
"""Your Python model must use the same class name. Every Python model
|
||||
that is created must have "TritonPythonModel" as the class name.
|
||||
"""
|
||||
|
||||
def initialize(self, args):
|
||||
"""`initialize` is called only once when the model is being loaded.
|
||||
Implementing `initialize` function is optional. This function allows
|
||||
the model to intialize any state associated with this model.
|
||||
Parameters
|
||||
----------
|
||||
args : dict
|
||||
Both keys and values are strings. The dictionary keys and values are:
|
||||
* model_config: A JSON string containing the model configuration
|
||||
* model_instance_kind: A string containing model instance kind
|
||||
* model_instance_device_id: A string containing model instance device ID
|
||||
* model_repository: Model repository path
|
||||
* model_version: Model version
|
||||
* model_name: Model name
|
||||
"""
|
||||
# You must parse model_config. JSON string is not parsed here
|
||||
self.model_config = json.loads(args['model_config'])
|
||||
print("model_config:", self.model_config)
|
||||
|
||||
self.input_names = []
|
||||
for input_config in self.model_config["input"]:
|
||||
self.input_names.append(input_config["name"])
|
||||
print("preprocess input names:", self.input_names)
|
||||
|
||||
self.output_names = []
|
||||
self.output_dtype = []
|
||||
for output_config in self.model_config["output"]:
|
||||
self.output_names.append(output_config["name"])
|
||||
dtype = pb_utils.triton_string_to_numpy(output_config["data_type"])
|
||||
self.output_dtype.append(dtype)
|
||||
print("preprocess output names:", self.output_names)
|
||||
self.preprocessor = fd.vision.ocr.DBDetectorPreprocessor()
|
||||
|
||||
def execute(self, requests):
|
||||
"""`execute` must be implemented in every Python model. `execute`
|
||||
function receives a list of pb_utils.InferenceRequest as the only
|
||||
argument. This function is called when an inference is requested
|
||||
for this model. Depending on the batching configuration (e.g. Dynamic
|
||||
Batching) used, `requests` may contain multiple requests. Every
|
||||
Python model, must create one pb_utils.InferenceResponse for every
|
||||
pb_utils.InferenceRequest in `requests`. If there is an error, you can
|
||||
set the error argument when creating a pb_utils.InferenceResponse.
|
||||
Parameters
|
||||
----------
|
||||
requests : list
|
||||
A list of pb_utils.InferenceRequest
|
||||
Returns
|
||||
-------
|
||||
list
|
||||
A list of pb_utils.InferenceResponse. The length of this list must
|
||||
be the same as `requests`
|
||||
"""
|
||||
responses = []
|
||||
for request in requests:
|
||||
data = pb_utils.get_input_tensor_by_name(request,
|
||||
self.input_names[0])
|
||||
data = data.as_numpy()
|
||||
outputs, im_infos = self.preprocessor.run(data)
|
||||
dlpack_tensor = outputs[0].to_dlpack()
|
||||
output_tensor_0 = pb_utils.Tensor.from_dlpack(self.output_names[0],
|
||||
dlpack_tensor)
|
||||
output_tensor_1 = pb_utils.Tensor(
|
||||
self.output_names[1], np.array(
|
||||
im_infos, dtype=np.int32))
|
||||
inference_response = pb_utils.InferenceResponse(
|
||||
output_tensors=[output_tensor_0, output_tensor_1])
|
||||
responses.append(inference_response)
|
||||
return responses
|
||||
|
||||
def finalize(self):
|
||||
"""`finalize` is called only once when the model is being unloaded.
|
||||
Implementing `finalize` function is optional. This function allows
|
||||
the model to perform any necessary clean ups before exit.
|
||||
"""
|
||||
print('Cleaning up...')
|
||||
@@ -0,0 +1,37 @@
|
||||
# optional, If name is specified it must match the name of the model repository directory containing the model.
|
||||
name: "det_preprocess"
|
||||
backend: "python"
|
||||
max_batch_size: 1
|
||||
|
||||
# Input configuration of the model
|
||||
input [
|
||||
{
|
||||
# input name
|
||||
name: "INPUT_0"
|
||||
# input type such as TYPE_FP32、TYPE_UINT8、TYPE_INT8、TYPE_INT16、TYPE_INT32、TYPE_INT64、TYPE_FP16、TYPE_STRING
|
||||
data_type: TYPE_UINT8
|
||||
# input shape, The batch dimension is omitted and the actual shape is [batch, c, h, w]
|
||||
dims: [ -1, -1, 3 ]
|
||||
}
|
||||
]
|
||||
|
||||
# The output of the model is configured in the same format as the input
|
||||
output [
|
||||
{
|
||||
name: "OUTPUT_0"
|
||||
data_type: TYPE_FP32
|
||||
dims: [ 3, -1, -1 ]
|
||||
},
|
||||
{
|
||||
name: "OUTPUT_1"
|
||||
data_type: TYPE_INT32
|
||||
dims: [ 4 ]
|
||||
}
|
||||
]
|
||||
|
||||
instance_group [
|
||||
{
|
||||
count: 1
|
||||
kind: KIND_CPU
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,52 @@
|
||||
# optional, If name is specified it must match the name of the model repository directory containing the model.
|
||||
name: "det_runtime"
|
||||
backend: "fastdeploy"
|
||||
max_batch_size: 1
|
||||
|
||||
# Input configuration of the model
|
||||
input [
|
||||
{
|
||||
# input name
|
||||
name: "x"
|
||||
# input type such as TYPE_FP32、TYPE_UINT8、TYPE_INT8、TYPE_INT16、TYPE_INT32、TYPE_INT64、TYPE_FP16、TYPE_STRING
|
||||
data_type: TYPE_FP32
|
||||
# input shape, The batch dimension is omitted and the actual shape is [batch, c, h, w]
|
||||
dims: [ 3, -1, -1 ]
|
||||
}
|
||||
]
|
||||
|
||||
# The output of the model is configured in the same format as the input
|
||||
output [
|
||||
{
|
||||
name: "sigmoid_0.tmp_0"
|
||||
data_type: TYPE_FP32
|
||||
dims: [ 1, -1, -1 ]
|
||||
}
|
||||
]
|
||||
|
||||
# Number of instances of the model
|
||||
instance_group [
|
||||
{
|
||||
# The number of instances is 1
|
||||
count: 1
|
||||
# Use GPU, CPU inference option is:KIND_CPU
|
||||
kind: KIND_GPU
|
||||
# The instance is deployed on the 0th GPU card
|
||||
gpus: [0]
|
||||
}
|
||||
]
|
||||
|
||||
optimization {
|
||||
execution_accelerators {
|
||||
# GPU推理配置, 配合KIND_GPU使用
|
||||
gpu_execution_accelerator : [
|
||||
{
|
||||
name : "paddle"
|
||||
# 设置推理并行计算线程数为4
|
||||
parameters { key: "cpu_threads" value: "4" }
|
||||
# 开启mkldnn加速,设置为0关闭mkldnn
|
||||
parameters { key: "use_mkldnn" value: "1" }
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
name: "pp_ocr"
|
||||
platform: "ensemble"
|
||||
max_batch_size: 1
|
||||
input [
|
||||
{
|
||||
name: "INPUT"
|
||||
data_type: TYPE_UINT8
|
||||
dims: [ -1, -1, 3 ]
|
||||
}
|
||||
]
|
||||
output [
|
||||
{
|
||||
name: "rec_texts"
|
||||
data_type: TYPE_STRING
|
||||
dims: [ -1, 1 ]
|
||||
},
|
||||
{
|
||||
name: "rec_scores"
|
||||
data_type: TYPE_FP32
|
||||
dims: [ -1, 1 ]
|
||||
},
|
||||
{
|
||||
name: "det_bboxes"
|
||||
data_type: TYPE_FP32
|
||||
dims: [ -1, -1, 1 ]
|
||||
}
|
||||
]
|
||||
ensemble_scheduling {
|
||||
step [
|
||||
{
|
||||
model_name: "det_preprocess"
|
||||
model_version: 1
|
||||
input_map {
|
||||
key: "INPUT_0"
|
||||
value: "INPUT"
|
||||
}
|
||||
output_map {
|
||||
key: "OUTPUT_0"
|
||||
value: "infer_input"
|
||||
}
|
||||
output_map {
|
||||
key: "OUTPUT_1"
|
||||
value: "infos"
|
||||
}
|
||||
},
|
||||
{
|
||||
model_name: "det_runtime"
|
||||
model_version: 1
|
||||
input_map {
|
||||
key: "x"
|
||||
value: "infer_input"
|
||||
}
|
||||
output_map {
|
||||
key: "sigmoid_0.tmp_0"
|
||||
value: "infer_output"
|
||||
}
|
||||
},
|
||||
{
|
||||
model_name: "det_postprocess"
|
||||
model_version: 1
|
||||
input_map {
|
||||
key: "POST_INPUT_0"
|
||||
value: "infer_output"
|
||||
}
|
||||
input_map {
|
||||
key: "POST_INPUT_1"
|
||||
value: "infos"
|
||||
}
|
||||
input_map {
|
||||
key: "ORI_IMG"
|
||||
value: "INPUT"
|
||||
}
|
||||
output_map {
|
||||
key: "POST_OUTPUT_0"
|
||||
value: "rec_texts"
|
||||
}
|
||||
output_map {
|
||||
key: "POST_OUTPUT_1"
|
||||
value: "rec_scores"
|
||||
}
|
||||
output_map {
|
||||
key: "POST_OUTPUT_2"
|
||||
value: "det_bboxes"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
import numpy as np
|
||||
import time
|
||||
import os
|
||||
import sys
|
||||
import codecs
|
||||
import fastdeploy as fd
|
||||
|
||||
# triton_python_backend_utils is available in every Triton Python model. You
|
||||
# need to use this module to create inference requests and responses. It also
|
||||
# contains some utility functions for extracting information from model_config
|
||||
# and converting Triton input/output types to numpy types.
|
||||
import triton_python_backend_utils as pb_utils
|
||||
|
||||
|
||||
class TritonPythonModel:
|
||||
"""Your Python model must use the same class name. Every Python model
|
||||
that is created must have "TritonPythonModel" as the class name.
|
||||
"""
|
||||
|
||||
def initialize(self, args):
|
||||
"""`initialize` is called only once when the model is being loaded.
|
||||
Implementing `initialize` function is optional. This function allows
|
||||
the model to intialize any state associated with this model.
|
||||
Parameters
|
||||
----------
|
||||
args : dict
|
||||
Both keys and values are strings. The dictionary keys and values are:
|
||||
* model_config: A JSON string containing the model configuration
|
||||
* model_instance_kind: A string containing model instance kind
|
||||
* model_instance_device_id: A string containing model instance device ID
|
||||
* model_repository: Model repository path
|
||||
* model_version: Model version
|
||||
* model_name: Model name
|
||||
"""
|
||||
# You must parse model_config. JSON string is not parsed here
|
||||
self.model_config = json.loads(args['model_config'])
|
||||
print("model_config:", self.model_config)
|
||||
|
||||
self.input_names = []
|
||||
for input_config in self.model_config["input"]:
|
||||
self.input_names.append(input_config["name"])
|
||||
print("postprocess input names:", self.input_names)
|
||||
|
||||
self.output_names = []
|
||||
self.output_dtype = []
|
||||
for output_config in self.model_config["output"]:
|
||||
self.output_names.append(output_config["name"])
|
||||
dtype = pb_utils.triton_string_to_numpy(output_config["data_type"])
|
||||
self.output_dtype.append(dtype)
|
||||
print("postprocess output names:", self.output_names)
|
||||
|
||||
dir_name = os.path.dirname(os.path.realpath(__file__)) + "/"
|
||||
file_name = dir_name + "ppocr_keys_v1.txt"
|
||||
#self.label_list = load_dict()
|
||||
self.postprocessor = fd.vision.ocr.RecognizerPostprocessor(file_name)
|
||||
|
||||
def execute(self, requests):
|
||||
"""`execute` must be implemented in every Python model. `execute`
|
||||
function receives a list of pb_utils.InferenceRequest as the only
|
||||
argument. This function is called when an inference is requested
|
||||
for this model. Depending on the batching configuration (e.g. Dynamic
|
||||
Batching) used, `requests` may contain multiple requests. Every
|
||||
Python model, must create one pb_utils.InferenceResponse for every
|
||||
pb_utils.InferenceRequest in `requests`. If there is an error, you can
|
||||
set the error argument when creating a pb_utils.InferenceResponse.
|
||||
Parameters
|
||||
----------
|
||||
requests : list
|
||||
A list of pb_utils.InferenceRequest
|
||||
Returns
|
||||
-------
|
||||
list
|
||||
A list of pb_utils.InferenceResponse. The length of this list must
|
||||
be the same as `requests`
|
||||
"""
|
||||
responses = []
|
||||
for request in requests:
|
||||
infer_outputs = pb_utils.get_input_tensor_by_name(
|
||||
request, self.input_names[0])
|
||||
infer_outputs = infer_outputs.as_numpy()
|
||||
results = self.postprocessor.run([infer_outputs])
|
||||
out_tensor_0 = pb_utils.Tensor(
|
||||
self.output_names[0], np.array(
|
||||
results[0], dtype=np.object_))
|
||||
out_tensor_1 = pb_utils.Tensor(self.output_names[1],
|
||||
np.array(results[1]))
|
||||
inference_response = pb_utils.InferenceResponse(
|
||||
output_tensors=[out_tensor_0, out_tensor_1])
|
||||
responses.append(inference_response)
|
||||
return responses
|
||||
|
||||
def finalize(self):
|
||||
"""`finalize` is called only once when the model is being unloaded.
|
||||
Implementing `finalize` function is optional. This function allows
|
||||
the model to perform any necessary clean ups before exit.
|
||||
"""
|
||||
print('Cleaning up...')
|
||||
@@ -0,0 +1,30 @@
|
||||
name: "rec_postprocess"
|
||||
backend: "python"
|
||||
max_batch_size: 128
|
||||
input [
|
||||
{
|
||||
name: "POST_INPUT_0"
|
||||
data_type: TYPE_FP32
|
||||
dims: [ -1, 6625 ]
|
||||
}
|
||||
]
|
||||
|
||||
output [
|
||||
{
|
||||
name: "POST_OUTPUT_0"
|
||||
data_type: TYPE_STRING
|
||||
dims: [ 1 ]
|
||||
},
|
||||
{
|
||||
name: "POST_OUTPUT_1"
|
||||
data_type: TYPE_FP32
|
||||
dims: [ 1 ]
|
||||
}
|
||||
]
|
||||
|
||||
instance_group [
|
||||
{
|
||||
count: 1
|
||||
kind: KIND_CPU
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,54 @@
|
||||
name: "rec_pp"
|
||||
platform: "ensemble"
|
||||
max_batch_size: 128
|
||||
input [
|
||||
{
|
||||
name: "x"
|
||||
data_type: TYPE_FP32
|
||||
dims: [ 3, 48, -1 ]
|
||||
}
|
||||
]
|
||||
output [
|
||||
{
|
||||
name: "rec_texts"
|
||||
data_type: TYPE_STRING
|
||||
dims: [ 1 ]
|
||||
},
|
||||
{
|
||||
name: "rec_scores"
|
||||
data_type: TYPE_FP32
|
||||
dims: [ 1 ]
|
||||
}
|
||||
]
|
||||
ensemble_scheduling {
|
||||
step [
|
||||
{
|
||||
model_name: "rec_runtime"
|
||||
model_version: 1
|
||||
input_map {
|
||||
key: "x"
|
||||
value: "x"
|
||||
}
|
||||
output_map {
|
||||
key: "softmax_5.tmp_0"
|
||||
value: "infer_output"
|
||||
}
|
||||
},
|
||||
{
|
||||
model_name: "rec_postprocess"
|
||||
model_version: 1
|
||||
input_map {
|
||||
key: "POST_INPUT_0"
|
||||
value: "infer_output"
|
||||
}
|
||||
output_map {
|
||||
key: "POST_OUTPUT_0"
|
||||
value: "rec_texts"
|
||||
}
|
||||
output_map {
|
||||
key: "POST_OUTPUT_1"
|
||||
value: "rec_scores"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
# optional, If name is specified it must match the name of the model repository directory containing the model.
|
||||
name: "rec_runtime"
|
||||
backend: "fastdeploy"
|
||||
max_batch_size: 128
|
||||
|
||||
# Input configuration of the model
|
||||
input [
|
||||
{
|
||||
# input name
|
||||
name: "x"
|
||||
# input type such as TYPE_FP32、TYPE_UINT8、TYPE_INT8、TYPE_INT16、TYPE_INT32、TYPE_INT64、TYPE_FP16、TYPE_STRING
|
||||
data_type: TYPE_FP32
|
||||
# input shape, The batch dimension is omitted and the actual shape is [batch, c, h, w]
|
||||
dims: [ 3, 48, -1 ]
|
||||
}
|
||||
]
|
||||
|
||||
# The output of the model is configured in the same format as the input
|
||||
output [
|
||||
{
|
||||
name: "softmax_5.tmp_0"
|
||||
data_type: TYPE_FP32
|
||||
dims: [ -1, 6625 ]
|
||||
}
|
||||
]
|
||||
|
||||
# Number of instances of the model
|
||||
instance_group [
|
||||
{
|
||||
# The number of instances is 1
|
||||
count: 1
|
||||
# Use GPU, CPU inference option is:KIND_CPU
|
||||
kind: KIND_GPU
|
||||
# The instance is deployed on the 0th GPU card
|
||||
gpus: [0]
|
||||
}
|
||||
]
|
||||
|
||||
optimization {
|
||||
execution_accelerators {
|
||||
# GPU推理配置, 配合KIND_GPU使用
|
||||
gpu_execution_accelerator : [
|
||||
{
|
||||
name : "paddle"
|
||||
# 设置推理并行计算线程数为4
|
||||
parameters { key: "cpu_threads" value: "4" }
|
||||
# 开启mkldnn加速,设置为0关闭mkldnn
|
||||
parameters { key: "use_mkldnn" value: "1" }
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
@@ -0,0 +1,54 @@
|
||||
简体中文 | [English](README.md)
|
||||
|
||||
|
||||
# PaddleOCR Python轻量服务化部署示例
|
||||
|
||||
PaddleOCR Python轻量服务化部署是FastDeploy基于Flask框架搭建的可快速验证线上模型部署可行性的服务化部署示例,基于http请求完成AI推理任务,适用于无并发推理的简单场景,如有高并发,高吞吐场景的需求请参考[fastdeploy_serving](../fastdeploy_serving/)
|
||||
|
||||
|
||||
## 1. 部署环境准备
|
||||
|
||||
在部署前,需确认软硬件环境,同时下载预编译python wheel 包,参考文档[FastDeploy预编译库安装](https://github.com/PaddlePaddle/FastDeploy/blob/develop/docs/cn/build_and_install#FastDeploy预编译库安装)
|
||||
|
||||
|
||||
## 2. 启动服务
|
||||
```bash
|
||||
# 下载部署示例代码
|
||||
git clone https://github.com/PaddlePaddle/FastDeploy.git
|
||||
cd FastDeploy/examples/vision/ocr/PP-OCR/serving/simple_serving
|
||||
|
||||
# 如果您希望从PaddleOCR下载示例代码,请运行
|
||||
git clone https://github.com/PaddlePaddle/PaddleOCR.git
|
||||
# 注意:如果当前分支找不到下面的fastdeploy测试代码,请切换到dygraph分支
|
||||
git checkout dygraph
|
||||
cd PaddleOCR/deploy/fastdeploy/serving/simple_serving
|
||||
|
||||
# 下载模型和字典文件
|
||||
wget https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_det_infer.tar
|
||||
tar xvf ch_PP-OCRv3_det_infer.tar
|
||||
|
||||
wget https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_cls_infer.tar
|
||||
tar -xvf ch_ppocr_mobile_v2.0_cls_infer.tar
|
||||
|
||||
wget https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_rec_infer.tar
|
||||
tar xvf ch_PP-OCRv3_rec_infer.tar
|
||||
|
||||
wget https://gitee.com/paddlepaddle/PaddleOCR/raw/release/2.6/ppocr/utils/ppocr_keys_v1.txt
|
||||
|
||||
# 启动服务,可修改server.py中的配置项来指定硬件、后端等
|
||||
# 可通过--host、--port指定IP和端口号
|
||||
fastdeploy simple_serving --app server:app
|
||||
```
|
||||
|
||||
## 3. 客户端请求
|
||||
```bash
|
||||
# 下载部署示例代码
|
||||
git clone https://github.com/PaddlePaddle/PaddleOCR.git
|
||||
cd PaddleOCR/deploy/fastdeploy/serving/simple_serving
|
||||
|
||||
# 下载测试图片
|
||||
wget https://gitee.com/paddlepaddle/PaddleOCR/raw/release/2.6/doc/imgs/12.jpg
|
||||
|
||||
# 请求服务,获取推理结果(如有必要,请修改脚本中的IP和端口号)
|
||||
python client.py
|
||||
```
|
||||
@@ -0,0 +1,24 @@
|
||||
import requests
|
||||
import json
|
||||
import cv2
|
||||
import fastdeploy as fd
|
||||
from fastdeploy.serving.utils import cv2_to_base64
|
||||
|
||||
if __name__ == '__main__':
|
||||
url = "http://127.0.0.1:8000/fd/ppocrv3"
|
||||
headers = {"Content-Type": "application/json"}
|
||||
|
||||
im = cv2.imread("12.jpg")
|
||||
data = {"data": {"image": cv2_to_base64(im)}, "parameters": {}}
|
||||
|
||||
resp = requests.post(url=url, headers=headers, data=json.dumps(data))
|
||||
if resp.status_code == 200:
|
||||
r_json = json.loads(resp.json()["result"])
|
||||
print(r_json)
|
||||
ocr_result = fd.vision.utils.json_to_ocr(r_json)
|
||||
vis_im = fd.vision.vis_ppocr(im, ocr_result)
|
||||
cv2.imwrite("visualized_result.jpg", vis_im)
|
||||
print("Visualized result save in ./visualized_result.jpg")
|
||||
else:
|
||||
print("Error code:", resp.status_code)
|
||||
print(resp.text)
|
||||
@@ -0,0 +1,80 @@
|
||||
import fastdeploy as fd
|
||||
from fastdeploy.serving.server import SimpleServer
|
||||
import os
|
||||
import logging
|
||||
|
||||
logging.getLogger().setLevel(logging.INFO)
|
||||
|
||||
# Configurations
|
||||
det_model_dir = 'ch_PP-OCRv3_det_infer'
|
||||
cls_model_dir = 'ch_ppocr_mobile_v2.0_cls_infer'
|
||||
rec_model_dir = 'ch_PP-OCRv3_rec_infer'
|
||||
rec_label_file = 'ppocr_keys_v1.txt'
|
||||
device = 'cpu'
|
||||
# backend: ['paddle', 'trt'], you can also use other backends, but need to modify
|
||||
# the runtime option below
|
||||
backend = 'paddle'
|
||||
|
||||
# Prepare models
|
||||
# Detection model
|
||||
det_model_file = os.path.join(det_model_dir, "inference.pdmodel")
|
||||
det_params_file = os.path.join(det_model_dir, "inference.pdiparams")
|
||||
# Classification model
|
||||
cls_model_file = os.path.join(cls_model_dir, "inference.pdmodel")
|
||||
cls_params_file = os.path.join(cls_model_dir, "inference.pdiparams")
|
||||
# Recognition model
|
||||
rec_model_file = os.path.join(rec_model_dir, "inference.pdmodel")
|
||||
rec_params_file = os.path.join(rec_model_dir, "inference.pdiparams")
|
||||
|
||||
# Setup runtime option to select hardware, backend, etc.
|
||||
option = fd.RuntimeOption()
|
||||
if device.lower() == 'gpu':
|
||||
option.use_gpu()
|
||||
if backend == 'trt':
|
||||
option.use_trt_backend()
|
||||
else:
|
||||
option.use_paddle_infer_backend()
|
||||
|
||||
det_option = option
|
||||
det_option.set_trt_input_shape("x", [1, 3, 64, 64], [1, 3, 640, 640],
|
||||
[1, 3, 960, 960])
|
||||
|
||||
# det_option.set_trt_cache_file("det_trt_cache.trt")
|
||||
print(det_model_file, det_params_file)
|
||||
det_model = fd.vision.ocr.DBDetector(
|
||||
det_model_file, det_params_file, runtime_option=det_option)
|
||||
|
||||
cls_batch_size = 1
|
||||
rec_batch_size = 6
|
||||
|
||||
cls_option = option
|
||||
cls_option.set_trt_input_shape("x", [1, 3, 48, 10],
|
||||
[cls_batch_size, 3, 48, 320],
|
||||
[cls_batch_size, 3, 48, 1024])
|
||||
|
||||
# cls_option.set_trt_cache_file("cls_trt_cache.trt")
|
||||
cls_model = fd.vision.ocr.Classifier(
|
||||
cls_model_file, cls_params_file, runtime_option=cls_option)
|
||||
|
||||
rec_option = option
|
||||
rec_option.set_trt_input_shape("x", [1, 3, 48, 10],
|
||||
[rec_batch_size, 3, 48, 320],
|
||||
[rec_batch_size, 3, 48, 2304])
|
||||
|
||||
# rec_option.set_trt_cache_file("rec_trt_cache.trt")
|
||||
rec_model = fd.vision.ocr.Recognizer(
|
||||
rec_model_file, rec_params_file, rec_label_file, runtime_option=rec_option)
|
||||
|
||||
# Create PPOCRv3 pipeline
|
||||
ppocr_v3 = fd.vision.ocr.PPOCRv3(
|
||||
det_model=det_model, cls_model=cls_model, rec_model=rec_model)
|
||||
|
||||
ppocr_v3.cls_batch_size = cls_batch_size
|
||||
ppocr_v3.rec_batch_size = rec_batch_size
|
||||
|
||||
# Create server, setup REST API
|
||||
app = SimpleServer()
|
||||
app.register(
|
||||
task_name="fd/ppocrv3",
|
||||
model_handler=fd.serving.handler.VisionModelHandler,
|
||||
predictor=ppocr_v3)
|
||||
Reference in New Issue
Block a user