init
This commit is contained in:
@@ -0,0 +1,285 @@
|
||||
# OCR Pipeline WebService
|
||||
|
||||
(English|[简体中文](./README_CN.md))
|
||||
|
||||
PaddleOCR provides two service deployment methods:
|
||||
- Based on **PaddleHub Serving**: Code path is "`./deploy/hubserving`". Please refer to the [tutorial](../../deploy/hubserving/readme_en.md)
|
||||
- Based on **PaddleServing**: Code path is "`./deploy/pdserving`". Please follow this tutorial.
|
||||
|
||||
# Service deployment based on PaddleServing
|
||||
|
||||
This document will introduce how to use the [PaddleServing](https://github.com/PaddlePaddle/Serving/blob/develop/README.md) to deploy the PPOCR dynamic graph model as a pipeline online service.
|
||||
|
||||
Some Key Features of Paddle Serving:
|
||||
- Integrate with Paddle training pipeline seamlessly, most paddle models can be deployed with one line command.
|
||||
- Industrial serving features supported, such as models management, online loading, online A/B testing etc.
|
||||
- Highly concurrent and efficient communication between clients and servers supported.
|
||||
|
||||
PaddleServing supports deployment in multiple languages. In this example, two deployment methods, python pipeline and C++, are provided. The comparison between the two is as follows:
|
||||
|
||||
| Language | Speed | Secondary development | Do you need to compile |
|
||||
|-----|-----|---------|------------|
|
||||
| C++ | fast | Slightly difficult | Single model prediction does not need to be compiled, multi-model concatenation needs to be compiled |
|
||||
| python | general | easy | single-model/multi-model no compilation required |
|
||||
|
||||
|
||||
The introduction and tutorial of Paddle Serving service deployment framework reference [document](https://github.com/PaddlePaddle/Serving/blob/develop/README.md).
|
||||
|
||||
|
||||
## Contents
|
||||
- [OCR Pipeline WebService](#ocr-pipeline-webservice)
|
||||
- [Service deployment based on PaddleServing](#service-deployment-based-on-paddleserving)
|
||||
- [Contents](#contents)
|
||||
- [Environmental preparation](#environmental-preparation)
|
||||
- [Model conversion](#model-conversion)
|
||||
- [Paddle Serving pipeline deployment](#paddle-serving-pipeline-deployment)
|
||||
- [Paddle Serving C++ deployment](#C++)
|
||||
- [WINDOWS Users](#windows-users)
|
||||
- [FAQ](#faq)
|
||||
|
||||
<a name="environmental-preparation"></a>
|
||||
## Environmental preparation
|
||||
|
||||
PaddleOCR operating environment and Paddle Serving operating environment are needed.
|
||||
|
||||
1. Please prepare PaddleOCR operating environment reference [link](../../doc/doc_ch/installation.md).
|
||||
Download the corresponding paddlepaddle whl package according to the environment, it is recommended to install version 2.2.2.
|
||||
|
||||
2. The steps of PaddleServing operating environment prepare are as follows:
|
||||
|
||||
|
||||
```bash
|
||||
# Install serving which used to start the service
|
||||
wget https://paddle-serving.bj.bcebos.com/test-dev/whl/paddle_serving_server_gpu-0.8.3.post102-py3-none-any.whl
|
||||
pip3 install paddle_serving_server_gpu-0.8.3.post102-py3-none-any.whl
|
||||
|
||||
# Install paddle-serving-server for cuda10.1
|
||||
# wget https://paddle-serving.bj.bcebos.com/test-dev/whl/paddle_serving_server_gpu-0.8.3.post101-py3-none-any.whl
|
||||
# pip3 install paddle_serving_server_gpu-0.8.3.post101-py3-none-any.whl
|
||||
|
||||
# Install serving which used to start the service
|
||||
wget https://paddle-serving.bj.bcebos.com/test-dev/whl/paddle_serving_client-0.8.3-cp37-none-any.whl
|
||||
pip3 install paddle_serving_client-0.8.3-cp37-none-any.whl
|
||||
|
||||
# Install serving-app
|
||||
wget https://paddle-serving.bj.bcebos.com/test-dev/whl/paddle_serving_app-0.8.3-py3-none-any.whl
|
||||
pip3 install paddle_serving_app-0.8.3-py3-none-any.whl
|
||||
```
|
||||
|
||||
**note:** If you want to install the latest version of PaddleServing, refer to [link](https://github.com/PaddlePaddle/Serving/blob/v0.8.3/doc/Latest_Packages_CN.md).
|
||||
|
||||
|
||||
<a name="model-conversion"></a>
|
||||
## Model conversion
|
||||
When using PaddleServing for service deployment, you need to convert the saved inference model into a serving model that is easy to deploy.
|
||||
|
||||
Firstly, download the [inference model](https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.3/README_ch.md#pp-ocr%E7%B3%BB%E5%88%97%E6%A8%A1%E5%9E%8B%E5%88%97%E8%A1%A8%E6%9B%B4%E6%96%B0%E4%B8%AD) of PPOCR
|
||||
```
|
||||
# Download and unzip the OCR text detection model
|
||||
wget https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_det_infer.tar -O ch_PP-OCRv3_det_infer.tar && tar -xf ch_PP-OCRv3_det_infer.tar
|
||||
# Download and unzip the OCR text recognition model
|
||||
wget https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_rec_infer.tar -O ch_PP-OCRv3_rec_infer.tar && tar -xf ch_PP-OCRv3_rec_infer.tar
|
||||
```
|
||||
Then, you can use installed paddle_serving_client tool to convert inference model to mobile model.
|
||||
```
|
||||
# Detection model conversion
|
||||
python3 -m paddle_serving_client.convert --dirname ./ch_PP-OCRv3_det_infer/ \
|
||||
--model_filename inference.pdmodel \
|
||||
--params_filename inference.pdiparams \
|
||||
--serving_server ./ppocr_det_v3_serving/ \
|
||||
--serving_client ./ppocr_det_v3_client/
|
||||
|
||||
# Recognition model conversion
|
||||
python3 -m paddle_serving_client.convert --dirname ./ch_PP-OCRv3_rec_infer/ \
|
||||
--model_filename inference.pdmodel \
|
||||
--params_filename inference.pdiparams \
|
||||
--serving_server ./ppocr_rec_v3_serving/ \
|
||||
--serving_client ./ppocr_rec_v3_client/
|
||||
|
||||
```
|
||||
|
||||
After the detection model is converted, there will be additional folders of `ppocr_det_v3_serving` and `ppocr_det_v3_client` in the current folder, with the following format:
|
||||
```
|
||||
|- ppocr_det_v3_serving/
|
||||
|- __model__
|
||||
|- __params__
|
||||
|- serving_server_conf.prototxt
|
||||
|- serving_server_conf.stream.prototxt
|
||||
|
||||
|- ppocr_det_v3_client
|
||||
|- serving_client_conf.prototxt
|
||||
|- serving_client_conf.stream.prototxt
|
||||
|
||||
```
|
||||
The recognition model is the same.
|
||||
|
||||
<a name="paddle-serving-pipeline-deployment"></a>
|
||||
## Paddle Serving pipeline deployment
|
||||
|
||||
1. Download the PaddleOCR code, if you have already downloaded it, you can skip this step.
|
||||
```
|
||||
git clone https://github.com/PaddlePaddle/PaddleOCR
|
||||
|
||||
# Enter the working directory
|
||||
cd PaddleOCR/deploy/pdserving/
|
||||
```
|
||||
|
||||
The pdserver directory contains the code to start the pipeline service and send prediction requests, including:
|
||||
```
|
||||
__init__.py
|
||||
config.yml # Start the service configuration file
|
||||
ocr_reader.py # OCR model pre-processing and post-processing code implementation
|
||||
pipeline_http_client.py # Script to send pipeline prediction request
|
||||
web_service.py # Start the script of the pipeline server
|
||||
```
|
||||
|
||||
2. Run the following command to start the service.
|
||||
```
|
||||
# Start the service and save the running log in log.txt
|
||||
python3 web_service.py --config=config.yml &>log.txt &
|
||||
```
|
||||
After the service is successfully started, a log similar to the following will be printed in log.txt
|
||||

|
||||
|
||||
3. Send service request
|
||||
```
|
||||
python3 pipeline_http_client.py
|
||||
```
|
||||
After successfully running, the predicted result of the model will be printed in the cmd window. An example of the result is:
|
||||

|
||||
|
||||
Adjust the number of concurrency in config.yml to get the largest QPS. Generally, the number of concurrent detection and recognition is 2:1
|
||||
|
||||
```
|
||||
det:
|
||||
concurrency: 8
|
||||
...
|
||||
rec:
|
||||
concurrency: 4
|
||||
...
|
||||
```
|
||||
|
||||
Multiple service requests can be sent at the same time if necessary.
|
||||
|
||||
The predicted performance data will be automatically written into the `PipelineServingLogs/pipeline.tracer` file.
|
||||
|
||||
Tested on 200 real pictures, and limited the detection long side to 960. The average QPS on T4 GPU can reach around 23:
|
||||
|
||||
```
|
||||
|
||||
2021-05-13 03:42:36,895 ==================== TRACER ======================
|
||||
2021-05-13 03:42:36,975 Op(rec):
|
||||
2021-05-13 03:42:36,976 in[14.472382882882883 ms]
|
||||
2021-05-13 03:42:36,976 prep[9.556855855855856 ms]
|
||||
2021-05-13 03:42:36,976 midp[59.921905405405404 ms]
|
||||
2021-05-13 03:42:36,976 postp[15.345945945945946 ms]
|
||||
2021-05-13 03:42:36,976 out[1.9921216216216215 ms]
|
||||
2021-05-13 03:42:36,976 idle[0.16254943864471572]
|
||||
2021-05-13 03:42:36,976 Op(det):
|
||||
2021-05-13 03:42:36,976 in[315.4468035714286 ms]
|
||||
2021-05-13 03:42:36,976 prep[69.5980625 ms]
|
||||
2021-05-13 03:42:36,976 midp[18.989535714285715 ms]
|
||||
2021-05-13 03:42:36,976 postp[18.857803571428573 ms]
|
||||
2021-05-13 03:42:36,977 out[3.1337544642857145 ms]
|
||||
2021-05-13 03:42:36,977 idle[0.7477961159203756]
|
||||
2021-05-13 03:42:36,977 DAGExecutor:
|
||||
2021-05-13 03:42:36,977 Query count[224]
|
||||
2021-05-13 03:42:36,977 QPS[22.4 q/s]
|
||||
2021-05-13 03:42:36,977 Succ[0.9910714285714286]
|
||||
2021-05-13 03:42:36,977 Error req[169, 170]
|
||||
2021-05-13 03:42:36,977 Latency:
|
||||
2021-05-13 03:42:36,977 ave[535.1678348214285 ms]
|
||||
2021-05-13 03:42:36,977 .50[172.651 ms]
|
||||
2021-05-13 03:42:36,977 .60[187.904 ms]
|
||||
2021-05-13 03:42:36,977 .70[245.675 ms]
|
||||
2021-05-13 03:42:36,977 .80[526.684 ms]
|
||||
2021-05-13 03:42:36,977 .90[854.596 ms]
|
||||
2021-05-13 03:42:36,977 .95[1722.728 ms]
|
||||
2021-05-13 03:42:36,977 .99[3990.292 ms]
|
||||
2021-05-13 03:42:36,978 Channel (server worker num[10]):
|
||||
2021-05-13 03:42:36,978 chl0(In: ['@DAGExecutor'], Out: ['det']) size[0/0]
|
||||
2021-05-13 03:42:36,979 chl1(In: ['det'], Out: ['rec']) size[6/0]
|
||||
2021-05-13 03:42:36,979 chl2(In: ['rec'], Out: ['@DAGExecutor']) size[0/0]
|
||||
```
|
||||
|
||||
<a name="C++"></a>
|
||||
## C++ Serving
|
||||
|
||||
Service deployment based on python obviously has the advantage of convenient secondary development. However, the real application often needs to pursue better performance. PaddleServing also provides a more performant C++ deployment version.
|
||||
|
||||
The C++ service deployment is the same as python in the environment setup and data preparation stages, the difference is when the service is started and the client sends requests.
|
||||
|
||||
|
||||
1. Compile Serving
|
||||
|
||||
To improve predictive performance, C++ services also provide multiple model concatenation services. Unlike Python Pipeline services, multiple model concatenation requires the pre - and post-model processing code to be written on the server side, so local recompilation is required to generate serving. Specific may refer to the official document: [how to compile Serving](https://github.com/PaddlePaddle/Serving/blob/v0.8.3/doc/Compile_EN.md)
|
||||
|
||||
2. Run the following command to start the service.
|
||||
```
|
||||
# Start the service and save the running log in log.txt
|
||||
python3 -m paddle_serving_server.serve --model ppocr_det_v3_serving ppocr_rec_v3_serving --op GeneralDetectionOp GeneralInferOp --port 8181 &>log.txt &
|
||||
```
|
||||
After the service is successfully started, a log similar to the following will be printed in log.txt
|
||||

|
||||
|
||||
3. Send service request
|
||||
|
||||
Due to the need for pre and post-processing in the C++Server part, in order to speed up the input to the C++Server is only the base64 encoded string of the picture, it needs to be manually modified
|
||||
Change the feed_type field and shape field in ppocr_det_v3_client/serving_client_conf.prototxt to the following:
|
||||
|
||||
```
|
||||
feed_var {
|
||||
name: "x"
|
||||
alias_name: "x"
|
||||
is_lod_tensor: false
|
||||
feed_type: 20
|
||||
shape: 1
|
||||
}
|
||||
```
|
||||
|
||||
start the client:
|
||||
|
||||
```
|
||||
python3 ocr_cpp_client.py ppocr_det_v3_client ppocr_rec_v3_client
|
||||
```
|
||||
After successfully running, the predicted result of the model will be printed in the cmd window. An example of the result is:
|
||||

|
||||
|
||||
## WINDOWS Users
|
||||
|
||||
Windows does not support Pipeline Serving, if we want to lauch paddle serving on Windows, we should use Web Service, for more infomation please refer to [Paddle Serving for Windows Users](https://github.com/PaddlePaddle/Serving/blob/develop/doc/Windows_Tutorial_EN.md)
|
||||
|
||||
|
||||
**WINDOWS user can only use version 0.5.0 CPU Mode**
|
||||
|
||||
**Prepare Stage:**
|
||||
|
||||
```
|
||||
pip3 install paddle-serving-server==0.5.0
|
||||
pip3 install paddle-serving-app==0.3.1
|
||||
```
|
||||
|
||||
1. Start Server
|
||||
|
||||
```
|
||||
cd win
|
||||
python3 ocr_web_server.py gpu(for gpu user)
|
||||
or
|
||||
python3 ocr_web_server.py cpu(for cpu user)
|
||||
```
|
||||
|
||||
2. Client Send Requests
|
||||
|
||||
```
|
||||
python3 ocr_web_client.py
|
||||
```
|
||||
|
||||
<a name="faq"></a>
|
||||
## FAQ
|
||||
**Q1**: No result return after sending the request.
|
||||
|
||||
**A1**: Do not set the proxy when starting the service and sending the request. You can close the proxy before starting the service and before sending the request. The command to close the proxy is:
|
||||
```
|
||||
unset https_proxy
|
||||
unset http_proxy
|
||||
```
|
||||
@@ -0,0 +1,304 @@
|
||||
# PPOCR 服务化部署
|
||||
|
||||
([English](./README.md)|简体中文)
|
||||
|
||||
PaddleOCR提供2种服务部署方式:
|
||||
- 基于PaddleHub Serving的部署:代码路径为"`./deploy/hubserving`",使用方法参考[文档](../../deploy/hubserving/readme.md);
|
||||
- 基于PaddleServing的部署:代码路径为"`./deploy/pdserving`",按照本教程使用。
|
||||
|
||||
|
||||
# 基于PaddleServing的服务部署
|
||||
|
||||
本文档将介绍如何使用[PaddleServing](https://github.com/PaddlePaddle/Serving/blob/develop/README_CN.md) 工具部署PP-OCR动态图模型的pipeline在线服务。
|
||||
|
||||
相比较于hubserving部署,PaddleServing具备以下优点:
|
||||
- 支持客户端和服务端之间高并发和高效通信
|
||||
- 支持 工业级的服务能力 例如模型管理,在线加载,在线A/B测试等
|
||||
- 支持 多种编程语言 开发客户端,例如C++, Python和Java
|
||||
|
||||
PaddleServing 支持多种语言部署,本例中提供了python pipeline 和 C++ 两种部署方式,两者的对比如下:
|
||||
|
||||
| 语言 | 速度 | 二次开发 | 是否需要编译 |
|
||||
|-----|-----|---------|------------|
|
||||
| C++ | 很快 | 略有难度 | 单模型预测无需编译,多模型串联需要编译 |
|
||||
| python | 一般 | 容易 | 单模型/多模型 均无需编译|
|
||||
|
||||
|
||||
更多有关PaddleServing服务化部署框架介绍和使用教程参考[文档](https://github.com/PaddlePaddle/Serving/blob/develop/README_CN.md)。
|
||||
|
||||
AIStudio演示案例可参考 [基于PaddleServing的OCR服务化部署实战](https://aistudio.baidu.com/aistudio/projectdetail/3630726)。
|
||||
|
||||
## 目录
|
||||
- [环境准备](#环境准备)
|
||||
- [模型转换](#模型转换)
|
||||
- [Paddle Serving pipeline部署](#部署)
|
||||
- [Paddle Serving C++部署](#C++)
|
||||
- [Windows用户](#Windows用户)
|
||||
- [FAQ](#FAQ)
|
||||
|
||||
<a name="环境准备"></a>
|
||||
## 环境准备
|
||||
|
||||
需要准备PaddleOCR的运行环境和Paddle Serving的运行环境。
|
||||
|
||||
- 准备PaddleOCR的运行环境[链接](../../doc/doc_ch/installation.md)
|
||||
|
||||
```
|
||||
git clone https://github.com/PaddlePaddle/PaddleOCR
|
||||
|
||||
# 进入到工作目录
|
||||
cd PaddleOCR/deploy/pdserving/
|
||||
```
|
||||
|
||||
- 准备PaddleServing的运行环境,步骤如下
|
||||
|
||||
```bash
|
||||
# 安装serving,用于启动服务
|
||||
wget https://paddle-serving.bj.bcebos.com/test-dev/whl/paddle_serving_server_gpu-0.8.3.post102-py3-none-any.whl
|
||||
pip3 install paddle_serving_server_gpu-0.8.3.post102-py3-none-any.whl
|
||||
# 如果是cuda10.1环境,可以使用下面的命令安装paddle-serving-server
|
||||
# wget https://paddle-serving.bj.bcebos.com/test-dev/whl/paddle_serving_server_gpu-0.8.3.post101-py3-none-any.whl
|
||||
# pip3 install paddle_serving_server_gpu-0.8.3.post101-py3-none-any.whl
|
||||
|
||||
# 安装client,用于向服务发送请求
|
||||
wget https://paddle-serving.bj.bcebos.com/test-dev/whl/paddle_serving_client-0.8.3-cp37-none-any.whl
|
||||
pip3 install paddle_serving_client-0.8.3-cp37-none-any.whl
|
||||
|
||||
# 安装serving-app
|
||||
wget https://paddle-serving.bj.bcebos.com/test-dev/whl/paddle_serving_app-0.8.3-py3-none-any.whl
|
||||
pip3 install paddle_serving_app-0.8.3-py3-none-any.whl
|
||||
```
|
||||
|
||||
**Note:** 如果要安装最新版本的PaddleServing参考[链接](https://github.com/PaddlePaddle/Serving/blob/v0.8.3/doc/Latest_Packages_CN.md)。
|
||||
|
||||
<a name="模型转换"></a>
|
||||
## 模型转换
|
||||
|
||||
使用PaddleServing做服务化部署时,需要将保存的inference模型转换为serving易于部署的模型。
|
||||
|
||||
首先,下载PP-OCR的[inference模型](https://github.com/PaddlePaddle/PaddleOCR#pp-ocr-series-model-listupdate-on-september-8th)
|
||||
|
||||
```bash
|
||||
# 下载并解压 OCR 文本检测模型
|
||||
wget https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_det_infer.tar -O ch_PP-OCRv3_det_infer.tar && tar -xf ch_PP-OCRv3_det_infer.tar
|
||||
# 下载并解压 OCR 文本识别模型
|
||||
wget https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_rec_infer.tar -O ch_PP-OCRv3_rec_infer.tar && tar -xf ch_PP-OCRv3_rec_infer.tar
|
||||
```
|
||||
|
||||
接下来,用安装的paddle_serving_client把下载的inference模型转换成易于server部署的模型格式。
|
||||
|
||||
```bash
|
||||
# 转换检测模型
|
||||
python3 -m paddle_serving_client.convert --dirname ./ch_PP-OCRv3_det_infer/ \
|
||||
--model_filename inference.pdmodel \
|
||||
--params_filename inference.pdiparams \
|
||||
--serving_server ./ppocr_det_v3_serving/ \
|
||||
--serving_client ./ppocr_det_v3_client/
|
||||
|
||||
# 转换识别模型
|
||||
python3 -m paddle_serving_client.convert --dirname ./ch_PP-OCRv3_rec_infer/ \
|
||||
--model_filename inference.pdmodel \
|
||||
--params_filename inference.pdiparams \
|
||||
--serving_server ./ppocr_rec_v3_serving/ \
|
||||
--serving_client ./ppocr_rec_v3_client/
|
||||
```
|
||||
|
||||
检测模型转换完成后,会在当前文件夹多出`ppocr_det_v3_serving` 和`ppocr_det_v3_client`的文件夹,具备如下格式:
|
||||
```
|
||||
|- ppocr_det_v3_serving/
|
||||
|- __model__
|
||||
|- __params__
|
||||
|- serving_server_conf.prototxt
|
||||
|- serving_server_conf.stream.prototxt
|
||||
|
||||
|- ppocr_det_v3_client
|
||||
|- serving_client_conf.prototxt
|
||||
|- serving_client_conf.stream.prototxt
|
||||
|
||||
```
|
||||
识别模型同理。
|
||||
|
||||
<a name="部署"></a>
|
||||
## Paddle Serving pipeline部署
|
||||
|
||||
1. 确认工作目录下文件结构:
|
||||
|
||||
pdserver目录包含启动pipeline服务和发送预测请求的代码,包括:
|
||||
```
|
||||
__init__.py
|
||||
config.yml # 启动服务的配置文件
|
||||
ocr_reader.py # OCR模型预处理和后处理的代码实现
|
||||
pipeline_http_client.py # 发送pipeline预测请求的脚本
|
||||
web_service.py # 启动pipeline服务端的脚本
|
||||
```
|
||||
|
||||
2. 启动服务可运行如下命令:
|
||||
```
|
||||
# 启动服务,运行日志保存在log.txt
|
||||
python3 web_service.py --config=config.yml &>log.txt &
|
||||
```
|
||||
成功启动服务后,log.txt中会打印类似如下日志
|
||||

|
||||
|
||||
3. 发送服务请求:
|
||||
```
|
||||
python3 pipeline_http_client.py
|
||||
```
|
||||
成功运行后,模型预测的结果会打印在cmd窗口中,结果示例为:
|
||||

|
||||
|
||||
调整 config.yml 中的并发个数获得最大的QPS, 一般检测和识别的并发数为2:1
|
||||
```
|
||||
det:
|
||||
#并发数,is_thread_op=True时,为线程并发;否则为进程并发
|
||||
concurrency: 8
|
||||
...
|
||||
rec:
|
||||
#并发数,is_thread_op=True时,为线程并发;否则为进程并发
|
||||
concurrency: 4
|
||||
...
|
||||
```
|
||||
有需要的话可以同时发送多个服务请求
|
||||
|
||||
预测性能数据会被自动写入 `PipelineServingLogs/pipeline.tracer` 文件中。
|
||||
|
||||
在200张真实图片上测试,把检测长边限制为960。T4 GPU 上 QPS 均值可达到23左右:
|
||||
|
||||
```
|
||||
2021-05-13 03:42:36,895 ==================== TRACER ======================
|
||||
2021-05-13 03:42:36,975 Op(rec):
|
||||
2021-05-13 03:42:36,976 in[14.472382882882883 ms]
|
||||
2021-05-13 03:42:36,976 prep[9.556855855855856 ms]
|
||||
2021-05-13 03:42:36,976 midp[59.921905405405404 ms]
|
||||
2021-05-13 03:42:36,976 postp[15.345945945945946 ms]
|
||||
2021-05-13 03:42:36,976 out[1.9921216216216215 ms]
|
||||
2021-05-13 03:42:36,976 idle[0.16254943864471572]
|
||||
2021-05-13 03:42:36,976 Op(det):
|
||||
2021-05-13 03:42:36,976 in[315.4468035714286 ms]
|
||||
2021-05-13 03:42:36,976 prep[69.5980625 ms]
|
||||
2021-05-13 03:42:36,976 midp[18.989535714285715 ms]
|
||||
2021-05-13 03:42:36,976 postp[18.857803571428573 ms]
|
||||
2021-05-13 03:42:36,977 out[3.1337544642857145 ms]
|
||||
2021-05-13 03:42:36,977 idle[0.7477961159203756]
|
||||
2021-05-13 03:42:36,977 DAGExecutor:
|
||||
2021-05-13 03:42:36,977 Query count[224]
|
||||
2021-05-13 03:42:36,977 QPS[22.4 q/s]
|
||||
2021-05-13 03:42:36,977 Succ[0.9910714285714286]
|
||||
2021-05-13 03:42:36,977 Error req[169, 170]
|
||||
2021-05-13 03:42:36,977 Latency:
|
||||
2021-05-13 03:42:36,977 ave[535.1678348214285 ms]
|
||||
2021-05-13 03:42:36,977 .50[172.651 ms]
|
||||
2021-05-13 03:42:36,977 .60[187.904 ms]
|
||||
2021-05-13 03:42:36,977 .70[245.675 ms]
|
||||
2021-05-13 03:42:36,977 .80[526.684 ms]
|
||||
2021-05-13 03:42:36,977 .90[854.596 ms]
|
||||
2021-05-13 03:42:36,977 .95[1722.728 ms]
|
||||
2021-05-13 03:42:36,977 .99[3990.292 ms]
|
||||
2021-05-13 03:42:36,978 Channel (server worker num[10]):
|
||||
2021-05-13 03:42:36,978 chl0(In: ['@DAGExecutor'], Out: ['det']) size[0/0]
|
||||
2021-05-13 03:42:36,979 chl1(In: ['det'], Out: ['rec']) size[6/0]
|
||||
2021-05-13 03:42:36,979 chl2(In: ['rec'], Out: ['@DAGExecutor']) size[0/0]
|
||||
```
|
||||
|
||||
<a name="C++"></a>
|
||||
## Paddle Serving C++ 部署
|
||||
|
||||
基于python的服务部署,显然具有二次开发便捷的优势,然而真正落地应用,往往需要追求更优的性能。PaddleServing 也提供了性能更优的C++部署版本。
|
||||
|
||||
C++ 服务部署在环境搭建和数据准备阶段与 python 相同,区别在于启动服务和客户端发送请求时不同。
|
||||
|
||||
1. 准备 Serving 环境
|
||||
|
||||
为了提高预测性能,C++ 服务同样提供了多模型串联服务。与python pipeline服务不同,多模型串联的过程中需要将模型前后处理代码写在服务端,因此需要在本地重新编译生成serving。
|
||||
|
||||
首先需要下载Serving代码库, 把OCR文本检测预处理相关代码替换到Serving库中
|
||||
|
||||
```
|
||||
git clone https://github.com/PaddlePaddle/Serving
|
||||
|
||||
cp -rf general_detection_op.cpp Serving/core/general-server/op
|
||||
|
||||
```
|
||||
|
||||
具体可参考官方文档:[如何编译Serving](https://github.com/PaddlePaddle/Serving/blob/v0.8.3/doc/Compile_CN.md),注意需要开启 WITH_OPENCV 选项。
|
||||
|
||||
完成编译后,注意要安装编译出的三个whl包,并设置SERVING_BIN环境变量。
|
||||
|
||||
2. 启动服务可运行如下命令:
|
||||
|
||||
一个服务启动两个模型串联,只需要在--model后依次按顺序传入模型文件夹的相对路径,且需要在--op后依次传入自定义C++OP类名称:
|
||||
|
||||
```
|
||||
# 启动服务,运行日志保存在log.txt
|
||||
python3 -m paddle_serving_server.serve --model ppocr_det_v3_serving ppocr_rec_v3_serving --op GeneralDetectionOp GeneralInferOp --port 8181 &>log.txt &
|
||||
```
|
||||
|
||||
成功启动服务后,log.txt中会打印类似如下日志
|
||||

|
||||
|
||||
3. 发送服务请求:
|
||||
|
||||
由于需要在C++Server部分进行前后处理,为了加速传入C++Server的仅仅是图片的base64编码的字符串,故需要手动修改
|
||||
ppocr_det_v3_client/serving_client_conf.prototxt 中 feed_type 字段 和 shape 字段,修改成如下内容:
|
||||
```
|
||||
feed_var {
|
||||
name: "x"
|
||||
alias_name: "x"
|
||||
is_lod_tensor: false
|
||||
feed_type: 20
|
||||
shape: 1
|
||||
}
|
||||
```
|
||||
启动客户端
|
||||
```
|
||||
python3 ocr_cpp_client.py ppocr_det_v3_client ppocr_rec_v3_client
|
||||
```
|
||||
|
||||
成功运行后,模型预测的结果会打印在cmd窗口中,结果示例为:
|
||||

|
||||
|
||||
在浏览器中输入服务器 ip:端口号,可以看到当前服务的实时QPS。(端口号范围需要是8000-9000)
|
||||
|
||||
在200张真实图片上测试,把检测长边限制为960。T4 GPU 上 QPS 峰值可达到51左右,约为pipeline的 2.12 倍。
|
||||
|
||||

|
||||
|
||||
|
||||
<a name="Windows用户"></a>
|
||||
## Windows用户
|
||||
|
||||
Windows用户不能使用上述的启动方式,需要使用Web Service,详情参见[Windows平台使用Paddle Serving指导](https://github.com/PaddlePaddle/Serving/blob/develop/doc/Windows_Tutorial_CN.md)
|
||||
|
||||
**WINDOWS只能使用0.5.0版本的CPU模式**
|
||||
|
||||
准备阶段:
|
||||
```
|
||||
pip3 install paddle-serving-server==0.5.0
|
||||
pip3 install paddle-serving-app==0.3.1
|
||||
```
|
||||
|
||||
1. 启动服务端程序
|
||||
|
||||
```
|
||||
cd win
|
||||
python3 ocr_web_server.py gpu(使用gpu方式)
|
||||
或者
|
||||
python3 ocr_web_server.py cpu(使用cpu方式)
|
||||
```
|
||||
|
||||
2. 发送服务请求
|
||||
|
||||
```
|
||||
python3 ocr_web_client.py
|
||||
```
|
||||
|
||||
|
||||
<a name="FAQ"></a>
|
||||
## FAQ
|
||||
**Q1**: 发送请求后没有结果返回或者提示输出解码报错
|
||||
|
||||
**A1**: 启动服务和发送请求时不要设置代理,可以在启动服务前和发送请求前关闭代理,关闭代理的命令是:
|
||||
```
|
||||
unset https_proxy
|
||||
unset http_proxy
|
||||
```
|
||||
@@ -0,0 +1,13 @@
|
||||
# Copyright (c) 2021 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.
|
||||
@@ -0,0 +1,71 @@
|
||||
#rpc端口, rpc_port和http_port不允许同时为空。当rpc_port为空且http_port不为空时,会自动将rpc_port设置为http_port+1
|
||||
rpc_port: 18091
|
||||
|
||||
#http端口, rpc_port和http_port不允许同时为空。当rpc_port可用且http_port为空时,不自动生成http_port
|
||||
http_port: 9998
|
||||
|
||||
#worker_num, 最大并发数。当build_dag_each_worker=True时, 框架会创建worker_num个进程,每个进程内构建grpcSever和DAG
|
||||
##当build_dag_each_worker=False时,框架会设置主线程grpc线程池的max_workers=worker_num
|
||||
worker_num: 10
|
||||
|
||||
#build_dag_each_worker, False,框架在进程内创建一条DAG;True,框架会每个进程内创建多个独立的DAG
|
||||
build_dag_each_worker: False
|
||||
|
||||
dag:
|
||||
#op资源类型, True, 为线程模型;False,为进程模型
|
||||
is_thread_op: False
|
||||
|
||||
#重试次数
|
||||
retry: 10
|
||||
|
||||
#使用性能分析, True,生成Timeline性能数据,对性能有一定影响;False为不使用
|
||||
use_profile: True
|
||||
|
||||
tracer:
|
||||
interval_s: 10
|
||||
op:
|
||||
det:
|
||||
#并发数,is_thread_op=True时,为线程并发;否则为进程并发
|
||||
concurrency: 8
|
||||
|
||||
#当op配置没有server_endpoints时,从local_service_conf读取本地服务配置
|
||||
local_service_conf:
|
||||
#client类型,包括brpc, grpc和local_predictor.local_predictor不启动Serving服务,进程内预测
|
||||
client_type: local_predictor
|
||||
|
||||
#det模型路径
|
||||
model_config: ./ppocr_det_v3_serving
|
||||
|
||||
#Fetch结果列表,以client_config中fetch_var的alias_name为准,不设置默认取全部输出变量
|
||||
#fetch_list: ["sigmoid_0.tmp_0"]
|
||||
|
||||
#计算硬件ID,当devices为""或不写时为CPU预测;当devices为"0", "0,1,2"时为GPU预测,表示使用的GPU卡
|
||||
devices: "0"
|
||||
|
||||
ir_optim: True
|
||||
rec:
|
||||
#并发数,is_thread_op=True时,为线程并发;否则为进程并发
|
||||
concurrency: 4
|
||||
|
||||
#超时时间, 单位ms
|
||||
timeout: -1
|
||||
|
||||
#Serving交互重试次数,默认不重试
|
||||
retry: 1
|
||||
|
||||
#当op配置没有server_endpoints时,从local_service_conf读取本地服务配置
|
||||
local_service_conf:
|
||||
|
||||
#client类型,包括brpc, grpc和local_predictor。local_predictor不启动Serving服务,进程内预测
|
||||
client_type: local_predictor
|
||||
|
||||
#rec模型路径
|
||||
model_config: ./ppocr_rec_v3_serving
|
||||
|
||||
#Fetch结果列表,以client_config中fetch_var的alias_name为准, 不设置默认取全部输出变量
|
||||
#fetch_list:
|
||||
|
||||
#计算硬件ID,当devices为""或不写时为CPU预测;当devices为"0", "0,1,2"时为GPU预测,表示使用的GPU卡
|
||||
devices: "0"
|
||||
|
||||
ir_optim: True
|
||||
@@ -0,0 +1,367 @@
|
||||
// Copyright (c) 2020 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.
|
||||
|
||||
#include "core/general-server/op/general_detection_op.h"
|
||||
#include "core/predictor/framework/infer.h"
|
||||
#include "core/predictor/framework/memory.h"
|
||||
#include "core/predictor/framework/resource.h"
|
||||
#include "core/util/include/timer.h"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
|
||||
/*
|
||||
#include "opencv2/imgcodecs/legacy/constants_c.h"
|
||||
#include "opencv2/imgproc/types_c.h"
|
||||
*/
|
||||
|
||||
namespace baidu {
|
||||
namespace paddle_serving {
|
||||
namespace serving {
|
||||
|
||||
using baidu::paddle_serving::Timer;
|
||||
using baidu::paddle_serving::predictor::MempoolWrapper;
|
||||
using baidu::paddle_serving::predictor::general_model::Tensor;
|
||||
using baidu::paddle_serving::predictor::general_model::Response;
|
||||
using baidu::paddle_serving::predictor::general_model::Request;
|
||||
using baidu::paddle_serving::predictor::InferManager;
|
||||
using baidu::paddle_serving::predictor::PaddleGeneralModelConfig;
|
||||
|
||||
int GeneralDetectionOp::inference() {
|
||||
VLOG(2) << "Going to run inference";
|
||||
const std::vector<std::string> pre_node_names = pre_names();
|
||||
if (pre_node_names.size() != 1) {
|
||||
LOG(ERROR) << "This op(" << op_name()
|
||||
<< ") can only have one predecessor op, but received "
|
||||
<< pre_node_names.size();
|
||||
return -1;
|
||||
}
|
||||
const std::string pre_name = pre_node_names[0];
|
||||
|
||||
const GeneralBlob *input_blob = get_depend_argument<GeneralBlob>(pre_name);
|
||||
if (!input_blob) {
|
||||
LOG(ERROR) << "input_blob is nullptr,error";
|
||||
return -1;
|
||||
}
|
||||
uint64_t log_id = input_blob->GetLogId();
|
||||
VLOG(2) << "(logid=" << log_id << ") Get precedent op name: " << pre_name;
|
||||
|
||||
GeneralBlob *output_blob = mutable_data<GeneralBlob>();
|
||||
if (!output_blob) {
|
||||
LOG(ERROR) << "output_blob is nullptr,error";
|
||||
return -1;
|
||||
}
|
||||
output_blob->SetLogId(log_id);
|
||||
|
||||
if (!input_blob) {
|
||||
LOG(ERROR) << "(logid=" << log_id
|
||||
<< ") Failed mutable depended argument, op:" << pre_name;
|
||||
return -1;
|
||||
}
|
||||
|
||||
const TensorVector *in = &input_blob->tensor_vector;
|
||||
TensorVector *out = &output_blob->tensor_vector;
|
||||
|
||||
int batch_size = input_blob->_batch_size;
|
||||
VLOG(2) << "(logid=" << log_id << ") input batch size: " << batch_size;
|
||||
|
||||
output_blob->_batch_size = batch_size;
|
||||
|
||||
std::vector<int> input_shape;
|
||||
int in_num = 0;
|
||||
void *databuf_data = NULL;
|
||||
char *databuf_char = NULL;
|
||||
size_t databuf_size = 0;
|
||||
// now only support single string
|
||||
char *total_input_ptr = static_cast<char *>(in->at(0).data.data());
|
||||
std::string base64str = total_input_ptr;
|
||||
|
||||
float ratio_h{};
|
||||
float ratio_w{};
|
||||
|
||||
cv::Mat img = Base2Mat(base64str);
|
||||
cv::Mat srcimg;
|
||||
cv::Mat resize_img;
|
||||
|
||||
cv::Mat resize_img_rec;
|
||||
cv::Mat crop_img;
|
||||
img.copyTo(srcimg);
|
||||
|
||||
this->resize_op_.Run(img, resize_img, this->max_side_len_, ratio_h, ratio_w,
|
||||
this->use_tensorrt_);
|
||||
|
||||
this->normalize_op_.Run(&resize_img, this->mean_det, this->scale_det,
|
||||
this->is_scale_);
|
||||
|
||||
std::vector<float> input(1 * 3 * resize_img.rows * resize_img.cols, 0.0f);
|
||||
this->permute_op_.Run(&resize_img, input.data());
|
||||
|
||||
TensorVector *real_in = new TensorVector();
|
||||
if (!real_in) {
|
||||
LOG(ERROR) << "real_in is nullptr,error";
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < in->size(); ++i) {
|
||||
input_shape = {1, 3, resize_img.rows, resize_img.cols};
|
||||
in_num = std::accumulate(input_shape.begin(), input_shape.end(), 1,
|
||||
std::multiplies<int>());
|
||||
databuf_size = in_num * sizeof(float);
|
||||
databuf_data = MempoolWrapper::instance().malloc(databuf_size);
|
||||
if (!databuf_data) {
|
||||
LOG(ERROR) << "Malloc failed, size: " << databuf_size;
|
||||
return -1;
|
||||
}
|
||||
memcpy(databuf_data, input.data(), databuf_size);
|
||||
databuf_char = reinterpret_cast<char *>(databuf_data);
|
||||
paddle::PaddleBuf paddleBuf(databuf_char, databuf_size);
|
||||
paddle::PaddleTensor tensor_in;
|
||||
tensor_in.name = in->at(i).name;
|
||||
tensor_in.dtype = paddle::PaddleDType::FLOAT32;
|
||||
tensor_in.shape = {1, 3, resize_img.rows, resize_img.cols};
|
||||
tensor_in.lod = in->at(i).lod;
|
||||
tensor_in.data = paddleBuf;
|
||||
real_in->push_back(tensor_in);
|
||||
}
|
||||
|
||||
Timer timeline;
|
||||
int64_t start = timeline.TimeStampUS();
|
||||
timeline.Start();
|
||||
|
||||
if (InferManager::instance().infer(engine_name().c_str(), real_in, out,
|
||||
batch_size)) {
|
||||
LOG(ERROR) << "(logid=" << log_id
|
||||
<< ") Failed do infer in fluid model: " << engine_name().c_str();
|
||||
return -1;
|
||||
}
|
||||
delete real_in;
|
||||
|
||||
std::vector<int> output_shape;
|
||||
int out_num = 0;
|
||||
void *databuf_data_out = NULL;
|
||||
char *databuf_char_out = NULL;
|
||||
size_t databuf_size_out = 0;
|
||||
// this is special add for PaddleOCR postprecess
|
||||
int infer_outnum = out->size();
|
||||
for (int k = 0; k < infer_outnum; ++k) {
|
||||
int n2 = out->at(k).shape[2];
|
||||
int n3 = out->at(k).shape[3];
|
||||
int n = n2 * n3;
|
||||
|
||||
float *out_data = static_cast<float *>(out->at(k).data.data());
|
||||
std::vector<float> pred(n, 0.0);
|
||||
std::vector<unsigned char> cbuf(n, ' ');
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
pred[i] = float(out_data[i]);
|
||||
cbuf[i] = (unsigned char)((out_data[i]) * 255);
|
||||
}
|
||||
|
||||
cv::Mat cbuf_map(n2, n3, CV_8UC1, (unsigned char *)cbuf.data());
|
||||
cv::Mat pred_map(n2, n3, CV_32F, (float *)pred.data());
|
||||
|
||||
const double threshold = this->det_db_thresh_ * 255;
|
||||
const double maxvalue = 255;
|
||||
cv::Mat bit_map;
|
||||
cv::threshold(cbuf_map, bit_map, threshold, maxvalue, cv::THRESH_BINARY);
|
||||
cv::Mat dilation_map;
|
||||
cv::Mat dila_ele =
|
||||
cv::getStructuringElement(cv::MORPH_RECT, cv::Size(2, 2));
|
||||
cv::dilate(bit_map, dilation_map, dila_ele);
|
||||
boxes = post_processor_.BoxesFromBitmap(pred_map, dilation_map,
|
||||
this->det_db_box_thresh_,
|
||||
this->det_db_unclip_ratio_);
|
||||
|
||||
boxes = post_processor_.FilterTagDetRes(boxes, ratio_h, ratio_w, srcimg);
|
||||
|
||||
float max_wh_ratio = 0.0f;
|
||||
std::vector<cv::Mat> crop_imgs;
|
||||
std::vector<cv::Mat> resize_imgs;
|
||||
int max_resize_w = 0;
|
||||
int max_resize_h = 0;
|
||||
int box_num = boxes.size();
|
||||
std::vector<std::vector<float>> output_rec;
|
||||
for (int i = 0; i < box_num; ++i) {
|
||||
cv::Mat line_img = GetRotateCropImage(img, boxes[i]);
|
||||
float wh_ratio = float(line_img.cols) / float(line_img.rows);
|
||||
max_wh_ratio = max_wh_ratio > wh_ratio ? max_wh_ratio : wh_ratio;
|
||||
crop_imgs.push_back(line_img);
|
||||
}
|
||||
|
||||
for (int i = 0; i < box_num; ++i) {
|
||||
cv::Mat resize_img;
|
||||
crop_img = crop_imgs[i];
|
||||
this->resize_op_rec.Run(crop_img, resize_img, max_wh_ratio,
|
||||
this->use_tensorrt_);
|
||||
|
||||
this->normalize_op_.Run(&resize_img, this->mean_rec, this->scale_rec,
|
||||
this->is_scale_);
|
||||
|
||||
max_resize_w = std::max(max_resize_w, resize_img.cols);
|
||||
max_resize_h = std::max(max_resize_h, resize_img.rows);
|
||||
resize_imgs.push_back(resize_img);
|
||||
}
|
||||
int buf_size = 3 * max_resize_h * max_resize_w;
|
||||
output_rec = std::vector<std::vector<float>>(
|
||||
box_num, std::vector<float>(buf_size, 0.0f));
|
||||
for (int i = 0; i < box_num; ++i) {
|
||||
resize_img_rec = resize_imgs[i];
|
||||
|
||||
this->permute_op_.Run(&resize_img_rec, output_rec[i].data());
|
||||
}
|
||||
|
||||
// Inference.
|
||||
output_shape = {box_num, 3, max_resize_h, max_resize_w};
|
||||
out_num = std::accumulate(output_shape.begin(), output_shape.end(), 1,
|
||||
std::multiplies<int>());
|
||||
databuf_size_out = out_num * sizeof(float);
|
||||
databuf_data_out = MempoolWrapper::instance().malloc(databuf_size_out);
|
||||
if (!databuf_data_out) {
|
||||
LOG(ERROR) << "Malloc failed, size: " << databuf_size_out;
|
||||
return -1;
|
||||
}
|
||||
int offset = buf_size * sizeof(float);
|
||||
for (int i = 0; i < box_num; ++i) {
|
||||
memcpy(databuf_data_out + i * offset, output_rec[i].data(), offset);
|
||||
}
|
||||
databuf_char_out = reinterpret_cast<char *>(databuf_data_out);
|
||||
paddle::PaddleBuf paddleBuf(databuf_char_out, databuf_size_out);
|
||||
paddle::PaddleTensor tensor_out;
|
||||
tensor_out.name = "x";
|
||||
tensor_out.dtype = paddle::PaddleDType::FLOAT32;
|
||||
tensor_out.shape = output_shape;
|
||||
tensor_out.data = paddleBuf;
|
||||
out->push_back(tensor_out);
|
||||
}
|
||||
out->erase(out->begin(), out->begin() + infer_outnum);
|
||||
|
||||
int64_t end = timeline.TimeStampUS();
|
||||
CopyBlobInfo(input_blob, output_blob);
|
||||
AddBlobInfo(output_blob, start);
|
||||
AddBlobInfo(output_blob, end);
|
||||
return 0;
|
||||
}
|
||||
|
||||
cv::Mat GeneralDetectionOp::Base2Mat(std::string &base64_data) {
|
||||
cv::Mat img;
|
||||
std::string s_mat;
|
||||
s_mat = base64Decode(base64_data.data(), base64_data.size());
|
||||
std::vector<char> base64_img(s_mat.begin(), s_mat.end());
|
||||
img = cv::imdecode(base64_img, cv::IMREAD_COLOR); // CV_LOAD_IMAGE_COLOR
|
||||
return img;
|
||||
}
|
||||
|
||||
std::string GeneralDetectionOp::base64Decode(const char *Data, int DataByte) {
|
||||
const char DecodeTable[] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
62, // '+'
|
||||
0, 0, 0,
|
||||
63, // '/'
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // '0'-'9'
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||||
10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // 'A'-'Z'
|
||||
0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
|
||||
37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // 'a'-'z'
|
||||
};
|
||||
|
||||
std::string strDecode;
|
||||
int nValue;
|
||||
int i = 0;
|
||||
while (i < DataByte) {
|
||||
if (*Data != '\r' && *Data != '\n') {
|
||||
nValue = DecodeTable[*Data++] << 18;
|
||||
nValue += DecodeTable[*Data++] << 12;
|
||||
strDecode += (nValue & 0x00FF0000) >> 16;
|
||||
if (*Data != '=') {
|
||||
nValue += DecodeTable[*Data++] << 6;
|
||||
strDecode += (nValue & 0x0000FF00) >> 8;
|
||||
if (*Data != '=') {
|
||||
nValue += DecodeTable[*Data++];
|
||||
strDecode += nValue & 0x000000FF;
|
||||
}
|
||||
}
|
||||
i += 4;
|
||||
} else // 回车换行,跳过
|
||||
{
|
||||
Data++;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return strDecode;
|
||||
}
|
||||
|
||||
cv::Mat
|
||||
GeneralDetectionOp::GetRotateCropImage(const cv::Mat &srcimage,
|
||||
std::vector<std::vector<int>> box) {
|
||||
cv::Mat image;
|
||||
srcimage.copyTo(image);
|
||||
std::vector<std::vector<int>> points = box;
|
||||
|
||||
int x_collect[4] = {box[0][0], box[1][0], box[2][0], box[3][0]};
|
||||
int y_collect[4] = {box[0][1], box[1][1], box[2][1], box[3][1]};
|
||||
int left = int(*std::min_element(x_collect, x_collect + 4));
|
||||
int right = int(*std::max_element(x_collect, x_collect + 4));
|
||||
int top = int(*std::min_element(y_collect, y_collect + 4));
|
||||
int bottom = int(*std::max_element(y_collect, y_collect + 4));
|
||||
|
||||
cv::Mat img_crop;
|
||||
image(cv::Rect(left, top, right - left, bottom - top)).copyTo(img_crop);
|
||||
|
||||
for (int i = 0; i < points.size(); i++) {
|
||||
points[i][0] -= left;
|
||||
points[i][1] -= top;
|
||||
}
|
||||
|
||||
int img_crop_width = int(sqrt(pow(points[0][0] - points[1][0], 2) +
|
||||
pow(points[0][1] - points[1][1], 2)));
|
||||
int img_crop_height = int(sqrt(pow(points[0][0] - points[3][0], 2) +
|
||||
pow(points[0][1] - points[3][1], 2)));
|
||||
|
||||
cv::Point2f pts_std[4];
|
||||
pts_std[0] = cv::Point2f(0., 0.);
|
||||
pts_std[1] = cv::Point2f(img_crop_width, 0.);
|
||||
pts_std[2] = cv::Point2f(img_crop_width, img_crop_height);
|
||||
pts_std[3] = cv::Point2f(0.f, img_crop_height);
|
||||
|
||||
cv::Point2f pointsf[4];
|
||||
pointsf[0] = cv::Point2f(points[0][0], points[0][1]);
|
||||
pointsf[1] = cv::Point2f(points[1][0], points[1][1]);
|
||||
pointsf[2] = cv::Point2f(points[2][0], points[2][1]);
|
||||
pointsf[3] = cv::Point2f(points[3][0], points[3][1]);
|
||||
|
||||
cv::Mat M = cv::getPerspectiveTransform(pointsf, pts_std);
|
||||
|
||||
cv::Mat dst_img;
|
||||
cv::warpPerspective(img_crop, dst_img, M,
|
||||
cv::Size(img_crop_width, img_crop_height),
|
||||
cv::BORDER_REPLICATE);
|
||||
|
||||
if (float(dst_img.rows) >= float(dst_img.cols) * 1.5) {
|
||||
cv::Mat srcCopy = cv::Mat(dst_img.rows, dst_img.cols, dst_img.depth());
|
||||
cv::transpose(dst_img, srcCopy);
|
||||
cv::flip(srcCopy, srcCopy, 0);
|
||||
return srcCopy;
|
||||
} else {
|
||||
return dst_img;
|
||||
}
|
||||
}
|
||||
|
||||
DEFINE_OP(GeneralDetectionOp);
|
||||
|
||||
} // namespace serving
|
||||
} // namespace paddle_serving
|
||||
} // namespace baidu
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 998 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 493 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 119 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 195 KiB |
@@ -0,0 +1,83 @@
|
||||
# Copyright (c) 2020 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.
|
||||
# pylint: disable=doc-string-missing
|
||||
|
||||
from paddle_serving_client import Client
|
||||
import sys
|
||||
import numpy as np
|
||||
import base64
|
||||
import os
|
||||
import cv2
|
||||
from paddle_serving_app.reader import Sequential, URL2Image, ResizeByFactor
|
||||
from paddle_serving_app.reader import Div, Normalize, Transpose
|
||||
from ocr_reader import OCRReader
|
||||
import codecs
|
||||
|
||||
client = Client()
|
||||
# TODO:load_client need to load more than one client model.
|
||||
# this need to figure out some details.
|
||||
client.load_client_config(sys.argv[1:])
|
||||
client.connect(["127.0.0.1:8181"])
|
||||
|
||||
import paddle
|
||||
test_img_dir = "../../doc/imgs/1.jpg"
|
||||
|
||||
ocr_reader = OCRReader(char_dict_path="../../ppocr/utils/ppocr_keys_v1.txt")
|
||||
|
||||
|
||||
def cv2_to_base64(image):
|
||||
return base64.b64encode(image).decode(
|
||||
'utf8') #data.tostring()).decode('utf8')
|
||||
|
||||
|
||||
def _check_image_file(path):
|
||||
img_end = {'jpg', 'bmp', 'png', 'jpeg', 'rgb', 'tif', 'tiff', 'gif'}
|
||||
return any([path.lower().endswith(e) for e in img_end])
|
||||
|
||||
|
||||
test_img_list = []
|
||||
if os.path.isfile(test_img_dir) and _check_image_file(test_img_dir):
|
||||
test_img_list.append(test_img_dir)
|
||||
elif os.path.isdir(test_img_dir):
|
||||
for single_file in os.listdir(test_img_dir):
|
||||
file_path = os.path.join(test_img_dir, single_file)
|
||||
if os.path.isfile(file_path) and _check_image_file(file_path):
|
||||
test_img_list.append(file_path)
|
||||
if len(test_img_list) == 0:
|
||||
raise Exception("not found any img file in {}".format(test_img_dir))
|
||||
|
||||
for img_file in test_img_list:
|
||||
with open(img_file, 'rb') as file:
|
||||
image_data = file.read()
|
||||
image = cv2_to_base64(image_data)
|
||||
res_list = []
|
||||
fetch_map = client.predict(feed={"x": image}, fetch=[], batch=True)
|
||||
if fetch_map is None:
|
||||
print('no results')
|
||||
else:
|
||||
if "text" in fetch_map:
|
||||
for x in fetch_map["text"]:
|
||||
x = codecs.encode(x)
|
||||
words = base64.b64decode(x).decode('utf-8')
|
||||
res_list.append(words)
|
||||
else:
|
||||
try:
|
||||
one_batch_res = ocr_reader.postprocess(
|
||||
fetch_map, with_score=True)
|
||||
for res in one_batch_res:
|
||||
res_list.append(res[0])
|
||||
except:
|
||||
print('no results')
|
||||
res = {"res": str(res_list)}
|
||||
print(res)
|
||||
@@ -0,0 +1,459 @@
|
||||
# Copyright (c) 2021 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 cv2
|
||||
import copy
|
||||
import numpy as np
|
||||
import math
|
||||
import re
|
||||
import sys
|
||||
import argparse
|
||||
import string
|
||||
from copy import deepcopy
|
||||
|
||||
|
||||
class DetResizeForTest(object):
|
||||
def __init__(self, **kwargs):
|
||||
super(DetResizeForTest, self).__init__()
|
||||
self.resize_type = 0
|
||||
if 'image_shape' in kwargs:
|
||||
self.image_shape = kwargs['image_shape']
|
||||
self.resize_type = 1
|
||||
elif 'limit_side_len' in kwargs:
|
||||
self.limit_side_len = kwargs['limit_side_len']
|
||||
self.limit_type = kwargs.get('limit_type', 'min')
|
||||
elif 'resize_short' in kwargs:
|
||||
self.limit_side_len = 736
|
||||
self.limit_type = 'min'
|
||||
else:
|
||||
self.resize_type = 2
|
||||
self.resize_long = kwargs.get('resize_long', 960)
|
||||
|
||||
def __call__(self, data):
|
||||
img = deepcopy(data)
|
||||
src_h, src_w, _ = img.shape
|
||||
|
||||
if self.resize_type == 0:
|
||||
img, [ratio_h, ratio_w] = self.resize_image_type0(img)
|
||||
elif self.resize_type == 2:
|
||||
img, [ratio_h, ratio_w] = self.resize_image_type2(img)
|
||||
else:
|
||||
img, [ratio_h, ratio_w] = self.resize_image_type1(img)
|
||||
|
||||
return img
|
||||
|
||||
def resize_image_type1(self, img):
|
||||
resize_h, resize_w = self.image_shape
|
||||
ori_h, ori_w = img.shape[:2] # (h, w, c)
|
||||
ratio_h = float(resize_h) / ori_h
|
||||
ratio_w = float(resize_w) / ori_w
|
||||
img = cv2.resize(img, (int(resize_w), int(resize_h)))
|
||||
return img, [ratio_h, ratio_w]
|
||||
|
||||
def resize_image_type0(self, img):
|
||||
"""
|
||||
resize image to a size multiple of 32 which is required by the network
|
||||
args:
|
||||
img(array): array with shape [h, w, c]
|
||||
return(tuple):
|
||||
img, (ratio_h, ratio_w)
|
||||
"""
|
||||
limit_side_len = self.limit_side_len
|
||||
h, w, _ = img.shape
|
||||
|
||||
# limit the max side
|
||||
if self.limit_type == 'max':
|
||||
if max(h, w) > limit_side_len:
|
||||
if h > w:
|
||||
ratio = float(limit_side_len) / h
|
||||
else:
|
||||
ratio = float(limit_side_len) / w
|
||||
else:
|
||||
ratio = 1.
|
||||
else:
|
||||
if min(h, w) < limit_side_len:
|
||||
if h < w:
|
||||
ratio = float(limit_side_len) / h
|
||||
else:
|
||||
ratio = float(limit_side_len) / w
|
||||
else:
|
||||
ratio = 1.
|
||||
resize_h = int(h * ratio)
|
||||
resize_w = int(w * ratio)
|
||||
|
||||
resize_h = int(round(resize_h / 32) * 32)
|
||||
resize_w = int(round(resize_w / 32) * 32)
|
||||
|
||||
try:
|
||||
if int(resize_w) <= 0 or int(resize_h) <= 0:
|
||||
return None, (None, None)
|
||||
img = cv2.resize(img, (int(resize_w), int(resize_h)))
|
||||
except:
|
||||
print(img.shape, resize_w, resize_h)
|
||||
sys.exit(0)
|
||||
ratio_h = resize_h / float(h)
|
||||
ratio_w = resize_w / float(w)
|
||||
# return img, np.array([h, w])
|
||||
return img, [ratio_h, ratio_w]
|
||||
|
||||
def resize_image_type2(self, img):
|
||||
h, w, _ = img.shape
|
||||
|
||||
resize_w = w
|
||||
resize_h = h
|
||||
|
||||
# Fix the longer side
|
||||
if resize_h > resize_w:
|
||||
ratio = float(self.resize_long) / resize_h
|
||||
else:
|
||||
ratio = float(self.resize_long) / resize_w
|
||||
|
||||
resize_h = int(resize_h * ratio)
|
||||
resize_w = int(resize_w * ratio)
|
||||
|
||||
max_stride = 128
|
||||
resize_h = (resize_h + max_stride - 1) // max_stride * max_stride
|
||||
resize_w = (resize_w + max_stride - 1) // max_stride * max_stride
|
||||
img = cv2.resize(img, (int(resize_w), int(resize_h)))
|
||||
ratio_h = resize_h / float(h)
|
||||
ratio_w = resize_w / float(w)
|
||||
|
||||
return img, [ratio_h, ratio_w]
|
||||
|
||||
|
||||
class BaseRecLabelDecode(object):
|
||||
""" Convert between text-label and text-index """
|
||||
|
||||
def __init__(self, config):
|
||||
support_character_type = [
|
||||
'ch', 'en', 'EN_symbol', 'french', 'german', 'japan', 'korean',
|
||||
'it', 'xi', 'pu', 'ru', 'ar', 'ta', 'ug', 'fa', 'ur', 'rs', 'oc',
|
||||
'rsc', 'bg', 'uk', 'be', 'te', 'ka', 'chinese_cht', 'hi', 'mr',
|
||||
'ne', 'EN'
|
||||
]
|
||||
character_type = config['character_type']
|
||||
character_dict_path = config['character_dict_path']
|
||||
use_space_char = True
|
||||
assert character_type in support_character_type, "Only {} are supported now but get {}".format(
|
||||
support_character_type, character_type)
|
||||
|
||||
self.beg_str = "sos"
|
||||
self.end_str = "eos"
|
||||
|
||||
if character_type == "en":
|
||||
self.character_str = "0123456789abcdefghijklmnopqrstuvwxyz"
|
||||
dict_character = list(self.character_str)
|
||||
elif character_type == "EN_symbol":
|
||||
# same with ASTER setting (use 94 char).
|
||||
self.character_str = string.printable[:-6]
|
||||
dict_character = list(self.character_str)
|
||||
elif character_type in support_character_type:
|
||||
self.character_str = ""
|
||||
assert character_dict_path is not None, "character_dict_path should not be None when character_type is {}".format(
|
||||
character_type)
|
||||
with open(character_dict_path, "rb") as fin:
|
||||
lines = fin.readlines()
|
||||
for line in lines:
|
||||
line = line.decode('utf-8').strip("\n").strip("\r\n")
|
||||
self.character_str += line
|
||||
if use_space_char:
|
||||
self.character_str += " "
|
||||
dict_character = list(self.character_str)
|
||||
|
||||
else:
|
||||
raise NotImplementedError
|
||||
self.character_type = character_type
|
||||
dict_character = self.add_special_char(dict_character)
|
||||
self.dict = {}
|
||||
for i, char in enumerate(dict_character):
|
||||
self.dict[char] = i
|
||||
self.character = dict_character
|
||||
|
||||
def add_special_char(self, dict_character):
|
||||
return dict_character
|
||||
|
||||
def decode(self, text_index, text_prob=None, is_remove_duplicate=False):
|
||||
""" convert text-index into text-label. """
|
||||
result_list = []
|
||||
ignored_tokens = self.get_ignored_tokens()
|
||||
batch_size = len(text_index)
|
||||
for batch_idx in range(batch_size):
|
||||
char_list = []
|
||||
conf_list = []
|
||||
for idx in range(len(text_index[batch_idx])):
|
||||
if text_index[batch_idx][idx] in ignored_tokens:
|
||||
continue
|
||||
if is_remove_duplicate:
|
||||
# only for predict
|
||||
if idx > 0 and text_index[batch_idx][idx - 1] == text_index[
|
||||
batch_idx][idx]:
|
||||
continue
|
||||
char_list.append(self.character[int(text_index[batch_idx][
|
||||
idx])])
|
||||
if text_prob is not None:
|
||||
conf_list.append(text_prob[batch_idx][idx])
|
||||
else:
|
||||
conf_list.append(1)
|
||||
text = ''.join(char_list)
|
||||
result_list.append((text, np.mean(conf_list)))
|
||||
return result_list
|
||||
|
||||
def get_ignored_tokens(self):
|
||||
return [0] # for ctc blank
|
||||
|
||||
|
||||
class CTCLabelDecode(BaseRecLabelDecode):
|
||||
""" Convert between text-label and text-index """
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config,
|
||||
#character_dict_path=None,
|
||||
#character_type='ch',
|
||||
#use_space_char=False,
|
||||
**kwargs):
|
||||
super(CTCLabelDecode, self).__init__(config)
|
||||
|
||||
def __call__(self, preds, label=None, *args, **kwargs):
|
||||
preds_idx = preds.argmax(axis=2)
|
||||
preds_prob = preds.max(axis=2)
|
||||
text = self.decode(preds_idx, preds_prob, is_remove_duplicate=True)
|
||||
if label is None:
|
||||
return text
|
||||
label = self.decode(label)
|
||||
return text, label
|
||||
|
||||
def add_special_char(self, dict_character):
|
||||
dict_character = ['blank'] + dict_character
|
||||
return dict_character
|
||||
|
||||
|
||||
class CharacterOps(object):
|
||||
""" Convert between text-label and text-index """
|
||||
|
||||
def __init__(self, config):
|
||||
self.character_type = config['character_type']
|
||||
self.loss_type = config['loss_type']
|
||||
if self.character_type == "en":
|
||||
self.character_str = "0123456789abcdefghijklmnopqrstuvwxyz"
|
||||
dict_character = list(self.character_str)
|
||||
elif self.character_type == "ch":
|
||||
character_dict_path = config['character_dict_path']
|
||||
self.character_str = ""
|
||||
with open(character_dict_path, "rb") as fin:
|
||||
lines = fin.readlines()
|
||||
for line in lines:
|
||||
line = line.decode('utf-8').strip("\n").strip("\r\n")
|
||||
self.character_str += line
|
||||
dict_character = list(self.character_str)
|
||||
elif self.character_type == "en_sensitive":
|
||||
# same with ASTER setting (use 94 char).
|
||||
self.character_str = string.printable[:-6]
|
||||
dict_character = list(self.character_str)
|
||||
else:
|
||||
self.character_str = None
|
||||
assert self.character_str is not None, \
|
||||
"Nonsupport type of the character: {}".format(self.character_str)
|
||||
self.beg_str = "sos"
|
||||
self.end_str = "eos"
|
||||
if self.loss_type == "attention":
|
||||
dict_character = [self.beg_str, self.end_str] + dict_character
|
||||
self.dict = {}
|
||||
for i, char in enumerate(dict_character):
|
||||
self.dict[char] = i
|
||||
self.character = dict_character
|
||||
|
||||
def encode(self, text):
|
||||
"""convert text-label into text-index.
|
||||
input:
|
||||
text: text labels of each image. [batch_size]
|
||||
|
||||
output:
|
||||
text: concatenated text index for CTCLoss.
|
||||
[sum(text_lengths)] = [text_index_0 + text_index_1 + ... + text_index_(n - 1)]
|
||||
length: length of each text. [batch_size]
|
||||
"""
|
||||
if self.character_type == "en":
|
||||
text = text.lower()
|
||||
|
||||
text_list = []
|
||||
for char in text:
|
||||
if char not in self.dict:
|
||||
continue
|
||||
text_list.append(self.dict[char])
|
||||
text = np.array(text_list)
|
||||
return text
|
||||
|
||||
def decode(self, text_index, is_remove_duplicate=False):
|
||||
""" convert text-index into text-label. """
|
||||
char_list = []
|
||||
char_num = self.get_char_num()
|
||||
|
||||
if self.loss_type == "attention":
|
||||
beg_idx = self.get_beg_end_flag_idx("beg")
|
||||
end_idx = self.get_beg_end_flag_idx("end")
|
||||
ignored_tokens = [beg_idx, end_idx]
|
||||
else:
|
||||
ignored_tokens = [char_num]
|
||||
|
||||
for idx in range(len(text_index)):
|
||||
if text_index[idx] in ignored_tokens:
|
||||
continue
|
||||
if is_remove_duplicate:
|
||||
if idx > 0 and text_index[idx - 1] == text_index[idx]:
|
||||
continue
|
||||
char_list.append(self.character[text_index[idx]])
|
||||
text = ''.join(char_list)
|
||||
return text
|
||||
|
||||
def get_char_num(self):
|
||||
return len(self.character)
|
||||
|
||||
def get_beg_end_flag_idx(self, beg_or_end):
|
||||
if self.loss_type == "attention":
|
||||
if beg_or_end == "beg":
|
||||
idx = np.array(self.dict[self.beg_str])
|
||||
elif beg_or_end == "end":
|
||||
idx = np.array(self.dict[self.end_str])
|
||||
else:
|
||||
assert False, "Unsupport type %s in get_beg_end_flag_idx"\
|
||||
% beg_or_end
|
||||
return idx
|
||||
else:
|
||||
err = "error in get_beg_end_flag_idx when using the loss %s"\
|
||||
% (self.loss_type)
|
||||
assert False, err
|
||||
|
||||
|
||||
class OCRReader(object):
|
||||
def __init__(self,
|
||||
algorithm="CRNN",
|
||||
image_shape=[3, 48, 320],
|
||||
char_type="ch",
|
||||
batch_num=1,
|
||||
char_dict_path="./ppocr_keys_v1.txt"):
|
||||
self.rec_image_shape = image_shape
|
||||
self.character_type = char_type
|
||||
self.rec_batch_num = batch_num
|
||||
char_ops_params = {}
|
||||
char_ops_params["character_type"] = char_type
|
||||
char_ops_params["character_dict_path"] = char_dict_path
|
||||
char_ops_params['loss_type'] = 'ctc'
|
||||
self.char_ops = CharacterOps(char_ops_params)
|
||||
self.label_ops = CTCLabelDecode(char_ops_params)
|
||||
|
||||
def resize_norm_img(self, img, max_wh_ratio):
|
||||
imgC, imgH, imgW = self.rec_image_shape
|
||||
if self.character_type == "ch":
|
||||
imgW = int(imgH * max_wh_ratio)
|
||||
h = img.shape[0]
|
||||
w = img.shape[1]
|
||||
ratio = w / float(h)
|
||||
if math.ceil(imgH * ratio) > imgW:
|
||||
resized_w = imgW
|
||||
else:
|
||||
resized_w = int(math.ceil(imgH * ratio))
|
||||
resized_image = cv2.resize(img, (resized_w, imgH))
|
||||
resized_image = resized_image.astype('float32')
|
||||
resized_image = resized_image.transpose((2, 0, 1)) / 255
|
||||
resized_image -= 0.5
|
||||
resized_image /= 0.5
|
||||
padding_im = np.zeros((imgC, imgH, imgW), dtype=np.float32)
|
||||
|
||||
padding_im[:, :, 0:resized_w] = resized_image
|
||||
return padding_im
|
||||
|
||||
def preprocess(self, img_list):
|
||||
img_num = len(img_list)
|
||||
norm_img_batch = []
|
||||
max_wh_ratio = 320/48.
|
||||
for ino in range(img_num):
|
||||
h, w = img_list[ino].shape[0:2]
|
||||
wh_ratio = w * 1.0 / h
|
||||
max_wh_ratio = max(max_wh_ratio, wh_ratio)
|
||||
|
||||
for ino in range(img_num):
|
||||
norm_img = self.resize_norm_img(img_list[ino], max_wh_ratio)
|
||||
norm_img = norm_img[np.newaxis, :]
|
||||
norm_img_batch.append(norm_img)
|
||||
norm_img_batch = np.concatenate(norm_img_batch)
|
||||
norm_img_batch = norm_img_batch.copy()
|
||||
|
||||
return norm_img_batch[0]
|
||||
|
||||
def postprocess(self, outputs, with_score=False):
|
||||
preds = list(outputs.values())[0]
|
||||
try:
|
||||
preds = preds.numpy()
|
||||
except:
|
||||
pass
|
||||
preds_idx = preds.argmax(axis=2)
|
||||
preds_prob = preds.max(axis=2)
|
||||
text = self.label_ops.decode(
|
||||
preds_idx, preds_prob, is_remove_duplicate=True)
|
||||
return text
|
||||
|
||||
|
||||
from argparse import ArgumentParser, RawDescriptionHelpFormatter
|
||||
import yaml
|
||||
|
||||
|
||||
class ArgsParser(ArgumentParser):
|
||||
def __init__(self):
|
||||
super(ArgsParser, self).__init__(
|
||||
formatter_class=RawDescriptionHelpFormatter)
|
||||
self.add_argument("-c", "--config", help="configuration file to use")
|
||||
self.add_argument(
|
||||
"-o", "--opt", nargs='+', help="set configuration options")
|
||||
|
||||
def parse_args(self, argv=None):
|
||||
args = super(ArgsParser, self).parse_args(argv)
|
||||
assert args.config is not None, \
|
||||
"Please specify --config=configure_file_path."
|
||||
args.conf_dict = self._parse_opt(args.opt, args.config)
|
||||
print("args config:", args.conf_dict)
|
||||
return args
|
||||
|
||||
def _parse_helper(self, v):
|
||||
if v.isnumeric():
|
||||
if "." in v:
|
||||
v = float(v)
|
||||
else:
|
||||
v = int(v)
|
||||
elif v == "True" or v == "False":
|
||||
v = (v == "True")
|
||||
return v
|
||||
|
||||
def _parse_opt(self, opts, conf_path):
|
||||
f = open(conf_path)
|
||||
config = yaml.load(f, Loader=yaml.Loader)
|
||||
if not opts:
|
||||
return config
|
||||
for s in opts:
|
||||
s = s.strip()
|
||||
k, v = s.split('=')
|
||||
v = self._parse_helper(v)
|
||||
print(k, v, type(v))
|
||||
cur = config
|
||||
parent = cur
|
||||
for kk in k.split("."):
|
||||
if kk not in cur:
|
||||
cur[kk] = {}
|
||||
parent = cur
|
||||
cur = cur[kk]
|
||||
else:
|
||||
parent = cur
|
||||
cur = cur[kk]
|
||||
parent[k.split(".")[-1]] = v
|
||||
return config
|
||||
@@ -0,0 +1,89 @@
|
||||
# Copyright (c) 2020 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 numpy as np
|
||||
import requests
|
||||
import json
|
||||
import base64
|
||||
import os
|
||||
|
||||
import argparse
|
||||
|
||||
|
||||
def str2bool(v):
|
||||
return v.lower() in ("true", "t", "1")
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(description="args for paddleserving")
|
||||
parser.add_argument("--image_dir", type=str, default="../../doc/imgs/")
|
||||
parser.add_argument("--det", type=str2bool, default=True)
|
||||
parser.add_argument("--rec", type=str2bool, default=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
def cv2_to_base64(image):
|
||||
return base64.b64encode(image).decode('utf8')
|
||||
|
||||
|
||||
def _check_image_file(path):
|
||||
img_end = {'jpg', 'bmp', 'png', 'jpeg', 'rgb', 'tif', 'tiff', 'gif'}
|
||||
return any([path.lower().endswith(e) for e in img_end])
|
||||
|
||||
|
||||
url = "http://127.0.0.1:9998/ocr/prediction"
|
||||
test_img_dir = args.image_dir
|
||||
|
||||
test_img_list = []
|
||||
if os.path.isfile(test_img_dir) and _check_image_file(test_img_dir):
|
||||
test_img_list.append(test_img_dir)
|
||||
elif os.path.isdir(test_img_dir):
|
||||
for single_file in os.listdir(test_img_dir):
|
||||
file_path = os.path.join(test_img_dir, single_file)
|
||||
if os.path.isfile(file_path) and _check_image_file(file_path):
|
||||
test_img_list.append(file_path)
|
||||
if len(test_img_list) == 0:
|
||||
raise Exception("not found any img file in {}".format(test_img_dir))
|
||||
|
||||
for idx, img_file in enumerate(test_img_list):
|
||||
with open(img_file, 'rb') as file:
|
||||
image_data1 = file.read()
|
||||
# print file name
|
||||
print('{}{}{}'.format('*' * 10, img_file, '*' * 10))
|
||||
|
||||
image = cv2_to_base64(image_data1)
|
||||
|
||||
data = {"key": ["image"], "value": [image]}
|
||||
r = requests.post(url=url, data=json.dumps(data))
|
||||
result = r.json()
|
||||
print("erro_no:{}, err_msg:{}".format(result["err_no"], result["err_msg"]))
|
||||
# check success
|
||||
if result["err_no"] == 0:
|
||||
ocr_result = result["value"][0]
|
||||
if not args.det:
|
||||
print(ocr_result)
|
||||
else:
|
||||
try:
|
||||
for item in eval(ocr_result):
|
||||
# return transcription and points
|
||||
print("{}, {}".format(item[0], item[1]))
|
||||
except Exception as e:
|
||||
print(ocr_result)
|
||||
print("No results")
|
||||
continue
|
||||
|
||||
else:
|
||||
print(
|
||||
"For details about error message, see PipelineServingLogs/pipeline.log"
|
||||
)
|
||||
print("==> total number of test imgs: ", len(test_img_list))
|
||||
@@ -0,0 +1,46 @@
|
||||
# Copyright (c) 2020 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.
|
||||
try:
|
||||
from paddle_serving_server_gpu.pipeline import PipelineClient
|
||||
except ImportError:
|
||||
from paddle_serving_server.pipeline import PipelineClient
|
||||
import numpy as np
|
||||
import requests
|
||||
import json
|
||||
import cv2
|
||||
import base64
|
||||
import os
|
||||
|
||||
client = PipelineClient()
|
||||
client.connect(['127.0.0.1:18091'])
|
||||
|
||||
|
||||
def cv2_to_base64(image):
|
||||
return base64.b64encode(image).decode('utf8')
|
||||
|
||||
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(description="args for paddleserving")
|
||||
parser.add_argument("--image_dir", type=str, default="../../doc/imgs/")
|
||||
args = parser.parse_args()
|
||||
test_img_dir = args.image_dir
|
||||
|
||||
for img_file in os.listdir(test_img_dir):
|
||||
with open(os.path.join(test_img_dir, img_file), 'rb') as file:
|
||||
image_data = file.read()
|
||||
image = cv2_to_base64(image_data)
|
||||
|
||||
for i in range(1):
|
||||
ret = client.predict(feed_dict={"image": image}, fetch=["res"])
|
||||
print(ret)
|
||||
@@ -0,0 +1,16 @@
|
||||
feed_var {
|
||||
name: "x"
|
||||
alias_name: "x"
|
||||
is_lod_tensor: false
|
||||
feed_type: 20
|
||||
shape: 1
|
||||
}
|
||||
fetch_var {
|
||||
name: "save_infer_model/scale_0.tmp_1"
|
||||
alias_name: "save_infer_model/scale_0.tmp_1"
|
||||
is_lod_tensor: false
|
||||
fetch_type: 1
|
||||
shape: 1
|
||||
shape: 640
|
||||
shape: 640
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
# Copyright (c) 2021 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.
|
||||
from paddle_serving_server.web_service import WebService, Op
|
||||
|
||||
import logging
|
||||
import numpy as np
|
||||
import copy
|
||||
import cv2
|
||||
import base64
|
||||
# from paddle_serving_app.reader import OCRReader
|
||||
from ocr_reader import OCRReader, DetResizeForTest, ArgsParser
|
||||
from paddle_serving_app.reader import Sequential, ResizeByFactor
|
||||
from paddle_serving_app.reader import Div, Normalize, Transpose
|
||||
from paddle_serving_app.reader import DBPostProcess, FilterBoxes, GetRotateCropImage, SortedBoxes
|
||||
|
||||
_LOGGER = logging.getLogger()
|
||||
|
||||
|
||||
class DetOp(Op):
|
||||
def init_op(self):
|
||||
self.det_preprocess = Sequential([
|
||||
DetResizeForTest(), Div(255),
|
||||
Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), Transpose(
|
||||
(2, 0, 1))
|
||||
])
|
||||
self.filter_func = FilterBoxes(10, 10)
|
||||
self.post_func = DBPostProcess({
|
||||
"thresh": 0.3,
|
||||
"box_thresh": 0.6,
|
||||
"max_candidates": 1000,
|
||||
"unclip_ratio": 1.5,
|
||||
"min_size": 3
|
||||
})
|
||||
|
||||
def preprocess(self, input_dicts, data_id, log_id):
|
||||
(_, input_dict), = input_dicts.items()
|
||||
data = base64.b64decode(input_dict["image"].encode('utf8'))
|
||||
self.raw_im = data
|
||||
data = np.fromstring(data, np.uint8)
|
||||
# Note: class variables(self.var) can only be used in process op mode
|
||||
im = cv2.imdecode(data, cv2.IMREAD_COLOR)
|
||||
self.ori_h, self.ori_w, _ = im.shape
|
||||
det_img = self.det_preprocess(im)
|
||||
_, self.new_h, self.new_w = det_img.shape
|
||||
return {"x": det_img[np.newaxis, :].copy()}, False, None, ""
|
||||
|
||||
def postprocess(self, input_dicts, fetch_dict, data_id, log_id):
|
||||
det_out = list(fetch_dict.values())[0]
|
||||
ratio_list = [
|
||||
float(self.new_h) / self.ori_h, float(self.new_w) / self.ori_w
|
||||
]
|
||||
dt_boxes_list = self.post_func(det_out, [ratio_list])
|
||||
dt_boxes = self.filter_func(dt_boxes_list[0], [self.ori_h, self.ori_w])
|
||||
out_dict = {"dt_boxes": dt_boxes, "image": self.raw_im}
|
||||
return out_dict, None, ""
|
||||
|
||||
|
||||
class RecOp(Op):
|
||||
def init_op(self):
|
||||
self.ocr_reader = OCRReader(
|
||||
char_dict_path="../../ppocr/utils/ppocr_keys_v1.txt")
|
||||
|
||||
self.get_rotate_crop_image = GetRotateCropImage()
|
||||
self.sorted_boxes = SortedBoxes()
|
||||
|
||||
def preprocess(self, input_dicts, data_id, log_id):
|
||||
(_, input_dict), = input_dicts.items()
|
||||
raw_im = input_dict["image"]
|
||||
data = np.frombuffer(raw_im, np.uint8)
|
||||
im = cv2.imdecode(data, cv2.IMREAD_COLOR)
|
||||
self.dt_list = input_dict["dt_boxes"]
|
||||
self.dt_list = self.sorted_boxes(self.dt_list)
|
||||
# deepcopy to save origin dt_boxes
|
||||
dt_boxes = copy.deepcopy(self.dt_list)
|
||||
feed_list = []
|
||||
img_list = []
|
||||
max_wh_ratio = 320 / 48.
|
||||
## Many mini-batchs, the type of feed_data is list.
|
||||
max_batch_size = 6 # len(dt_boxes)
|
||||
|
||||
# If max_batch_size is 0, skipping predict stage
|
||||
if max_batch_size == 0:
|
||||
return {}, True, None, ""
|
||||
boxes_size = len(dt_boxes)
|
||||
batch_size = boxes_size // max_batch_size
|
||||
rem = boxes_size % max_batch_size
|
||||
for bt_idx in range(0, batch_size + 1):
|
||||
imgs = None
|
||||
boxes_num_in_one_batch = 0
|
||||
if bt_idx == batch_size:
|
||||
if rem == 0:
|
||||
continue
|
||||
else:
|
||||
boxes_num_in_one_batch = rem
|
||||
elif bt_idx < batch_size:
|
||||
boxes_num_in_one_batch = max_batch_size
|
||||
else:
|
||||
_LOGGER.error("batch_size error, bt_idx={}, batch_size={}".
|
||||
format(bt_idx, batch_size))
|
||||
break
|
||||
|
||||
start = bt_idx * max_batch_size
|
||||
end = start + boxes_num_in_one_batch
|
||||
img_list = []
|
||||
for box_idx in range(start, end):
|
||||
boximg = self.get_rotate_crop_image(im, dt_boxes[box_idx])
|
||||
img_list.append(boximg)
|
||||
h, w = boximg.shape[0:2]
|
||||
wh_ratio = w * 1.0 / h
|
||||
max_wh_ratio = max(max_wh_ratio, wh_ratio)
|
||||
_, w, h = self.ocr_reader.resize_norm_img(img_list[0],
|
||||
max_wh_ratio).shape
|
||||
|
||||
imgs = np.zeros((boxes_num_in_one_batch, 3, w, h)).astype('float32')
|
||||
for id, img in enumerate(img_list):
|
||||
norm_img = self.ocr_reader.resize_norm_img(img, max_wh_ratio)
|
||||
imgs[id] = norm_img
|
||||
feed = {"x": imgs.copy()}
|
||||
feed_list.append(feed)
|
||||
return feed_list, False, None, ""
|
||||
|
||||
def postprocess(self, input_dicts, fetch_data, data_id, log_id):
|
||||
rec_list = []
|
||||
dt_num = len(self.dt_list)
|
||||
if isinstance(fetch_data, dict):
|
||||
if len(fetch_data) > 0:
|
||||
rec_batch_res = self.ocr_reader.postprocess(
|
||||
fetch_data, with_score=True)
|
||||
for res in rec_batch_res:
|
||||
rec_list.append(res)
|
||||
elif isinstance(fetch_data, list):
|
||||
for one_batch in fetch_data:
|
||||
one_batch_res = self.ocr_reader.postprocess(
|
||||
one_batch, with_score=True)
|
||||
for res in one_batch_res:
|
||||
rec_list.append(res)
|
||||
result_list = []
|
||||
for i in range(dt_num):
|
||||
text = rec_list[i]
|
||||
dt_box = self.dt_list[i]
|
||||
if text[1] >= 0.5:
|
||||
result_list.append([text, dt_box.tolist()])
|
||||
res = {"result": str(result_list)}
|
||||
return res, None, ""
|
||||
|
||||
|
||||
class OcrService(WebService):
|
||||
def get_pipeline_response(self, read_op):
|
||||
det_op = DetOp(name="det", input_ops=[read_op])
|
||||
rec_op = RecOp(name="rec", input_ops=[det_op])
|
||||
return rec_op
|
||||
|
||||
|
||||
uci_service = OcrService(name="ocr")
|
||||
FLAGS = ArgsParser().parse_args()
|
||||
uci_service.prepare_pipeline_config(yml_dict=FLAGS.conf_dict)
|
||||
uci_service.run_service()
|
||||
@@ -0,0 +1,78 @@
|
||||
# Copyright (c) 2021 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.
|
||||
from paddle_serving_server.web_service import WebService, Op
|
||||
|
||||
import logging
|
||||
import numpy as np
|
||||
import cv2
|
||||
import base64
|
||||
# from paddle_serving_app.reader import OCRReader
|
||||
from ocr_reader import OCRReader, DetResizeForTest, ArgsParser
|
||||
from paddle_serving_app.reader import Sequential, ResizeByFactor
|
||||
from paddle_serving_app.reader import Div, Normalize, Transpose
|
||||
from paddle_serving_app.reader import DBPostProcess, FilterBoxes, GetRotateCropImage, SortedBoxes
|
||||
|
||||
_LOGGER = logging.getLogger()
|
||||
|
||||
|
||||
class DetOp(Op):
|
||||
def init_op(self):
|
||||
self.det_preprocess = Sequential([
|
||||
DetResizeForTest(), Div(255),
|
||||
Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), Transpose(
|
||||
(2, 0, 1))
|
||||
])
|
||||
self.filter_func = FilterBoxes(10, 10)
|
||||
self.post_func = DBPostProcess({
|
||||
"thresh": 0.3,
|
||||
"box_thresh": 0.5,
|
||||
"max_candidates": 1000,
|
||||
"unclip_ratio": 1.5,
|
||||
"min_size": 3
|
||||
})
|
||||
|
||||
def preprocess(self, input_dicts, data_id, log_id):
|
||||
(_, input_dict), = input_dicts.items()
|
||||
data = base64.b64decode(input_dict["image"].encode('utf8'))
|
||||
self.raw_im = data
|
||||
data = np.fromstring(data, np.uint8)
|
||||
# Note: class variables(self.var) can only be used in process op mode
|
||||
im = cv2.imdecode(data, cv2.IMREAD_COLOR)
|
||||
self.ori_h, self.ori_w, _ = im.shape
|
||||
det_img = self.det_preprocess(im)
|
||||
_, self.new_h, self.new_w = det_img.shape
|
||||
return {"x": det_img[np.newaxis, :].copy()}, False, None, ""
|
||||
|
||||
def postprocess(self, input_dicts, fetch_dict, data_id, log_id):
|
||||
det_out = list(fetch_dict.values())[0]
|
||||
ratio_list = [
|
||||
float(self.new_h) / self.ori_h, float(self.new_w) / self.ori_w
|
||||
]
|
||||
dt_boxes_list = self.post_func(det_out, [ratio_list])
|
||||
dt_boxes = self.filter_func(dt_boxes_list[0], [self.ori_h, self.ori_w])
|
||||
out_dict = {"dt_boxes": str(dt_boxes)}
|
||||
|
||||
return out_dict, None, ""
|
||||
|
||||
|
||||
class OcrService(WebService):
|
||||
def get_pipeline_response(self, read_op):
|
||||
det_op = DetOp(name="det", input_ops=[read_op])
|
||||
return det_op
|
||||
|
||||
|
||||
uci_service = OcrService(name="ocr")
|
||||
FLAGS = ArgsParser().parse_args()
|
||||
uci_service.prepare_pipeline_config(yml_dict=FLAGS.conf_dict)
|
||||
uci_service.run_service()
|
||||
@@ -0,0 +1,87 @@
|
||||
# Copyright (c) 2021 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.
|
||||
from paddle_serving_server.web_service import WebService, Op
|
||||
|
||||
import logging
|
||||
import numpy as np
|
||||
import cv2
|
||||
import base64
|
||||
# from paddle_serving_app.reader import OCRReader
|
||||
from ocr_reader import OCRReader, DetResizeForTest, ArgsParser
|
||||
from paddle_serving_app.reader import Sequential, ResizeByFactor
|
||||
from paddle_serving_app.reader import Div, Normalize, Transpose
|
||||
|
||||
_LOGGER = logging.getLogger()
|
||||
|
||||
|
||||
class RecOp(Op):
|
||||
def init_op(self):
|
||||
self.ocr_reader = OCRReader(
|
||||
char_dict_path="../../ppocr/utils/ppocr_keys_v1.txt")
|
||||
|
||||
def preprocess(self, input_dicts, data_id, log_id):
|
||||
(_, input_dict), = input_dicts.items()
|
||||
raw_im = base64.b64decode(input_dict["image"].encode('utf8'))
|
||||
data = np.fromstring(raw_im, np.uint8)
|
||||
im = cv2.imdecode(data, cv2.IMREAD_COLOR)
|
||||
feed_list = []
|
||||
max_wh_ratio = 0
|
||||
## Many mini-batchs, the type of feed_data is list.
|
||||
max_batch_size = 6 # len(dt_boxes)
|
||||
|
||||
# If max_batch_size is 0, skipping predict stage
|
||||
if max_batch_size == 0:
|
||||
return {}, True, None, ""
|
||||
boxes_size = max_batch_size
|
||||
rem = boxes_size % max_batch_size
|
||||
|
||||
h, w = im.shape[0:2]
|
||||
wh_ratio = w * 1.0 / h
|
||||
max_wh_ratio = max(max_wh_ratio, wh_ratio)
|
||||
_, w, h = self.ocr_reader.resize_norm_img(im, max_wh_ratio).shape
|
||||
norm_img = self.ocr_reader.resize_norm_img(im, max_batch_size)
|
||||
norm_img = norm_img[np.newaxis, :]
|
||||
feed = {"x": norm_img.copy()}
|
||||
feed_list.append(feed)
|
||||
return feed_list, False, None, ""
|
||||
|
||||
def postprocess(self, input_dicts, fetch_data, data_id, log_id):
|
||||
res_list = []
|
||||
if isinstance(fetch_data, dict):
|
||||
if len(fetch_data) > 0:
|
||||
rec_batch_res = self.ocr_reader.postprocess(
|
||||
fetch_data, with_score=True)
|
||||
for res in rec_batch_res:
|
||||
res_list.append(res[0])
|
||||
elif isinstance(fetch_data, list):
|
||||
for one_batch in fetch_data:
|
||||
one_batch_res = self.ocr_reader.postprocess(
|
||||
one_batch, with_score=True)
|
||||
for res in one_batch_res:
|
||||
res_list.append(res[0])
|
||||
|
||||
res = {"res": str(res_list)}
|
||||
return res, None, ""
|
||||
|
||||
|
||||
class OcrService(WebService):
|
||||
def get_pipeline_response(self, read_op):
|
||||
rec_op = RecOp(name="rec", input_ops=[read_op])
|
||||
return rec_op
|
||||
|
||||
|
||||
uci_service = OcrService(name="ocr")
|
||||
FLAGS = ArgsParser().parse_args()
|
||||
uci_service.prepare_pipeline_config(yml_dict=FLAGS.conf_dict)
|
||||
uci_service.run_service()
|
||||
@@ -0,0 +1,405 @@
|
||||
# Copyright (c) 2021 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 cv2
|
||||
import copy
|
||||
import numpy as np
|
||||
import math
|
||||
import re
|
||||
import sys
|
||||
import argparse
|
||||
import string
|
||||
from copy import deepcopy
|
||||
|
||||
|
||||
class DetResizeForTest(object):
|
||||
def __init__(self, **kwargs):
|
||||
super(DetResizeForTest, self).__init__()
|
||||
self.resize_type = 0
|
||||
if 'image_shape' in kwargs:
|
||||
self.image_shape = kwargs['image_shape']
|
||||
self.resize_type = 1
|
||||
elif 'limit_side_len' in kwargs:
|
||||
self.limit_side_len = kwargs['limit_side_len']
|
||||
self.limit_type = kwargs.get('limit_type', 'min')
|
||||
elif 'resize_short' in kwargs:
|
||||
self.limit_side_len = 736
|
||||
self.limit_type = 'min'
|
||||
else:
|
||||
self.resize_type = 2
|
||||
self.resize_long = kwargs.get('resize_long', 960)
|
||||
|
||||
def __call__(self, data):
|
||||
img = deepcopy(data)
|
||||
src_h, src_w, _ = img.shape
|
||||
|
||||
if self.resize_type == 0:
|
||||
img, [ratio_h, ratio_w] = self.resize_image_type0(img)
|
||||
elif self.resize_type == 2:
|
||||
img, [ratio_h, ratio_w] = self.resize_image_type2(img)
|
||||
else:
|
||||
img, [ratio_h, ratio_w] = self.resize_image_type1(img)
|
||||
|
||||
return img
|
||||
|
||||
def resize_image_type1(self, img):
|
||||
resize_h, resize_w = self.image_shape
|
||||
ori_h, ori_w = img.shape[:2] # (h, w, c)
|
||||
ratio_h = float(resize_h) / ori_h
|
||||
ratio_w = float(resize_w) / ori_w
|
||||
img = cv2.resize(img, (int(resize_w), int(resize_h)))
|
||||
return img, [ratio_h, ratio_w]
|
||||
|
||||
def resize_image_type0(self, img):
|
||||
"""
|
||||
resize image to a size multiple of 32 which is required by the network
|
||||
args:
|
||||
img(array): array with shape [h, w, c]
|
||||
return(tuple):
|
||||
img, (ratio_h, ratio_w)
|
||||
"""
|
||||
limit_side_len = self.limit_side_len
|
||||
h, w, _ = img.shape
|
||||
|
||||
# limit the max side
|
||||
if self.limit_type == 'max':
|
||||
if max(h, w) > limit_side_len:
|
||||
if h > w:
|
||||
ratio = float(limit_side_len) / h
|
||||
else:
|
||||
ratio = float(limit_side_len) / w
|
||||
else:
|
||||
ratio = 1.
|
||||
else:
|
||||
if min(h, w) < limit_side_len:
|
||||
if h < w:
|
||||
ratio = float(limit_side_len) / h
|
||||
else:
|
||||
ratio = float(limit_side_len) / w
|
||||
else:
|
||||
ratio = 1.
|
||||
resize_h = int(h * ratio)
|
||||
resize_w = int(w * ratio)
|
||||
|
||||
resize_h = int(round(resize_h / 32) * 32)
|
||||
resize_w = int(round(resize_w / 32) * 32)
|
||||
|
||||
try:
|
||||
if int(resize_w) <= 0 or int(resize_h) <= 0:
|
||||
return None, (None, None)
|
||||
img = cv2.resize(img, (int(resize_w), int(resize_h)))
|
||||
except:
|
||||
print(img.shape, resize_w, resize_h)
|
||||
sys.exit(0)
|
||||
ratio_h = resize_h / float(h)
|
||||
ratio_w = resize_w / float(w)
|
||||
# return img, np.array([h, w])
|
||||
return img, [ratio_h, ratio_w]
|
||||
|
||||
def resize_image_type2(self, img):
|
||||
h, w, _ = img.shape
|
||||
|
||||
resize_w = w
|
||||
resize_h = h
|
||||
|
||||
# Fix the longer side
|
||||
if resize_h > resize_w:
|
||||
ratio = float(self.resize_long) / resize_h
|
||||
else:
|
||||
ratio = float(self.resize_long) / resize_w
|
||||
|
||||
resize_h = int(resize_h * ratio)
|
||||
resize_w = int(resize_w * ratio)
|
||||
|
||||
max_stride = 128
|
||||
resize_h = (resize_h + max_stride - 1) // max_stride * max_stride
|
||||
resize_w = (resize_w + max_stride - 1) // max_stride * max_stride
|
||||
img = cv2.resize(img, (int(resize_w), int(resize_h)))
|
||||
ratio_h = resize_h / float(h)
|
||||
ratio_w = resize_w / float(w)
|
||||
|
||||
return img, [ratio_h, ratio_w]
|
||||
|
||||
|
||||
class BaseRecLabelDecode(object):
|
||||
""" Convert between text-label and text-index """
|
||||
|
||||
def __init__(self, config):
|
||||
support_character_type = [
|
||||
'ch', 'en', 'EN_symbol', 'french', 'german', 'japan', 'korean',
|
||||
'it', 'xi', 'pu', 'ru', 'ar', 'ta', 'ug', 'fa', 'ur', 'rs', 'oc',
|
||||
'rsc', 'bg', 'uk', 'be', 'te', 'ka', 'chinese_cht', 'hi', 'mr',
|
||||
'ne', 'EN'
|
||||
]
|
||||
character_type = config['character_type']
|
||||
character_dict_path = config['character_dict_path']
|
||||
use_space_char = True
|
||||
assert character_type in support_character_type, "Only {} are supported now but get {}".format(
|
||||
support_character_type, character_type)
|
||||
|
||||
self.beg_str = "sos"
|
||||
self.end_str = "eos"
|
||||
|
||||
if character_type == "en":
|
||||
self.character_str = "0123456789abcdefghijklmnopqrstuvwxyz"
|
||||
dict_character = list(self.character_str)
|
||||
elif character_type == "EN_symbol":
|
||||
# same with ASTER setting (use 94 char).
|
||||
self.character_str = string.printable[:-6]
|
||||
dict_character = list(self.character_str)
|
||||
elif character_type in support_character_type:
|
||||
self.character_str = ""
|
||||
assert character_dict_path is not None, "character_dict_path should not be None when character_type is {}".format(
|
||||
character_type)
|
||||
with open(character_dict_path, "rb") as fin:
|
||||
lines = fin.readlines()
|
||||
for line in lines:
|
||||
line = line.decode('utf-8').strip("\n").strip("\r\n")
|
||||
self.character_str += line
|
||||
if use_space_char:
|
||||
self.character_str += " "
|
||||
dict_character = list(self.character_str)
|
||||
|
||||
else:
|
||||
raise NotImplementedError
|
||||
self.character_type = character_type
|
||||
dict_character = self.add_special_char(dict_character)
|
||||
self.dict = {}
|
||||
for i, char in enumerate(dict_character):
|
||||
self.dict[char] = i
|
||||
self.character = dict_character
|
||||
|
||||
def add_special_char(self, dict_character):
|
||||
return dict_character
|
||||
|
||||
def decode(self, text_index, text_prob=None, is_remove_duplicate=False):
|
||||
""" convert text-index into text-label. """
|
||||
result_list = []
|
||||
ignored_tokens = self.get_ignored_tokens()
|
||||
batch_size = len(text_index)
|
||||
for batch_idx in range(batch_size):
|
||||
char_list = []
|
||||
conf_list = []
|
||||
for idx in range(len(text_index[batch_idx])):
|
||||
if text_index[batch_idx][idx] in ignored_tokens:
|
||||
continue
|
||||
if is_remove_duplicate:
|
||||
# only for predict
|
||||
if idx > 0 and text_index[batch_idx][idx - 1] == text_index[
|
||||
batch_idx][idx]:
|
||||
continue
|
||||
char_list.append(self.character[int(text_index[batch_idx][
|
||||
idx])])
|
||||
if text_prob is not None:
|
||||
conf_list.append(text_prob[batch_idx][idx])
|
||||
else:
|
||||
conf_list.append(1)
|
||||
text = ''.join(char_list)
|
||||
result_list.append((text, np.mean(conf_list)))
|
||||
return result_list
|
||||
|
||||
def get_ignored_tokens(self):
|
||||
return [0] # for ctc blank
|
||||
|
||||
|
||||
class CTCLabelDecode(BaseRecLabelDecode):
|
||||
""" Convert between text-label and text-index """
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config,
|
||||
#character_dict_path=None,
|
||||
#character_type='ch',
|
||||
#use_space_char=False,
|
||||
**kwargs):
|
||||
super(CTCLabelDecode, self).__init__(config)
|
||||
|
||||
def __call__(self, preds, label=None, *args, **kwargs):
|
||||
preds_idx = preds.argmax(axis=2)
|
||||
preds_prob = preds.max(axis=2)
|
||||
text = self.decode(preds_idx, preds_prob, is_remove_duplicate=True)
|
||||
if label is None:
|
||||
return text
|
||||
label = self.decode(label)
|
||||
return text, label
|
||||
|
||||
def add_special_char(self, dict_character):
|
||||
dict_character = ['blank'] + dict_character
|
||||
return dict_character
|
||||
|
||||
|
||||
class CharacterOps(object):
|
||||
""" Convert between text-label and text-index """
|
||||
|
||||
def __init__(self, config):
|
||||
self.character_type = config['character_type']
|
||||
self.loss_type = config['loss_type']
|
||||
if self.character_type == "en":
|
||||
self.character_str = "0123456789abcdefghijklmnopqrstuvwxyz"
|
||||
dict_character = list(self.character_str)
|
||||
elif self.character_type == "ch":
|
||||
character_dict_path = config['character_dict_path']
|
||||
self.character_str = ""
|
||||
with open(character_dict_path, "rb") as fin:
|
||||
lines = fin.readlines()
|
||||
for line in lines:
|
||||
line = line.decode('utf-8').strip("\n").strip("\r\n")
|
||||
self.character_str += line
|
||||
dict_character = list(self.character_str)
|
||||
elif self.character_type == "en_sensitive":
|
||||
# same with ASTER setting (use 94 char).
|
||||
self.character_str = string.printable[:-6]
|
||||
dict_character = list(self.character_str)
|
||||
else:
|
||||
self.character_str = None
|
||||
assert self.character_str is not None, \
|
||||
"Nonsupport type of the character: {}".format(self.character_str)
|
||||
self.beg_str = "sos"
|
||||
self.end_str = "eos"
|
||||
if self.loss_type == "attention":
|
||||
dict_character = [self.beg_str, self.end_str] + dict_character
|
||||
self.dict = {}
|
||||
for i, char in enumerate(dict_character):
|
||||
self.dict[char] = i
|
||||
self.character = dict_character
|
||||
|
||||
def encode(self, text):
|
||||
"""convert text-label into text-index.
|
||||
input:
|
||||
text: text labels of each image. [batch_size]
|
||||
|
||||
output:
|
||||
text: concatenated text index for CTCLoss.
|
||||
[sum(text_lengths)] = [text_index_0 + text_index_1 + ... + text_index_(n - 1)]
|
||||
length: length of each text. [batch_size]
|
||||
"""
|
||||
if self.character_type == "en":
|
||||
text = text.lower()
|
||||
|
||||
text_list = []
|
||||
for char in text:
|
||||
if char not in self.dict:
|
||||
continue
|
||||
text_list.append(self.dict[char])
|
||||
text = np.array(text_list)
|
||||
return text
|
||||
|
||||
def decode(self, text_index, is_remove_duplicate=False):
|
||||
""" convert text-index into text-label. """
|
||||
char_list = []
|
||||
char_num = self.get_char_num()
|
||||
|
||||
if self.loss_type == "attention":
|
||||
beg_idx = self.get_beg_end_flag_idx("beg")
|
||||
end_idx = self.get_beg_end_flag_idx("end")
|
||||
ignored_tokens = [beg_idx, end_idx]
|
||||
else:
|
||||
ignored_tokens = [char_num]
|
||||
|
||||
for idx in range(len(text_index)):
|
||||
if text_index[idx] in ignored_tokens:
|
||||
continue
|
||||
if is_remove_duplicate:
|
||||
if idx > 0 and text_index[idx - 1] == text_index[idx]:
|
||||
continue
|
||||
char_list.append(self.character[text_index[idx]])
|
||||
text = ''.join(char_list)
|
||||
return text
|
||||
|
||||
def get_char_num(self):
|
||||
return len(self.character)
|
||||
|
||||
def get_beg_end_flag_idx(self, beg_or_end):
|
||||
if self.loss_type == "attention":
|
||||
if beg_or_end == "beg":
|
||||
idx = np.array(self.dict[self.beg_str])
|
||||
elif beg_or_end == "end":
|
||||
idx = np.array(self.dict[self.end_str])
|
||||
else:
|
||||
assert False, "Unsupport type %s in get_beg_end_flag_idx"\
|
||||
% beg_or_end
|
||||
return idx
|
||||
else:
|
||||
err = "error in get_beg_end_flag_idx when using the loss %s"\
|
||||
% (self.loss_type)
|
||||
assert False, err
|
||||
|
||||
|
||||
class OCRReader(object):
|
||||
def __init__(self,
|
||||
algorithm="CRNN",
|
||||
image_shape=[3, 32, 320],
|
||||
char_type="ch",
|
||||
batch_num=1,
|
||||
char_dict_path="./ppocr_keys_v1.txt"):
|
||||
self.rec_image_shape = image_shape
|
||||
self.character_type = char_type
|
||||
self.rec_batch_num = batch_num
|
||||
char_ops_params = {}
|
||||
char_ops_params["character_type"] = char_type
|
||||
char_ops_params["character_dict_path"] = char_dict_path
|
||||
char_ops_params['loss_type'] = 'ctc'
|
||||
self.char_ops = CharacterOps(char_ops_params)
|
||||
self.label_ops = CTCLabelDecode(char_ops_params)
|
||||
|
||||
def resize_norm_img(self, img, max_wh_ratio):
|
||||
imgC, imgH, imgW = self.rec_image_shape
|
||||
if self.character_type == "ch":
|
||||
imgW = int(32 * max_wh_ratio)
|
||||
h = img.shape[0]
|
||||
w = img.shape[1]
|
||||
ratio = w / float(h)
|
||||
if math.ceil(imgH * ratio) > imgW:
|
||||
resized_w = imgW
|
||||
else:
|
||||
resized_w = int(math.ceil(imgH * ratio))
|
||||
resized_image = cv2.resize(img, (resized_w, imgH))
|
||||
resized_image = resized_image.astype('float32')
|
||||
resized_image = resized_image.transpose((2, 0, 1)) / 255
|
||||
resized_image -= 0.5
|
||||
resized_image /= 0.5
|
||||
padding_im = np.zeros((imgC, imgH, imgW), dtype=np.float32)
|
||||
|
||||
padding_im[:, :, 0:resized_w] = resized_image
|
||||
return padding_im
|
||||
|
||||
def preprocess(self, img_list):
|
||||
img_num = len(img_list)
|
||||
norm_img_batch = []
|
||||
max_wh_ratio = 0
|
||||
for ino in range(img_num):
|
||||
h, w = img_list[ino].shape[0:2]
|
||||
wh_ratio = w * 1.0 / h
|
||||
max_wh_ratio = max(max_wh_ratio, wh_ratio)
|
||||
|
||||
for ino in range(img_num):
|
||||
norm_img = self.resize_norm_img(img_list[ino], max_wh_ratio)
|
||||
norm_img = norm_img[np.newaxis, :]
|
||||
norm_img_batch.append(norm_img)
|
||||
norm_img_batch = np.concatenate(norm_img_batch)
|
||||
norm_img_batch = norm_img_batch.copy()
|
||||
|
||||
return norm_img_batch[0]
|
||||
|
||||
def postprocess(self, outputs, with_score=False):
|
||||
preds = outputs["softmax_5.tmp_0"]
|
||||
try:
|
||||
preds = preds.numpy()
|
||||
except:
|
||||
pass
|
||||
preds_idx = preds.argmax(axis=2)
|
||||
preds_prob = preds.max(axis=2)
|
||||
text = self.label_ops.decode(
|
||||
preds_idx, preds_prob, is_remove_duplicate=True)
|
||||
return text
|
||||
@@ -0,0 +1,45 @@
|
||||
# Copyright (c) 2020 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.
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import requests
|
||||
import json
|
||||
import cv2
|
||||
import base64
|
||||
import os, sys
|
||||
import time
|
||||
|
||||
|
||||
def cv2_to_base64(image):
|
||||
#data = cv2.imencode('.jpg', image)[1]
|
||||
return base64.b64encode(image).decode(
|
||||
'utf8') #data.tostring()).decode('utf8')
|
||||
|
||||
|
||||
headers = {"Content-type": "application/json"}
|
||||
url = "http://127.0.0.1:9292/ocr/prediction"
|
||||
|
||||
test_img_dir = "../../../doc/imgs/"
|
||||
for idx, img_file in enumerate(os.listdir(test_img_dir)):
|
||||
with open(os.path.join(test_img_dir, img_file), 'rb') as file:
|
||||
image_data1 = file.read()
|
||||
|
||||
image = cv2_to_base64(image_data1)
|
||||
for i in range(1):
|
||||
data = {"feed": [{"image": image}], "fetch": ["save_infer_model/scale_0.tmp_1"]}
|
||||
r = requests.post(url=url, headers=headers, data=json.dumps(data))
|
||||
print(r.json())
|
||||
|
||||
test_img_dir = "../../../doc/imgs/"
|
||||
print("==> total number of test imgs: ", len(os.listdir(test_img_dir)))
|
||||
@@ -0,0 +1,114 @@
|
||||
# Copyright (c) 2020 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.
|
||||
|
||||
from paddle_serving_client import Client
|
||||
import cv2
|
||||
import sys
|
||||
import numpy as np
|
||||
import os
|
||||
from paddle_serving_client import Client
|
||||
from paddle_serving_app.reader import Sequential, URL2Image, ResizeByFactor
|
||||
from paddle_serving_app.reader import Div, Normalize, Transpose
|
||||
from paddle_serving_app.reader import DBPostProcess, FilterBoxes, GetRotateCropImage, SortedBoxes
|
||||
from ocr_reader import OCRReader
|
||||
try:
|
||||
from paddle_serving_server_gpu.web_service import WebService
|
||||
except ImportError:
|
||||
from paddle_serving_server.web_service import WebService
|
||||
from paddle_serving_app.local_predict import LocalPredictor
|
||||
import time
|
||||
import re
|
||||
import base64
|
||||
|
||||
|
||||
class OCRService(WebService):
|
||||
def init_det_debugger(self, det_model_config):
|
||||
self.det_preprocess = Sequential([
|
||||
ResizeByFactor(32, 960), Div(255),
|
||||
Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), Transpose(
|
||||
(2, 0, 1))
|
||||
])
|
||||
self.det_client = LocalPredictor()
|
||||
if sys.argv[1] == 'gpu':
|
||||
self.det_client.load_model_config(
|
||||
det_model_config, use_gpu=True, gpu_id=0)
|
||||
elif sys.argv[1] == 'cpu':
|
||||
self.det_client.load_model_config(det_model_config)
|
||||
self.ocr_reader = OCRReader(
|
||||
char_dict_path="../../../ppocr/utils/ppocr_keys_v1.txt")
|
||||
|
||||
def preprocess(self, feed=[], fetch=[]):
|
||||
data = base64.b64decode(feed[0]["image"].encode('utf8'))
|
||||
data = np.fromstring(data, np.uint8)
|
||||
im = cv2.imdecode(data, cv2.IMREAD_COLOR)
|
||||
ori_h, ori_w, _ = im.shape
|
||||
det_img = self.det_preprocess(im)
|
||||
_, new_h, new_w = det_img.shape
|
||||
det_img = det_img[np.newaxis, :]
|
||||
det_img = det_img.copy()
|
||||
det_out = self.det_client.predict(
|
||||
feed={"x": det_img}, fetch=["save_infer_model/scale_0.tmp_1"], batch=True)
|
||||
filter_func = FilterBoxes(10, 10)
|
||||
post_func = DBPostProcess({
|
||||
"thresh": 0.3,
|
||||
"box_thresh": 0.5,
|
||||
"max_candidates": 1000,
|
||||
"unclip_ratio": 1.5,
|
||||
"min_size": 3
|
||||
})
|
||||
sorted_boxes = SortedBoxes()
|
||||
ratio_list = [float(new_h) / ori_h, float(new_w) / ori_w]
|
||||
dt_boxes_list = post_func(det_out["save_infer_model/scale_0.tmp_1"], [ratio_list])
|
||||
dt_boxes = filter_func(dt_boxes_list[0], [ori_h, ori_w])
|
||||
dt_boxes = sorted_boxes(dt_boxes)
|
||||
get_rotate_crop_image = GetRotateCropImage()
|
||||
img_list = []
|
||||
max_wh_ratio = 0
|
||||
for i, dtbox in enumerate(dt_boxes):
|
||||
boximg = get_rotate_crop_image(im, dt_boxes[i])
|
||||
img_list.append(boximg)
|
||||
h, w = boximg.shape[0:2]
|
||||
wh_ratio = w * 1.0 / h
|
||||
max_wh_ratio = max(max_wh_ratio, wh_ratio)
|
||||
if len(img_list) == 0:
|
||||
return [], []
|
||||
_, w, h = self.ocr_reader.resize_norm_img(img_list[0],
|
||||
max_wh_ratio).shape
|
||||
imgs = np.zeros((len(img_list), 3, w, h)).astype('float32')
|
||||
for id, img in enumerate(img_list):
|
||||
norm_img = self.ocr_reader.resize_norm_img(img, max_wh_ratio)
|
||||
imgs[id] = norm_img
|
||||
feed = {"x": imgs.copy()}
|
||||
fetch = ["save_infer_model/scale_0.tmp_1"]
|
||||
return feed, fetch, True
|
||||
|
||||
def postprocess(self, feed={}, fetch=[], fetch_map=None):
|
||||
rec_res = self.ocr_reader.postprocess(fetch_map, with_score=True)
|
||||
res_lst = []
|
||||
for res in rec_res:
|
||||
res_lst.append(res[0])
|
||||
res = {"res": res_lst}
|
||||
return res
|
||||
|
||||
|
||||
ocr_service = OCRService(name="ocr")
|
||||
ocr_service.load_model_config("../ppocr_rec_mobile_2.0_serving")
|
||||
ocr_service.prepare_server(workdir="workdir", port=9292)
|
||||
ocr_service.init_det_debugger(det_model_config="../ppocr_det_mobile_2.0_serving")
|
||||
if sys.argv[1] == 'gpu':
|
||||
ocr_service.set_gpus("0")
|
||||
ocr_service.run_debugger_service(gpu=True)
|
||||
elif sys.argv[1] == 'cpu':
|
||||
ocr_service.run_debugger_service()
|
||||
ocr_service.run_web_service()
|
||||
Reference in New Issue
Block a user