From 9816aebdb078ec14ca3141560f2431981c8948f5 Mon Sep 17 00:00:00 2001 From: Topdu <784990967@qq.com> Date: Fri, 27 May 2022 06:49:52 +0000 Subject: [PATCH 01/45] add rec vitstr algorithm. --- configs/rec/rec_mtb_nrtr.yml | 10 +- configs/rec/rec_svtrnet.yml | 4 +- configs/rec/rec_vitstr.yml | 100 ++++++++++++ doc/doc_ch/algorithm_overview.md | 3 +- doc/doc_ch/algorithm_rec_vitstr.md | 154 ++++++++++++++++++ doc/doc_en/algorithm_overview_en.md | 3 +- doc/doc_en/algorithm_rec_vitstr_en.md | 134 +++++++++++++++ ppocr/data/imaug/__init__.py | 2 +- ppocr/data/imaug/label_ops.py | 35 +++- ppocr/data/imaug/rec_img_aug.py | 19 ++- ppocr/losses/__init__.py | 7 +- ...rec_nrtr_loss.py => rec_ce_smooth_loss.py} | 14 +- ppocr/modeling/backbones/__init__.py | 3 +- ppocr/modeling/backbones/rec_svtrnet.py | 102 ++++++------ ppocr/modeling/backbones/rec_vitstr.py | 120 ++++++++++++++ ppocr/postprocess/__init__.py | 4 +- ppocr/postprocess/rec_postprocess.py | 34 +++- .../configs/rec_mtb_nrtr/rec_mtb_nrtr.yml | 6 +- test_tipc/configs/rec_svtrnet/rec_svtrnet.yml | 119 ++++++++++++++ .../rec_svtrnet/train_infer_python.txt | 53 ++++++ test_tipc/configs/rec_vitstr/rec_vitstr.yml | 101 ++++++++++++ .../configs/rec_vitstr/train_infer_python.txt | 53 ++++++ tools/export_model.py | 6 + tools/infer/predict_rec.py | 19 ++- tools/program.py | 32 ++-- 25 files changed, 1044 insertions(+), 93 deletions(-) create mode 100644 configs/rec/rec_vitstr.yml create mode 100644 doc/doc_ch/algorithm_rec_vitstr.md create mode 100644 doc/doc_en/algorithm_rec_vitstr_en.md rename ppocr/losses/{rec_nrtr_loss.py => rec_ce_smooth_loss.py} (73%) create mode 100644 ppocr/modeling/backbones/rec_vitstr.py create mode 100644 test_tipc/configs/rec_svtrnet/rec_svtrnet.yml create mode 100644 test_tipc/configs/rec_svtrnet/train_infer_python.txt create mode 100644 test_tipc/configs/rec_vitstr/rec_vitstr.yml create mode 100644 test_tipc/configs/rec_vitstr/train_infer_python.txt diff --git a/configs/rec/rec_mtb_nrtr.yml b/configs/rec/rec_mtb_nrtr.yml index 0426750085..dfe2cc9811 100644 --- a/configs/rec/rec_mtb_nrtr.yml +++ b/configs/rec/rec_mtb_nrtr.yml @@ -49,7 +49,7 @@ Architecture: Loss: - name: NRTRLoss + name: CESmoothingLoss smoothing: True PostProcess: @@ -68,8 +68,8 @@ Train: img_mode: BGR channel_first: False - NRTRLabelEncode: # Class handling label - - NRTRRecResizeImg: - image_shape: [100, 32] + - GrayRecResizeImg: + image_shape: [100, 32] # W H resize_type: PIL # PIL or OpenCV - KeepKeys: keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order @@ -88,8 +88,8 @@ Eval: img_mode: BGR channel_first: False - NRTRLabelEncode: # Class handling label - - NRTRRecResizeImg: - image_shape: [100, 32] + - GrayRecResizeImg: + image_shape: [100, 32] # W H resize_type: PIL # PIL or OpenCV - KeepKeys: keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order diff --git a/configs/rec/rec_svtrnet.yml b/configs/rec/rec_svtrnet.yml index 233d5e2765..a700e4bd92 100644 --- a/configs/rec/rec_svtrnet.yml +++ b/configs/rec/rec_svtrnet.yml @@ -77,7 +77,7 @@ Metric: Train: dataset: name: LMDBDataSet - data_dir: ./train_data/data_lmdb_release/training/ + data_dir: ./train_data/data_lmdb_release/training transforms: - DecodeImage: # load image img_mode: BGR @@ -98,7 +98,7 @@ Train: Eval: dataset: name: LMDBDataSet - data_dir: ./train_data/data_lmdb_release/validation/ + data_dir: ./train_data/data_lmdb_release/validation transforms: - DecodeImage: # load image img_mode: BGR diff --git a/configs/rec/rec_vitstr.yml b/configs/rec/rec_vitstr.yml new file mode 100644 index 0000000000..005db0184a --- /dev/null +++ b/configs/rec/rec_vitstr.yml @@ -0,0 +1,100 @@ +Global: + use_gpu: True + epoch_num: 20 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/vitstr/ + save_epoch_step: 1 + # evaluation is run every 2000 iterations after the 0th iteration# + eval_batch_step: [0, 50] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: ppocr/utils/EN_symbol_dict.txt + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_vitstr.txt + + +Optimizer: + name: Adadelta + epsilon: 0.00000001 + rho: 0.95 + clip_norm: 5.0 + lr: + learning_rate: 1.0 + +Architecture: + model_type: rec + algorithm: ViTSTR + in_channels: 1 + Transform: + Backbone: + name: ViTSTR + scale: tiny + Neck: + name: SequenceEncoder + encoder_type: reshape + Head: + name: CTCHead + +Loss: + name: CESmoothingLoss + smoothing: False + with_all: True + +PostProcess: + name: ViTSTRLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - ViTSTRLabelEncode: # Class handling label + - GrayRecResizeImg: + image_shape: [224, 224] # W H + resize_type: PIL # PIL or OpenCV + inter_type: 'Image.BICUBIC' + scale: false + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 48 + drop_last: True + num_workers: 2 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/validation + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - ViTSTRLabelEncode: # Class handling label + - GrayRecResizeImg: + image_shape: [224, 224] # W H + resize_type: PIL # PIL or OpenCV + inter_type: 'Image.BICUBIC' + scale: false + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 2 diff --git a/doc/doc_ch/algorithm_overview.md b/doc/doc_ch/algorithm_overview.md index 6227a21498..934ac08537 100755 --- a/doc/doc_ch/algorithm_overview.md +++ b/doc/doc_ch/algorithm_overview.md @@ -66,6 +66,7 @@ - [x] [SAR](./algorithm_rec_sar.md) - [x] [SEED](./algorithm_rec_seed.md) - [x] [SVTR](./algorithm_rec_svtr.md) +- [x] [ViTSTR](./algorithm_rec_vitstr.md) 参考[DTRB](https://arxiv.org/abs/1904.01906)[3]文字识别训练和评估流程,使用MJSynth和SynthText两个文字识别数据集训练,在IIIT, SVT, IC03, IC13, IC15, SVTP, CUTE数据集上进行评估,算法效果如下: @@ -84,7 +85,7 @@ |SAR|Resnet31| 87.20% | rec_r31_sar | [训练模型](https://paddleocr.bj.bcebos.com/dygraph_v2.1/rec/rec_r31_sar_train.tar) | |SEED|Aster_Resnet| 85.35% | rec_resnet_stn_bilstm_att | [训练模型](https://paddleocr.bj.bcebos.com/dygraph_v2.1/rec/rec_resnet_stn_bilstm_att.tar) | |SVTR|SVTR-Tiny| 89.25% | rec_svtr_tiny_none_ctc_en | [训练模型](https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/rec_svtr_tiny_none_ctc_en_train.tar) | - +|ViTSTR|ViTSTR| 79.82% | rec_vitstr_none_ctc_en | [训练模型](https://paddleocr.bj.bcebos.com/rec_vitstr_none_none_train.tar) | diff --git a/doc/doc_ch/algorithm_rec_vitstr.md b/doc/doc_ch/algorithm_rec_vitstr.md new file mode 100644 index 0000000000..bd83b8d9c2 --- /dev/null +++ b/doc/doc_ch/algorithm_rec_vitstr.md @@ -0,0 +1,154 @@ +# 场景文本识别算法-ViTSTR + +- [1. 算法简介](#1) +- [2. 环境配置](#2) +- [3. 模型训练、评估、预测](#3) + - [3.1 训练](#3-1) + - [3.2 评估](#3-2) + - [3.3 预测](#3-3) +- [4. 推理部署](#4) + - [4.1 Python推理](#4-1) + - [4.2 C++推理](#4-2) + - [4.3 Serving服务化部署](#4-3) + - [4.4 更多推理部署](#4-4) +- [5. FAQ](#5) + + +## 1. 算法简介 + +论文信息: +> [Vision Transformer for Fast and Efficient Scene Text Recognition](https://arxiv.org/abs/2105.08582) +> Rowel Atienza +> ICDAR, 2021 + + + +`ViTSTR`使用MJSynth和SynthText两个文字识别数据集训练,在IIIT, SVT, IC03, IC13, IC15, SVTP, CUTE数据集上进行评估,算法复现效果如下: + +|模型|骨干网络|配置文件|Acc|下载链接| +| --- | --- | --- | --- | --- | +|ViTSTR|ViTSTR|[rec_vitstr.yml](../../configs/rec/rec_vitstr.yml)|79.82%|[训练模型](https://paddleocr.bj.bcebos.com/rec_vitstr_none_none_train.tar)| + + +## 2. 环境配置 +请先参考[《运行环境准备》](./environment.md)配置PaddleOCR运行环境,参考[《项目克隆》](./clone.md)克隆项目代码。 + + + +## 3. 模型训练、评估、预测 + + +### 3.1 模型训练 + +请参考[文本识别训练教程](./recognition.md)。PaddleOCR对代码进行了模块化,训练`ViTSTR`识别模型时需要**更换配置文件**为`ViTSTR`的[配置文件](../../configs/rec/rec_ViTSTR.yml)。 + +#### 启动训练 + + +具体地,在完成数据准备后,便可以启动训练,训练命令如下: +```shell +#单卡训练(训练周期长,不建议) +python3 tools/train.py -c configs/rec/rec_vitstr.yml + +#多卡训练,通过--gpus参数指定卡号 +python3 -m paddle.distributed.launch --gpus '0,1,2,3' tools/train.py -c configs/rec/rec_vitstr.yml +``` + + +### 3.2 评估 + +可下载已训练完成的[模型文件](#model),使用如下命令进行评估: + +```shell +# 注意将pretrained_model的路径设置为本地路径。 +python3 -m paddle.distributed.launch --gpus '0' tools/eval.py -c configs/rec/rec_vitstr.yml -o Global.pretrained_model=./rec_vitstr_train/best_accuracy +``` + + +### 3.3 预测 + +使用如下命令进行单张图片预测: +```shell +# 注意将pretrained_model的路径设置为本地路径。 +python3 tools/infer_rec.py -c configs/rec/rec_vitstr.yml -o Global.infer_img='./doc/imgs_words_en/word_10.png' Global.pretrained_model=./rec_vitstr_train/best_accuracy +# 预测文件夹下所有图像时,可修改infer_img为文件夹,如 Global.infer_img='./doc/imgs_words_en/'。 +``` + + + +## 4. 推理部署 + + +### 4.1 Python推理 +首先将训练得到best模型,转换成inference model。这里以训练完成的模型为例([模型下载地址](https://paddleocr.bj.bcebos.com/rec_vitstr_none_none_train.tar) ),可以使用如下命令进行转换: + +```shell +# 注意将pretrained_model的路径设置为本地路径。 +python3 tools/export_model.py -c configs/rec/rec_vitstr.yml -o Global.pretrained_model=./rec_vitstr_train/best_accuracy Global.save_inference_dir=./inference/rec_vitstr/ +``` +**注意:** +- 如果您是在自己的数据集上训练的模型,并且调整了字典文件,请注意修改配置文件中的`character_dict_path`是否是所需要的字典文件。 +- 如果您修改了训练时的输入大小,请修改`tools/export_model.py`文件中的对应NRTR的`infer_shape`。 + +转换成功后,在目录下有三个文件: +``` +/inference/rec_vitstr/ + ├── inference.pdiparams # 识别inference模型的参数文件 + ├── inference.pdiparams.info # 识别inference模型的参数信息,可忽略 + └── inference.pdmodel # 识别inference模型的program文件 +``` + +执行如下命令进行模型推理: + +```shell +python3 tools/infer/predict_rec.py --image_dir='./doc/imgs_words_en/word_10.png' --rec_model_dir='./inference/rec_vitstr/' --rec_algorithm='ViTSTR' --rec_image_shape='1,224,224' --rec_char_dict_path='./ppocr/utils/EN_symbol_dict.txt' +# 预测文件夹下所有图像时,可修改image_dir为文件夹,如 --image_dir='./doc/imgs_words_en/'。 +``` + +![](../imgs_words_en/word_10.png) + +执行命令后,上面图像的预测结果(识别的文本和得分)会打印到屏幕上,示例如下: +结果如下: +```shell +Predicts of ./doc/imgs_words_en/word_10.png:('pain', 0.9265879392623901) +``` + +**注意**: + +- 训练上述模型采用的图像分辨率是[1,224,224],需要通过参数`rec_image_shape`设置为您训练时的识别图像形状。 +- 在推理时需要设置参数`rec_char_dict_path`指定字典,如果您修改了字典,请修改该参数为您的字典文件。 +- 如果您修改了预处理方法,需修改`tools/infer/predict_rec.py`中NRTR的预处理为您的预处理方法。 + + + +### 4.2 C++推理部署 + +由于C++预处理后处理还未支持NRTR,所以暂未支持 + + +### 4.3 Serving服务化部署 + +暂不支持 + + +### 4.4 更多推理部署 + +暂不支持 + + +## 5. FAQ + +1. 在`ViTSTR`论文中,使用在ImageNet1k上的预训练权重进行初始化训练,我们在训练未采用预训练权重,最终精度没有变化甚至有所提高。 +2. 我们仅仅复现了`ViTSTR`中的tiny版本,如果有需要使用small、base版本,可直接使用源开源repo中的预训练权重转为Paddle权重即可使用。 + +## 引用 + +```bibtex +@article{Atienza2021ViTSTR, + title = {Vision Transformer for Fast and Efficient Scene Text Recognition}, + author = {Rowel Atienza}, + booktitle = {ICDAR}, + year = {2021}, + url = {https://arxiv.org/abs/2105.08582} +} +``` diff --git a/doc/doc_en/algorithm_overview_en.md b/doc/doc_en/algorithm_overview_en.md index 383cbe39bb..213d95807d 100755 --- a/doc/doc_en/algorithm_overview_en.md +++ b/doc/doc_en/algorithm_overview_en.md @@ -65,6 +65,7 @@ Supported text recognition algorithms (Click the link to get the tutorial): - [x] [SAR](./algorithm_rec_sar_en.md) - [x] [SEED](./algorithm_rec_seed_en.md) - [x] [SVTR](./algorithm_rec_svtr_en.md) +- [x] [ViTSTR](./algorithm_rec_vitstr_en.md) Refer to [DTRB](https://arxiv.org/abs/1904.01906), the training and evaluation result of these above text recognition (using MJSynth and SynthText for training, evaluate on IIIT, SVT, IC03, IC13, IC15, SVTP, CUTE) is as follow: @@ -83,7 +84,7 @@ Refer to [DTRB](https://arxiv.org/abs/1904.01906), the training and evaluation r |SAR|Resnet31| 87.20% | rec_r31_sar | [trained model](https://paddleocr.bj.bcebos.com/dygraph_v2.1/rec/rec_r31_sar_train.tar) | |SEED|Aster_Resnet| 85.35% | rec_resnet_stn_bilstm_att | [trained model](https://paddleocr.bj.bcebos.com/dygraph_v2.1/rec/rec_resnet_stn_bilstm_att.tar) | |SVTR|SVTR-Tiny| 89.25% | rec_svtr_tiny_none_ctc_en | [trained model](https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/rec_svtr_tiny_none_ctc_en_train.tar) | - +|ViTSTR|ViTSTR| 79.82% | rec_vitstr_none_ctc_en | [trained model](https://paddleocr.bj.bcebos.com/rec_vitstr_none_none_train.tar) | diff --git a/doc/doc_en/algorithm_rec_vitstr_en.md b/doc/doc_en/algorithm_rec_vitstr_en.md new file mode 100644 index 0000000000..666798c3e0 --- /dev/null +++ b/doc/doc_en/algorithm_rec_vitstr_en.md @@ -0,0 +1,134 @@ +# ViTSTR + +- [1. Introduction](#1) +- [2. Environment](#2) +- [3. Model Training / Evaluation / Prediction](#3) + - [3.1 Training](#3-1) + - [3.2 Evaluation](#3-2) + - [3.3 Prediction](#3-3) +- [4. Inference and Deployment](#4) + - [4.1 Python Inference](#4-1) + - [4.2 C++ Inference](#4-2) + - [4.3 Serving](#4-3) + - [4.4 More](#4-4) +- [5. FAQ](#5) + + +## 1. Introduction + +Paper: +> [Vision Transformer for Fast and Efficient Scene Text Recognition](https://arxiv.org/abs/2105.08582) +> Rowel Atienza +> ICDAR, 2021 + +Using MJSynth and SynthText two text recognition datasets for training, and evaluating on IIIT, SVT, IC03, IC13, IC15, SVTP, CUTE datasets, the algorithm reproduction effect is as follows: + +|Model|Backbone|config|Acc|Download link| +| --- | --- | --- | --- | --- | +|ViTSTR|ViTSTR|[rec_vitstr.yml](../../configs/rec/rec_vitstr.yml)|79.82%|[训练模型](https://paddleocr.bj.bcebos.com/rec_vitstr_none_none_train.tar)| + + +## 2. Environment +Please refer to ["Environment Preparation"](./environment_en.md) to configure the PaddleOCR environment, and refer to ["Project Clone"](./clone_en.md) to clone the project code. + + + +## 3. Model Training / Evaluation / Prediction + +Please refer to [Text Recognition Tutorial](./recognition_en.md). PaddleOCR modularizes the code, and training different recognition models only requires **changing the configuration file**. + +Training: + +Specifically, after the data preparation is completed, the training can be started. The training command is as follows: + +``` +#Single GPU training (long training period, not recommended) +python3 tools/train.py -c configs/rec/rec_vitstr.yml + +#Multi GPU training, specify the gpu number through the --gpus parameter +python3 -m paddle.distributed.launch --gpus '0,1,2,3' tools/train.py -c configs/rec/rec_vitstr.yml +``` + +Evaluation: + +``` +# GPU evaluation +python3 -m paddle.distributed.launch --gpus '0' tools/eval.py -c configs/rec/rec_vitstr.yml -o Global.pretrained_model={path/to/weights}/best_accuracy +``` + +Prediction: + +``` +# The configuration file used for prediction must match the training +python3 tools/infer_rec.py -c configs/rec/rec_vitstr.yml -o Global.infer_img='./doc/imgs_words_en/word_10.png' Global.pretrained_model=./rec_vitstr_train/best_accuracy +``` + + +## 4. Inference and Deployment + + +### 4.1 Python Inference +First, the model saved during the ViTSTR text recognition training process is converted into an inference model. ( [Model download link](https://paddleocr.bj.bcebos.com/rec_vitstr_none_none_train.tar)) ), you can use the following command to convert: + +``` +python3 tools/export_model.py -c configs/rec/rec_vitstr.yml -o Global.pretrained_model=./rec_vitstr_train/best_accuracy Global.save_inference_dir=./inference/rec_vitstr +``` + +**Note:** +- If you are training the model on your own dataset and have modified the dictionary file, please pay attention to modify the `character_dict_path` in the configuration file to the modified dictionary file. +- If you modified the input size during training, please modify the `infer_shape` corresponding to ViTSTR in the `tools/export_model.py` file. + +After the conversion is successful, there are three files in the directory: +``` +/inference/rec_vitstr/ + ├── inference.pdiparams + ├── inference.pdiparams.info + └── inference.pdmodel +``` + + +For ViTSTR text recognition model inference, the following commands can be executed: + +``` +python3 tools/infer/predict_rec.py --image_dir='./doc/imgs_words_en/word_10.png' --rec_model_dir='./inference/rec_vitstr/' --rec_algorithm='ViTSTR' --rec_image_shape='1,224,224' --rec_char_dict_path='./ppocr/utils/EN_symbol_dict.txt' +``` + +![](../imgs_words_en/word_10.png) + +After executing the command, the prediction result (recognized text and score) of the image above is printed to the screen, an example is as follows: +The result is as follows: +```shell +Predicts of ./doc/imgs_words_en/word_10.png:('pain', 0.9265879392623901) +``` + + +### 4.2 C++ Inference + +Not supported + + +### 4.3 Serving + +Not supported + + +### 4.4 More + +Not supported + + +## 5. FAQ + +1. In the `ViTSTR` paper, using pre-trained weights on ImageNet1k for initial training, we did not use pre-trained weights in training, and the final accuracy did not change or even improved. + +## Citation + +```bibtex +@article{Atienza2021ViTSTR, + title = {Vision Transformer for Fast and Efficient Scene Text Recognition}, + author = {Rowel Atienza}, + booktitle = {ICDAR}, + year = {2021}, + url = {https://arxiv.org/abs/2105.08582} +} +``` diff --git a/ppocr/data/imaug/__init__.py b/ppocr/data/imaug/__init__.py index 548832fb0d..2dbc92a703 100644 --- a/ppocr/data/imaug/__init__.py +++ b/ppocr/data/imaug/__init__.py @@ -23,7 +23,7 @@ from .random_crop_data import EastRandomCropData, RandomCropImgMask from .make_pse_gt import MakePseGt from .rec_img_aug import RecAug, RecConAug, RecResizeImg, ClsResizeImg, \ - SRNRecResizeImg, NRTRRecResizeImg, SARRecResizeImg, PRENResizeImg + SRNRecResizeImg, GrayRecResizeImg, SARRecResizeImg, PRENResizeImg from .ssl_img_aug import SSLRotateResize from .randaugment import RandAugment from .copy_paste import CopyPaste diff --git a/ppocr/data/imaug/label_ops.py b/ppocr/data/imaug/label_ops.py index 02a5187dad..0366e3f785 100644 --- a/ppocr/data/imaug/label_ops.py +++ b/ppocr/data/imaug/label_ops.py @@ -443,7 +443,9 @@ class KieLabelEncode(object): elif 'key_cls' in anno.keys(): labels.append(anno['key_cls']) else: - raise ValueError("Cannot found 'key_cls' in ann.keys(), please check your training annotation.") + raise ValueError( + "Cannot found 'key_cls' in ann.keys(), please check your training annotation." + ) edges.append(ann.get('edge', 0)) ann_infos = dict( image=data['image'], @@ -838,6 +840,37 @@ class PRENLabelEncode(BaseRecLabelEncode): return data +class ViTSTRLabelEncode(BaseRecLabelEncode): + """ Convert between text-label and text-index """ + + def __init__(self, + max_text_length, + character_dict_path=None, + use_space_char=False, + **kwargs): + + super(ViTSTRLabelEncode, self).__init__( + max_text_length, character_dict_path, use_space_char) + + def __call__(self, data): + text = data['label'] + text = self.encode(text) + if text is None: + return None + if len(text) >= self.max_text_len: + return None + data['length'] = np.array(len(text)) + text.insert(0, 0) + text.append(1) + text = text + [0] * (self.max_text_len + 2 - len(text)) + data['label'] = np.array(text) + return data + + def add_special_char(self, dict_character): + dict_character = ['', ''] + dict_character + return dict_character + + class VQATokenLabelEncode(object): """ Label encode for NLP VQA methods diff --git a/ppocr/data/imaug/rec_img_aug.py b/ppocr/data/imaug/rec_img_aug.py index 7483dffe5b..0697baf436 100644 --- a/ppocr/data/imaug/rec_img_aug.py +++ b/ppocr/data/imaug/rec_img_aug.py @@ -87,11 +87,19 @@ class ClsResizeImg(object): return data -class NRTRRecResizeImg(object): - def __init__(self, image_shape, resize_type, padding=False, **kwargs): +class GrayRecResizeImg(object): + def __init__(self, + image_shape, + resize_type, + inter_type='Image.ANTIALIAS', + scale=True, + padding=False, + **kwargs): self.image_shape = image_shape self.resize_type = resize_type self.padding = padding + self.inter_type = eval(inter_type) + self.scale = scale def __call__(self, data): img = data['image'] @@ -117,13 +125,16 @@ class NRTRRecResizeImg(object): return data if self.resize_type == 'PIL': image_pil = Image.fromarray(np.uint8(img)) - img = image_pil.resize(self.image_shape, Image.ANTIALIAS) + img = image_pil.resize(self.image_shape, self.inter_type) img = np.array(img) if self.resize_type == 'OpenCV': img = cv2.resize(img, self.image_shape) norm_img = np.expand_dims(img, -1) norm_img = norm_img.transpose((2, 0, 1)) - data['image'] = norm_img.astype(np.float32) / 128. - 1. + if self.scale: + data['image'] = norm_img.astype(np.float32) / 128. - 1. + else: + data['image'] = norm_img.astype(np.float32) / 255. return data diff --git a/ppocr/losses/__init__.py b/ppocr/losses/__init__.py index de8419b7c1..6c4545eb21 100755 --- a/ppocr/losses/__init__.py +++ b/ppocr/losses/__init__.py @@ -30,7 +30,7 @@ from .det_fce_loss import FCELoss from .rec_ctc_loss import CTCLoss from .rec_att_loss import AttentionLoss from .rec_srn_loss import SRNLoss -from .rec_nrtr_loss import NRTRLoss +from .rec_ce_smooth_loss import CESmoothingLoss from .rec_sar_loss import SARLoss from .rec_aster_loss import AsterLoss from .rec_pren_loss import PRENLoss @@ -60,8 +60,9 @@ def build_loss(config): support_dict = [ 'DBLoss', 'PSELoss', 'EASTLoss', 'SASTLoss', 'FCELoss', 'CTCLoss', 'ClsLoss', 'AttentionLoss', 'SRNLoss', 'PGLoss', 'CombinedLoss', - 'NRTRLoss', 'TableAttentionLoss', 'SARLoss', 'AsterLoss', 'SDMGRLoss', - 'VQASerTokenLayoutLMLoss', 'LossFromOutput', 'PRENLoss', 'MultiLoss' + 'CESmoothingLoss', 'TableAttentionLoss', 'SARLoss', 'AsterLoss', + 'SDMGRLoss', 'VQASerTokenLayoutLMLoss', 'LossFromOutput', 'PRENLoss', + 'MultiLoss' ] config = copy.deepcopy(config) module_name = config.pop('name') diff --git a/ppocr/losses/rec_nrtr_loss.py b/ppocr/losses/rec_ce_smooth_loss.py similarity index 73% rename from ppocr/losses/rec_nrtr_loss.py rename to ppocr/losses/rec_ce_smooth_loss.py index 200a6d0486..22243ed41f 100644 --- a/ppocr/losses/rec_nrtr_loss.py +++ b/ppocr/losses/rec_ce_smooth_loss.py @@ -3,16 +3,20 @@ from paddle import nn import paddle.nn.functional as F -class NRTRLoss(nn.Layer): - def __init__(self, smoothing=True, **kwargs): - super(NRTRLoss, self).__init__() +class CESmoothingLoss(nn.Layer): + def __init__(self, smoothing=True, with_all=False, **kwargs): + super(CESmoothingLoss, self).__init__() self.loss_func = nn.CrossEntropyLoss(reduction='mean', ignore_index=0) self.smoothing = smoothing + self.with_all = with_all def forward(self, pred, batch): pred = pred.reshape([-1, pred.shape[2]]) - max_len = batch[2].max() - tgt = batch[1][:, 1:2 + max_len] + if self.with_all: + tgt = batch[1] + else: + max_len = batch[2].max() + tgt = batch[1][:, 1:2 + max_len] tgt = tgt.reshape([-1]) if self.smoothing: eps = 0.1 diff --git a/ppocr/modeling/backbones/__init__.py b/ppocr/modeling/backbones/__init__.py index 072d6e0f84..a368e74816 100755 --- a/ppocr/modeling/backbones/__init__.py +++ b/ppocr/modeling/backbones/__init__.py @@ -32,10 +32,11 @@ def build_backbone(config, model_type): from .rec_micronet import MicroNet from .rec_efficientb3_pren import EfficientNetb3_PREN from .rec_svtrnet import SVTRNet + from .rec_vitstr import ViTSTR support_dict = [ 'MobileNetV1Enhance', 'MobileNetV3', 'ResNet', 'ResNetFPN', 'MTB', "ResNet31", "ResNet_ASTER", 'MicroNet', 'EfficientNetb3_PREN', - 'SVTRNet' + 'SVTRNet', 'ViTSTR' ] elif model_type == "e2e": from .e2e_resnet_vd_pg import ResNet diff --git a/ppocr/modeling/backbones/rec_svtrnet.py b/ppocr/modeling/backbones/rec_svtrnet.py index c57bf46345..c2c07f4476 100644 --- a/ppocr/modeling/backbones/rec_svtrnet.py +++ b/ppocr/modeling/backbones/rec_svtrnet.py @@ -147,7 +147,7 @@ class Attention(nn.Layer): dim, num_heads=8, mixer='Global', - HW=[8, 25], + HW=None, local_k=[7, 11], qkv_bias=False, qk_scale=None, @@ -210,7 +210,7 @@ class Block(nn.Layer): num_heads, mixer='Global', local_mixer=[7, 11], - HW=[8, 25], + HW=None, mlp_ratio=4., qkv_bias=False, qk_scale=None, @@ -274,7 +274,9 @@ class PatchEmbed(nn.Layer): img_size=[32, 100], in_channels=3, embed_dim=768, - sub_num=2): + sub_num=2, + patch_size=[4, 4], + mode='pope'): super().__init__() num_patches = (img_size[1] // (2 ** sub_num)) * \ (img_size[0] // (2 ** sub_num)) @@ -282,50 +284,56 @@ class PatchEmbed(nn.Layer): self.num_patches = num_patches self.embed_dim = embed_dim self.norm = None - if sub_num == 2: - self.proj = nn.Sequential( - ConvBNLayer( - in_channels=in_channels, - out_channels=embed_dim // 2, - kernel_size=3, - stride=2, - padding=1, - act=nn.GELU, - bias_attr=None), - ConvBNLayer( - in_channels=embed_dim // 2, - out_channels=embed_dim, - kernel_size=3, - stride=2, - padding=1, - act=nn.GELU, - bias_attr=None)) - if sub_num == 3: - self.proj = nn.Sequential( - ConvBNLayer( - in_channels=in_channels, - out_channels=embed_dim // 4, - kernel_size=3, - stride=2, - padding=1, - act=nn.GELU, - bias_attr=None), - ConvBNLayer( - in_channels=embed_dim // 4, - out_channels=embed_dim // 2, - kernel_size=3, - stride=2, - padding=1, - act=nn.GELU, - bias_attr=None), - ConvBNLayer( - in_channels=embed_dim // 2, - out_channels=embed_dim, - kernel_size=3, - stride=2, - padding=1, - act=nn.GELU, - bias_attr=None)) + if mode == 'pope': + if sub_num == 2: + self.proj = nn.Sequential( + ConvBNLayer( + in_channels=in_channels, + out_channels=embed_dim // 2, + kernel_size=3, + stride=2, + padding=1, + act=nn.GELU, + bias_attr=None), + ConvBNLayer( + in_channels=embed_dim // 2, + out_channels=embed_dim, + kernel_size=3, + stride=2, + padding=1, + act=nn.GELU, + bias_attr=None)) + if sub_num == 3: + self.proj = nn.Sequential( + ConvBNLayer( + in_channels=in_channels, + out_channels=embed_dim // 4, + kernel_size=3, + stride=2, + padding=1, + act=nn.GELU, + bias_attr=None), + ConvBNLayer( + in_channels=embed_dim // 4, + out_channels=embed_dim // 2, + kernel_size=3, + stride=2, + padding=1, + act=nn.GELU, + bias_attr=None), + ConvBNLayer( + in_channels=embed_dim // 2, + out_channels=embed_dim, + kernel_size=3, + stride=2, + padding=1, + act=nn.GELU, + bias_attr=None)) + elif mode == 'linear': + self.proj = nn.Conv2D( + 1, embed_dim, kernel_size=patch_size, stride=patch_size) + self.num_patches = img_size[0] // patch_size[0] * img_size[ + 1] // patch_size[1] def forward(self, x): B, C, H, W = x.shape diff --git a/ppocr/modeling/backbones/rec_vitstr.py b/ppocr/modeling/backbones/rec_vitstr.py new file mode 100644 index 0000000000..d5d7d5148a --- /dev/null +++ b/ppocr/modeling/backbones/rec_vitstr.py @@ -0,0 +1,120 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# 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. +""" +This code is refer from: +https://github.com/roatienza/deep-text-recognition-benchmark/blob/master/modules/vitstr.py +""" + +import numpy as np +import paddle +import paddle.nn as nn +from ppocr.modeling.backbones.rec_svtrnet import Block, PatchEmbed, zeros_, trunc_normal_, ones_ + +scale_dim_heads = {'tiny': [192, 3], 'small': [384, 6], 'base': [768, 12]} + + +class ViTSTR(nn.Layer): + def __init__(self, + img_size=[224, 224], + in_channels=1, + scale='tiny', + seqlen=27, + patch_size=[16, 16], + embed_dim=None, + depth=12, + num_heads=None, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_path_rate=0., + drop_rate=0., + attn_drop_rate=0., + norm_layer='nn.LayerNorm', + act_layer='nn.GELU', + epsilon=1e-6, + out_channels=None, + **kwargs): + super().__init__() + self.seqlen = seqlen + embed_dim = embed_dim if embed_dim is not None else scale_dim_heads[ + scale][0] + num_heads = num_heads if num_heads is not None else scale_dim_heads[ + scale][1] + out_channels = out_channels if out_channels is not None else embed_dim + self.patch_embed = PatchEmbed( + img_size=img_size, + in_channels=in_channels, + embed_dim=embed_dim, + patch_size=patch_size, + mode='linear') + num_patches = self.patch_embed.num_patches + + self.pos_embed = self.create_parameter( + shape=[1, num_patches + 1, embed_dim], default_initializer=zeros_) + self.add_parameter("pos_embed", self.pos_embed) + self.cls_token = self.create_parameter( + shape=[1, 1, embed_dim], default_initializer=zeros_) + self.add_parameter("cls_token", self.cls_token) + + self.pos_drop = nn.Dropout(p=drop_rate) + + dpr = np.linspace(0, drop_path_rate, depth) + self.blocks = nn.LayerList([ + Block( + dim=embed_dim, + num_heads=num_heads, + mlp_ratio=mlp_ratio, + qkv_bias=qkv_bias, + qk_scale=qk_scale, + drop=drop_rate, + attn_drop=attn_drop_rate, + drop_path=dpr[i], + norm_layer=norm_layer, + act_layer=eval(act_layer), + epsilon=epsilon, + prenorm=False) for i in range(depth) + ]) + self.norm = eval(norm_layer)(embed_dim, epsilon=epsilon) + + self.out_channels = out_channels + + trunc_normal_(self.pos_embed) + trunc_normal_(self.cls_token) + self.apply(self._init_weights) + + def _init_weights(self, m): + if isinstance(m, nn.Linear): + trunc_normal_(m.weight) + if isinstance(m, nn.Linear) and m.bias is not None: + zeros_(m.bias) + elif isinstance(m, nn.LayerNorm): + zeros_(m.bias) + ones_(m.weight) + + def forward_features(self, x): + B = x.shape[0] + x = self.patch_embed(x) + cls_tokens = paddle.tile(self.cls_token, repeat_times=[B, 1, 1]) + x = paddle.concat((cls_tokens, x), axis=1) + x = x + self.pos_embed + x = self.pos_drop(x) + for blk in self.blocks: + x = blk(x) + x = self.norm(x) + return x + + def forward(self, x): + x = self.forward_features(x) + x = x[:, :self.seqlen] + return x.transpose([0, 2, 1]).unsqueeze(2) diff --git a/ppocr/postprocess/__init__.py b/ppocr/postprocess/__init__.py index f50b5f1c5f..4f900ee1fc 100644 --- a/ppocr/postprocess/__init__.py +++ b/ppocr/postprocess/__init__.py @@ -27,7 +27,7 @@ from .sast_postprocess import SASTPostProcess from .fce_postprocess import FCEPostProcess from .rec_postprocess import CTCLabelDecode, AttnLabelDecode, SRNLabelDecode, \ DistillationCTCLabelDecode, TableLabelDecode, NRTRLabelDecode, SARLabelDecode, \ - SEEDLabelDecode, PRENLabelDecode + SEEDLabelDecode, PRENLabelDecode, ViTSTRLabelDecode from .cls_postprocess import ClsPostProcess from .pg_postprocess import PGPostProcess from .vqa_token_ser_layoutlm_postprocess import VQASerTokenLayoutLMPostProcess @@ -42,7 +42,7 @@ def build_post_process(config, global_config=None): 'DistillationDBPostProcess', 'NRTRLabelDecode', 'SARLabelDecode', 'SEEDLabelDecode', 'VQASerTokenLayoutLMPostProcess', 'VQAReTokenLayoutLMPostProcess', 'PRENLabelDecode', - 'DistillationSARLabelDecode' + 'DistillationSARLabelDecode', 'ViTSTRLabelDecode' ] if config['name'] == 'PSEPostProcess': diff --git a/ppocr/postprocess/rec_postprocess.py b/ppocr/postprocess/rec_postprocess.py index bf0fd890bf..df6203fada 100644 --- a/ppocr/postprocess/rec_postprocess.py +++ b/ppocr/postprocess/rec_postprocess.py @@ -188,13 +188,13 @@ class NRTRLabelDecode(BaseRecLabelDecode): char_list = [] conf_list = [] for idx in range(len(text_index[batch_idx])): - if text_index[batch_idx][idx] == 3: # end - break try: - char_list.append(self.character[int(text_index[batch_idx][ - idx])]) + char_idx = self.character[int(text_index[batch_idx][idx])] except: continue + if char_idx == '': # end + break + char_list.append(char_idx) if text_prob is not None: conf_list.append(text_prob[batch_idx][idx]) else: @@ -204,6 +204,32 @@ class NRTRLabelDecode(BaseRecLabelDecode): return result_list +class ViTSTRLabelDecode(NRTRLabelDecode): + """ Convert between text-label and text-index """ + + def __init__(self, character_dict_path=None, use_space_char=False, + **kwargs): + super(ViTSTRLabelDecode, self).__init__(character_dict_path, + use_space_char) + + def __call__(self, preds, label=None, *args, **kwargs): + if isinstance(preds, paddle.Tensor): + preds = preds[:, 1:].numpy() + else: + preds = preds[:, 1:] + preds_idx = preds.argmax(axis=2) + preds_prob = preds.max(axis=2) + text = self.decode(preds_idx, preds_prob, is_remove_duplicate=False) + if label is None: + return text + label = self.decode(label[:, 1:]) + return text, label + + def add_special_char(self, dict_character): + dict_character = ['', ''] + dict_character + return dict_character + + class AttnLabelDecode(BaseRecLabelDecode): """ Convert between text-label and text-index """ diff --git a/test_tipc/configs/rec_mtb_nrtr/rec_mtb_nrtr.yml b/test_tipc/configs/rec_mtb_nrtr/rec_mtb_nrtr.yml index 15119bb2a9..3936ab58ad 100644 --- a/test_tipc/configs/rec_mtb_nrtr/rec_mtb_nrtr.yml +++ b/test_tipc/configs/rec_mtb_nrtr/rec_mtb_nrtr.yml @@ -49,7 +49,7 @@ Architecture: Loss: - name: NRTRLoss + name: CESmoothingLoss smoothing: True PostProcess: @@ -69,7 +69,7 @@ Train: img_mode: BGR channel_first: False - NRTRLabelEncode: # Class handling label - - NRTRRecResizeImg: + - GrayRecResizeImg: image_shape: [100, 32] resize_type: PIL # PIL or OpenCV - KeepKeys: @@ -90,7 +90,7 @@ Eval: img_mode: BGR channel_first: False - NRTRLabelEncode: # Class handling label - - NRTRRecResizeImg: + - GrayRecResizeImg: image_shape: [100, 32] resize_type: PIL # PIL or OpenCV - KeepKeys: diff --git a/test_tipc/configs/rec_svtrnet/rec_svtrnet.yml b/test_tipc/configs/rec_svtrnet/rec_svtrnet.yml new file mode 100644 index 0000000000..26facca34d --- /dev/null +++ b/test_tipc/configs/rec_svtrnet/rec_svtrnet.yml @@ -0,0 +1,119 @@ +Global: + use_gpu: True + epoch_num: 20 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/svtr/ + save_epoch_step: 1 + # evaluation is run every 2000 iterations after the 0th iteration + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + character_type: en + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_svtr_tiny.txt + + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.99 + epsilon: 0.00000008 + weight_decay: 0.05 + no_weight_decay_name: norm pos_embed + one_dim_param_no_weight_decay: true + lr: + name: Cosine + learning_rate: 0.0005 + warmup_epoch: 2 + +Architecture: + model_type: rec + algorithm: SVTR + Transform: + name: STN_ON + tps_inputsize: [32, 64] + tps_outputsize: [32, 100] + num_control_points: 20 + tps_margins: [0.05,0.05] + stn_activation: none + Backbone: + name: SVTRNet + img_size: [32, 100] + out_char_num: 25 + out_channels: 192 + patch_merging: 'Conv' + embed_dim: [64, 128, 256] + depth: [3, 6, 3] + num_heads: [2, 4, 8] + mixer: ['Local','Local','Local','Local','Local','Local','Global','Global','Global','Global','Global','Global'] + local_mixer: [[7, 11], [7, 11], [7, 11]] + last_stage: True + prenorm: false + Neck: + name: SequenceEncoder + encoder_type: reshape + Head: + name: CTCHead + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data/ + label_file_list: ["./train_data/ic15_data/rec_gt_train.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + character_dict_path: + image_shape: [3, 64, 256] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 512 + drop_last: True + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data + label_file_list: ["./train_data/ic15_data/rec_gt_test.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + character_dict_path: + image_shape: [3, 64, 256] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 2 diff --git a/test_tipc/configs/rec_svtrnet/train_infer_python.txt b/test_tipc/configs/rec_svtrnet/train_infer_python.txt new file mode 100644 index 0000000000..72526063e0 --- /dev/null +++ b/test_tipc/configs/rec_svtrnet/train_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:rec_svtrnet +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=2|whole_train_whole_infer=300 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=64 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/rec_svtrnet/rec_svtrnet.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c test_tipc/configs/rec_svtrnet/rec_svtrnet.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/rec_svtrnet/rec_svtrnet.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/rec_svtrnet_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/rec_svtrnet/rec_svtrnet.yml -o +infer_quant:False +inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/EN_symbol_dict.txt --rec_image_shape="1,224,224" --rec_algorithm="SVTR" +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:1|6 +--rec_batch_num:1|6 +--use_tensorrt:False +--precision:fp32|int8 +--rec_model_dir: +--image_dir:./inference/rec_inference +--save_log_path:./test/output/ +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[1,224,224]}] diff --git a/test_tipc/configs/rec_vitstr/rec_vitstr.yml b/test_tipc/configs/rec_vitstr/rec_vitstr.yml new file mode 100644 index 0000000000..427bce4b5a --- /dev/null +++ b/test_tipc/configs/rec_vitstr/rec_vitstr.yml @@ -0,0 +1,101 @@ +Global: + use_gpu: True + epoch_num: 20 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/vitstr/ + save_epoch_step: 1 + # evaluation is run every 2000 iterations after the 0th iteration# + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: ppocr/utils/EN_symbol_dict.txt + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_vitstr.txt + + +Optimizer: + name: Adadelta + epsilon: 0.00000001 + rho: 0.95 + clip_norm: 5.0 + lr: + learning_rate: 1.0 + +Architecture: + model_type: rec + algorithm: ViTSTR + in_channels: 1 + Transform: + Backbone: + name: ViTSTR + Neck: + name: SequenceEncoder + encoder_type: reshape + Head: + name: CTCHead + +Loss: + name: CESmoothingLoss + smoothing: False + with_all: True + +PostProcess: + name: ViTSTRLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data/ + label_file_list: ["./train_data/ic15_data/rec_gt_train.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - ViTSTRLabelEncode: # Class handling label + - GrayRecResizeImg: + image_shape: [224, 224] # W H + resize_type: PIL # PIL or OpenCV + inter_type: 'Image.BICUBIC' + scale: false + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 48 + drop_last: True + num_workers: 2 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data + label_file_list: ["./train_data/ic15_data/rec_gt_test.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - ViTSTRLabelEncode: # Class handling label + - GrayRecResizeImg: + image_shape: [224, 224] # W H + resize_type: PIL # PIL or OpenCV + inter_type: 'Image.BICUBIC' + scale: false + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 2 diff --git a/test_tipc/configs/rec_vitstr/train_infer_python.txt b/test_tipc/configs/rec_vitstr/train_infer_python.txt new file mode 100644 index 0000000000..6c7af1fb87 --- /dev/null +++ b/test_tipc/configs/rec_vitstr/train_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:rec_vitstr +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=2|whole_train_whole_infer=300 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=64 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/rec_vitstr/rec_vitstr.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c test_tipc/configs/rec_vitstr/rec_vitstr.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/rec_vitstr/rec_vitstr.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/rec_vitstr_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/rec_vitstr/rec_vitstr.yml -o +infer_quant:False +inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/EN_symbol_dict.txt --rec_image_shape="1,224,224" --rec_algorithm="ViTSTR" +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:1|6 +--rec_batch_num:1|6 +--use_tensorrt:False +--precision:fp32|int8 +--rec_model_dir: +--image_dir:./inference/rec_inference +--save_log_path:./test/output/ +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[1,224,224]}] diff --git a/tools/export_model.py b/tools/export_model.py index c0cbcd361c..6e003f2ffa 100755 --- a/tools/export_model.py +++ b/tools/export_model.py @@ -73,6 +73,12 @@ def export_single_model(model, arch_config, save_path, logger, quanter=None): shape=[None, 3, 64, 512], dtype="float32"), ] model = to_static(model, input_spec=other_shape) + elif arch_config["algorithm"] == "ViTSTR": + other_shape = [ + paddle.static.InputSpec( + shape=[None, 1, 224, 224], dtype="float32"), + ] + model = to_static(model, input_spec=other_shape) else: infer_shape = [3, -1, -1] if arch_config["model_type"] == "rec": diff --git a/tools/infer/predict_rec.py b/tools/infer/predict_rec.py index 3664ef2caf..1945667972 100755 --- a/tools/infer/predict_rec.py +++ b/tools/infer/predict_rec.py @@ -69,6 +69,12 @@ class TextRecognizer(object): "character_dict_path": args.rec_char_dict_path, "use_space_char": args.use_space_char } + elif self.rec_algorithm == 'ViTSTR': + postprocess_params = { + 'name': 'ViTSTRLabelDecode', + "character_dict_path": args.rec_char_dict_path, + "use_space_char": args.use_space_char + } self.postprocess_op = build_post_process(postprocess_params) self.predictor, self.input_tensor, self.output_tensors, self.config = \ utility.create_predictor(args, 'rec', logger) @@ -96,15 +102,22 @@ class TextRecognizer(object): def resize_norm_img(self, img, max_wh_ratio): imgC, imgH, imgW = self.rec_image_shape - if self.rec_algorithm == 'NRTR': + if self.rec_algorithm == 'NRTR' or self.rec_algorithm == 'ViTSTR': img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # return padding_im image_pil = Image.fromarray(np.uint8(img)) - img = image_pil.resize([100, 32], Image.ANTIALIAS) + if self.rec_algorithm == 'ViTSTR': + img = image_pil.resize([imgW, imgH], Image.BICUBIC) + else: + img = image_pil.resize([imgW, imgH], Image.ANTIALIAS) img = np.array(img) norm_img = np.expand_dims(img, -1) norm_img = norm_img.transpose((2, 0, 1)) - return norm_img.astype(np.float32) / 128. - 1. + if self.rec_algorithm == 'ViTSTR': + norm_img = norm_img.astype(np.float32) / 255. + else: + norm_img = norm_img.astype(np.float32) / 128. - 1. + return norm_img assert imgC == img.shape[2] imgW = int((imgH * max_wh_ratio)) diff --git a/tools/program.py b/tools/program.py index aa0d2698cf..745c28b872 100755 --- a/tools/program.py +++ b/tools/program.py @@ -307,7 +307,8 @@ def train(config, train_stats.update(stats) if log_writer is not None and dist.get_rank() == 0: - log_writer.log_metrics(metrics=train_stats.get(), prefix="TRAIN", step=global_step) + log_writer.log_metrics( + metrics=train_stats.get(), prefix="TRAIN", step=global_step) if dist.get_rank() == 0 and ( (global_step > 0 and global_step % print_batch_step == 0) or @@ -354,7 +355,8 @@ def train(config, # logger metric if log_writer is not None: - log_writer.log_metrics(metrics=cur_metric, prefix="EVAL", step=global_step) + log_writer.log_metrics( + metrics=cur_metric, prefix="EVAL", step=global_step) if cur_metric[main_indicator] >= best_model_dict[ main_indicator]: @@ -377,11 +379,18 @@ def train(config, logger.info(best_str) # logger best metric if log_writer is not None: - log_writer.log_metrics(metrics={ - "best_{}".format(main_indicator): best_model_dict[main_indicator] - }, prefix="EVAL", step=global_step) - - log_writer.log_model(is_best=True, prefix="best_accuracy", metadata=best_model_dict) + log_writer.log_metrics( + metrics={ + "best_{}".format(main_indicator): + best_model_dict[main_indicator] + }, + prefix="EVAL", + step=global_step) + + log_writer.log_model( + is_best=True, + prefix="best_accuracy", + metadata=best_model_dict) reader_start = time.time() if dist.get_rank() == 0: @@ -413,7 +422,8 @@ def train(config, epoch=epoch, global_step=global_step) if log_writer is not None: - log_writer.log_model(is_best=False, prefix='iter_epoch_{}'.format(epoch)) + log_writer.log_model( + is_best=False, prefix='iter_epoch_{}'.format(epoch)) best_str = 'best metric, {}'.format(', '.join( ['{}: {}'.format(k, v) for k, v in best_model_dict.items()])) @@ -564,7 +574,8 @@ def preprocess(is_train=False): assert alg in [ 'EAST', 'DB', 'SAST', 'Rosetta', 'CRNN', 'STARNet', 'RARE', 'SRN', 'CLS', 'PGNet', 'Distillation', 'NRTR', 'TableAttn', 'SAR', 'PSE', - 'SEED', 'SDMGR', 'LayoutXLM', 'LayoutLM', 'PREN', 'FCE', 'SVTR' + 'SEED', 'SDMGR', 'LayoutXLM', 'LayoutLM', 'PREN', 'FCE', 'SVTR', + 'ViTSTR' ] if use_xpu: @@ -585,7 +596,8 @@ def preprocess(is_train=False): vdl_writer_path = '{}/vdl/'.format(save_model_dir) log_writer = VDLLogger(save_model_dir) loggers.append(log_writer) - if ('use_wandb' in config['Global'] and config['Global']['use_wandb']) or 'wandb' in config: + if ('use_wandb' in config['Global'] and + config['Global']['use_wandb']) or 'wandb' in config: save_dir = config['Global']['save_model_dir'] wandb_writer_path = "{}/wandb".format(save_dir) if "wandb" in config: From c956f4a79c229c907ba60a7d4bf87024a4e6dc76 Mon Sep 17 00:00:00 2001 From: Topdu <784990967@qq.com> Date: Fri, 27 May 2022 07:50:48 +0000 Subject: [PATCH 02/45] fix cpu_thread and precision --- test_tipc/configs/rec_svtrnet/train_infer_python.txt | 4 ++-- test_tipc/configs/rec_vitstr/train_infer_python.txt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test_tipc/configs/rec_svtrnet/train_infer_python.txt b/test_tipc/configs/rec_svtrnet/train_infer_python.txt index 72526063e0..df7638faad 100644 --- a/test_tipc/configs/rec_svtrnet/train_infer_python.txt +++ b/test_tipc/configs/rec_svtrnet/train_infer_python.txt @@ -40,10 +40,10 @@ infer_quant:False inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/EN_symbol_dict.txt --rec_image_shape="1,224,224" --rec_algorithm="SVTR" --use_gpu:True|False --enable_mkldnn:False ---cpu_threads:1|6 +--cpu_threads:6 --rec_batch_num:1|6 --use_tensorrt:False ---precision:fp32|int8 +--precision:fp32 --rec_model_dir: --image_dir:./inference/rec_inference --save_log_path:./test/output/ diff --git a/test_tipc/configs/rec_vitstr/train_infer_python.txt b/test_tipc/configs/rec_vitstr/train_infer_python.txt index 6c7af1fb87..8b58e220f7 100644 --- a/test_tipc/configs/rec_vitstr/train_infer_python.txt +++ b/test_tipc/configs/rec_vitstr/train_infer_python.txt @@ -40,10 +40,10 @@ infer_quant:False inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/EN_symbol_dict.txt --rec_image_shape="1,224,224" --rec_algorithm="ViTSTR" --use_gpu:True|False --enable_mkldnn:False ---cpu_threads:1|6 +--cpu_threads:6 --rec_batch_num:1|6 --use_tensorrt:False ---precision:fp32|int8 +--precision:fp32 --rec_model_dir: --image_dir:./inference/rec_inference --save_log_path:./test/output/ From bc5522b6f38dc1119bd3af06f985ee80bc9bda9d Mon Sep 17 00:00:00 2001 From: Topdu <784990967@qq.com> Date: Fri, 27 May 2022 08:29:32 +0000 Subject: [PATCH 03/45] fix svtr tipc --- test_tipc/configs/rec_svtrnet/train_infer_python.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test_tipc/configs/rec_svtrnet/train_infer_python.txt b/test_tipc/configs/rec_svtrnet/train_infer_python.txt index df7638faad..60fd25ad4f 100644 --- a/test_tipc/configs/rec_svtrnet/train_infer_python.txt +++ b/test_tipc/configs/rec_svtrnet/train_infer_python.txt @@ -37,7 +37,7 @@ export2:null train_model:./inference/rec_svtrnet_train/best_accuracy infer_export:tools/export_model.py -c test_tipc/configs/rec_svtrnet/rec_svtrnet.yml -o infer_quant:False -inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/EN_symbol_dict.txt --rec_image_shape="1,224,224" --rec_algorithm="SVTR" +inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/EN_symbol_dict.txt --rec_image_shape="3,64,256" --rec_algorithm="SVTR" --use_gpu:True|False --enable_mkldnn:False --cpu_threads:6 @@ -50,4 +50,4 @@ inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/EN_symbo --benchmark:True null:null ===========================infer_benchmark_params========================== -random_infer_input:[{float32,[1,224,224]}] +random_infer_input:[{float32,[1=3,64,256]}] From c269e1e42f79d33aa87d5e0ba49b488c56f97ae4 Mon Sep 17 00:00:00 2001 From: Topdu <784990967@qq.com> Date: Mon, 30 May 2022 08:36:11 +0000 Subject: [PATCH 04/45] modify vitstr name --- configs/rec/rec_mtb_nrtr.yml | 2 +- ...{rec_vitstr.yml => rec_vitstr_none_ce.yml} | 4 +-- doc/doc_ch/algorithm_overview.md | 2 +- doc/doc_ch/algorithm_rec_vitstr.md | 26 +++++++++---------- doc/doc_en/algorithm_overview_en.md | 2 +- doc/doc_en/algorithm_rec_vitstr_en.md | 14 +++++----- ppocr/losses/__init__.py | 7 +++-- .../{rec_ce_smooth_loss.py => rec_ce_loss.py} | 4 +-- .../configs/rec_mtb_nrtr/rec_mtb_nrtr.yml | 2 +- .../rec_mtb_nrtr/train_infer_python.txt | 4 +-- .../rec_vitstr_none_ce.yml} | 4 +-- .../train_infer_python.txt | 10 +++---- 12 files changed, 40 insertions(+), 41 deletions(-) rename configs/rec/{rec_vitstr.yml => rec_vitstr_none_ce.yml} (97%) rename ppocr/losses/{rec_ce_smooth_loss.py => rec_ce_loss.py} (93%) rename test_tipc/configs/{rec_vitstr/rec_vitstr.yml => rec_vitstr_none_ce/rec_vitstr_none_ce.yml} (97%) rename test_tipc/configs/{rec_vitstr => rec_vitstr_none_ce}/train_infer_python.txt (82%) diff --git a/configs/rec/rec_mtb_nrtr.yml b/configs/rec/rec_mtb_nrtr.yml index dfe2cc9811..405674882c 100644 --- a/configs/rec/rec_mtb_nrtr.yml +++ b/configs/rec/rec_mtb_nrtr.yml @@ -49,7 +49,7 @@ Architecture: Loss: - name: CESmoothingLoss + name: CELoss smoothing: True PostProcess: diff --git a/configs/rec/rec_vitstr.yml b/configs/rec/rec_vitstr_none_ce.yml similarity index 97% rename from configs/rec/rec_vitstr.yml rename to configs/rec/rec_vitstr_none_ce.yml index 005db0184a..065cc785f8 100644 --- a/configs/rec/rec_vitstr.yml +++ b/configs/rec/rec_vitstr_none_ce.yml @@ -3,7 +3,7 @@ Global: epoch_num: 20 log_smooth_window: 20 print_batch_step: 10 - save_model_dir: ./output/rec/vitstr/ + save_model_dir: ./output/rec/vitstr_none_ce/ save_epoch_step: 1 # evaluation is run every 2000 iterations after the 0th iteration# eval_batch_step: [0, 50] @@ -44,7 +44,7 @@ Architecture: name: CTCHead Loss: - name: CESmoothingLoss + name: CELoss smoothing: False with_all: True diff --git a/doc/doc_ch/algorithm_overview.md b/doc/doc_ch/algorithm_overview.md index 934ac08537..4196ec05af 100755 --- a/doc/doc_ch/algorithm_overview.md +++ b/doc/doc_ch/algorithm_overview.md @@ -85,7 +85,7 @@ |SAR|Resnet31| 87.20% | rec_r31_sar | [训练模型](https://paddleocr.bj.bcebos.com/dygraph_v2.1/rec/rec_r31_sar_train.tar) | |SEED|Aster_Resnet| 85.35% | rec_resnet_stn_bilstm_att | [训练模型](https://paddleocr.bj.bcebos.com/dygraph_v2.1/rec/rec_resnet_stn_bilstm_att.tar) | |SVTR|SVTR-Tiny| 89.25% | rec_svtr_tiny_none_ctc_en | [训练模型](https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/rec_svtr_tiny_none_ctc_en_train.tar) | -|ViTSTR|ViTSTR| 79.82% | rec_vitstr_none_ctc_en | [训练模型](https://paddleocr.bj.bcebos.com/rec_vitstr_none_none_train.tar) | +|ViTSTR|ViTSTR| 79.82% | rec_vitstr_none_ce_en | [训练模型](https://paddleocr.bj.bcebos.com/rec_vitstr_none_ce_train.tar) | diff --git a/doc/doc_ch/algorithm_rec_vitstr.md b/doc/doc_ch/algorithm_rec_vitstr.md index bd83b8d9c2..ab12be7202 100644 --- a/doc/doc_ch/algorithm_rec_vitstr.md +++ b/doc/doc_ch/algorithm_rec_vitstr.md @@ -27,7 +27,7 @@ |模型|骨干网络|配置文件|Acc|下载链接| | --- | --- | --- | --- | --- | -|ViTSTR|ViTSTR|[rec_vitstr.yml](../../configs/rec/rec_vitstr.yml)|79.82%|[训练模型](https://paddleocr.bj.bcebos.com/rec_vitstr_none_none_train.tar)| +|ViTSTR|ViTSTR|[rec_vitstr_none_ce.yml](../../configs/rec/rec_vitstr_none_ce.yml)|79.82%|[训练模型](https://paddleocr.bj.bcebos.com/rec_vitstr_none_ce_train.tar)| ## 2. 环境配置 @@ -40,7 +40,7 @@ ### 3.1 模型训练 -请参考[文本识别训练教程](./recognition.md)。PaddleOCR对代码进行了模块化,训练`ViTSTR`识别模型时需要**更换配置文件**为`ViTSTR`的[配置文件](../../configs/rec/rec_ViTSTR.yml)。 +请参考[文本识别训练教程](./recognition.md)。PaddleOCR对代码进行了模块化,训练`ViTSTR`识别模型时需要**更换配置文件**为`ViTSTR`的[配置文件](../../configs/rec/rec_vitstr_none_ce.yml)。 #### 启动训练 @@ -48,10 +48,10 @@ 具体地,在完成数据准备后,便可以启动训练,训练命令如下: ```shell #单卡训练(训练周期长,不建议) -python3 tools/train.py -c configs/rec/rec_vitstr.yml +python3 tools/train.py -c configs/rec/rec_vitstr_none_ce.yml #多卡训练,通过--gpus参数指定卡号 -python3 -m paddle.distributed.launch --gpus '0,1,2,3' tools/train.py -c configs/rec/rec_vitstr.yml +python3 -m paddle.distributed.launch --gpus '0,1,2,3' tools/train.py -c configs/rec/rec_vitstr_none_ce.yml ``` @@ -61,7 +61,7 @@ python3 -m paddle.distributed.launch --gpus '0,1,2,3' tools/train.py -c configs ```shell # 注意将pretrained_model的路径设置为本地路径。 -python3 -m paddle.distributed.launch --gpus '0' tools/eval.py -c configs/rec/rec_vitstr.yml -o Global.pretrained_model=./rec_vitstr_train/best_accuracy +python3 -m paddle.distributed.launch --gpus '0' tools/eval.py -c configs/rec/rec_vitstr_none_ce.yml -o Global.pretrained_model=./rec_vitstr_none_ce_train/best_accuracy ``` @@ -70,7 +70,7 @@ python3 -m paddle.distributed.launch --gpus '0' tools/eval.py -c configs/rec/rec 使用如下命令进行单张图片预测: ```shell # 注意将pretrained_model的路径设置为本地路径。 -python3 tools/infer_rec.py -c configs/rec/rec_vitstr.yml -o Global.infer_img='./doc/imgs_words_en/word_10.png' Global.pretrained_model=./rec_vitstr_train/best_accuracy +python3 tools/infer_rec.py -c configs/rec/rec_vitstr_none_ce.yml -o Global.infer_img='./doc/imgs_words_en/word_10.png' Global.pretrained_model=./rec_vitstr_none_ce_train/best_accuracy # 预测文件夹下所有图像时,可修改infer_img为文件夹,如 Global.infer_img='./doc/imgs_words_en/'。 ``` @@ -80,15 +80,15 @@ python3 tools/infer_rec.py -c configs/rec/rec_vitstr.yml -o Global.infer_img='./ ### 4.1 Python推理 -首先将训练得到best模型,转换成inference model。这里以训练完成的模型为例([模型下载地址](https://paddleocr.bj.bcebos.com/rec_vitstr_none_none_train.tar) ),可以使用如下命令进行转换: +首先将训练得到best模型,转换成inference model。这里以训练完成的模型为例([模型下载地址](https://paddleocr.bj.bcebos.com/rec_vitstr_none_ce_train.tar) ),可以使用如下命令进行转换: ```shell # 注意将pretrained_model的路径设置为本地路径。 -python3 tools/export_model.py -c configs/rec/rec_vitstr.yml -o Global.pretrained_model=./rec_vitstr_train/best_accuracy Global.save_inference_dir=./inference/rec_vitstr/ +python3 tools/export_model.py -c configs/rec/rec_vitstr_none_ce.yml -o Global.pretrained_model=./rec_vitstr_none_ce_train/best_accuracy Global.save_inference_dir=./inference/rec_vitstr/ ``` **注意:** - 如果您是在自己的数据集上训练的模型,并且调整了字典文件,请注意修改配置文件中的`character_dict_path`是否是所需要的字典文件。 -- 如果您修改了训练时的输入大小,请修改`tools/export_model.py`文件中的对应NRTR的`infer_shape`。 +- 如果您修改了训练时的输入大小,请修改`tools/export_model.py`文件中的对应ViTSTR的`infer_shape`。 转换成功后,在目录下有三个文件: ``` @@ -110,20 +110,20 @@ python3 tools/infer/predict_rec.py --image_dir='./doc/imgs_words_en/word_10.png' 执行命令后,上面图像的预测结果(识别的文本和得分)会打印到屏幕上,示例如下: 结果如下: ```shell -Predicts of ./doc/imgs_words_en/word_10.png:('pain', 0.9265879392623901) +Predicts of ./doc/imgs_words_en/word_10.png:('pain', 0.9998350143432617) ``` **注意**: - 训练上述模型采用的图像分辨率是[1,224,224],需要通过参数`rec_image_shape`设置为您训练时的识别图像形状。 - 在推理时需要设置参数`rec_char_dict_path`指定字典,如果您修改了字典,请修改该参数为您的字典文件。 -- 如果您修改了预处理方法,需修改`tools/infer/predict_rec.py`中NRTR的预处理为您的预处理方法。 +- 如果您修改了预处理方法,需修改`tools/infer/predict_rec.py`中ViTSTR的预处理为您的预处理方法。 ### 4.2 C++推理部署 -由于C++预处理后处理还未支持NRTR,所以暂未支持 +由于C++预处理后处理还未支持ViTSTR,所以暂未支持 ### 4.3 Serving服务化部署 @@ -139,7 +139,7 @@ Predicts of ./doc/imgs_words_en/word_10.png:('pain', 0.9265879392623901) ## 5. FAQ 1. 在`ViTSTR`论文中,使用在ImageNet1k上的预训练权重进行初始化训练,我们在训练未采用预训练权重,最终精度没有变化甚至有所提高。 -2. 我们仅仅复现了`ViTSTR`中的tiny版本,如果有需要使用small、base版本,可直接使用源开源repo中的预训练权重转为Paddle权重即可使用。 +2. 我们仅仅复现了`ViTSTR`中的tiny版本,如果需要使用small、base版本,可将[ViTSTR源repo](https://github.com/roatienza/deep-text-recognition-benchmark) 中的预训练权重转为Paddle权重使用。 ## 引用 diff --git a/doc/doc_en/algorithm_overview_en.md b/doc/doc_en/algorithm_overview_en.md index 213d95807d..f2284542ed 100755 --- a/doc/doc_en/algorithm_overview_en.md +++ b/doc/doc_en/algorithm_overview_en.md @@ -84,7 +84,7 @@ Refer to [DTRB](https://arxiv.org/abs/1904.01906), the training and evaluation r |SAR|Resnet31| 87.20% | rec_r31_sar | [trained model](https://paddleocr.bj.bcebos.com/dygraph_v2.1/rec/rec_r31_sar_train.tar) | |SEED|Aster_Resnet| 85.35% | rec_resnet_stn_bilstm_att | [trained model](https://paddleocr.bj.bcebos.com/dygraph_v2.1/rec/rec_resnet_stn_bilstm_att.tar) | |SVTR|SVTR-Tiny| 89.25% | rec_svtr_tiny_none_ctc_en | [trained model](https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/rec_svtr_tiny_none_ctc_en_train.tar) | -|ViTSTR|ViTSTR| 79.82% | rec_vitstr_none_ctc_en | [trained model](https://paddleocr.bj.bcebos.com/rec_vitstr_none_none_train.tar) | +|ViTSTR|ViTSTR| 79.82% | rec_vitstr_none_ce_en | [trained model](https://paddleocr.bj.bcebos.com/rec_vitstr_none_none_train.tar) | diff --git a/doc/doc_en/algorithm_rec_vitstr_en.md b/doc/doc_en/algorithm_rec_vitstr_en.md index 666798c3e0..b6f26d39e2 100644 --- a/doc/doc_en/algorithm_rec_vitstr_en.md +++ b/doc/doc_en/algorithm_rec_vitstr_en.md @@ -25,7 +25,7 @@ Using MJSynth and SynthText two text recognition datasets for training, and eval |Model|Backbone|config|Acc|Download link| | --- | --- | --- | --- | --- | -|ViTSTR|ViTSTR|[rec_vitstr.yml](../../configs/rec/rec_vitstr.yml)|79.82%|[训练模型](https://paddleocr.bj.bcebos.com/rec_vitstr_none_none_train.tar)| +|ViTSTR|ViTSTR|[rec_vitstr_none_ce.yml](../../configs/rec/rec_vitstr_none_ce.yml)|79.82%|[训练模型](https://paddleocr.bj.bcebos.com/rec_vitstr_none_none_train.tar)| ## 2. Environment @@ -43,24 +43,24 @@ Specifically, after the data preparation is completed, the training can be start ``` #Single GPU training (long training period, not recommended) -python3 tools/train.py -c configs/rec/rec_vitstr.yml +python3 tools/train.py -c configs/rec/rec_vitstr_none_ce.yml #Multi GPU training, specify the gpu number through the --gpus parameter -python3 -m paddle.distributed.launch --gpus '0,1,2,3' tools/train.py -c configs/rec/rec_vitstr.yml +python3 -m paddle.distributed.launch --gpus '0,1,2,3' tools/train.py -c configs/rec/rec_vitstr_none_ce.yml ``` Evaluation: ``` # GPU evaluation -python3 -m paddle.distributed.launch --gpus '0' tools/eval.py -c configs/rec/rec_vitstr.yml -o Global.pretrained_model={path/to/weights}/best_accuracy +python3 -m paddle.distributed.launch --gpus '0' tools/eval.py -c configs/rec/rec_vitstr_none_ce.yml -o Global.pretrained_model={path/to/weights}/best_accuracy ``` Prediction: ``` # The configuration file used for prediction must match the training -python3 tools/infer_rec.py -c configs/rec/rec_vitstr.yml -o Global.infer_img='./doc/imgs_words_en/word_10.png' Global.pretrained_model=./rec_vitstr_train/best_accuracy +python3 tools/infer_rec.py -c configs/rec/rec_vitstr_none_ce.yml -o Global.infer_img='./doc/imgs_words_en/word_10.png' Global.pretrained_model=./rec_vitstr_none_ce_train/best_accuracy ``` @@ -71,7 +71,7 @@ python3 tools/infer_rec.py -c configs/rec/rec_vitstr.yml -o Global.infer_img='./ First, the model saved during the ViTSTR text recognition training process is converted into an inference model. ( [Model download link](https://paddleocr.bj.bcebos.com/rec_vitstr_none_none_train.tar)) ), you can use the following command to convert: ``` -python3 tools/export_model.py -c configs/rec/rec_vitstr.yml -o Global.pretrained_model=./rec_vitstr_train/best_accuracy Global.save_inference_dir=./inference/rec_vitstr +python3 tools/export_model.py -c configs/rec/rec_vitstr_none_ce.yml -o Global.pretrained_model=./rec_vitstr_none_ce_train/best_accuracy Global.save_inference_dir=./inference/rec_vitstr ``` **Note:** @@ -98,7 +98,7 @@ python3 tools/infer/predict_rec.py --image_dir='./doc/imgs_words_en/word_10.png' After executing the command, the prediction result (recognized text and score) of the image above is printed to the screen, an example is as follows: The result is as follows: ```shell -Predicts of ./doc/imgs_words_en/word_10.png:('pain', 0.9265879392623901) +Predicts of ./doc/imgs_words_en/word_10.png:('pain', 0.9998350143432617) ``` diff --git a/ppocr/losses/__init__.py b/ppocr/losses/__init__.py index 6c4545eb21..7bea87f62f 100755 --- a/ppocr/losses/__init__.py +++ b/ppocr/losses/__init__.py @@ -30,7 +30,7 @@ from .det_fce_loss import FCELoss from .rec_ctc_loss import CTCLoss from .rec_att_loss import AttentionLoss from .rec_srn_loss import SRNLoss -from .rec_ce_smooth_loss import CESmoothingLoss +from .rec_ce_loss import CELoss from .rec_sar_loss import SARLoss from .rec_aster_loss import AsterLoss from .rec_pren_loss import PRENLoss @@ -60,9 +60,8 @@ def build_loss(config): support_dict = [ 'DBLoss', 'PSELoss', 'EASTLoss', 'SASTLoss', 'FCELoss', 'CTCLoss', 'ClsLoss', 'AttentionLoss', 'SRNLoss', 'PGLoss', 'CombinedLoss', - 'CESmoothingLoss', 'TableAttentionLoss', 'SARLoss', 'AsterLoss', - 'SDMGRLoss', 'VQASerTokenLayoutLMLoss', 'LossFromOutput', 'PRENLoss', - 'MultiLoss' + 'CELoss', 'TableAttentionLoss', 'SARLoss', 'AsterLoss', 'SDMGRLoss', + 'VQASerTokenLayoutLMLoss', 'LossFromOutput', 'PRENLoss', 'MultiLoss' ] config = copy.deepcopy(config) module_name = config.pop('name') diff --git a/ppocr/losses/rec_ce_smooth_loss.py b/ppocr/losses/rec_ce_loss.py similarity index 93% rename from ppocr/losses/rec_ce_smooth_loss.py rename to ppocr/losses/rec_ce_loss.py index 22243ed41f..b837ac27bb 100644 --- a/ppocr/losses/rec_ce_smooth_loss.py +++ b/ppocr/losses/rec_ce_loss.py @@ -3,9 +3,9 @@ from paddle import nn import paddle.nn.functional as F -class CESmoothingLoss(nn.Layer): +class CELoss(nn.Layer): def __init__(self, smoothing=True, with_all=False, **kwargs): - super(CESmoothingLoss, self).__init__() + super(CELoss, self).__init__() self.loss_func = nn.CrossEntropyLoss(reduction='mean', ignore_index=0) self.smoothing = smoothing self.with_all = with_all diff --git a/test_tipc/configs/rec_mtb_nrtr/rec_mtb_nrtr.yml b/test_tipc/configs/rec_mtb_nrtr/rec_mtb_nrtr.yml index 3936ab58ad..ba6728d836 100644 --- a/test_tipc/configs/rec_mtb_nrtr/rec_mtb_nrtr.yml +++ b/test_tipc/configs/rec_mtb_nrtr/rec_mtb_nrtr.yml @@ -49,7 +49,7 @@ Architecture: Loss: - name: CESmoothingLoss + name: CELoss smoothing: True PostProcess: diff --git a/test_tipc/configs/rec_mtb_nrtr/train_infer_python.txt b/test_tipc/configs/rec_mtb_nrtr/train_infer_python.txt index de6de5a0ca..20d984e38b 100644 --- a/test_tipc/configs/rec_mtb_nrtr/train_infer_python.txt +++ b/test_tipc/configs/rec_mtb_nrtr/train_infer_python.txt @@ -40,10 +40,10 @@ infer_quant:False inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/EN_symbol_dict.txt --rec_image_shape="1,32,100" --rec_algorithm="NRTR" --use_gpu:True|False --enable_mkldnn:True|False ---cpu_threads:1|6 +--cpu_threads:6 --rec_batch_num:1|6 --use_tensorrt:True|False ---precision:fp32|int8 +--precision:fp32 --rec_model_dir: --image_dir:./inference/rec_inference --save_log_path:./test/output/ diff --git a/test_tipc/configs/rec_vitstr/rec_vitstr.yml b/test_tipc/configs/rec_vitstr_none_ce/rec_vitstr_none_ce.yml similarity index 97% rename from test_tipc/configs/rec_vitstr/rec_vitstr.yml rename to test_tipc/configs/rec_vitstr_none_ce/rec_vitstr_none_ce.yml index 427bce4b5a..ccd93c9eaf 100644 --- a/test_tipc/configs/rec_vitstr/rec_vitstr.yml +++ b/test_tipc/configs/rec_vitstr_none_ce/rec_vitstr_none_ce.yml @@ -3,7 +3,7 @@ Global: epoch_num: 20 log_smooth_window: 20 print_batch_step: 10 - save_model_dir: ./output/rec/vitstr/ + save_model_dir: ./output/rec/vitstr_none_ce/ save_epoch_step: 1 # evaluation is run every 2000 iterations after the 0th iteration# eval_batch_step: [0, 2000] @@ -43,7 +43,7 @@ Architecture: name: CTCHead Loss: - name: CESmoothingLoss + name: CELoss smoothing: False with_all: True diff --git a/test_tipc/configs/rec_vitstr/train_infer_python.txt b/test_tipc/configs/rec_vitstr_none_ce/train_infer_python.txt similarity index 82% rename from test_tipc/configs/rec_vitstr/train_infer_python.txt rename to test_tipc/configs/rec_vitstr_none_ce/train_infer_python.txt index 8b58e220f7..04c5742ea2 100644 --- a/test_tipc/configs/rec_vitstr/train_infer_python.txt +++ b/test_tipc/configs/rec_vitstr_none_ce/train_infer_python.txt @@ -13,7 +13,7 @@ train_infer_img_dir:./inference/rec_inference null:null ## trainer:norm_train -norm_train:tools/train.py -c test_tipc/configs/rec_vitstr/rec_vitstr.yml -o +norm_train:tools/train.py -c test_tipc/configs/rec_vitstr_none_ce/rec_vitstr_none_ce.yml -o pact_train:null fpgm_train:null distill_train:null @@ -21,21 +21,21 @@ null:null null:null ## ===========================eval_params=========================== -eval:tools/eval.py -c test_tipc/configs/rec_vitstr/rec_vitstr.yml -o +eval:tools/eval.py -c test_tipc/configs/rec_vitstr_none_ce/rec_vitstr_none_ce.yml -o null:null ## ===========================infer_params=========================== Global.save_inference_dir:./output/ Global.checkpoints: -norm_export:tools/export_model.py -c test_tipc/configs/rec_vitstr/rec_vitstr.yml -o +norm_export:tools/export_model.py -c test_tipc/configs/rec_vitstr_none_ce/rec_vitstr_none_ce.yml -o quant_export:null fpgm_export:null distill_export:null export1:null export2:null ## -train_model:./inference/rec_vitstr_train/best_accuracy -infer_export:tools/export_model.py -c test_tipc/configs/rec_vitstr/rec_vitstr.yml -o +train_model:./inference/rec_vitstr_none_ce_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/rec_vitstr_none_ce/rec_vitstr_none_ce.yml -o infer_quant:False inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/EN_symbol_dict.txt --rec_image_shape="1,224,224" --rec_algorithm="ViTSTR" --use_gpu:True|False From 275c5a2ba93d0791e7b8009e3a595f4ac7554989 Mon Sep 17 00:00:00 2001 From: Topdu <784990967@qq.com> Date: Mon, 30 May 2022 08:38:07 +0000 Subject: [PATCH 05/45] modify vitstr config batchsize --- configs/rec/rec_vitstr_none_ce.yml | 2 +- test_tipc/configs/rec_vitstr_none_ce/rec_vitstr_none_ce.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/configs/rec/rec_vitstr_none_ce.yml b/configs/rec/rec_vitstr_none_ce.yml index 065cc785f8..c71eba0cc2 100644 --- a/configs/rec/rec_vitstr_none_ce.yml +++ b/configs/rec/rec_vitstr_none_ce.yml @@ -75,7 +75,7 @@ Train: shuffle: True batch_size_per_card: 48 drop_last: True - num_workers: 2 + num_workers: 8 Eval: dataset: diff --git a/test_tipc/configs/rec_vitstr_none_ce/rec_vitstr_none_ce.yml b/test_tipc/configs/rec_vitstr_none_ce/rec_vitstr_none_ce.yml index ccd93c9eaf..4e8151f599 100644 --- a/test_tipc/configs/rec_vitstr_none_ce/rec_vitstr_none_ce.yml +++ b/test_tipc/configs/rec_vitstr_none_ce/rec_vitstr_none_ce.yml @@ -75,7 +75,7 @@ Train: shuffle: True batch_size_per_card: 48 drop_last: True - num_workers: 2 + num_workers: 8 Eval: dataset: From 995a6d03e14c1cd9466533d1cec03578f84ccc7a Mon Sep 17 00:00:00 2001 From: tink2123 Date: Thu, 16 Jun 2022 11:07:06 +0800 Subject: [PATCH 06/45] add digital doc --- applications/corpus/digital.txt | 43 +++ applications/fonts/DS-DIGI.TTF | Bin 0 -> 24448 bytes applications/fonts/DS-DIGIB.TTF | Bin 0 -> 24896 bytes applications/光功率计数码管字符识别.md | 454 +++++++++++++++++++++++++ 4 files changed, 497 insertions(+) create mode 100644 applications/corpus/digital.txt create mode 100644 applications/fonts/DS-DIGI.TTF create mode 100644 applications/fonts/DS-DIGIB.TTF create mode 100644 applications/光功率计数码管字符识别.md diff --git a/applications/corpus/digital.txt b/applications/corpus/digital.txt new file mode 100644 index 0000000000..26b06e784e --- /dev/null +++ b/applications/corpus/digital.txt @@ -0,0 +1,43 @@ +46.39 +40.08 +89.52 +-71.93 +23.19 +-81.02 +-34.09 +05.87 +-67.80 +-51.56 +-34.58 +37.91 +56.98 +29.01 +-90.13 +35.55 +66.07 +-90.35 +-50.93 +42.42 +21.40 +-30.99 +-71.78 +25.60 +-48.69 +-72.28 +-17.55 +-99.93 +-47.35 +-64.89 +-31.28 +-90.01 +05.17 +30.91 +30.56 +-06.90 +79.05 +67.74 +-32.31 +94.22 +28.75 +51.03 +-58.96 diff --git a/applications/fonts/DS-DIGI.TTF b/applications/fonts/DS-DIGI.TTF new file mode 100644 index 0000000000000000000000000000000000000000..09258773c7219ad9e60b92b918e3c50b58f43c9e GIT binary patch literal 24448 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 + +目前光功率计缺少将数据直接输出的功能,需要人工读数。这一项工作单调重复,如果可以使用机器替代人工,将节约大量成本。针对上述问题,希望通过摄像头拍照->智能读数的方式高效地完成此任务。 + +为实现智能读数,通常会采取文本检测+文本识别的方案: + +第一步,使用文本检测模型定位出光功率计中的数字部分; + +第二步,使用文本识别模型获得准确的数字和单位信息。 + +本项目主要介绍如何完成第二步文本识别部分。 + +## 2. PaddleOCR 快速使用 + +PaddleOCR 旨在打造一套丰富、领先、且实用的OCR工具库,助力开发者训练出更好的模型,并应用落地。 + +![](https://github.com/PaddlePaddle/PaddleOCR/raw/release/2.5/doc/imgs_results/ch_ppocr_mobile_v2.0/test_add_91.jpg) + + +官方提供了适用于通用场景的高精轻量模型,首先使用官方提供的 PP-OCRv3 模型预测图片,验证下当前模型在光功率计场景上的效果。 + +- 准备环境 + +``` +python3 -m pip install -U pip +python3 -m pip install paddleocr +``` + + +- 测试效果 + +测试图: + +![](https://ai-studio-static-online.cdn.bcebos.com/8dca91f016884e16ad9216d416da72ea08190f97d87b4be883f15079b7ebab9a) + + +``` +paddleocr --lang=ch --det=Fase --image_dir=data +``` + +得到如下测试结果: + +``` +('.7000', 0.6885431408882141) +``` + +发现数字识别较准,然而对负号和小数点识别不准确。 由于PP-OCRv3的训练数据大多为通用场景数据,在特定的场景上效果可能不够好。因此需要基于场景数据进行微调。 + +下面就主要介绍如何在光功率计(数码管)场景上微调训练。 + + +## 3. 开始训练 + +### 3.1 数据准备 + +特定的工业场景往往很难获取开源的真实数据集,光功率计也是如此。在实际工业场景中,可以通过摄像头采集的方法收集大量真实数据,本例中重点介绍数据合成方法和真实数据挖掘方法,如何利用有限的数据优化模型精度。 + +数据集分为两个部分:合成数据,真实数据, 其中合成数据由 text_renderer 工具批量生成得到, 真实数据通过爬虫等方式在百度图片中搜索并使用 PPOCRLabel 标注得到。 + + +- 合成数据 + +本例中数据合成工具使用的是 [text_renderer](https://github.com/Sanster/text_renderer), 该工具可以合成用于文本识别训练的文本行数据: + +![](https://github.com/oh-my-ocr/text_renderer/raw/master/example_data/effect_layout_image/char_spacing_compact.jpg) + +![](https://github.com/oh-my-ocr/text_renderer/raw/master/example_data/effect_layout_image/color_image.jpg) + + +``` +export https_proxy=http://172.19.57.45:3128 +git clone https://github.com/oh-my-ocr/text_renderer +``` + +``` +import os +python3 setup.py develop +python3 -m pip install -r docker/requirements.txt +python3 main.py \ + --config example_data/example.py \ + --dataset img \ + --num_processes 2 \ + --log_period 10 +``` + +给定字体和语料,就可以合成较为丰富样式的文本行数据。 光功率计识别场景,目标是正确识别数码管文本,因此需要收集部分数码管字体,训练语料,用于合成文本识别数据。 + +将收集好的语料存放在 example_data 路径下: + +``` +ln -s ./fonts/DS* text_renderer/example_data/font/ +ln -s ./corpus/digital.txt text_renderer/example_data/text/ +``` + +修改 text_renderer/example_data/font_list/font_list.txt ,选择需要的字体开始合成: + +``` +python3 main.py \ + --config example_data/digital_example.py \ + --dataset img \ + --num_processes 2 \ + --log_period 10 +``` + +合成图片会被存在目录 text_renderer/example_data/digital/chn_data 下 + +查看合成的数据样例: + +![](https://ai-studio-static-online.cdn.bcebos.com/7d5774a273f84efba5b9ce7fd3f86e9ef24b6473e046444db69fa3ca20ac0986) + + +- 真实数据挖掘 + +模型训练需要使用真实数据作为评价指标,否则很容易过拟合到简单的合成数据中。没有开源数据的情况下,可以利用部分无标注数据+标注工具获得真实数据。 + + +1. 数据搜集 + +使用[爬虫工具](https://github.com/Joeclinton1/google-images-download.git)获得无标注数据 + +2. [PPOCRLabel](https://github.com/PaddlePaddle/PaddleOCR/tree/release/2.5/PPOCRLabel) 完成半自动标注 + +PPOCRLabel是一款适用于OCR领域的半自动化图形标注工具,内置PP-OCR模型对数据自动标注和重新识别。使用Python3和PyQT5编写,支持矩形框标注、表格标注、不规则文本标注、关键信息标注模式,导出格式可直接用于PaddleOCR检测和识别模型的训练。 + +![](https://github.com/PaddlePaddle/PaddleOCR/raw/release/2.5/PPOCRLabel/data/gif/steps_en.gif) + + +收集完数据后就可以进行分配了,验证集中一般都是真实数据,训练集中包含合成数据+真实数据。本例中标注了155张图片,其中训练集和验证集的数目为100和55。 + + +最终 `data` 文件夹应包含以下几部分: + +``` +|-data + |- synth_train.txt + |- real_train.txt + |- real_eval.txt + |- synthetic_data + |- word_001.png + |- word_002.jpg + |- word_003.jpg + | ... + |- real_data + |- word_001.png + |- word_002.jpg + |- word_003.jpg + | ... + ... +``` + +### 3.2 模型选择 + +本案例提供了2种文本识别模型:PP-OCRv3 识别模型 和 SVTR_Tiny: + +[PP-OCRv3 识别模型](https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.5/doc/doc_ch/PP-OCRv3_introduction.md):PP-OCRv3的识别模块是基于文本识别算法SVTR优化。SVTR不再采用RNN结构,通过引入Transformers结构更加有效地挖掘文本行图像的上下文信息,从而提升文本识别能力。并进行了一系列结构改进加速模型预测。 + +[SVTR_Tiny](https://arxiv.org/abs/2205.00159):SVTR提出了一种用于场景文本识别的单视觉模型,该模型在patch-wise image tokenization框架内,完全摒弃了序列建模,在精度具有竞争力的前提下,模型参数量更少,速度更快。 + +以上两个策略在自建中文数据集上的精度和速度对比如下: + +| ID | 策略 | 模型大小 | 精度 | 预测耗时(CPU + MKLDNN)| +|-----|-----|--------|----| --- | +| 01 | PP-OCRv2 | 8M | 74.8% | 8.54ms | +| 02 | SVTR_Tiny | 21M | 80.1% | 97ms | +| 03 | SVTR_LCNet(h32) | 12M | 71.9% | 6.6ms | +| 04 | SVTR_LCNet(h48) | 12M | 73.98% | 7.6ms | +| 05 | + GTC | 12M | 75.8% | 7.6ms | +| 06 | + TextConAug | 12M | 76.3% | 7.6ms | +| 07 | + TextRotNet | 12M | 76.9% | 7.6ms | +| 08 | + UDML | 12M | 78.4% | 7.6ms | +| 09 | + UIM | 12M | 79.4% | 7.6ms | + + +### 3.3 开始训练 + +首先下载 PaddleOCR 代码库 + +``` +git clone -b release/2.5 https://github.com/PaddlePaddle/PaddleOCR.git +``` + +PaddleOCR提供了训练脚本、评估脚本和预测脚本,本节将以 PP-OCRv3 中文识别模型为例: + +**Step1:下载预训练模型** + +首先下载 pretrain model,您可以下载训练好的模型在自定义数据上进行finetune + +``` +cd PaddleOCR/ +# 下载PP-OCRv3 中文预训练模型 +wget -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_rec_train.tar +# 解压模型参数 +cd pretrain_models +tar -xf ch_PP-OCRv3_rec_train.tar && rm -rf ch_PP-OCRv3_rec_train.tar +``` + +**Step2:自定义字典文件** + +接下来需要提供一个字典({word_dict_name}.txt),使模型在训练时,可以将所有出现的字符映射为字典的索引。 + +因此字典需要包含所有希望被正确识别的字符,{word_dict_name}.txt需要写成如下格式,并以 `utf-8` 编码格式保存: + +``` +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +- +. +``` + +word_dict.txt 每行有一个单字,将字符与数字索引映射在一起,“3.14” 将被映射成 [3, 11, 1, 4] + +* 内置字典 + +PaddleOCR内置了一部分字典,可以按需使用。 + +`ppocr/utils/ppocr_keys_v1.txt` 是一个包含6623个字符的中文字典 + +`ppocr/utils/ic15_dict.txt` 是一个包含36个字符的英文字典 + +* 自定义字典 + +内置字典面向通用场景,具体的工业场景中,可能需要识别特殊字符,或者只需识别某几个字符,此时自定义字典会更提升模型精度。例如在光功率计场景中,需要识别数字和单位。 + +遍历真实数据标签中的字符,制作字典`digital_dict.txt`如下所示: + +``` +- +. +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +B +E +F +H +L +N +T +W +d +k +m +n +o +z +``` + + + + +**Step3:修改配置文件** + +为了更好的使用预训练模型,训练推荐使用[ch_PP-OCRv3_rec_distillation.yml](../../configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml)配置文件,并参考下列说明修改配置文件: + +以 `ch_PP-OCRv3_rec_distillation.yml` 为例: +``` +Global: + ... + # 添加自定义字典,如修改字典请将路径指向新字典 + character_dict_path: ppocr/utils/dict/digital_dict.txt + ... + # 识别空格 + use_space_char: True + + +Optimizer: + ... + # 添加学习率衰减策略 + lr: + name: Cosine + learning_rate: 0.001 + ... + +... + +Train: + dataset: + # 数据集格式,支持LMDBDataSet以及SimpleDataSet + name: SimpleDataSet + # 数据集路径 + data_dir: ./data/ + # 训练集标签文件 + label_file_list: + - ./train_data/digital_img/digital_train.txt #11w + - ./train_data/digital_img/real_train.txt #100 + - ./train_data/digital_img/dbm_img/dbm.txt #3w + ratio_list: + - 0.3 + - 1.0 + - 1.0 + transforms: + ... + - RecResizeImg: + # 修改 image_shape 以适应长文本 + image_shape: [3, 48, 320] + ... + loader: + ... + # 单卡训练的batch_size + batch_size_per_card: 256 + ... + +Eval: + dataset: + # 数据集格式,支持LMDBDataSet以及SimpleDataSet + name: SimpleDataSet + # 数据集路径 + data_dir: ./data + # 验证集标签文件 + label_file_list: + - ./train_data/digital_img/real_val.txt + transforms: + ... + - RecResizeImg: + # 修改 image_shape 以适应长文本 + image_shape: [3, 48, 320] + ... + loader: + # 单卡验证的batch_size + batch_size_per_card: 256 + ... +``` +**注意,训练/预测/评估时的配置文件请务必与训练一致。** + +**Step4:启动训练** + +*如果您安装的是cpu版本,请将配置文件中的 `use_gpu` 字段修改为false* + +``` +# GPU训练 支持单卡,多卡训练 +# 训练数码管数据 训练日志会自动保存为 "{save_model_dir}" 下的train.log + +#单卡训练(训练周期长,不建议) +python3 tools/train.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml -o Global.pretrained_model=./pretrain_models/ch_PP-OCRv3_rec_train/best_accuracy + +#多卡训练,通过--gpus参数指定卡号 +python3 -m paddle.distributed.launch --gpus '0,1,2,3' tools/train.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml -o Global.pretrained_model=./pretrain_models/en_PP-OCRv3_rec_train/best_accuracy +``` + + +PaddleOCR支持训练和评估交替进行, 可以在 `configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml` 中修改 `eval_batch_step` 设置评估频率,默认每500个iter评估一次。评估过程中默认将最佳acc模型,保存为 `output/ch_PP-OCRv3_rec_distill/best_accuracy` 。 + +如果验证集很大,测试将会比较耗时,建议减少评估次数,或训练完再进行评估。 + +### SVTR_Tiny 训练 + +SVTR_Tiny 训练步骤与上面一致,SVTR支持的配置和模型训练权重可以参考[算法介绍文档](https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.5/doc/doc_ch/algorithm_rec_svtr.md) + +**Step1:下载预训练模型** + +``` +# 下载 SVTR_Tiny 中文识别预训练模型和配置文件 +wget https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/rec_svtr_tiny_none_ctc_ch_train.tar +# 解压模型参数 +tar -xf rec_svtr_tiny_none_ctc_ch_train.tar && rm -rf rec_svtr_tiny_none_ctc_ch_train.tar +``` +**Step2:自定义字典文件** + +字典依然使用自定义的 digital_dict.txt + +**Step3:修改配置文件** + +配置文件中对应修改字典路径和数据路径 + +**Step4:启动训练** + +``` +## 单卡训练 +python tools/train.py -c rec_svtr_tiny_none_ctc_ch_train/rec_svtr_tiny_6local_6global_stn_ch.yml \ + -o Global.pretrained_model=./rec_svtr_tiny_none_ctc_ch_train/best_accuracy +``` + +### 3.4 验证效果 + +**本例中的评估集和模型路径,可通过填写[问卷](todo:add link)获得** + +* 指标评估 + +训练中模型参数默认保存在`Global.save_model_dir`目录下。在评估指标时,需要设置`Global.checkpoints`指向保存的参数文件。评估数据集可以通过 `configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml` 修改Eval中的 `label_file_path` 设置。 + +``` +# GPU 评估, Global.checkpoints 为待测权重 +python3 -m paddle.distributed.launch --gpus '0' tools/eval.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml -o Global.checkpoints={path/to/weights}/best_accuracy +``` + +* 测试识别效果 + +使用 PaddleOCR 训练好的模型,可以通过以下脚本进行快速预测。 + +默认预测图片存储在 `infer_img` 里,通过 `-o Global.checkpoints` 加载训练好的参数文件: + +根据配置文件中设置的 `save_model_dir` 和 `save_epoch_step` 字段,会有以下几种参数被保存下来: + +``` +output/rec/ +├── best_accuracy.pdopt +├── best_accuracy.pdparams +├── best_accuracy.states +├── config.yml +├── iter_epoch_3.pdopt +├── iter_epoch_3.pdparams +├── iter_epoch_3.states +├── latest.pdopt +├── latest.pdparams +├── latest.states +└── train.log +``` + +其中 best_accuracy.* 是评估集上的最优模型;iter_epoch_x.* 是以 `save_epoch_step` 为间隔保存下来的模型;latest.* 是最后一个epoch的模型。 + +``` +# 预测英文结果 +python3 tools/infer_rec.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml -o Global.pretrained_model={path/to/weights}/best_accuracy Global.infer_img=test_digital.png +``` + +预测图片: + +![](https://ai-studio-static-online.cdn.bcebos.com/8dca91f016884e16ad9216d416da72ea08190f97d87b4be883f15079b7ebab9a) + + +得到输入图像的预测结果: + +``` +infer_img: test_digital.png + result: ('-70.00', 0.9998967) +``` From e1f947b961596623b7340700f8881938300833d7 Mon Sep 17 00:00:00 2001 From: andyjpaddle Date: Thu, 16 Jun 2022 09:35:09 +0000 Subject: [PATCH 07/45] add case --- applications/imgs/det.png | Bin 0 -> 66713 bytes applications/imgs/rec.png | Bin 0 -> 13765 bytes applications/imgs/sys_res.png | Bin 0 -> 291470 bytes applications/液晶屏读数识别.md | 577 +++++++++++++++++++++++++++++++++ 4 files changed, 577 insertions(+) create mode 100644 applications/imgs/det.png create mode 100644 applications/imgs/rec.png create mode 100644 applications/imgs/sys_res.png create mode 100644 applications/液晶屏读数识别.md diff --git a/applications/imgs/det.png b/applications/imgs/det.png new file mode 100644 index 0000000000000000000000000000000000000000..6f21ee170ed272e6ae9c3ef475f60855a064cbbc GIT binary patch literal 66713 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 O+|-mcmCB!*h5kPSJgL$E literal 0 HcmV?d00001 diff --git a/applications/imgs/rec.png b/applications/imgs/rec.png new file mode 100644 index 0000000000000000000000000000000000000000..c9cd069e42198947cf65aeb14923765be296ab5e GIT binary patch literal 13765 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 Z>2m3xtLkl?K*`lLbrpzm@k@)a{|Bz*xt;(3 literal 0 HcmV?d00001 diff --git a/applications/imgs/sys_res.png b/applications/imgs/sys_res.png new file mode 100644 index 0000000000000000000000000000000000000000..6a9305c62d529f87a9ab063f9933b686c8c63228 GIT binary patch literal 291470 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

{N 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 {}\n".format(key, new_key)) + assert all_params[key].shape == t_params[new_key].shape + all_params[key] = t_params[new_key] + + if "Student." in key: + new_key = key.replace("Student.", "") + #print("{} >> {}\n".format(key, new_key)) + assert all_params[key].shape == s_params[new_key].shape + all_params[key] = s_params[new_key] + + if "Student2." in key: + new_key = key.replace("Student2.", "") + print("{} >> {}\n".format(key, new_key)) + assert all_params[key].shape == s_params[new_key].shape + all_params[key] = s_params[new_key] + +paddle.save(all_params, "./pretrained_models/ch_PP-OCRv3_det_distill_train/teacher_cml_student.pdparams") +``` + +训练命令如下: + +```python +python tools/train.py -c configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_cml.yml -o Global.pretrained_model=./pretrained_models/ch_PP-OCRv3_det_distill_train/teacher_cml_student Global.save_model_dir=./output/ch_PP-OCR_v3_det_finetune/ +``` + +评估训练好的模型: + +```python +# 评估训练好的模型 +python tools/eval.py -c configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_cml.yml -o Global.pretrained_model="./output/ch_PP-OCR_v3_det_finetune/best_accuracy" +``` + +结果如下: +| | 方案 |hmeans| +|---|---------------------------|---| +| 0 | PP-OCRv3中英文超轻量检测预训练模型直接预测 |47.5%| +| 1 | PP-OCRv3中英文超轻量检测预训练模型fintune |65.2%| +| 2 | PP-OCRv3中英文超轻量检测预训练模型fintune学生模型 |80.0%| +| 3 | PP-OCRv3中英文超轻量检测预训练模型fintune教师模型 |84.8%| +| 4 | 基于2和3训练好的模型fintune |82.7%| + +### 模型导出推理 +训练完成后,可以将训练模型转换成inference模型。inference 模型会额外保存模型的结构信息,在预测部署、加速推理上性能优越,灵活方便,适合于实际系统集成。 +#### 模型导出 +导出命令如下: + +```python +# 转化为推理模型 +python tools/export_model.py \ +-c configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_cml.yml \ +-o Global.pretrained_model=./output/ch_PP-OCR_v3_det_finetune/best_accuracy \ +-o Global.save_inference_dir="./inference/det_ppocrv3" + +``` + +#### 模型推理 +导出模型后,可以使用如下命令进行推理预测: + +```python +# 推理预测 +python tools/infer/predict_det.py --image_dir="train_data/icdar2015/text_localization/test/1.jpg" --det_model_dir="./inference/det_ppocrv3/Student" +``` + +## 文字识别 +文本识别的任务是识别出图像中的文字内容,一般输入来自于文本检测得到的文本框截取出的图像文字区域。文本识别一般可以根据待识别文本形状分为规则文本识别和不规则文本识别两大类。规则文本主要指印刷字体、扫描文本等,文本大致处在水平线位置;不规则文本往往不在水平位置,存在弯曲、遮挡、模糊等问题。不规则文本场景具有很大的挑战性,也是目前文本识别领域的主要研究方向。本项目基于PP-OCRv3算法进行优化。 + +### PP-OCRv3识别算法介绍 +PP-OCRv3的识别模块是基于文本识别算法[SVTR](https://arxiv.org/abs/2205.00159)优化。SVTR不再采用RNN结构,通过引入Transformers结构更加有效地挖掘文本行图像的上下文信息,从而提升文本识别能力。如下图所示,PP-OCRv3采用了6个优化策略。 +![](https://ai-studio-static-online.cdn.bcebos.com/d4f5344b5b854d50be738671598a89a45689c6704c4d481fb904dd7cf72f2a1a) + +优化策略汇总如下: +* SVTR_LCNet:轻量级文本识别网络 +* GTC:Attention指导CTC训练策略 +* TextConAug:挖掘文字上下文信息的数据增广策略 +* TextRotNet:自监督的预训练模型 +* UDML:联合互学习策略 +* UIM:无标注数据挖掘方案 + +详细优化策略描述请参考[PP-OCRv3优化策略](https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.5/doc/doc_ch/PP-OCRv3_introduction.md#3-%E8%AF%86%E5%88%AB%E4%BC%98%E5%8C%96) + +### 数据准备 +[计量设备屏幕字符识别数据集](https://aistudio.baidu.com/aistudio/datasetdetail/128714)数据来源于实际项目中各种计量设备的数显屏,以及在网上搜集的一些其他数显屏,包含训练集19912张,测试集4099张。 + +```python +# 解压下载的数据集到指定路径下 +unzip ic15_data.zip -d train_data +``` + +```python +# 随机查看文字检测数据集图片 +from PIL import Image +import matplotlib.pyplot as plt +import numpy as np +import os + +train = './train_data/ic15_data/train' +# 从指定目录中选取一张图片 +def get_one_image(train): + plt.figure() + files = os.listdir(train) + n = len(files) + ind = np.random.randint(0,n) + img_dir = os.path.join(train,files[ind]) + image = Image.open(img_dir) + plt.imshow(image) + plt.show() + image = image.resize([208, 208]) + +get_one_image(train) +``` + +![rec_png](./imgs/rec.png) + +### 模型训练 +#### 下载预训练模型 +下载我们需要的PP-OCRv3识别预训练模型,更多选择请自行选择其他的[文字识别模型](https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.5/doc/doc_ch/models_list.md#2-%E6%96%87%E6%9C%AC%E8%AF%86%E5%88%AB%E6%A8%A1%E5%9E%8B) + +```python +# 使用该指令下载需要的预训练模型 +wget -P ./pretrained_models/ https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_rec_train.tar +# 解压预训练模型文件 +tar -xf ./pretrained_models/ch_PP-OCRv3_rec_train.tar -C pretrained_models +``` + +#### 修改配置文件 +我们使用configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml,主要修改训练轮数和学习率参相关参数,设置预训练模型路径,设置数据集路径。 另外,batch_size可根据自己机器显存大小进行调整。 具体修改如下几个地方: +``` + epoch_num: 100 # 训练epoch数 + save_model_dir: ./output/ch_PP-OCR_v3_rec + save_epoch_step: 10 + eval_batch_step: [0, 100] # 评估间隔,每隔100step评估一次 + cal_metric_during_train: true + pretrained_model: ./pretrained_models/ch_PP-OCRv3_rec_train/best_accuracy # 预训练模型路径 + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + use_space_char: true # 使用空格 + + lr: + name: Cosine # 修改学习率衰减策略为Cosine + learning_rate: 0.0002 # 修改fine-tune的学习率 + warmup_epoch: 2 # 修改warmup轮数 + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data/ # 训练集图片路径 + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/ic15_data/rec_gt_train.txt # 训练集标签 + ratio_list: + - 1.0 + loader: + shuffle: true + batch_size_per_card: 64 + drop_last: true + num_workers: 4 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data/ # 测试集图片路径 + label_file_list: + - ./train_data/ic15_data/rec_gt_test.txt # 测试集标签 + ratio_list: + - 1.0 + loader: + shuffle: false + drop_last: false + batch_size_per_card: 64 + num_workers: 4 +``` + +在训练之前,我们可以直接使用下面命令来评估预训练模型的效果: + +```python +# 评估预训练模型 +python tools/eval.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml -o Global.pretrained_model="./pretrained_models/ch_PP-OCRv3_rec_train/best_accuracy" +``` + +结果如下: +| | 方案 |accuracy| +|---|---------------------------|---| +| 0 | PP-OCRv3中英文超轻量识别预训练模型直接预测 |70.4%| + +#### 开始训练 +我们使用上面修改好的配置文件configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml,预训练模型,数据集路径,学习率,训练轮数等都已经设置完毕后,可以使用下面命令开始训练。 + +```python +# 开始训练识别模型 +python tools/train.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml +``` + +训练完成后,可以对训练模型中最好的进行测试,评估命令如下: + +```python +# 评估finetune效果 +python tools/eval.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml -o Global.checkpoints="./output/ch_PP-OCR_v3_rec/best_accuracy" +``` + +结果如下: +| | 方案 |accuracy| +|---|---------------------------|---| +| 0 | PP-OCRv3中英文超轻量识别预训练模型直接预测 |70.4%| +| 1 | PP-OCRv3中英文超轻量识别预训练模型finetune |82.2%| + +### 模型导出推理 +训练完成后,可以将训练模型转换成inference模型。inference 模型会额外保存模型的结构信息,在预测部署、加速推理上性能优越,灵活方便,适合于实际系统集成。 +#### 模型导出 +导出命令如下: + +```python +# 转化为推理模型 +python tools/export_model.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml -o Global.pretrained_model="./output/ch_PP-OCR_v3_rec/best_accuracy" Global.save_inference_dir="./inference/rec_ppocrv3/" +``` + +#### 模型推理 +导出模型后,可以使用如下命令进行推理预测 + +```python +# 推理预测 +python tools/infer/predict_rec.py --image_dir="train_data/ic15_data/test/1_crop_0.jpg" --rec_model_dir="./inference/rec_ppocrv3/Student" +``` + +## 系统串联 +我们将上面训练好的检测和识别模型进行系统串联测试,命令如下: + +```python +#串联测试 +python3 tools/infer/predict_system.py --image_dir="./train_data/icdar2015/text_localization/test/142.jpg" --det_model_dir="./inference/det_ppocrv3/Student" --rec_model_dir="./inference/rec_ppocrv3/Student" +``` + +测试结果保存在`./inference_results/`目录下,可以用下面代码进行可视化 + +```python +%cd /home/aistudio/PaddleOCR +# 显示结果 +import matplotlib.pyplot as plt +from PIL import Image +img_path= "./inference_results/142.jpg" +img = Image.open(img_path) +plt.figure("test_img", figsize=(30,30)) +plt.imshow(img) +plt.show() +``` + +![sys_res_png](./imgs/sys_res.png) + +### 后处理 +如果需要获取key-value信息,可以基于启发式的规则,将识别结果与关键字库进行匹配;如果匹配上了,则取该字段为key, 后面一个字段为value。 + +```python +def postprocess(rec_res): + keys = ["型号", "厂家", "版本号", "检定校准分类", "计量器具编号", "烟尘流量", + "累积体积", "烟气温度", "动压", "静压", "时间", "试验台编号", "预测流速", + "全压", "烟温", "流速", "工况流量", "标杆流量", "烟尘直读嘴", "烟尘采样嘴", + "大气压", "计前温度", "计前压力", "干球温度", "湿球温度", "流量", "含湿量"] + key_value = [] + if len(rec_res) > 1: + for i in range(len(rec_res) - 1): + rec_str, _ = rec_res[i] + for key in keys: + if rec_str in key: + key_value.append([rec_str, rec_res[i + 1][0]]) + break + return key_value +key_value = postprocess(filter_rec_res) +``` + +## PaddleServing部署 +首先需要安装PaddleServing部署相关的环境 + +```python +python -m pip install paddle-serving-server-gpu +python -m pip install paddle_serving_client +python -m pip install paddle-serving-app +``` + +### 转化检测模型 + +```python +cd deploy/pdserving/ +python -m paddle_serving_client.convert --dirname ../../inference/det_ppocrv3/Student/ \ + --model_filename inference.pdmodel \ + --params_filename inference.pdiparams \ + --serving_server ./ppocr_det_v3_serving/ \ + --serving_client ./ppocr_det_v3_client/ +``` + +### 转化识别模型 + +```python +python -m paddle_serving_client.convert --dirname ../../inference/rec_ppocrv3/Student \ + --model_filename inference.pdmodel \ + --params_filename inference.pdiparams \ + --serving_server ./ppocr_rec_v3_serving/ \ + --serving_client ./ppocr_rec_v3_client/ +``` + + +### 启动服务 +首先可以将后处理代码加入到web_service.py中,具体修改如下: +``` +# 代码153行后面增加下面代码 +def _postprocess(rec_res): + keys = ["型号", "厂家", "版本号", "检定校准分类", "计量器具编号", "烟尘流量", + "累积体积", "烟气温度", "动压", "静压", "时间", "试验台编号", "预测流速", + "全压", "烟温", "流速", "工况流量", "标杆流量", "烟尘直读嘴", "烟尘采样嘴", + "大气压", "计前温度", "计前压力", "干球温度", "湿球温度", "流量", "含湿量"] + key_value = [] + if len(rec_res) > 1: + for i in range(len(rec_res) - 1): + rec_str, _ = rec_res[i] + for key in keys: + if rec_str in key: + key_value.append([rec_str, rec_res[i + 1][0]]) + break + return key_value +key_value = _postprocess(rec_list) +res = {"result": str(key_value)} +# res = {"result": str(result_list)} +``` + +启动服务端 +```python +python web_service.py 2>&1 >log.txt +``` + +### 发送请求 +然后再开启一个新的终端,运行下面的客户端代码 + +```python +python pipeline_http_client.py --image_dir ../../train_data/icdar2015/text_localization/test/142.jpg +``` + +可以获取到最终的key-value结果: +``` +大气压, 100.07kPa +干球温度, 0000℃ +计前温度, 0000℃ +湿球温度, 0000℃ +计前压力, -0000kPa +流量, 00.0L/min +静压, 00000kPa +含湿量, 00.0 % +``` From 9cc37f213a60bf0eb0cf565693e597fb7dc12760 Mon Sep 17 00:00:00 2001 From: andyjpaddle Date: Thu, 16 Jun 2022 10:52:35 +0000 Subject: [PATCH 08/45] add case --- applications/液晶屏读数识别.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/applications/液晶屏读数识别.md b/applications/液晶屏读数识别.md index 4a41afec77..428e317b9d 100644 --- a/applications/液晶屏读数识别.md +++ b/applications/液晶屏读数识别.md @@ -58,7 +58,7 @@ def get_one_image(train): get_one_image(train) ``` -![det_png](./imgs/det.png) +![det_png](imgs/det.png) ### 模型训练 @@ -343,7 +343,7 @@ def get_one_image(train): get_one_image(train) ``` -![rec_png](./imgs/rec.png) +![rec_png](imgs/rec.png) ### 模型训练 #### 下载预训练模型 @@ -475,7 +475,7 @@ plt.imshow(img) plt.show() ``` -![sys_res_png](./imgs/sys_res.png) +![sys_res_png](imgs/sys_res.png) ### 后处理 如果需要获取key-value信息,可以基于启发式的规则,将识别结果与关键字库进行匹配;如果匹配上了,则取该字段为key, 后面一个字段为value。 From 9832834c3f8b7fc66c6e8b6f3c8c3e39b4de15e3 Mon Sep 17 00:00:00 2001 From: andyjpaddle Date: Thu, 16 Jun 2022 12:53:39 +0000 Subject: [PATCH 09/45] add case --- applications/液晶屏读数识别.md | 91 ++++++++++++++++++++++------------ 1 file changed, 58 insertions(+), 33 deletions(-) diff --git a/applications/液晶屏读数识别.md b/applications/液晶屏读数识别.md index 428e317b9d..6b1ea24306 100644 --- a/applications/液晶屏读数识别.md +++ b/applications/液晶屏读数识别.md @@ -1,12 +1,37 @@ -# 项目背景及意义 +# 基于PP-OCRv3的液晶屏读数识别 + +- [1. 项目背景及意义](#1-项目背景及意义) +- [2. 项目内容](#2-项目内容) +- [3. 安装环境](#3-安装环境) +- [4. 文字检测](#4-文字检测) + - [4.1 PP-OCRv3检测算法介绍](#41-PP-OCRv3检测算法介绍) + - [4.2 数据准备](#42-数据准备) + - [4.3 模型训练](#43-模型训练) + - [4.3.1 预训练模型直接评估](#431-预训练模型直接评估) + - [4.3.2 预训练模型直接finetune](#432-预训练模型直接finetune) + - [4.3.3 基于预训练模型Finetune_student模型](#433-基于预训练模型Finetune_student模型) + - [4.3.4 基于预训练模型Finetune_teacher模型](#434-基于预训练模型Finetune_teacher模型) + - [4.3.5 采用CML蒸馏进一步提升student模型精度](#435-采用CML蒸馏进一步提升student模型精度) + - [4.3.6 模型导出推理](#436-4.3.6-模型导出推理) +- [5. 文字识别](#5-文字识别) + - [5.1 PP-OCRv3识别算法介绍](#51-PP-OCRv3识别算法介绍) + - [5.2 数据准备](#52-数据准备) + - [5.3 模型训练](#53-模型训练) + - [5.4 模型导出推理](#54-模型导出推理) +- [6. 系统串联](#6-系统串联) + - [6.1 后处理](#61-后处理) +- [7. PaddleServing部署](#7-PaddleServing部署) + + +# 1. 项目背景及意义 目前光学字符识别(OCR)技术在我们的生活当中被广泛使用,但是大多数模型在通用场景下的准确性还有待提高,针对于此我们借助飞桨提供的PaddleOCR套件较容易的实现了在垂类场景下的应用。 该项目以国家质量基础(NQI)为准绳,充分利用大数据、云计算、物联网等高新技术,构建覆盖计量端、实验室端、数据端和硬件端的完整计量解决方案,解决传统计量校准中存在的难题,拓宽计量检测服务体系和服务领域;解决无数传接口或数传接口不统一、不公开的计量设备,以及计量设备所处的环境比较恶劣,不适合人工读取数据。通过OCR技术实现远程计量,引领计量行业向智慧计量转型和发展。 -# 项目内容 +# 2. 项目内容 本项目基于PaddleOCR开源套件,以PP-OCRv3检测和识别模型为基础,针对液晶屏读数识别场景进行优化。 -## 安装环境 +# 3. 安装环境 ```python # 首先git官方的PaddleOCR项目,安装需要的依赖 @@ -16,16 +41,16 @@ cd PaddleOCR pip install -r requirements.txt ``` -## 文字检测 +# 4. 文字检测 文本检测的任务是定位出输入图像中的文字区域。近年来学术界关于文本检测的研究非常丰富,一类方法将文本检测视为目标检测中的一个特定场景,基于通用目标检测算法进行改进适配,如TextBoxes[1]基于一阶段目标检测器SSD[2]算法,调整目标框使之适合极端长宽比的文本行,CTPN[3]则是基于Faster RCNN[4]架构改进而来。但是文本检测与目标检测在目标信息以及任务本身上仍存在一些区别,如文本一般长宽比较大,往往呈“条状”,文本行之间可能比较密集,弯曲文本等,因此又衍生了很多专用于文本检测的算法。本项目基于PP-OCRv3算法进行优化。 -### PP-OCRv3检测算法介绍 +## 4.1 PP-OCRv3检测算法介绍 PP-OCRv3检测模型是对PP-OCRv2中的CML(Collaborative Mutual Learning) 协同互学习文本检测蒸馏策略进行了升级。如下图所示,CML的核心思想结合了①传统的Teacher指导Student的标准蒸馏与 ②Students网络之间的DML互学习,可以让Students网络互学习的同时,Teacher网络予以指导。PP-OCRv3分别针对教师模型和学生模型进行进一步效果优化。其中,在对教师模型优化时,提出了大感受野的PAN结构LK-PAN和引入了DML(Deep Mutual Learning)蒸馏策略;在对学生模型优化时,提出了残差注意力机制的FPN结构RSE-FPN。 ![](https://ai-studio-static-online.cdn.bcebos.com/c306b2f028364805a55494d435ab553a76cf5ae5dd3f4649a948ea9aeaeb28b8) 详细优化策略描述请参考[PP-OCRv3优化策略](https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.5/doc/doc_ch/PP-OCRv3_introduction.md#2) -### 数据准备 +## 4.2 数据准备 [计量设备屏幕字符检测数据集](https://aistudio.baidu.com/aistudio/datasetdetail/127845)数据来源于实际项目中各种计量设备的数显屏,以及在网上搜集的一些其他数显屏,包含训练集755张,测试集355张。 ```python @@ -60,9 +85,9 @@ get_one_image(train) ``` ![det_png](imgs/det.png) -### 模型训练 +## 4.3 模型训练 -#### 下载预训练模型 +### 4.3.1 预训练模型直接评估 下载我们需要的PP-OCRv3检测预训练模型,更多选择请自行选择其他的[文字检测模型](https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.5/doc/doc_ch/models_list.md#1-%E6%96%87%E6%9C%AC%E6%A3%80%E6%B5%8B%E6%A8%A1%E5%9E%8B) ```python @@ -85,7 +110,7 @@ python tools/eval.py -c configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_cml.yml -o Globa |---|---------------------------|---| | 0 | PP-OCRv3中英文超轻量检测预训练模型直接预测 |47.5%| -**1)直接用全量策略finetune训练** +### 4.3.2 预训练模型直接finetune #### 修改配置文件 我们使用configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_cml.yml,主要修改训练轮数和学习率参相关参数,设置预训练模型路径,设置数据集路径。 另外,batch_size可根据自己机器显存大小进行调整。 具体修改如下几个地方: ``` @@ -119,7 +144,7 @@ python tools/eval.py -c configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_cml.yml -o Globa | 0 | PP-OCRv3中英文超轻量检测预训练模型直接预测 |47.5%| | 1 | PP-OCRv3中英文超轻量检测预训练模型fintune |65.2%| -**2)基于预训练模型Finetune student模型** +### 4.3.3 基于预训练模型Finetune_student模型 我们使用configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_student.yml,主要修改训练轮数和学习率参相关参数,设置预训练模型路径,设置数据集路径。 另外,batch_size可根据自己机器显存大小进行调整。 具体修改如下几个地方: ``` @@ -152,7 +177,7 @@ python tools/eval.py -c configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_student.yml -o G | 1 | PP-OCRv3中英文超轻量检测预训练模型fintune |65.2%| | 2 | PP-OCRv3中英文超轻量检测预训练模型fintune学生模型 |80.0%| -**3)基于预训练模型Finetune teacher模型** +### 4.3.4 基于预训练模型Finetune_teacher模型 首先需要从提供的预训练模型best_accuracy.pdparams中提取teacher参数,组合成适合dml训练的初始化模型,提取代码如下: @@ -211,9 +236,9 @@ python tools/eval.py -c configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_dml.yml -o Globa | 2 | PP-OCRv3中英文超轻量检测预训练模型fintune学生模型 |80.0%| | 3 | PP-OCRv3中英文超轻量检测预训练模型fintune教师模型 |84.8%| -**4)采用CML蒸馏,进一步提升student模型精度** +### 4.3.5 采用CML蒸馏进一步提升student模型精度 -需要从2) 、3)训练得到的best_accuracy.pdparams中提取各自代表student和teacher的参数,组合成适合cml训练的初始化模型,提取代码如下: +需要从4.3.3和4.3.4训练得到的best_accuracy.pdparams中提取各自代表student和teacher的参数,组合成适合cml训练的初始化模型,提取代码如下: ```python # transform teacher params and student parameters into cml model @@ -273,9 +298,9 @@ python tools/eval.py -c configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_cml.yml -o Globa | 3 | PP-OCRv3中英文超轻量检测预训练模型fintune教师模型 |84.8%| | 4 | 基于2和3训练好的模型fintune |82.7%| -### 模型导出推理 +### 4.3.6 模型导出推理 训练完成后,可以将训练模型转换成inference模型。inference 模型会额外保存模型的结构信息,在预测部署、加速推理上性能优越,灵活方便,适合于实际系统集成。 -#### 模型导出 +#### 4.3.6.1 模型导出 导出命令如下: ```python @@ -287,7 +312,7 @@ python tools/export_model.py \ ``` -#### 模型推理 +#### 4.3.6.2 模型推理 导出模型后,可以使用如下命令进行推理预测: ```python @@ -295,10 +320,10 @@ python tools/export_model.py \ python tools/infer/predict_det.py --image_dir="train_data/icdar2015/text_localization/test/1.jpg" --det_model_dir="./inference/det_ppocrv3/Student" ``` -## 文字识别 +# 5. 文字识别 文本识别的任务是识别出图像中的文字内容,一般输入来自于文本检测得到的文本框截取出的图像文字区域。文本识别一般可以根据待识别文本形状分为规则文本识别和不规则文本识别两大类。规则文本主要指印刷字体、扫描文本等,文本大致处在水平线位置;不规则文本往往不在水平位置,存在弯曲、遮挡、模糊等问题。不规则文本场景具有很大的挑战性,也是目前文本识别领域的主要研究方向。本项目基于PP-OCRv3算法进行优化。 -### PP-OCRv3识别算法介绍 +## 5.1 PP-OCRv3识别算法介绍 PP-OCRv3的识别模块是基于文本识别算法[SVTR](https://arxiv.org/abs/2205.00159)优化。SVTR不再采用RNN结构,通过引入Transformers结构更加有效地挖掘文本行图像的上下文信息,从而提升文本识别能力。如下图所示,PP-OCRv3采用了6个优化策略。 ![](https://ai-studio-static-online.cdn.bcebos.com/d4f5344b5b854d50be738671598a89a45689c6704c4d481fb904dd7cf72f2a1a) @@ -312,7 +337,7 @@ PP-OCRv3的识别模块是基于文本识别算法[SVTR](https://arxiv.org/abs/2 详细优化策略描述请参考[PP-OCRv3优化策略](https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.5/doc/doc_ch/PP-OCRv3_introduction.md#3-%E8%AF%86%E5%88%AB%E4%BC%98%E5%8C%96) -### 数据准备 +## 5.2 数据准备 [计量设备屏幕字符识别数据集](https://aistudio.baidu.com/aistudio/datasetdetail/128714)数据来源于实际项目中各种计量设备的数显屏,以及在网上搜集的一些其他数显屏,包含训练集19912张,测试集4099张。 ```python @@ -345,8 +370,8 @@ get_one_image(train) ![rec_png](imgs/rec.png) -### 模型训练 -#### 下载预训练模型 +## 5.3 模型训练 +### 下载预训练模型 下载我们需要的PP-OCRv3识别预训练模型,更多选择请自行选择其他的[文字识别模型](https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.5/doc/doc_ch/models_list.md#2-%E6%96%87%E6%9C%AC%E8%AF%86%E5%88%AB%E6%A8%A1%E5%9E%8B) ```python @@ -356,7 +381,7 @@ wget -P ./pretrained_models/ https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch tar -xf ./pretrained_models/ch_PP-OCRv3_rec_train.tar -C pretrained_models ``` -#### 修改配置文件 +### 修改配置文件 我们使用configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml,主要修改训练轮数和学习率参相关参数,设置预训练模型路径,设置数据集路径。 另外,batch_size可根据自己机器显存大小进行调整。 具体修改如下几个地方: ``` epoch_num: 100 # 训练epoch数 @@ -414,7 +439,7 @@ python tools/eval.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml -o |---|---------------------------|---| | 0 | PP-OCRv3中英文超轻量识别预训练模型直接预测 |70.4%| -#### 开始训练 +### 开始训练 我们使用上面修改好的配置文件configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml,预训练模型,数据集路径,学习率,训练轮数等都已经设置完毕后,可以使用下面命令开始训练。 ```python @@ -435,9 +460,9 @@ python tools/eval.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml -o | 0 | PP-OCRv3中英文超轻量识别预训练模型直接预测 |70.4%| | 1 | PP-OCRv3中英文超轻量识别预训练模型finetune |82.2%| -### 模型导出推理 +## 5.4 模型导出推理 训练完成后,可以将训练模型转换成inference模型。inference 模型会额外保存模型的结构信息,在预测部署、加速推理上性能优越,灵活方便,适合于实际系统集成。 -#### 模型导出 +### 模型导出 导出命令如下: ```python @@ -445,7 +470,7 @@ python tools/eval.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml -o python tools/export_model.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml -o Global.pretrained_model="./output/ch_PP-OCR_v3_rec/best_accuracy" Global.save_inference_dir="./inference/rec_ppocrv3/" ``` -#### 模型推理 +### 模型推理 导出模型后,可以使用如下命令进行推理预测 ```python @@ -453,7 +478,7 @@ python tools/export_model.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillatio python tools/infer/predict_rec.py --image_dir="train_data/ic15_data/test/1_crop_0.jpg" --rec_model_dir="./inference/rec_ppocrv3/Student" ``` -## 系统串联 +# 6. 系统串联 我们将上面训练好的检测和识别模型进行系统串联测试,命令如下: ```python @@ -477,7 +502,7 @@ plt.show() ![sys_res_png](imgs/sys_res.png) -### 后处理 +## 6.1 后处理 如果需要获取key-value信息,可以基于启发式的规则,将识别结果与关键字库进行匹配;如果匹配上了,则取该字段为key, 后面一个字段为value。 ```python @@ -498,7 +523,7 @@ def postprocess(rec_res): key_value = postprocess(filter_rec_res) ``` -## PaddleServing部署 +# 7. PaddleServing部署 首先需要安装PaddleServing部署相关的环境 ```python @@ -507,7 +532,7 @@ python -m pip install paddle_serving_client python -m pip install paddle-serving-app ``` -### 转化检测模型 +## 7.1 转化检测模型 ```python cd deploy/pdserving/ @@ -518,7 +543,7 @@ python -m paddle_serving_client.convert --dirname ../../inference/det_ppocrv3/St --serving_client ./ppocr_det_v3_client/ ``` -### 转化识别模型 +## 7.2 转化识别模型 ```python python -m paddle_serving_client.convert --dirname ../../inference/rec_ppocrv3/Student \ @@ -529,7 +554,7 @@ python -m paddle_serving_client.convert --dirname ../../inference/rec_ppocrv3/St ``` -### 启动服务 +## 7.3 启动服务 首先可以将后处理代码加入到web_service.py中,具体修改如下: ``` # 代码153行后面增加下面代码 @@ -557,7 +582,7 @@ res = {"result": str(key_value)} python web_service.py 2>&1 >log.txt ``` -### 发送请求 +## 7.4 发送请求 然后再开启一个新的终端,运行下面的客户端代码 ```python From 4f5b2b9759982822c5a076fc0d023915e7714ab7 Mon Sep 17 00:00:00 2001 From: huangjun12 <12272008@bjtu.edu.cn> Date: Tue, 21 Jun 2022 07:27:35 +0000 Subject: [PATCH 10/45] add package producation data applications --- applications/包装生产日期识别.md | 827 +++++++++++++++++++++++++++++++ 1 file changed, 827 insertions(+) create mode 100644 applications/包装生产日期识别.md diff --git a/applications/包装生产日期识别.md b/applications/包装生产日期识别.md new file mode 100644 index 0000000000..4f74a4a148 --- /dev/null +++ b/applications/包装生产日期识别.md @@ -0,0 +1,827 @@ +# 一种基于PaddleOCR的产品包装生产日期识别模型 + +- [1. 项目介绍](#1-项目介绍) +- [2. 环境搭建](#2-环境搭建) +- [3. 数据准备](#3-数据准备) +- [4. 直接使用PP-OCRv3模型评估](#4-直接使用PPOCRv3模型评估) +- [5. 基于合成数据finetune](#5-基于合成数据finetune) + - [5.1 Text Renderer数据合成方法](#51-TextRenderer数据合成方法) + - [5.1.1 下载Text Renderer代码](#511-下载TextRenderer代码) + - [5.1.2 准备背景图片](#512-准备背景图片) + - [5.1.3 准备语料](#513-准备语料) + - [5.1.4 下载字体](#514-下载字体) + - [5.1.5 运行数据合成命令](#515-运行数据合成命令) + - [5.2 模型训练](#52-模型训练) +- [6. 基于真实数据finetune](#6-基于真实数据finetune) + - [6.1 python爬虫获取数据](#61-python爬虫获取数据) + - [6.2 数据挖掘](#62-数据挖掘) + - [6.3 模型训练](#63-模型训练) +- [7. 基于合成+真实数据finetune](#7-基于合成+真实数据finetune) + + +## 1. 项目介绍 + +产品包装生产日期是计算机视觉图像识别技术在工业场景中的一种应用。产品包装生产日期识别技术要求能够将产品生产日期从复杂背景中提取并识别出来,在物流管理、物资管理中得到广泛应用。 + +![](https://ai-studio-static-online.cdn.bcebos.com/d9e0533cc1df47ffa3bbe99de9e42639a3ebfa5bce834bafb1ca4574bf9db684) + + +- 项目难点 + +1. 没有训练数据 +2. 图像质量层次不齐: 角度倾斜、图片模糊、光照不足、过曝等问题严重 + +针对以上问题, 本例选用PP-OCRv3这一开源超轻量OCR系统进行包装产品生产日期识别系统的开发。直接使用PP-OCRv3进行评估的精度为62.99%。为提升识别精度,我们首先使用数据合成工具合成了3k数据,基于这部分数据进行finetune,识别精度提升至73.66%。由于合成数据与真实数据之间的分布存在差异,为进一步提升精度,我们使用网络爬虫配合数据挖掘策略得到了1k带标签的真实数据,基于真实数据finetune的精度为71.33%。最后,我们综合使用合成数据和真实数据进行finetune,将识别精度提升至86.99%。各策略的精度提升效果如下: + +| 策略 | 精度| +| :--------------- | :-------- | +| PP-OCRv3评估 | 62.99| +| 合成数据finetune | 73.66| +| 真实数据finetune | 71.33| +| 真实+合成数据finetune | 86.99| + +## 2. 环境搭建 + +本任务基于Aistudio完成, 具体环境如下: + +- 操作系统: Linux +- PaddlePaddle: 2.3 +- PaddleOCR: Release/2.5 +- text_renderer: master + +下载PaddlleOCR代码并安装依赖库: +```bash +git clone -b dygraph https://gitee.com/paddlepaddle/PaddleOCR + +# 安装依赖库 +cd PaddleOCR +pip install -r PaddleOCR/requirements.txt +``` + +## 3. 数据准备 + +本项目使用人工预标注的300张图像作为测试集。 + +部分数据示例如下: + +![](https://ai-studio-static-online.cdn.bcebos.com/39ff30e0ab0442579712255e6a9ea6b5271169c98e624e6eb2b8781f003bfea0) + + +标签文件格式如下: +```txt +数据路径 标签(中间以制表符分隔) +``` + +|数据集类型|数量| +|---|---| +|测试集| 300| + +数据集[下载链接](https://aistudio.baidu.com/aistudio/datasetdetail/149770),下载后可以通过下方命令解压: + +```bash +tar -xvf data.tar +mv data ${PaddleOCR_root} +``` + +数据解压后的文件结构如下: + +```shell +PaddleOCR +├── data +│ ├── mining_images # 挖掘的真实数据示例 +│ ├── mining_train.list # 挖掘的真实数据文件列表 +│ ├── render_images # 合成数据示例 +│ ├── render_train.list # 合成数据文件列表 +│ ├── val # 测试集数据 +│ └── val.list # 测试集数据文件列表 +| ├── bg # 合成数据所需背景图像 +│ └── corpus # 合成数据所需语料 +│ └── rec_vit_sub_64_363_all/ # svtr_tiny高精度识别模型 +``` + +## 4. 直接使用PP-OCRv3模型评估 + +准备好测试数据后,可以使用PaddleOCR的PP-OCRv3模型进行识别。 + +- 下载预训练模型 + +首先需要下载PP-OCR v3中英文识别模型文件,下载链接可以在https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.5/doc/doc_ch/ppocr_introduction.md#6 获取,下载命令: + +```bash +cd ${PaddleOCR_root} +mkdir ckpt +wget -nc -P ckpt https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_rec_train.tar +pushd ckpt/ +tar -xvf ch_PP-OCRv3_rec_train.tar +popd +``` + +- 模型评估 + +使用以下命令进行PP-OCRv3评估: + +```bash +python tools/eval.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml \ + -o Global.checkpoints=ckpt/ch_PP-OCRv3_rec_train/best_accuracy \ + Eval.dataset.data_dir=./data \ + Eval.dataset.label_file_list=["./data/val.list"] + +``` + +其中各参数含义如下: + +```bash +-c: 指定使用的配置文件,ch_PP-OCRv3_rec_distillation.yml对应于OCRv3识别模型。 +-o: 覆盖配置文件中参数 +Global.checkpoints: 指定评估使用的模型文件路径 +Eval.dataset.data_dir: 指定评估数据集路径 +Eval.dataset.label_file_list: 指定评估数据集文件列表 +``` + +## 5. 基于合成数据finetune + +### 5.1 Text Renderer数据合成方法 + +#### 5.1.1 下载Text Renderer代码 + +首先从github或gitee下载Text Renderer代码,并安装相关依赖。 + +```bash +git clone https://gitee.com/wowowoll/text_renderer.git + +# 安装依赖库 +cd text_renderer +pip install -r requirements.txt +``` + +使用text renderer合成数据之前需要准备好背景图片、语料以及字体库,下面将逐一介绍各个步骤。 + +#### 5.1.2 准备背景图片 + +观察日常生活中常见的包装生产日期图片,我们可以发现其背景相对简单。为此我们可以从网上找一下图片,截取部分图像块作为背景图像。 + +本项目已准备了部分图像作为背景图片,在第3部分完成数据准备后,可以得到我们准备好的背景图像,示例如下: + +![](https://ai-studio-static-online.cdn.bcebos.com/456ae2acb27d4a94896c478812aee0bc3551c703d7bd40c9be4dc983c7b3fc8a) + + + +背景图像存放于如下位置: + +```shell +PaddleOCR +├── data +| ├── bg # 合成数据所需背景图像 +``` + +#### 5.1.3 准备语料 + +观察测试集生产日期图像,我们可以知道如下数据有如下特点: +1. 由年月日组成,中间可能以“/”、“-”、“:”、“.”或者空格间隔,也可能以汉字年月日分隔 +2. 有些生产日期包含在产品批号中,此时可能包含具体时间、英文字母或数字标识 + +基于以上两点,我们编写语料生成脚本: + +```python +import random +from random import choice +import os + +cropus_num = 2000 #设置语料数量 + +def get_cropus(f): + # 随机生成年份 + year = random.randint(0, 22) + # 随机生成月份 + month = random.randint(1, 12) + # 随机生成日期 + day_dict = {31: [1,3,5,7,8,10,12], 30: [4,6,9,11], 28: [2]} + for item in day_dict: + if month in day_dict[item]: + day = random.randint(0, item) + # 随机生成小时 + hours = random.randint(0, 24) + # 随机生成分钟 + minute = random.randint(0, 60) + # 随机生成秒数 + second = random.randint(0, 60) + + # 随机生成产品标识字符 + length = random.randint(0, 6) + file_id = [] + flag = 0 + my_dict = [i for i in range(48,58)] + [j for j in range(40, 42)] + [k for k in range(65,90)] # 大小写字母 + 括号 + + for i in range(1, length): + if flag: + if i == flag+2: #括号匹配 + file_id.append(')') + flag = 0 + continue + sel = choice(my_dict) + if sel == 41: + continue + if sel == 40: + if i == 1 or i > length-3: + continue + flag = i + my_ascii = chr(sel) + file_id.append(my_ascii) + file_id_str = ''.join(file_id) + + #随机生成产品标识字符 + file_id2 = random.randint(0, 9) + + rad = random.random() + if rad < 0.3: + f.write('20{:02d}{:02d}{:02d} {}'.format(year, month, day, file_id_str)) + elif 0.3 < rad < 0.5: + f.write('20{:02d}年{:02d}月{:02d}日'.format(year, month, day)) + elif 0.5 < rad < 0.7: + f.write('20{:02d}/{:02d}/{:02d}'.format(year, month, day)) + elif 0.7 < rad < 0.8: + f.write('20{:02d}-{:02d}-{:02d}'.format(year, month, day)) + elif 0.8 < rad < 0.9: + f.write('20{:02d}.{:02d}.{:02d}'.format(year, month, day)) + else: + f.write('{:02d}:{:02d}:{:02d} {:02d}'.format(hours, minute, second, file_id2)) + +if __name__ == "__main__": + file_path = '/home/aistudio/text_renderer/my_data/cropus' + if not os.path.exists(file_path): + os.makedirs(file_path) + file_name = os.path.join(file_path, 'books.txt') + f = open(file_name, 'w') + for i in range(cropus_num): + get_cropus(f) + if i < cropus_num-1: + f.write('\n') + + f.close() +``` + +本项目已准备了部分语料,在第3部分完成数据准备后,可以得到我们准备好的语料库,默认位置如下: + +```shell +PaddleOCR +├── data +│ └── corpus #合成数据所需语料 +``` + +#### 5.1.4 下载字体 + +观察包装生产日期,我们可以发现其使用的字体为点阵体。字体可以在如下网址下载: +https://www.fonts.net.cn/fonts-en/tag-dianzhen-1.html + +本项目已准备了部分字体,在第3部分完成数据准备后,可以得到我们准备好的字体,默认位置如下: + +```shell +PaddleOCR +├── data +│ └── fonts #合成数据所需字体 +``` + +下载好字体后,还需要在list文件中指定字体文件存放路径,脚本如下: + +```bash +cd text_renderer/my_data/ +touch fonts.list +ls /home/aistudio/PaddleOCR/data/fonts/* > fonts.list +``` + +#### 5.1.5 运行数据合成命令 + +完成数据准备后,my_data文件结构如下: + +```shell +my_data/ +├── cropus +│ └── books.txt #语料库 +├── eng.txt #字符列表 +└── fonts.list #字体列表 +``` + +在运行合成数据命令之前,还有两处细节需要手动修改: +1. 将默认配置文件`text_renderer/configs/default.yaml`中第9行enable的值设为`true`,即允许合成彩色图像。否则合成的都是灰度图。 + +```yaml + # color boundary is in R,G,B format + font_color: ++ enable: true #false +``` + +2. 将`text_renderer/textrenderer/renderer.py`第184行作如下修改,取消padding。否则图片两端会有一些空白。 + +```python +padding = random.randint(s_bbox_width // 10, s_bbox_width // 8) #修改前 +padding = 0 #修改后 +``` + +运行数据合成命令: + +```bash +cd /home/aistudio/text_renderer/ +python main.py --num_img=3000 \ + --fonts_list='./my_data/fonts.list' \ + --corpus_dir "./my_data/cropus" \ + --corpus_mode "list" \ + --bg_dir "/home/aistudio/PaddleOCR/data/bg/" \ + --img_width 0 +``` + +合成好的数据默认保存在`text_renderer/output`目录下,可进入该目录查看合成的数据。 + + +合成数据示例如下 +![](https://ai-studio-static-online.cdn.bcebos.com/d686a48d465a43d09fbee51924fdca42ee21c50e676646da8559fb9967b94185) + +数据合成好后,还需要生成如下格式的训练所需的标注文件, +``` +图像路径 标签 +``` + +使用如下脚本即可生成标注文件: + +```python +import random + +abspath = '/home/aistudio/text_renderer/output/default/' + +#标注文件生成路径 +fout = open('./render_train.list', 'w', encoding='utf-8') + +with open('./output/default/tmp_labels.txt','r') as f: + lines = f.readlines() + for item in lines: + label = item[9:] + filename = item[:8] + '.jpg' + fout.write(abspath + filename + '\t' + label) + + fout.close() +``` + +经过以上步骤,我们便完成了包装生产日期数据合成。 +数据位于`text_renderer/output`,标注文件位于`text_renderer/render_train.list`。 + +本项目提供了生成好的数据供大家体验,完成步骤3的数据准备后,可得数据路径位于: + +```shell +PaddleOCR +├── data +│ ├── render_images # 合成数据示例 +│ ├── render_train.list #合成数据文件列表 +``` + +### 5.2 模型训练 + +准备好合成数据后,我们可以使用以下命令,利用合成数据进行finetune: +```bash +cd ${PaddleOCR_root} +python tools/train.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml \ + -o Global.pretrained_model=./ckpt/ch_PP-OCRv3_rec_train/best_accuracy \ + Global.epoch_num=20 \ + Global.eval_batch_step='[0, 20]' \ + Train.dataset.data_dir=./data \ + Train.dataset.label_file_list=['./data/render_train.list'] \ + Train.loader.batch_size_per_card=64 \ + Eval.dataset.data_dir=./data \ + Eval.dataset.label_file_list=["./data/val.list"] \ + Eval.loader.batch_size_per_card=64 + +``` + +其中各参数含义如下: + +```txt +-c: 指定使用的配置文件,ch_PP-OCRv3_rec_distillation.yml对应于OCRv3识别模型。 +-o: 覆盖配置文件中参数 +Global.pretrained_model: 指定finetune使用的预训练模型 +Global.epoch_num: 指定训练的epoch数 +Global.eval_batch_step: 间隔多少step做一次评估 +Train.dataset.data_dir: 训练数据集路径 +Train.dataset.label_file_list: 训练集文件列表 +Train.loader.batch_size_per_card: 训练单卡batch size +Eval.dataset.data_dir: 评估数据集路径 +Eval.dataset.label_file_list: 评估数据集文件列表 +Eval.loader.batch_size_per_card: 评估单卡batch size +``` + +## 6. 基于真实数据finetune + + +使用合成数据finetune能提升我们模型的识别精度,但由于合成数据和真实数据之间的分布可能有一定差异,因此作用有限。为进一步提高识别精度,本节介绍如何挖掘真实数据进行模型finetune。 + +数据挖掘的整体思路如下: +1. 使用python爬虫从网上获取大量无标签数据 +2. 使用模型从大量无标签数据中构建出有效训练集 + +### 6.1 python爬虫获取数据 + +本节介绍如何使用python脚本爬取网络图片,这里以爬取百度图片为例。首先将图片链接都爬取下来,然后用多进程下载图片。使用到的工具有requests库和谷歌浏览器(抓包工具)。 + +准备工作: +1. 获取请求头需要的的设备信息: +(1) 使用谷歌浏览器,打开百度图片,搜索关键词【包装生产日期】 +(2) 右键,选择【检查】,打开抓包工具 +(3) Network >> XHR >> 选定网址 >> Headers >> RequestHeaders >> UserAgents +![](https://ai-studio-static-online.cdn.bcebos.com/715fa3f1255544789ddf5699084783b6b88055835a7049e3b588f4f564e14972) + +2. 获取网页参数 +用类似的方式,在XHR中选择Payload获取网页参数 +![](https://ai-studio-static-online.cdn.bcebos.com/2a8b91ed854a40e589cc9a9596873192cc730c60d5754373a403f395a81ec44f) + + +接下来编写代码进行图片抓取: + +```python +import requests +import json +import tqdm +import os + +img_num = 0 # 记录保存下来的图片数量 +num_per_page = 50 # 每一页展示的图片数量 +page_num = 40 # 页数 +page_id = 0 # 每一页开始索引 + +# 图片链接保存文件路径 +list_path = '/home/aistudio/sprider' +if not os.path.exists(list_path): + os.mkdir(list_path) +f_list = open(os.path.join(list_path, 'file.list'), 'w') + + +img_dict = {} # 图片链接字典,去重 + +# 搜索关键词 +key_word = ['包装产品生产日期', '包装日期', '生产日期', '保质期', '纸箱生产日期', '商品日期', '黑色生产日期', '黑底生产日期'] + +for key in key_word: + bar = tqdm.tqdm(total=page_num*num_per_page) + for page_id in range(page_num): + # 使用准备好的请求头设备信息 + header = { + 'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36' + } + + # 使用准备好的网页参数 + url = 'https://image.baidu.com/search/acjson?' + param = { + 'tn': 'resultjson_com', + 'logid': '8931113288182398301', + 'ipn': 'rj', + 'ct': '201326592', + 'is': '', + 'fp': 'result', + 'fr':'', + 'word': key, # 搜索关键词 + 'queryWord': key, # 搜索关键词 + 'cl': '2', + 'lm': '-1', + 'ie': 'utf-8', + 'oe': 'utf-8', + 'adpicid': '', + 'st': '-1', + 'z': '', + 'ic': '0', + 'hd': '', + 'latest': '', + 'copyright': '', + 's': '', + 'se': '', + 'tab': '', + 'width': '', + 'height': '', + 'face': '0', + 'istype': '2', + 'qc': '', + 'nc': '1', + 'expermode': '', + 'nojc': '', + 'isAsync': '', + 'pn': str(page_id*num_per_page), # 每一页开始索引 + 'rn': str(num_per_page), # 每一页爬取的图片数量 + 'gsm': '1e', + '1652689395185':'' + } + + # 获取网页信息 + page_text = requests.get(url=url, headers=header, params=param, timeout=20) + + try: + page_text = page_text.json() # 网页信息转成json字典 + except: + continue + + info_list = page_text['data'] + + # 获取不到有效信息后,退出搜索 + if info_list == [{}]: + print('===break===') + break + + del info_list[-1] + + img_path_list = [] + for info in info_list: + img_url = info['thumbURL'] # 图片源地址 + #bar.update(1) + if img_url not in img_dict: + img_dict[img_url] = 1 + f_list.write(img_url + '\n') + +f_list.close() +print('saved image number:', img_num+1) +``` + +```python +# 使用多进程下载网页图片 + +import requests +from concurrent.futures import ProcessPoolExecutor + +file_path = '/home/aistudio/sprider/file.list' +image_save_path = '/home/aistudio/sprider/data' + +if not os.path.exists(image_save_path): + os.mkdir(image_save_path) + +f = open(file_path, 'r') +lines = f.readlines() + +header = { + 'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36' +} + +def process(img_url, img_num): + try: + img_data = requests.get(url=img_url, headers=header, timeout=20).content + img_path = os.path.join(image_save_path, '{:05d}.jpg'.format(img_num)) + with open(img_path, 'wb') as fp: + fp.write(img_data) + img_num += 1 + except Exception as e: + print('connect Failed, just continue') + +with ProcessPoolExecutor(max_workers=10) as executer: + for idx, line in enumerate(lines): + executer.submit(process, line.strip(), idx) + +print('done') +``` + +完成以上步骤,便可得到获取的图片,目录格式如下: + +```txt +sprider +├── file.list +├── data +│ ├── 00000.jpg +│ ├── 00001.jpg +... +``` + +### 6.2 数据挖掘 + +我们使用PaddleOCR对获取到的图片进行挖掘,具体步骤如下: +1. 使用 PP-OCRv3检测模型+svtr-tiny识别模型,对每张图片进行预测。 +2. 使用数据挖掘策略,得到有效图片。 +3. 将有效图片对应的图像区域和标签提取出来,构建训练集。 + + +首先下载预训练模型,PP-OCRv3检测模型下载链接:https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_det_infer.tar + +svtr-tiny识别模型,我们使用的是在内部数据上训练的高精度模型,本项目提供下载链接,完成步骤3的数据准备后,可得数据路径位于: + +```shell +PaddleOCR +├── data +│ ├── rec_vit_sub_64_363_all/ # svtr_tiny高精度识别模型 +``` + +```bash +# 下载解压PP-OCRv3检测模型 +cd ${PaddleOCR_root} +wget -nc -P ckpt https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_det_infer.tar +pushd ckpt +tar -xvf ch_PP-OCRv3_det_infer.tar +popd ckpt +``` + +在使用PPOCRv3检测模型+svtr-tiny识别模型进行预测之前,有如下两处细节需要手动修改: +1. 将`tools/infer/predict_rec.py`中第110行`imgW`修改为`320` + +```python +#imgW = int((imgH * max_wh_ratio)) +imgW = 320 +``` + +2. 将`tools/infer/predict_system.py`第169行添加如下一行,将预测分数也写入结果文件中。 + +```python +"scores": rec_res[idx][1], +``` + +模型预测命令: +```bash +python tools/infer/predict_system.py \ + --image_dir="/home/aistudio/sprider/data" \ + --det_model_dir="./ckpt/ch_PP-OCRv3_det_infer/" \ + --rec_model_dir="/home/aistudio/PaddleOCR/data/rec_vit_sub_64_363_all/" \ + --rec_image_shape="3,32,320" +``` + +获得预测结果后,我们使用数据挖掘策略得到有效图片。具体挖掘策略如下: +1. 预测置信度高于95% +2. 识别结果包含字符‘20’,即年份 +3. 没有中文,或者有中文并且‘日’和'月'同时在识别结果中 + +```python +# 获取有效预测 + +import json +import re + +zh_pattern = re.compile(u'[\u4e00-\u9fa5]+') #正则表达式,筛选字符是否包含中文 + +file_path = '/home/aistudio/PaddleOCR/inference_results/system_results.txt' +out_path = '/home/aistudio/PaddleOCR/selected_results.txt' +f_out = open(out_path, 'w') + +with open(file_path, "r", encoding='utf-8') as fin: + lines = fin.readlines() + + +for line in lines: + flag = False + # 读取文件内容 + file_name, json_file = line.strip().split('\t') + preds = json.loads(json_file) + res = [] + for item in preds: + transcription = item['transcription'] #获取识别结果 + scores = item['scores'] #获取识别得分 + # 挖掘策略 + if scores > 0.95: + if '20' in transcription and len(transcription) > 4 and len(transcription) < 12: + word = transcription + if not(zh_pattern.search(word) and ('日' not in word or '月' not in word)): + flag = True + res.append(item) + save_pred = file_name + "\t" + json.dumps( + res, ensure_ascii=False) + "\n" + if flag ==True: + f_out.write(save_pred) + +f_out.close() +``` + +然后将有效预测对应的图像区域和标签提取出来,构建训练集。具体实现脚本如下: + +```python +import cv2 +import json +import numpy as np + +PATH = '/home/aistudio/PaddleOCR/inference_results/' #数据原始路径 +SAVE_PATH = '/home/aistudio/mining_images/' #裁剪后数据保存路径 +file_list = '/home/aistudio/PaddleOCR/selected_results.txt' #数据预测结果 +label_file = '/home/aistudio/mining_images/mining_train.list' #输出真实数据训练集标签list + +if not os.path.exists(SAVE_PATH): + os.mkdir(SAVE_PATH) + +f_label = open(label_file, 'w') + + +def get_rotate_crop_image(img, points): + """ + 根据检测结果points,从输入图像img中裁剪出相应的区域 + """ + assert len(points) == 4, "shape of points must be 4*2" + img_crop_width = int( + max( + np.linalg.norm(points[0] - points[1]), + np.linalg.norm(points[2] - points[3]))) + img_crop_height = int( + max( + np.linalg.norm(points[0] - points[3]), + np.linalg.norm(points[1] - points[2]))) + pts_std = np.float32([[0, 0], [img_crop_width, 0], + [img_crop_width, img_crop_height], + [0, img_crop_height]]) + M = cv2.getPerspectiveTransform(points, pts_std) + # 形变或倾斜,会做透视变换,reshape成矩形 + dst_img = cv2.warpPerspective( + img, + M, (img_crop_width, img_crop_height), + borderMode=cv2.BORDER_REPLICATE, + flags=cv2.INTER_CUBIC) + dst_img_height, dst_img_width = dst_img.shape[0:2] + if dst_img_height * 1.0 / dst_img_width >= 1.5: + dst_img = np.rot90(dst_img) + return dst_img + +def crop_and_get_filelist(file_list): + with open(file_list, "r", encoding='utf-8') as fin: + lines = fin.readlines() + + img_num = 0 + for line in lines: + img_name, json_file = line.strip().split('\t') + preds = json.loads(json_file) + for item in preds: + transcription = item['transcription'] + points = item['points'] + points = np.array(points).astype('float32') + #print('processing {}...'.format(img_name)) + + img = cv2.imread(PATH+img_name) + dst_img = get_rotate_crop_image(img, points) + h, w, c = dst_img.shape + newWidth = int((32. / h) * w) + newImg = cv2.resize(dst_img, (newWidth, 32)) + new_img_name = '{:05d}.jpg'.format(img_num) + cv2.imwrite(SAVE_PATH+new_img_name, dst_img) + f_label.write(SAVE_PATH+new_img_name+'\t'+transcription+'\n') + img_num += 1 + + +crop_and_get_filelist(file_list) +f_label.close() +``` + +### 6.3 模型训练 + +通过数据挖掘,我们得到了真实场景数据和对应的标签。接下来使用真实数据finetune,观察精度提升效果。 + + +利用真实数据进行finetune: + +```bash +cd ${PaddleOCR_root} +python tools/train.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml \ + -o Global.pretrained_model=./ckpt/ch_PP-OCRv3_rec_train/best_accuracy \ + Global.epoch_num=20 \ + Global.eval_batch_step='[0, 20]' \ + Train.dataset.data_dir=./data \ + Train.dataset.label_file_list=['./data/mining_train.list'] \ + Train.loader.batch_size_per_card=64 \ + Eval.dataset.data_dir=./data \ + Eval.dataset.label_file_list=["./data/val.list"] \ + Eval.loader.batch_size_per_card=64 +``` + +各参数含义参考第6部分合成数据finetune,只需要对训练数据路径做相应的修改: + +```txt +Train.dataset.data_dir: 训练数据集路径 +Train.dataset.label_file_list: 训练集文件列表 +``` + +示例使用我们提供的真实数据进行finetune,如想换成自己的数据,只需要相应的修改`Train.dataset.data_dir`和`Train.dataset.label_file_list`参数即可。 + +由于数据量不大,这里仅训练20个epoch即可。训练完成后,可以得到合成数据finetune后的精度为best acc=**71.33%**。 + +由于数量比较少,精度会比合成数据finetue的略低。 + + +## 7. 基于合成+真实数据finetune + +为进一步提升模型精度,我们结合使用合成数据和挖掘到的真实数据进行finetune。 + +利用合成+真实数据进行finetune,各参数含义参考第6部分合成数据finetune,只需要对训练数据路径做相应的修改: + +```txt +Train.dataset.data_dir: 训练数据集路径 +Train.dataset.label_file_list: 训练集文件列表 +``` + +生成训练list文件: +```bash +# 生成训练集文件list +cat /home/aistudio/PaddleOCR/data/render_train.list /home/aistudio/PaddleOCR/data/mining_train.list > /home/aistudio/PaddleOCR/data/render_mining_train.list +``` + +启动训练: +```bash +cd ${PaddleOCR_root} +python tools/train.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml \ + -o Global.pretrained_model=./ckpt/ch_PP-OCRv3_rec_train/best_accuracy \ + Global.epoch_num=40 \ + Global.eval_batch_step='[0, 20]' \ + Train.dataset.data_dir=./data \ + Train.dataset.label_file_list=['./data/render_mining_train.list'] \ + Train.loader.batch_size_per_card=64 \ + Eval.dataset.data_dir=./data \ + Eval.dataset.label_file_list=["./data/val.list"] \ + Eval.loader.batch_size_per_card=64 +``` + +示例使用我们提供的真实+合成数据进行finetune,如想换成自己的数据,只需要相应的修改Train.dataset.data_dir和Train.dataset.label_file_list参数即可。 + +由于数据量不大,这里仅训练40个epoch即可。训练完成后,可以得到合成数据finetune后的精度为best acc=**86.99%**。 + +可以看到,相较于原始PP-OCRv3的识别精度62.99%,使用合成数据+真实数据finetune后,识别精度能提升24%。 + +训练完成后,PP-OCRv3模型的推理部署方法可以参考repo文档: https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.5/deploy/README_ch.md From c503dc2f9352272615dc3cc11737b833036c6ccc Mon Sep 17 00:00:00 2001 From: Topdu <784990967@qq.com> Date: Wed, 22 Jun 2022 14:19:55 +0000 Subject: [PATCH 11/45] [New Rec] add vitstr and ABINet --- configs/rec/rec_mtb_nrtr.yml | 6 +- configs/rec/rec_r45_abinet.yml | 103 ++++ configs/rec/rec_svtrnet.yml | 2 +- configs/rec/rec_vitstr_none_ce.yml | 12 +- doc/doc_ch/algorithm_overview.md | 2 + doc/doc_ch/algorithm_rec_abinet.md | 155 ++++++ doc/doc_ch/algorithm_rec_nrtr.md | 140 ++++- doc/doc_en/algorithm_overview_en.md | 2 + doc/doc_en/algorithm_rec_abinet_en.md | 136 +++++ doc/doc_en/algorithm_rec_nrtr_en.md | 141 ++++- doc/doc_en/algorithm_rec_vitstr_en.md | 2 +- ppocr/data/imaug/__init__.py | 2 +- ppocr/data/imaug/label_ops.py | 158 +++--- ppocr/data/imaug/operators.py | 33 -- ppocr/data/imaug/rec_img_aug.py | 38 ++ ppocr/losses/rec_ce_loss.py | 76 ++- ppocr/modeling/backbones/__init__.py | 18 +- ppocr/modeling/backbones/rec_resnet.py | 280 ++++++++++ ppocr/modeling/heads/__init__.py | 3 +- ppocr/modeling/heads/multiheadAttention.py | 163 ------ ppocr/modeling/heads/rec_abinet_head.py | 296 +++++++++++ ppocr/modeling/heads/rec_nrtr_head.py | 489 ++++++------------ ppocr/postprocess/__init__.py | 4 +- ppocr/postprocess/rec_postprocess.py | 209 ++++---- .../configs/rec_mtb_nrtr/rec_mtb_nrtr.yml | 2 +- .../configs/rec_r45_abinet/rec_r45_abinet.yml | 105 ++++ .../rec_r45_abinet/train_infer_python.txt | 53 ++ test_tipc/configs/rec_svtrnet/rec_svtrnet.yml | 2 +- .../rec_svtrnet/train_infer_python.txt | 4 +- .../rec_vitstr_none_ce/rec_vitstr_none_ce.yml | 5 +- tools/export_model.py | 15 +- tools/infer/predict_rec.py | 51 +- tools/program.py | 2 +- 33 files changed, 1973 insertions(+), 736 deletions(-) create mode 100644 configs/rec/rec_r45_abinet.yml create mode 100644 doc/doc_ch/algorithm_rec_abinet.md create mode 100644 doc/doc_en/algorithm_rec_abinet_en.md create mode 100644 ppocr/modeling/backbones/rec_resnet.py delete mode 100755 ppocr/modeling/heads/multiheadAttention.py create mode 100644 ppocr/modeling/heads/rec_abinet_head.py create mode 100644 test_tipc/configs/rec_r45_abinet/rec_r45_abinet.yml create mode 100644 test_tipc/configs/rec_r45_abinet/train_infer_python.txt diff --git a/configs/rec/rec_mtb_nrtr.yml b/configs/rec/rec_mtb_nrtr.yml index 405674882c..4e5826adc9 100644 --- a/configs/rec/rec_mtb_nrtr.yml +++ b/configs/rec/rec_mtb_nrtr.yml @@ -9,7 +9,7 @@ Global: eval_batch_step: [0, 2000] cal_metric_during_train: True pretrained_model: - checkpoints: + checkpoints: save_inference_dir: use_visualdl: False infer_img: doc/imgs_words_en/word_10.png @@ -82,7 +82,7 @@ Train: Eval: dataset: name: LMDBDataSet - data_dir: ./train_data/data_lmdb_release/evaluation/ + data_dir: ./train_data/data_lmdb_release/validation/ transforms: - DecodeImage: # load image img_mode: BGR @@ -97,5 +97,5 @@ Eval: shuffle: False drop_last: False batch_size_per_card: 256 - num_workers: 1 + num_workers: 4 use_shared_memory: False diff --git a/configs/rec/rec_r45_abinet.yml b/configs/rec/rec_r45_abinet.yml new file mode 100644 index 0000000000..a756fead9f --- /dev/null +++ b/configs/rec/rec_r45_abinet.yml @@ -0,0 +1,103 @@ +Global: + use_gpu: True + epoch_num: 10 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/r45_abinet/ + save_epoch_step: 1 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + character_type: en + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_abinet.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.99 + clip_norm: 20.0 + lr: + name: Piecewise + decay_epochs: [6] + values: [0.0001, 0.00001] + regularizer: + name: 'L2' + factor: 0. + +Architecture: + model_type: rec + algorithm: ABINet + in_channels: 3 + Transform: + Backbone: + name: ResNet45 + + Head: + name: ABINetHead + use_lang: True + iter_size: 3 + + +Loss: + name: CELoss + ignore_index: &ignore_index 100 # Must be greater than the number of character classes + +PostProcess: + name: ABINetLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - ABINetLabelEncode: # Class handling label + ignore_index: *ignore_index + - ABINetRecResizeImg: + image_shape: [3, 32, 128] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 96 + drop_last: True + num_workers: 4 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/validation/ + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - ABINetLabelEncode: # Class handling label + ignore_index: *ignore_index + - ABINetRecResizeImg: + image_shape: [3, 32, 128] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 4 + use_shared_memory: False diff --git a/configs/rec/rec_svtrnet.yml b/configs/rec/rec_svtrnet.yml index a700e4bd92..a3d292b6ba 100644 --- a/configs/rec/rec_svtrnet.yml +++ b/configs/rec/rec_svtrnet.yml @@ -26,7 +26,7 @@ Optimizer: name: AdamW beta1: 0.9 beta2: 0.99 - epsilon: 0.00000008 + epsilon: 8.e-8 weight_decay: 0.05 no_weight_decay_name: norm pos_embed one_dim_param_no_weight_decay: true diff --git a/configs/rec/rec_vitstr_none_ce.yml b/configs/rec/rec_vitstr_none_ce.yml index c71eba0cc2..b969c83a5d 100644 --- a/configs/rec/rec_vitstr_none_ce.yml +++ b/configs/rec/rec_vitstr_none_ce.yml @@ -6,7 +6,7 @@ Global: save_model_dir: ./output/rec/vitstr_none_ce/ save_epoch_step: 1 # evaluation is run every 2000 iterations after the 0th iteration# - eval_batch_step: [0, 50] + eval_batch_step: [0, 2000] cal_metric_during_train: True pretrained_model: checkpoints: @@ -23,7 +23,7 @@ Global: Optimizer: name: Adadelta - epsilon: 0.00000001 + epsilon: 1.e-8 rho: 0.95 clip_norm: 5.0 lr: @@ -45,8 +45,8 @@ Architecture: Loss: name: CELoss - smoothing: False with_all: True + ignore_index: &ignore_index 0 # Must be zero or greater than the number of character classes PostProcess: name: ViTSTRLabelDecode @@ -58,12 +58,13 @@ Metric: Train: dataset: name: LMDBDataSet - data_dir: ./train_data/data_lmdb_release/training + data_dir: ./train_data/data_lmdb_release/training/ transforms: - DecodeImage: # load image img_mode: BGR channel_first: False - ViTSTRLabelEncode: # Class handling label + ignore_index: *ignore_index - GrayRecResizeImg: image_shape: [224, 224] # W H resize_type: PIL # PIL or OpenCV @@ -80,12 +81,13 @@ Train: Eval: dataset: name: LMDBDataSet - data_dir: ./train_data/data_lmdb_release/validation + data_dir: ./train_data/data_lmdb_release/validation/ transforms: - DecodeImage: # load image img_mode: BGR channel_first: False - ViTSTRLabelEncode: # Class handling label + ignore_index: *ignore_index - GrayRecResizeImg: image_shape: [224, 224] # W H resize_type: PIL # PIL or OpenCV diff --git a/doc/doc_ch/algorithm_overview.md b/doc/doc_ch/algorithm_overview.md index 4196ec05af..eb81e4cd6d 100755 --- a/doc/doc_ch/algorithm_overview.md +++ b/doc/doc_ch/algorithm_overview.md @@ -67,6 +67,7 @@ - [x] [SEED](./algorithm_rec_seed.md) - [x] [SVTR](./algorithm_rec_svtr.md) - [x] [ViTSTR](./algorithm_rec_vitstr.md) +- [x] [ABINet](./algorithm_rec_abinet.md) 参考[DTRB](https://arxiv.org/abs/1904.01906)[3]文字识别训练和评估流程,使用MJSynth和SynthText两个文字识别数据集训练,在IIIT, SVT, IC03, IC13, IC15, SVTP, CUTE数据集上进行评估,算法效果如下: @@ -86,6 +87,7 @@ |SEED|Aster_Resnet| 85.35% | rec_resnet_stn_bilstm_att | [训练模型](https://paddleocr.bj.bcebos.com/dygraph_v2.1/rec/rec_resnet_stn_bilstm_att.tar) | |SVTR|SVTR-Tiny| 89.25% | rec_svtr_tiny_none_ctc_en | [训练模型](https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/rec_svtr_tiny_none_ctc_en_train.tar) | |ViTSTR|ViTSTR| 79.82% | rec_vitstr_none_ce_en | [训练模型](https://paddleocr.bj.bcebos.com/rec_vitstr_none_ce_train.tar) | +|ABINet|Resnet45| 90.75% | rec_r45_abinet_en | [训练模型](https://paddleocr.bj.bcebos.com/rec_r45_abinet_train.tar) | diff --git a/doc/doc_ch/algorithm_rec_abinet.md b/doc/doc_ch/algorithm_rec_abinet.md new file mode 100644 index 0000000000..d20c703014 --- /dev/null +++ b/doc/doc_ch/algorithm_rec_abinet.md @@ -0,0 +1,155 @@ +# 场景文本识别算法-ABINet + +- [1. 算法简介](#1) +- [2. 环境配置](#2) +- [3. 模型训练、评估、预测](#3) + - [3.1 训练](#3-1) + - [3.2 评估](#3-2) + - [3.3 预测](#3-3) +- [4. 推理部署](#4) + - [4.1 Python推理](#4-1) + - [4.2 C++推理](#4-2) + - [4.3 Serving服务化部署](#4-3) + - [4.4 更多推理部署](#4-4) +- [5. FAQ](#5) + + +## 1. 算法简介 + +论文信息: +> [ABINet: Read Like Humans: Autonomous, Bidirectional and Iterative Language Modeling for Scene Text Recognition](https://openaccess.thecvf.com/content/CVPR2021/papers/Fang_Read_Like_Humans_Autonomous_Bidirectional_and_Iterative_Language_Modeling_for_CVPR_2021_paper.pdf) +> Shancheng Fang and Hongtao Xie and Yuxin Wang and Zhendong Mao and Yongdong Zhang +> CVPR, 2021 + + + +`ABINet`使用MJSynth和SynthText两个文字识别数据集训练,在IIIT, SVT, IC03, IC13, IC15, SVTP, CUTE数据集上进行评估,算法复现效果如下: + +|模型|骨干网络|配置文件|Acc|下载链接| +| --- | --- | --- | --- | --- | +|ABINet|ResNet45|[rec_r45_abinet.yml](../../configs/rec/rec_r45_abinet.yml)|90.75%|[训练模型]()/[预训练模型]| + + +## 2. 环境配置 +请先参考[《运行环境准备》](./environment.md)配置PaddleOCR运行环境,参考[《项目克隆》](./clone.md)克隆项目代码。 + + + +## 3. 模型训练、评估、预测 + + +### 3.1 模型训练 + +请参考[文本识别训练教程](./recognition.md)。PaddleOCR对代码进行了模块化,训练`ABINet`识别模型时需要**更换配置文件**为`ABINet`的[配置文件](../../configs/rec/rec_r45_abinet.yml)。 + +#### 启动训练 + + +具体地,在完成数据准备后,便可以启动训练,训练命令如下: +```shell +#单卡训练(训练周期长,不建议) +python3 tools/train.py -c configs/rec/rec_r45_abinet.yml + +#多卡训练,通过--gpus参数指定卡号 +python3 -m paddle.distributed.launch --gpus '0,1,2,3' tools/train.py -c configs/rec/rec_r45_abinet.yml +``` + + +### 3.2 评估 + +可下载已训练完成的[模型文件](#model),使用如下命令进行评估: + +```shell +# 注意将pretrained_model的路径设置为本地路径。 +python3 -m paddle.distributed.launch --gpus '0' tools/eval.py -c configs/rec/rec_r45_abinet.yml -o Global.pretrained_model=./rec_r45_abinet_train/best_accuracy +``` + + +### 3.3 预测 + +使用如下命令进行单张图片预测: +```shell +# 注意将pretrained_model的路径设置为本地路径。 +python3 tools/infer_rec.py -c configs/rec/rec_r45_abinet.yml -o Global.infer_img='./doc/imgs_words_en/word_10.png' Global.pretrained_model=./rec_r45_abinet_train/best_accuracy +# 预测文件夹下所有图像时,可修改infer_img为文件夹,如 Global.infer_img='./doc/imgs_words_en/'。 +``` + + + +## 4. 推理部署 + + +### 4.1 Python推理 +首先将训练得到best模型,转换成inference model。这里以训练完成的模型为例([模型下载地址]() ),可以使用如下命令进行转换: + +```shell +# 注意将pretrained_model的路径设置为本地路径。 +python3 tools/export_model.py -c configs/rec/rec_r45_abinet.yml -o Global.pretrained_model=./rec_r45_abinet_train/best_accuracy Global.save_inference_dir=./inference/rec_r45_abinet/ +``` +**注意:** +- 如果您是在自己的数据集上训练的模型,并且调整了字典文件,请注意修改配置文件中的`character_dict_path`是否是所需要的字典文件。 +- 如果您修改了训练时的输入大小,请修改`tools/export_model.py`文件中的对应ABINet的`infer_shape`。 + +转换成功后,在目录下有三个文件: +``` +/inference/rec_r45_abinet/ + ├── inference.pdiparams # 识别inference模型的参数文件 + ├── inference.pdiparams.info # 识别inference模型的参数信息,可忽略 + └── inference.pdmodel # 识别inference模型的program文件 +``` + +执行如下命令进行模型推理: + +```shell +python3 tools/infer/predict_rec.py --image_dir='./doc/imgs_words_en/word_10.png' --rec_model_dir='./inference/rec_r45_abinet/' --rec_algorithm='ABINet' --rec_image_shape='3,32,128' --rec_char_dict_path='./ppocr/utils/ic15_dict.txt' +# 预测文件夹下所有图像时,可修改image_dir为文件夹,如 --image_dir='./doc/imgs_words_en/'。 +``` + +![](../imgs_words_en/word_10.png) + +执行命令后,上面图像的预测结果(识别的文本和得分)会打印到屏幕上,示例如下: +结果如下: +```shell +Predicts of ./doc/imgs_words_en/word_10.png:('pain', 0.9999995231628418) +``` + +**注意**: + +- 训练上述模型采用的图像分辨率是[3,32,128],需要通过参数`rec_image_shape`设置为您训练时的识别图像形状。 +- 在推理时需要设置参数`rec_char_dict_path`指定字典,如果您修改了字典,请修改该参数为您的字典文件。 +- 如果您修改了预处理方法,需修改`tools/infer/predict_rec.py`中ABINet的预处理为您的预处理方法。 + + + +### 4.2 C++推理部署 + +由于C++预处理后处理还未支持ABINet,所以暂未支持 + + +### 4.3 Serving服务化部署 + +暂不支持 + + +### 4.4 更多推理部署 + +暂不支持 + + +## 5. FAQ + +1. MJSynth和SynthText两种数据集来自于[ABINet源repo](https://github.com/FangShancheng/ABINet) 。 +2. 我们使用ABINet作者提供的预训练模型进行finetune训练。 + +## 引用 + +```bibtex +@article{Fang2021ABINet, + title = {ABINet: Read Like Humans: Autonomous, Bidirectional and Iterative Language Modeling for Scene Text Recognition}, + author = {Shancheng Fang and Hongtao Xie and Yuxin Wang and Zhendong Mao and Yongdong Zhang}, + booktitle = {CVPR}, + year = {2021}, + url = {https://arxiv.org/abs/2103.06495}, + pages = {7098-7107} +} +``` diff --git a/doc/doc_ch/algorithm_rec_nrtr.md b/doc/doc_ch/algorithm_rec_nrtr.md index d3b626d024..c619ac1dbc 100644 --- a/doc/doc_ch/algorithm_rec_nrtr.md +++ b/doc/doc_ch/algorithm_rec_nrtr.md @@ -12,6 +12,7 @@ - [4.3 Serving服务化部署](#4-3) - [4.4 更多推理部署](#4-4) - [5. FAQ](#5) +- [6. 发行公告](#6) ## 1. 算法简介 @@ -110,7 +111,7 @@ python3 tools/infer/predict_rec.py --image_dir='./doc/imgs_words_en/word_10.png' 执行命令后,上面图像的预测结果(识别的文本和得分)会打印到屏幕上,示例如下: 结果如下: ```shell -Predicts of ./doc/imgs_words_en/word_10.png:('pain', 0.9265879392623901) +Predicts of ./doc/imgs_words_en/word_10.png:('pain', 0.9465042352676392) ``` **注意**: @@ -140,12 +141,147 @@ Predicts of ./doc/imgs_words_en/word_10.png:('pain', 0.9265879392623901) 1. `NRTR`论文中使用Beam搜索进行解码字符,但是速度较慢,这里默认未使用Beam搜索,以贪婪搜索进行解码字符。 + +## 6. 发行公告 + +1. release/2.6更新NRTR代码结构,新版NRTR可加载旧版(release/2.5及之前)模型参数,使用下面示例代码将旧版模型参数转换为新版模型参数: + +```python + + params = paddle.load('path/' + '.pdparams') # 旧版本参数 + state_dict = model.state_dict() # 新版模型参数 + new_state_dict = {} + + for k1, v1 in state_dict.items(): + + k = k1 + if 'encoder' in k and 'self_attn' in k and 'qkv' in k and 'weight' in k: + + k_para = k[:13] + 'layers.' + k[13:] + q = params[k_para.replace('qkv', 'conv1')].transpose((1, 0, 2, 3)) + k = params[k_para.replace('qkv', 'conv2')].transpose((1, 0, 2, 3)) + v = params[k_para.replace('qkv', 'conv3')].transpose((1, 0, 2, 3)) + + new_state_dict[k1] = np.concatenate([q[:, :, 0, 0], k[:, :, 0, 0], v[:, :, 0, 0]], -1) + + elif 'encoder' in k and 'self_attn' in k and 'qkv' in k and 'bias' in k: + + k_para = k[:13] + 'layers.' + k[13:] + q = params[k_para.replace('qkv', 'conv1')] + k = params[k_para.replace('qkv', 'conv2')] + v = params[k_para.replace('qkv', 'conv3')] + + new_state_dict[k1] = np.concatenate([q, k, v], -1) + + elif 'encoder' in k and 'self_attn' in k and 'out_proj' in k: + + k_para = k[:13] + 'layers.' + k[13:] + new_state_dict[k1] = params[k_para] + + elif 'encoder' in k and 'norm3' in k: + k_para = k[:13] + 'layers.' + k[13:] + new_state_dict[k1] = params[k_para.replace('norm3', 'norm2')] + + elif 'encoder' in k and 'norm1' in k: + k_para = k[:13] + 'layers.' + k[13:] + new_state_dict[k1] = params[k_para] + + + elif 'decoder' in k and 'self_attn' in k and 'qkv' in k and 'weight' in k: + k_para = k[:13] + 'layers.' + k[13:] + q = params[k_para.replace('qkv', 'conv1')].transpose((1, 0, 2, 3)) + k = params[k_para.replace('qkv', 'conv2')].transpose((1, 0, 2, 3)) + v = params[k_para.replace('qkv', 'conv3')].transpose((1, 0, 2, 3)) + new_state_dict[k1] = np.concatenate([q[:, :, 0, 0], k[:, :, 0, 0], v[:, :, 0, 0]], -1) + + elif 'decoder' in k and 'self_attn' in k and 'qkv' in k and 'bias' in k: + k_para = k[:13] + 'layers.' + k[13:] + q = params[k_para.replace('qkv', 'conv1')] + k = params[k_para.replace('qkv', 'conv2')] + v = params[k_para.replace('qkv', 'conv3')] + new_state_dict[k1] = np.concatenate([q, k, v], -1) + + elif 'decoder' in k and 'self_attn' in k and 'out_proj' in k: + + k_para = k[:13] + 'layers.' + k[13:] + new_state_dict[k1] = params[k_para] + + elif 'decoder' in k and 'cross_attn' in k and 'q' in k and 'weight' in k: + k_para = k[:13] + 'layers.' + k[13:] + k_para = k_para.replace('cross_attn', 'multihead_attn') + q = params[k_para.replace('q', 'conv1')].transpose((1, 0, 2, 3)) + new_state_dict[k1] = q[:, :, 0, 0] + + elif 'decoder' in k and 'cross_attn' in k and 'q' in k and 'bias' in k: + k_para = k[:13] + 'layers.' + k[13:] + k_para = k_para.replace('cross_attn', 'multihead_attn') + q = params[k_para.replace('q', 'conv1')] + new_state_dict[k1] = q + + elif 'decoder' in k and 'cross_attn' in k and 'kv' in k and 'weight' in k: + k_para = k[:13] + 'layers.' + k[13:] + k_para = k_para.replace('cross_attn', 'multihead_attn') + k = params[k_para.replace('kv', 'conv2')].transpose((1, 0, 2, 3)) + v = params[k_para.replace('kv', 'conv3')].transpose((1, 0, 2, 3)) + new_state_dict[k1] = np.concatenate([k[:, :, 0, 0], v[:, :, 0, 0]], -1) + + elif 'decoder' in k and 'cross_attn' in k and 'kv' in k and 'bias' in k: + k_para = k[:13] + 'layers.' + k[13:] + k_para = k_para.replace('cross_attn', 'multihead_attn') + k = params[k_para.replace('kv', 'conv2')] + v = params[k_para.replace('kv', 'conv3')] + new_state_dict[k1] = np.concatenate([k, v], -1) + + elif 'decoder' in k and 'cross_attn' in k and 'out_proj' in k: + + k_para = k[:13] + 'layers.' + k[13:] + k_para = k_para.replace('cross_attn', 'multihead_attn') + new_state_dict[k1] = params[k_para] + elif 'decoder' in k and 'norm' in k: + k_para = k[:13] + 'layers.' + k[13:] + new_state_dict[k1] = params[k_para] + elif 'mlp' in k and 'weight' in k: + k_para = k[:13] + 'layers.' + k[13:] + k_para = k_para.replace('fc', 'conv') + k_para = k_para.replace('mlp.', '') + w = params[k_para].transpose((1, 0, 2, 3)) + new_state_dict[k1] = w[:, :, 0, 0] + elif 'mlp' in k and 'bias' in k: + k_para = k[:13] + 'layers.' + k[13:] + k_para = k_para.replace('fc', 'conv') + k_para = k_para.replace('mlp.', '') + w = params[k_para] + new_state_dict[k1] = w + + else: + new_state_dict[k1] = params[k1] + + if list(new_state_dict[k1].shape) != list(v1.shape): + print(k1) + + + for k, v1 in state_dict.items(): + if k not in new_state_dict.keys(): + print(1, k) + elif list(new_state_dict[k].shape) != list(v1.shape): + print(2, k) + + + + model.set_state_dict(new_state_dict) + paddle.save(model.state_dict(), 'nrtrnew_from_old_params.pdparams') + +``` + +2. 新版相比与旧版,代码结构简洁,推理速度有所提高。 + + ## 引用 ```bibtex @article{Sheng2019NRTR, title = {NRTR: A No-Recurrence Sequence-to-Sequence Model For Scene Text Recognition}, - author = {Fenfen Sheng and Zhineng Chen andBo Xu}, + author = {Fenfen Sheng and Zhineng Chen and Bo Xu}, booktitle = {ICDAR}, year = {2019}, url = {http://arxiv.org/abs/1806.00926}, diff --git a/doc/doc_en/algorithm_overview_en.md b/doc/doc_en/algorithm_overview_en.md index f2284542ed..28aca7c0d1 100755 --- a/doc/doc_en/algorithm_overview_en.md +++ b/doc/doc_en/algorithm_overview_en.md @@ -66,6 +66,7 @@ Supported text recognition algorithms (Click the link to get the tutorial): - [x] [SEED](./algorithm_rec_seed_en.md) - [x] [SVTR](./algorithm_rec_svtr_en.md) - [x] [ViTSTR](./algorithm_rec_vitstr_en.md) +- [x] [ABINet](./algorithm_rec_abinet_en.md) Refer to [DTRB](https://arxiv.org/abs/1904.01906), the training and evaluation result of these above text recognition (using MJSynth and SynthText for training, evaluate on IIIT, SVT, IC03, IC13, IC15, SVTP, CUTE) is as follow: @@ -85,6 +86,7 @@ Refer to [DTRB](https://arxiv.org/abs/1904.01906), the training and evaluation r |SEED|Aster_Resnet| 85.35% | rec_resnet_stn_bilstm_att | [trained model](https://paddleocr.bj.bcebos.com/dygraph_v2.1/rec/rec_resnet_stn_bilstm_att.tar) | |SVTR|SVTR-Tiny| 89.25% | rec_svtr_tiny_none_ctc_en | [trained model](https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/rec_svtr_tiny_none_ctc_en_train.tar) | |ViTSTR|ViTSTR| 79.82% | rec_vitstr_none_ce_en | [trained model](https://paddleocr.bj.bcebos.com/rec_vitstr_none_none_train.tar) | +|ABINet|Resnet45| 90.75% | rec_r45_abinet_en | [trained model](https://paddleocr.bj.bcebos.com/rec_r45_abinet_train.tar) | diff --git a/doc/doc_en/algorithm_rec_abinet_en.md b/doc/doc_en/algorithm_rec_abinet_en.md new file mode 100644 index 0000000000..3b0f6c09a9 --- /dev/null +++ b/doc/doc_en/algorithm_rec_abinet_en.md @@ -0,0 +1,136 @@ +# ABINet + +- [1. Introduction](#1) +- [2. Environment](#2) +- [3. Model Training / Evaluation / Prediction](#3) + - [3.1 Training](#3-1) + - [3.2 Evaluation](#3-2) + - [3.3 Prediction](#3-3) +- [4. Inference and Deployment](#4) + - [4.1 Python Inference](#4-1) + - [4.2 C++ Inference](#4-2) + - [4.3 Serving](#4-3) + - [4.4 More](#4-4) +- [5. FAQ](#5) + + +## 1. Introduction + +Paper: +> [ABINet: Read Like Humans: Autonomous, Bidirectional and Iterative Language Modeling for Scene Text Recognition](https://openaccess.thecvf.com/content/CVPR2021/papers/Fang_Read_Like_Humans_Autonomous_Bidirectional_and_Iterative_Language_Modeling_for_CVPR_2021_paper.pdf) +> Shancheng Fang and Hongtao Xie and Yuxin Wang and Zhendong Mao and Yongdong Zhang +> CVPR, 2021 + +Using MJSynth and SynthText two text recognition datasets for training, and evaluating on IIIT, SVT, IC03, IC13, IC15, SVTP, CUTE datasets, the algorithm reproduction effect is as follows: + +|Model|Backbone|config|Acc|Download link| +| --- | --- | --- | --- | --- | +|ABINet|ResNet45|[rec_r45_abinet.yml](../../configs/rec/rec_r45_abinet.yml)|90.75%|[trained model]()/[pretrained model]()| + + +## 2. Environment +Please refer to ["Environment Preparation"](./environment_en.md) to configure the PaddleOCR environment, and refer to ["Project Clone"](./clone_en.md) to clone the project code. + + + +## 3. Model Training / Evaluation / Prediction + +Please refer to [Text Recognition Tutorial](./recognition_en.md). PaddleOCR modularizes the code, and training different recognition models only requires **changing the configuration file**. + +Training: + +Specifically, after the data preparation is completed, the training can be started. The training command is as follows: + +``` +#Single GPU training (long training period, not recommended) +python3 tools/train.py -c configs/rec/rec_r45_abinet.yml + +#Multi GPU training, specify the gpu number through the --gpus parameter +python3 -m paddle.distributed.launch --gpus '0,1,2,3' tools/train.py -c configs/rec/rec_r45_abinet.yml +``` + +Evaluation: + +``` +# GPU evaluation +python3 -m paddle.distributed.launch --gpus '0' tools/eval.py -c configs/rec/rec_r45_abinet.yml -o Global.pretrained_model={path/to/weights}/best_accuracy +``` + +Prediction: + +``` +# The configuration file used for prediction must match the training +python3 tools/infer_rec.py -c configs/rec/rec_r45_abinet.yml -o Global.infer_img='./doc/imgs_words_en/word_10.png' Global.pretrained_model=./rec_r45_abinet_train/best_accuracy +``` + + +## 4. Inference and Deployment + + +### 4.1 Python Inference +First, the model saved during the ABINet text recognition training process is converted into an inference model. ( [Model download link]()) ), you can use the following command to convert: + +``` +python3 tools/export_model.py -c configs/rec/rec_r45_abinet.yml -o Global.pretrained_model=./rec_r45_abinet_train/best_accuracy Global.save_inference_dir=./inference/rec_r45_abinet +``` + +**Note:** +- If you are training the model on your own dataset and have modified the dictionary file, please pay attention to modify the `character_dict_path` in the configuration file to the modified dictionary file. +- If you modified the input size during training, please modify the `infer_shape` corresponding to ABINet in the `tools/export_model.py` file. + +After the conversion is successful, there are three files in the directory: +``` +/inference/rec_r45_abinet/ + ├── inference.pdiparams + ├── inference.pdiparams.info + └── inference.pdmodel +``` + + +For ABINet text recognition model inference, the following commands can be executed: + +``` +python3 tools/infer/predict_rec.py --image_dir='./doc/imgs_words_en/word_10.png' --rec_model_dir='./inference/rec_r45_abinet/' --rec_algorithm='ABINet' --rec_image_shape='3,32,128' --rec_char_dict_path='./ppocr/utils/ic15_dict.txt' +``` + +![](../imgs_words_en/word_10.png) + +After executing the command, the prediction result (recognized text and score) of the image above is printed to the screen, an example is as follows: +The result is as follows: +```shell +Predicts of ./doc/imgs_words_en/word_10.png:('pain', 0.9999995231628418) +``` + + +### 4.2 C++ Inference + +Not supported + + +### 4.3 Serving + +Not supported + + +### 4.4 More + +Not supported + + +## 5. FAQ + +1. Note that the MJSynth and SynthText datasets come from [ABINet repo](https://github.com/FangShancheng/ABINet). +2. We use the pre-trained model provided by the ABINet authors for finetune training. + +## Citation + +```bibtex +@article{Fang2021ABINet, + title = {ABINet: Read Like Humans: Autonomous, Bidirectional and Iterative Language Modeling for Scene Text Recognition}, + author = {Shancheng Fang and Hongtao Xie and Yuxin Wang and Zhendong Mao and Yongdong Zhang}, + booktitle = {CVPR}, + year = {2021}, + url = {https://arxiv.org/abs/2103.06495}, + pages = {7098-7107} +} +``` diff --git a/doc/doc_en/algorithm_rec_nrtr_en.md b/doc/doc_en/algorithm_rec_nrtr_en.md index 40c9b91629..309d7ab123 100644 --- a/doc/doc_en/algorithm_rec_nrtr_en.md +++ b/doc/doc_en/algorithm_rec_nrtr_en.md @@ -12,6 +12,7 @@ - [4.3 Serving](#4-3) - [4.4 More](#4-4) - [5. FAQ](#5) +- [6. Release Note](#6) ## 1. Introduction @@ -25,7 +26,7 @@ Using MJSynth and SynthText two text recognition datasets for training, and eval |Model|Backbone|config|Acc|Download link| | --- | --- | --- | --- | --- | -|NRTR|MTB|[rec_mtb_nrtr.yml](../../configs/rec/rec_mtb_nrtr.yml)|84.21%|[train model](https://paddleocr.bj.bcebos.com/dygraph_v2.0/en/rec_mtb_nrtr_train.tar)| +|NRTR|MTB|[rec_mtb_nrtr.yml](../../configs/rec/rec_mtb_nrtr.yml)|84.21%|[trained model](https://paddleocr.bj.bcebos.com/dygraph_v2.0/en/rec_mtb_nrtr_train.tar)| ## 2. Environment @@ -98,7 +99,7 @@ python3 tools/infer/predict_rec.py --image_dir='./doc/imgs_words_en/word_10.png' After executing the command, the prediction result (recognized text and score) of the image above is printed to the screen, an example is as follows: The result is as follows: ```shell -Predicts of ./doc/imgs_words_en/word_10.png:('pain', 0.9265879392623901) +Predicts of ./doc/imgs_words_en/word_10.png:('pain', 0.9465042352676392) ``` @@ -121,12 +122,146 @@ Not supported 1. In the `NRTR` paper, Beam search is used to decode characters, but the speed is slow. Beam search is not used by default here, and greedy search is used to decode characters. + +## 6. Release Note + +1. The release/2.6 version updates the NRTR code structure. The new version of NRTR can load the model parameters of the old version (release/2.5 and before), and you may use the following code to convert the old version model parameters to the new version model parameters: + +```python + + params = paddle.load('path/' + '.pdparams') # the old version parameters + state_dict = model.state_dict() # the new version model parameters + new_state_dict = {} + + for k1, v1 in state_dict.items(): + + k = k1 + if 'encoder' in k and 'self_attn' in k and 'qkv' in k and 'weight' in k: + + k_para = k[:13] + 'layers.' + k[13:] + q = params[k_para.replace('qkv', 'conv1')].transpose((1, 0, 2, 3)) + k = params[k_para.replace('qkv', 'conv2')].transpose((1, 0, 2, 3)) + v = params[k_para.replace('qkv', 'conv3')].transpose((1, 0, 2, 3)) + + new_state_dict[k1] = np.concatenate([q[:, :, 0, 0], k[:, :, 0, 0], v[:, :, 0, 0]], -1) + + elif 'encoder' in k and 'self_attn' in k and 'qkv' in k and 'bias' in k: + + k_para = k[:13] + 'layers.' + k[13:] + q = params[k_para.replace('qkv', 'conv1')] + k = params[k_para.replace('qkv', 'conv2')] + v = params[k_para.replace('qkv', 'conv3')] + + new_state_dict[k1] = np.concatenate([q, k, v], -1) + + elif 'encoder' in k and 'self_attn' in k and 'out_proj' in k: + + k_para = k[:13] + 'layers.' + k[13:] + new_state_dict[k1] = params[k_para] + + elif 'encoder' in k and 'norm3' in k: + k_para = k[:13] + 'layers.' + k[13:] + new_state_dict[k1] = params[k_para.replace('norm3', 'norm2')] + + elif 'encoder' in k and 'norm1' in k: + k_para = k[:13] + 'layers.' + k[13:] + new_state_dict[k1] = params[k_para] + + + elif 'decoder' in k and 'self_attn' in k and 'qkv' in k and 'weight' in k: + k_para = k[:13] + 'layers.' + k[13:] + q = params[k_para.replace('qkv', 'conv1')].transpose((1, 0, 2, 3)) + k = params[k_para.replace('qkv', 'conv2')].transpose((1, 0, 2, 3)) + v = params[k_para.replace('qkv', 'conv3')].transpose((1, 0, 2, 3)) + new_state_dict[k1] = np.concatenate([q[:, :, 0, 0], k[:, :, 0, 0], v[:, :, 0, 0]], -1) + + elif 'decoder' in k and 'self_attn' in k and 'qkv' in k and 'bias' in k: + k_para = k[:13] + 'layers.' + k[13:] + q = params[k_para.replace('qkv', 'conv1')] + k = params[k_para.replace('qkv', 'conv2')] + v = params[k_para.replace('qkv', 'conv3')] + new_state_dict[k1] = np.concatenate([q, k, v], -1) + + elif 'decoder' in k and 'self_attn' in k and 'out_proj' in k: + + k_para = k[:13] + 'layers.' + k[13:] + new_state_dict[k1] = params[k_para] + + elif 'decoder' in k and 'cross_attn' in k and 'q' in k and 'weight' in k: + k_para = k[:13] + 'layers.' + k[13:] + k_para = k_para.replace('cross_attn', 'multihead_attn') + q = params[k_para.replace('q', 'conv1')].transpose((1, 0, 2, 3)) + new_state_dict[k1] = q[:, :, 0, 0] + + elif 'decoder' in k and 'cross_attn' in k and 'q' in k and 'bias' in k: + k_para = k[:13] + 'layers.' + k[13:] + k_para = k_para.replace('cross_attn', 'multihead_attn') + q = params[k_para.replace('q', 'conv1')] + new_state_dict[k1] = q + + elif 'decoder' in k and 'cross_attn' in k and 'kv' in k and 'weight' in k: + k_para = k[:13] + 'layers.' + k[13:] + k_para = k_para.replace('cross_attn', 'multihead_attn') + k = params[k_para.replace('kv', 'conv2')].transpose((1, 0, 2, 3)) + v = params[k_para.replace('kv', 'conv3')].transpose((1, 0, 2, 3)) + new_state_dict[k1] = np.concatenate([k[:, :, 0, 0], v[:, :, 0, 0]], -1) + + elif 'decoder' in k and 'cross_attn' in k and 'kv' in k and 'bias' in k: + k_para = k[:13] + 'layers.' + k[13:] + k_para = k_para.replace('cross_attn', 'multihead_attn') + k = params[k_para.replace('kv', 'conv2')] + v = params[k_para.replace('kv', 'conv3')] + new_state_dict[k1] = np.concatenate([k, v], -1) + + elif 'decoder' in k and 'cross_attn' in k and 'out_proj' in k: + + k_para = k[:13] + 'layers.' + k[13:] + k_para = k_para.replace('cross_attn', 'multihead_attn') + new_state_dict[k1] = params[k_para] + elif 'decoder' in k and 'norm' in k: + k_para = k[:13] + 'layers.' + k[13:] + new_state_dict[k1] = params[k_para] + elif 'mlp' in k and 'weight' in k: + k_para = k[:13] + 'layers.' + k[13:] + k_para = k_para.replace('fc', 'conv') + k_para = k_para.replace('mlp.', '') + w = params[k_para].transpose((1, 0, 2, 3)) + new_state_dict[k1] = w[:, :, 0, 0] + elif 'mlp' in k and 'bias' in k: + k_para = k[:13] + 'layers.' + k[13:] + k_para = k_para.replace('fc', 'conv') + k_para = k_para.replace('mlp.', '') + w = params[k_para] + new_state_dict[k1] = w + + else: + new_state_dict[k1] = params[k1] + + if list(new_state_dict[k1].shape) != list(v1.shape): + print(k1) + + + for k, v1 in state_dict.items(): + if k not in new_state_dict.keys(): + print(1, k) + elif list(new_state_dict[k].shape) != list(v1.shape): + print(2, k) + + + + model.set_state_dict(new_state_dict) + paddle.save(model.state_dict(), 'nrtrnew_from_old_params.pdparams') + +``` + +2. The new version has a clean code structure and improved inference speed compared with the old version. + ## Citation ```bibtex @article{Sheng2019NRTR, title = {NRTR: A No-Recurrence Sequence-to-Sequence Model For Scene Text Recognition}, - author = {Fenfen Sheng and Zhineng Chen andBo Xu}, + author = {Fenfen Sheng and Zhineng Chen and Bo Xu}, booktitle = {ICDAR}, year = {2019}, url = {http://arxiv.org/abs/1806.00926}, diff --git a/doc/doc_en/algorithm_rec_vitstr_en.md b/doc/doc_en/algorithm_rec_vitstr_en.md index b6f26d39e2..a6f9e2f15d 100644 --- a/doc/doc_en/algorithm_rec_vitstr_en.md +++ b/doc/doc_en/algorithm_rec_vitstr_en.md @@ -25,7 +25,7 @@ Using MJSynth and SynthText two text recognition datasets for training, and eval |Model|Backbone|config|Acc|Download link| | --- | --- | --- | --- | --- | -|ViTSTR|ViTSTR|[rec_vitstr_none_ce.yml](../../configs/rec/rec_vitstr_none_ce.yml)|79.82%|[训练模型](https://paddleocr.bj.bcebos.com/rec_vitstr_none_none_train.tar)| +|ViTSTR|ViTSTR|[rec_vitstr_none_ce.yml](../../configs/rec/rec_vitstr_none_ce.yml)|79.82%|[trained model](https://paddleocr.bj.bcebos.com/rec_vitstr_none_none_train.tar)| ## 2. Environment diff --git a/ppocr/data/imaug/__init__.py b/ppocr/data/imaug/__init__.py index 88c9603fb0..58e8a5c780 100644 --- a/ppocr/data/imaug/__init__.py +++ b/ppocr/data/imaug/__init__.py @@ -24,7 +24,7 @@ from .make_pse_gt import MakePseGt from .rec_img_aug import RecAug, RecConAug, RecResizeImg, ClsResizeImg, \ - SRNRecResizeImg, GrayRecResizeImg, SARRecResizeImg, PRENResizeImg + SRNRecResizeImg, GrayRecResizeImg, SARRecResizeImg, PRENResizeImg, ABINetRecResizeImg from .ssl_img_aug import SSLRotateResize from .randaugment import RandAugment from .copy_paste import CopyPaste diff --git a/ppocr/data/imaug/label_ops.py b/ppocr/data/imaug/label_ops.py index 0366e3f785..d96ecf639a 100644 --- a/ppocr/data/imaug/label_ops.py +++ b/ppocr/data/imaug/label_ops.py @@ -157,37 +157,6 @@ class BaseRecLabelEncode(object): return text_list -class NRTRLabelEncode(BaseRecLabelEncode): - """ Convert between text-label and text-index """ - - def __init__(self, - max_text_length, - character_dict_path=None, - use_space_char=False, - **kwargs): - - super(NRTRLabelEncode, self).__init__( - max_text_length, character_dict_path, use_space_char) - - def __call__(self, data): - text = data['label'] - text = self.encode(text) - if text is None: - return None - if len(text) >= self.max_text_len - 1: - return None - data['length'] = np.array(len(text)) - text.insert(0, 2) - text.append(3) - text = text + [0] * (self.max_text_len - len(text)) - data['label'] = np.array(text) - return data - - def add_special_char(self, dict_character): - dict_character = ['blank', '', '', ''] + dict_character - return dict_character - - class CTCLabelEncode(BaseRecLabelEncode): """ Convert between text-label and text-index """ @@ -840,37 +809,6 @@ class PRENLabelEncode(BaseRecLabelEncode): return data -class ViTSTRLabelEncode(BaseRecLabelEncode): - """ Convert between text-label and text-index """ - - def __init__(self, - max_text_length, - character_dict_path=None, - use_space_char=False, - **kwargs): - - super(ViTSTRLabelEncode, self).__init__( - max_text_length, character_dict_path, use_space_char) - - def __call__(self, data): - text = data['label'] - text = self.encode(text) - if text is None: - return None - if len(text) >= self.max_text_len: - return None - data['length'] = np.array(len(text)) - text.insert(0, 0) - text.append(1) - text = text + [0] * (self.max_text_len + 2 - len(text)) - data['label'] = np.array(text) - return data - - def add_special_char(self, dict_character): - dict_character = ['', ''] + dict_character - return dict_character - - class VQATokenLabelEncode(object): """ Label encode for NLP VQA methods @@ -1077,3 +1015,99 @@ class MultiLabelEncode(BaseRecLabelEncode): data_out['label_sar'] = sar['label'] data_out['length'] = ctc['length'] return data_out + + +class NRTRLabelEncode(BaseRecLabelEncode): + """ Convert between text-label and text-index """ + + def __init__(self, + max_text_length, + character_dict_path=None, + use_space_char=False, + **kwargs): + + super(NRTRLabelEncode, self).__init__( + max_text_length, character_dict_path, use_space_char) + + def __call__(self, data): + text = data['label'] + text = self.encode(text) + if text is None: + return None + if len(text) >= self.max_text_len - 1: + return None + data['length'] = np.array(len(text)) + text.insert(0, 2) + text.append(3) + text = text + [0] * (self.max_text_len - len(text)) + data['label'] = np.array(text) + return data + + def add_special_char(self, dict_character): + dict_character = ['blank', '', '', ''] + dict_character + return dict_character + + +class ViTSTRLabelEncode(BaseRecLabelEncode): + """ Convert between text-label and text-index """ + + def __init__(self, + max_text_length, + character_dict_path=None, + use_space_char=False, + ignore_index=0, + **kwargs): + + super(ViTSTRLabelEncode, self).__init__( + max_text_length, character_dict_path, use_space_char) + self.ignore_index = ignore_index + + def __call__(self, data): + text = data['label'] + text = self.encode(text) + if text is None: + return None + if len(text) >= self.max_text_len: + return None + data['length'] = np.array(len(text)) + text.insert(0, self.ignore_index) + text.append(1) + text = text + [self.ignore_index] * (self.max_text_len + 2 - len(text)) + data['label'] = np.array(text) + return data + + def add_special_char(self, dict_character): + dict_character = ['', ''] + dict_character + return dict_character + + +class ABINetLabelEncode(BaseRecLabelEncode): + """ Convert between text-label and text-index """ + + def __init__(self, + max_text_length, + character_dict_path=None, + use_space_char=False, + ignore_index=100, + **kwargs): + + super(ABINetLabelEncode, self).__init__( + max_text_length, character_dict_path, use_space_char) + self.ignore_index = ignore_index + + def __call__(self, data): + text = data['label'] + text = self.encode(text) + if text is None: + return None + if len(text) >= self.max_text_len: + return None + data['length'] = np.array(len(text)) + text.append(0) + text = text + [self.ignore_index] * (self.max_text_len + 1 - len(text)) + data['label'] = np.array(text) + return data + + def add_special_char(self, dict_character): + dict_character = [''] + dict_character + return dict_character diff --git a/ppocr/data/imaug/operators.py b/ppocr/data/imaug/operators.py index 09736515e7..5397d71ccb 100644 --- a/ppocr/data/imaug/operators.py +++ b/ppocr/data/imaug/operators.py @@ -67,39 +67,6 @@ class DecodeImage(object): return data -class NRTRDecodeImage(object): - """ decode image """ - - def __init__(self, img_mode='RGB', channel_first=False, **kwargs): - self.img_mode = img_mode - self.channel_first = channel_first - - def __call__(self, data): - img = data['image'] - if six.PY2: - assert type(img) is str and len( - img) > 0, "invalid input 'img' in DecodeImage" - else: - assert type(img) is bytes and len( - img) > 0, "invalid input 'img' in DecodeImage" - img = np.frombuffer(img, dtype='uint8') - - img = cv2.imdecode(img, 1) - - if img is None: - return None - if self.img_mode == 'GRAY': - img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) - elif self.img_mode == 'RGB': - assert img.shape[2] == 3, 'invalid shape of image[%s]' % (img.shape) - img = img[:, :, ::-1] - img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) - if self.channel_first: - img = img.transpose((2, 0, 1)) - data['image'] = img - return data - - class NormalizeImage(object): """ normalize image such as substract mean, divide std """ diff --git a/ppocr/data/imaug/rec_img_aug.py b/ppocr/data/imaug/rec_img_aug.py index d3120f5950..a8b3b813b3 100644 --- a/ppocr/data/imaug/rec_img_aug.py +++ b/ppocr/data/imaug/rec_img_aug.py @@ -279,6 +279,24 @@ class PRENResizeImg(object): return data +class ABINetRecResizeImg(object): + def __init__(self, + image_shape, + infer_mode=False, + character_dict_path=None, + **kwargs): + self.image_shape = image_shape + self.infer_mode = infer_mode + self.character_dict_path = character_dict_path + + def __call__(self, data): + img = data['image'] + norm_img, valid_ratio = resize_norm_img_abinet(img, self.image_shape) + data['image'] = norm_img + data['valid_ratio'] = valid_ratio + return data + + def resize_norm_img_sar(img, image_shape, width_downsample_ratio=0.25): imgC, imgH, imgW_min, imgW_max = image_shape h = img.shape[0] @@ -397,6 +415,26 @@ def resize_norm_img_srn(img, image_shape): return np.reshape(img_black, (c, row, col)).astype(np.float32) +def resize_norm_img_abinet(img, image_shape): + imgC, imgH, imgW = image_shape + + resized_image = cv2.resize( + img, (imgW, imgH), interpolation=cv2.INTER_LINEAR) + resized_w = imgW + resized_image = resized_image.astype('float32') + resized_image = resized_image / 255. + + mean = np.array([0.485, 0.456, 0.406]) + std = np.array([0.229, 0.224, 0.225]) + resized_image = ( + resized_image - mean[None, None, ...]) / std[None, None, ...] + resized_image = resized_image.transpose((2, 0, 1)) + resized_image = resized_image.astype('float32') + + valid_ratio = min(1.0, float(resized_w / imgW)) + return resized_image, valid_ratio + + def srn_other_inputs(image_shape, num_heads, max_text_length): imgC, imgH, imgW = image_shape diff --git a/ppocr/losses/rec_ce_loss.py b/ppocr/losses/rec_ce_loss.py index b837ac27bb..614384de86 100644 --- a/ppocr/losses/rec_ce_loss.py +++ b/ppocr/losses/rec_ce_loss.py @@ -4,31 +4,63 @@ import paddle.nn.functional as F class CELoss(nn.Layer): - def __init__(self, smoothing=True, with_all=False, **kwargs): + def __init__(self, + smoothing=False, + with_all=False, + ignore_index=-1, + **kwargs): super(CELoss, self).__init__() - self.loss_func = nn.CrossEntropyLoss(reduction='mean', ignore_index=0) + if ignore_index >= 0: + self.loss_func = nn.CrossEntropyLoss( + reduction='mean', ignore_index=ignore_index) + else: + self.loss_func = nn.CrossEntropyLoss(reduction='mean') self.smoothing = smoothing self.with_all = with_all def forward(self, pred, batch): - pred = pred.reshape([-1, pred.shape[2]]) - if self.with_all: - tgt = batch[1] + + if isinstance(pred, dict): # for ABINet + loss = {} + loss_sum = [] + for name, logits in pred.items(): + if isinstance(logits, list): + logit_num = len(logits) + all_tgt = paddle.concat([batch[1]] * logit_num, 0) + all_logits = paddle.concat(logits, 0) + flt_logtis = all_logits.reshape([-1, all_logits.shape[2]]) + flt_tgt = all_tgt.reshape([-1]) + else: + flt_logtis = logits.reshape([-1, logits.shape[2]]) + flt_tgt = batch[1].reshape([-1]) + loss[name + '_loss'] = self.loss_func(flt_logtis, flt_tgt) + loss_sum.append(loss[name + '_loss']) + loss['loss'] = sum(loss_sum) + return loss else: - max_len = batch[2].max() - tgt = batch[1][:, 1:2 + max_len] - tgt = tgt.reshape([-1]) - if self.smoothing: - eps = 0.1 - n_class = pred.shape[1] - one_hot = F.one_hot(tgt, pred.shape[1]) - one_hot = one_hot * (1 - eps) + (1 - one_hot) * eps / (n_class - 1) - log_prb = F.log_softmax(pred, axis=1) - non_pad_mask = paddle.not_equal( - tgt, paddle.zeros( - tgt.shape, dtype=tgt.dtype)) - loss = -(one_hot * log_prb).sum(axis=1) - loss = loss.masked_select(non_pad_mask).mean() - else: - loss = self.loss_func(pred, tgt) - return {'loss': loss} + if self.with_all: # for ViTSTR + tgt = batch[1] + pred = pred.reshape([-1, pred.shape[2]]) + tgt = tgt.reshape([-1]) + loss = self.loss_func(pred, tgt) + return {'loss': loss} + else: # for NRTR + max_len = batch[2].max() + tgt = batch[1][:, 1:2 + max_len] + pred = pred.reshape([-1, pred.shape[2]]) + tgt = tgt.reshape([-1]) + if self.smoothing: + eps = 0.1 + n_class = pred.shape[1] + one_hot = F.one_hot(tgt, pred.shape[1]) + one_hot = one_hot * (1 - eps) + (1 - one_hot) * eps / ( + n_class - 1) + log_prb = F.log_softmax(pred, axis=1) + non_pad_mask = paddle.not_equal( + tgt, paddle.zeros( + tgt.shape, dtype=tgt.dtype)) + loss = -(one_hot * log_prb).sum(axis=1) + loss = loss.masked_select(non_pad_mask).mean() + else: + loss = self.loss_func(pred, tgt) + return {'loss': loss} diff --git a/ppocr/modeling/backbones/__init__.py b/ppocr/modeling/backbones/__init__.py index a368e74816..56d2c2b9db 100755 --- a/ppocr/modeling/backbones/__init__.py +++ b/ppocr/modeling/backbones/__init__.py @@ -27,7 +27,7 @@ def build_backbone(config, model_type): from .rec_resnet_fpn import ResNetFPN from .rec_mv1_enhance import MobileNetV1Enhance from .rec_nrtr_mtb import MTB - from .rec_resnet_31 import ResNet31 + from .rec_resnet import ResNet31, ResNet45 from .rec_resnet_aster import ResNet_ASTER from .rec_micronet import MicroNet from .rec_efficientb3_pren import EfficientNetb3_PREN @@ -35,29 +35,29 @@ def build_backbone(config, model_type): from .rec_vitstr import ViTSTR support_dict = [ 'MobileNetV1Enhance', 'MobileNetV3', 'ResNet', 'ResNetFPN', 'MTB', - "ResNet31", "ResNet_ASTER", 'MicroNet', 'EfficientNetb3_PREN', - 'SVTRNet', 'ViTSTR' + 'ResNet31', 'ResNet45', 'ResNet_ASTER', 'MicroNet', + 'EfficientNetb3_PREN', 'SVTRNet', 'ViTSTR' ] - elif model_type == "e2e": + elif model_type == 'e2e': from .e2e_resnet_vd_pg import ResNet support_dict = ['ResNet'] elif model_type == 'kie': from .kie_unet_sdmgr import Kie_backbone support_dict = ['Kie_backbone'] - elif model_type == "table": + elif model_type == 'table': from .table_resnet_vd import ResNet from .table_mobilenet_v3 import MobileNetV3 - support_dict = ["ResNet", "MobileNetV3"] + support_dict = ['ResNet', 'MobileNetV3'] elif model_type == 'vqa': from .vqa_layoutlm import LayoutLMForSer, LayoutLMv2ForSer, LayoutLMv2ForRe, LayoutXLMForSer, LayoutXLMForRe support_dict = [ - "LayoutLMForSer", "LayoutLMv2ForSer", 'LayoutLMv2ForRe', - "LayoutXLMForSer", 'LayoutXLMForRe' + 'LayoutLMForSer', 'LayoutLMv2ForSer', 'LayoutLMv2ForRe', + 'LayoutXLMForSer', 'LayoutXLMForRe' ] else: raise NotImplementedError - module_name = config.pop("name") + module_name = config.pop('name') assert module_name in support_dict, Exception( "when model typs is {}, backbone only support {}".format(model_type, support_dict)) diff --git a/ppocr/modeling/backbones/rec_resnet.py b/ppocr/modeling/backbones/rec_resnet.py new file mode 100644 index 0000000000..ce76b642cc --- /dev/null +++ b/ppocr/modeling/backbones/rec_resnet.py @@ -0,0 +1,280 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# 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. +""" +This code is refer from: +https://github.com/open-mmlab/mmocr/blob/main/mmocr/models/textrecog/layers/conv_layer.py +https://github.com/open-mmlab/mmocr/blob/main/mmocr/models/textrecog/backbones/resnet31_ocr.py +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import ParamAttr +from paddle.nn.initializer import KaimingNormal +import paddle.nn as nn +import paddle.nn.functional as F +import numpy as np +import math + +__all__ = ["ResNet31", "ResNet45"] + + +def conv1x1(in_planes, out_planes, stride=1): + return nn.Conv2D( + in_planes, + out_planes, + kernel_size=1, + stride=stride, + weight_attr=ParamAttr(initializer=KaimingNormal()), + bias_attr=False) + + +def conv3x3(in_channel, out_channel, stride=1): + return nn.Conv2D( + in_channel, + out_channel, + kernel_size=3, + stride=stride, + padding=1, + weight_attr=ParamAttr(initializer=KaimingNormal()), + bias_attr=False) + + +class BasicBlock(nn.Layer): + expansion = 1 + + def __init__(self, in_channels, channels, stride=1, downsample=None): + super().__init__() + self.conv1 = conv1x1(in_channels, channels) + self.bn1 = nn.BatchNorm2D(channels) + self.relu = nn.ReLU() + self.conv2 = conv3x3(channels, channels, stride) + self.bn2 = nn.BatchNorm2D(channels) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + + if self.downsample is not None: + residual = self.downsample(x) + out += residual + out = self.relu(out) + + return out + + +class ResNet31(nn.Layer): + ''' + Args: + in_channels (int): Number of channels of input image tensor. + layers (list[int]): List of BasicBlock number for each stage. + channels (list[int]): List of out_channels of Conv2d layer. + out_indices (None | Sequence[int]): Indices of output stages. + last_stage_pool (bool): If True, add `MaxPool2d` layer to last stage. + ''' + + def __init__(self, + in_channels=3, + layers=[1, 2, 5, 3], + channels=[64, 128, 256, 256, 512, 512, 512], + out_indices=None, + last_stage_pool=False): + super(ResNet31, self).__init__() + assert isinstance(in_channels, int) + assert isinstance(last_stage_pool, bool) + + self.out_indices = out_indices + self.last_stage_pool = last_stage_pool + + # conv 1 (Conv Conv) + self.conv1_1 = nn.Conv2D( + in_channels, channels[0], kernel_size=3, stride=1, padding=1) + self.bn1_1 = nn.BatchNorm2D(channels[0]) + self.relu1_1 = nn.ReLU() + + self.conv1_2 = nn.Conv2D( + channels[0], channels[1], kernel_size=3, stride=1, padding=1) + self.bn1_2 = nn.BatchNorm2D(channels[1]) + self.relu1_2 = nn.ReLU() + + # conv 2 (Max-pooling, Residual block, Conv) + self.pool2 = nn.MaxPool2D( + kernel_size=2, stride=2, padding=0, ceil_mode=True) + self.block2 = self._make_layer(channels[1], channels[2], layers[0]) + self.conv2 = nn.Conv2D( + channels[2], channels[2], kernel_size=3, stride=1, padding=1) + self.bn2 = nn.BatchNorm2D(channels[2]) + self.relu2 = nn.ReLU() + + # conv 3 (Max-pooling, Residual block, Conv) + self.pool3 = nn.MaxPool2D( + kernel_size=2, stride=2, padding=0, ceil_mode=True) + self.block3 = self._make_layer(channels[2], channels[3], layers[1]) + self.conv3 = nn.Conv2D( + channels[3], channels[3], kernel_size=3, stride=1, padding=1) + self.bn3 = nn.BatchNorm2D(channels[3]) + self.relu3 = nn.ReLU() + + # conv 4 (Max-pooling, Residual block, Conv) + self.pool4 = nn.MaxPool2D( + kernel_size=(2, 1), stride=(2, 1), padding=0, ceil_mode=True) + self.block4 = self._make_layer(channels[3], channels[4], layers[2]) + self.conv4 = nn.Conv2D( + channels[4], channels[4], kernel_size=3, stride=1, padding=1) + self.bn4 = nn.BatchNorm2D(channels[4]) + self.relu4 = nn.ReLU() + + # conv 5 ((Max-pooling), Residual block, Conv) + self.pool5 = None + if self.last_stage_pool: + self.pool5 = nn.MaxPool2D( + kernel_size=2, stride=2, padding=0, ceil_mode=True) + self.block5 = self._make_layer(channels[4], channels[5], layers[3]) + self.conv5 = nn.Conv2D( + channels[5], channels[5], kernel_size=3, stride=1, padding=1) + self.bn5 = nn.BatchNorm2D(channels[5]) + self.relu5 = nn.ReLU() + + self.out_channels = channels[-1] + + def _make_layer(self, input_channels, output_channels, blocks): + layers = [] + for _ in range(blocks): + downsample = None + if input_channels != output_channels: + downsample = nn.Sequential( + nn.Conv2D( + input_channels, + output_channels, + kernel_size=1, + stride=1, + weight_attr=ParamAttr(initializer=KaimingNormal()), + bias_attr=False), + nn.BatchNorm2D(output_channels), ) + + layers.append( + BasicBlock( + input_channels, output_channels, downsample=downsample)) + input_channels = output_channels + return nn.Sequential(*layers) + + def forward(self, x): + x = self.conv1_1(x) + x = self.bn1_1(x) + x = self.relu1_1(x) + + x = self.conv1_2(x) + x = self.bn1_2(x) + x = self.relu1_2(x) + + outs = [] + for i in range(4): + layer_index = i + 2 + pool_layer = getattr(self, f'pool{layer_index}') + block_layer = getattr(self, f'block{layer_index}') + conv_layer = getattr(self, f'conv{layer_index}') + bn_layer = getattr(self, f'bn{layer_index}') + relu_layer = getattr(self, f'relu{layer_index}') + + if pool_layer is not None: + x = pool_layer(x) + x = block_layer(x) + x = conv_layer(x) + x = bn_layer(x) + x = relu_layer(x) + + outs.append(x) + + if self.out_indices is not None: + return tuple([outs[i] for i in self.out_indices]) + + return x + + +class ResNet(nn.Layer): + def __init__(self, block, layers, in_channels=3): + self.inplanes = 32 + super(ResNet, self).__init__() + self.conv1 = nn.Conv2D( + 3, + 32, + kernel_size=3, + stride=1, + padding=1, + weight_attr=ParamAttr(initializer=KaimingNormal()), + bias_attr=False) + self.bn1 = nn.BatchNorm2D(32) + self.relu = nn.ReLU() + + self.layer1 = self._make_layer(block, 32, layers[0], stride=2) + self.layer2 = self._make_layer(block, 64, layers[1], stride=1) + self.layer3 = self._make_layer(block, 128, layers[2], stride=2) + self.layer4 = self._make_layer(block, 256, layers[3], stride=1) + self.layer5 = self._make_layer(block, 512, layers[4], stride=1) + self.out_channels = 512 + + # for m in self.modules(): + # if isinstance(m, nn.Conv2D): + # n = m._kernel_size[0] * m._kernel_size[1] * m._out_channels + # m.weight.data.normal_(0, math.sqrt(2. / n)) + + def _make_layer(self, block, planes, blocks, stride=1): + downsample = None + if stride != 1 or self.inplanes != planes * block.expansion: + # downsample = True + downsample = nn.Sequential( + nn.Conv2D( + self.inplanes, + planes * block.expansion, + kernel_size=1, + stride=stride, + weight_attr=ParamAttr(initializer=KaimingNormal()), + bias_attr=False), + nn.BatchNorm2D(planes * block.expansion), ) + + layers = [] + layers.append(block(self.inplanes, planes, stride, downsample)) + self.inplanes = planes * block.expansion + for i in range(1, blocks): + layers.append(block(self.inplanes, planes)) + + return nn.Sequential(*layers) + + def forward(self, x): + + x = self.conv1(x) + x = self.bn1(x) + x = self.relu(x) + # print(x) + x = self.layer1(x) + x = self.layer2(x) + x = self.layer3(x) + # print(x) + x = self.layer4(x) + x = self.layer5(x) + return x + + +def ResNet45(in_channels=3): + return ResNet(BasicBlock, [3, 4, 6, 6, 3], in_channels=in_channels) diff --git a/ppocr/modeling/heads/__init__.py b/ppocr/modeling/heads/__init__.py index 1670ea38e6..14e6aab854 100755 --- a/ppocr/modeling/heads/__init__.py +++ b/ppocr/modeling/heads/__init__.py @@ -33,6 +33,7 @@ def build_head(config): from .rec_aster_head import AsterHead from .rec_pren_head import PRENHead from .rec_multi_head import MultiHead + from .rec_abinet_head import ABINetHead # cls head from .cls_head import ClsHead @@ -46,7 +47,7 @@ def build_head(config): 'DBHead', 'PSEHead', 'FCEHead', 'EASTHead', 'SASTHead', 'CTCHead', 'ClsHead', 'AttentionHead', 'SRNHead', 'PGHead', 'Transformer', 'TableAttentionHead', 'SARHead', 'AsterHead', 'SDMGRHead', 'PRENHead', - 'MultiHead' + 'MultiHead', 'ABINetHead' ] #table head diff --git a/ppocr/modeling/heads/multiheadAttention.py b/ppocr/modeling/heads/multiheadAttention.py deleted file mode 100755 index 900865ba1a..0000000000 --- a/ppocr/modeling/heads/multiheadAttention.py +++ /dev/null @@ -1,163 +0,0 @@ -# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. -# -# 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 paddle -from paddle import nn -import paddle.nn.functional as F -from paddle.nn import Linear -from paddle.nn.initializer import XavierUniform as xavier_uniform_ -from paddle.nn.initializer import Constant as constant_ -from paddle.nn.initializer import XavierNormal as xavier_normal_ - -zeros_ = constant_(value=0.) -ones_ = constant_(value=1.) - - -class MultiheadAttention(nn.Layer): - """Allows the model to jointly attend to information - from different representation subspaces. - See reference: Attention Is All You Need - - .. math:: - \text{MultiHead}(Q, K, V) = \text{Concat}(head_1,\dots,head_h)W^O - \text{where} head_i = \text{Attention}(QW_i^Q, KW_i^K, VW_i^V) - - Args: - embed_dim: total dimension of the model - num_heads: parallel attention layers, or heads - - """ - - def __init__(self, - embed_dim, - num_heads, - dropout=0., - bias=True, - add_bias_kv=False, - add_zero_attn=False): - super(MultiheadAttention, self).__init__() - self.embed_dim = embed_dim - self.num_heads = num_heads - self.dropout = dropout - self.head_dim = embed_dim // num_heads - assert self.head_dim * num_heads == self.embed_dim, "embed_dim must be divisible by num_heads" - self.scaling = self.head_dim**-0.5 - self.out_proj = Linear(embed_dim, embed_dim, bias_attr=bias) - self._reset_parameters() - self.conv1 = paddle.nn.Conv2D( - in_channels=embed_dim, out_channels=embed_dim, kernel_size=(1, 1)) - self.conv2 = paddle.nn.Conv2D( - in_channels=embed_dim, out_channels=embed_dim, kernel_size=(1, 1)) - self.conv3 = paddle.nn.Conv2D( - in_channels=embed_dim, out_channels=embed_dim, kernel_size=(1, 1)) - - def _reset_parameters(self): - xavier_uniform_(self.out_proj.weight) - - def forward(self, - query, - key, - value, - key_padding_mask=None, - incremental_state=None, - attn_mask=None): - """ - Inputs of forward function - query: [target length, batch size, embed dim] - key: [sequence length, batch size, embed dim] - value: [sequence length, batch size, embed dim] - key_padding_mask: if True, mask padding based on batch size - incremental_state: if provided, previous time steps are cashed - need_weights: output attn_output_weights - static_kv: key and value are static - - Outputs of forward function - attn_output: [target length, batch size, embed dim] - attn_output_weights: [batch size, target length, sequence length] - """ - q_shape = paddle.shape(query) - src_shape = paddle.shape(key) - q = self._in_proj_q(query) - k = self._in_proj_k(key) - v = self._in_proj_v(value) - q *= self.scaling - q = paddle.transpose( - paddle.reshape( - q, [q_shape[0], q_shape[1], self.num_heads, self.head_dim]), - [1, 2, 0, 3]) - k = paddle.transpose( - paddle.reshape( - k, [src_shape[0], q_shape[1], self.num_heads, self.head_dim]), - [1, 2, 0, 3]) - v = paddle.transpose( - paddle.reshape( - v, [src_shape[0], q_shape[1], self.num_heads, self.head_dim]), - [1, 2, 0, 3]) - if key_padding_mask is not None: - assert key_padding_mask.shape[0] == q_shape[1] - assert key_padding_mask.shape[1] == src_shape[0] - attn_output_weights = paddle.matmul(q, - paddle.transpose(k, [0, 1, 3, 2])) - if attn_mask is not None: - attn_mask = paddle.unsqueeze(paddle.unsqueeze(attn_mask, 0), 0) - attn_output_weights += attn_mask - if key_padding_mask is not None: - attn_output_weights = paddle.reshape( - attn_output_weights, - [q_shape[1], self.num_heads, q_shape[0], src_shape[0]]) - key = paddle.unsqueeze(paddle.unsqueeze(key_padding_mask, 1), 2) - key = paddle.cast(key, 'float32') - y = paddle.full( - shape=paddle.shape(key), dtype='float32', fill_value='-inf') - y = paddle.where(key == 0., key, y) - attn_output_weights += y - attn_output_weights = F.softmax( - attn_output_weights.astype('float32'), - axis=-1, - dtype=paddle.float32 if attn_output_weights.dtype == paddle.float16 - else attn_output_weights.dtype) - attn_output_weights = F.dropout( - attn_output_weights, p=self.dropout, training=self.training) - - attn_output = paddle.matmul(attn_output_weights, v) - attn_output = paddle.reshape( - paddle.transpose(attn_output, [2, 0, 1, 3]), - [q_shape[0], q_shape[1], self.embed_dim]) - attn_output = self.out_proj(attn_output) - - return attn_output - - def _in_proj_q(self, query): - query = paddle.transpose(query, [1, 2, 0]) - query = paddle.unsqueeze(query, axis=2) - res = self.conv1(query) - res = paddle.squeeze(res, axis=2) - res = paddle.transpose(res, [2, 0, 1]) - return res - - def _in_proj_k(self, key): - key = paddle.transpose(key, [1, 2, 0]) - key = paddle.unsqueeze(key, axis=2) - res = self.conv2(key) - res = paddle.squeeze(res, axis=2) - res = paddle.transpose(res, [2, 0, 1]) - return res - - def _in_proj_v(self, value): - value = paddle.transpose(value, [1, 2, 0]) #(1, 2, 0) - value = paddle.unsqueeze(value, axis=2) - res = self.conv3(value) - res = paddle.squeeze(res, axis=2) - res = paddle.transpose(res, [2, 0, 1]) - return res diff --git a/ppocr/modeling/heads/rec_abinet_head.py b/ppocr/modeling/heads/rec_abinet_head.py new file mode 100644 index 0000000000..a0f60f1be1 --- /dev/null +++ b/ppocr/modeling/heads/rec_abinet_head.py @@ -0,0 +1,296 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# 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. +""" +This code is refer from: +https://github.com/FangShancheng/ABINet/tree/main/modules +""" + +import math +import paddle +from paddle import nn +import paddle.nn.functional as F +from paddle.nn import LayerList +from ppocr.modeling.heads.rec_nrtr_head import TransformerBlock, PositionalEncoding + + +class BCNLanguage(nn.Layer): + def __init__(self, + d_model=512, + nhead=8, + num_layers=4, + dim_feedforward=2048, + dropout=0., + max_length=25, + detach=True, + num_classes=37): + super().__init__() + + self.d_model = d_model + self.detach = detach + self.max_length = max_length + 1 # additional stop token + self.proj = nn.Linear(num_classes, d_model, bias_attr=False) + self.token_encoder = PositionalEncoding( + dropout=0.1, dim=d_model, max_len=self.max_length) + self.pos_encoder = PositionalEncoding( + dropout=0, dim=d_model, max_len=self.max_length) + + self.decoder = nn.LayerList([ + TransformerBlock( + d_model=d_model, + nhead=nhead, + dim_feedforward=dim_feedforward, + attention_dropout_rate=dropout, + residual_dropout_rate=dropout, + with_self_attn=False, + with_cross_attn=True) for i in range(num_layers) + ]) + + self.cls = nn.Linear(d_model, num_classes) + + def forward(self, tokens, lengths): + """ + Args: + tokens: (B, N, C) where N is length, B is batch size and C is classes number + lengths: (B,) + """ + if self.detach: tokens = tokens.detach() + embed = self.proj(tokens) # (B, N, C) + embed = self.token_encoder(embed) # (B, N, C) + padding_mask = _get_mask(lengths, self.max_length) + zeros = paddle.zeros_like(embed) # (B, N, C) + qeury = self.pos_encoder(zeros) + for decoder_layer in self.decoder: + qeury = decoder_layer(qeury, embed, cross_mask=padding_mask) + output = qeury # (B, N, C) + + logits = self.cls(output) # (B, N, C) + + return output, logits + + +def encoder_layer(in_c, out_c, k=3, s=2, p=1): + return nn.Sequential( + nn.Conv2D(in_c, out_c, k, s, p), nn.BatchNorm2D(out_c), nn.ReLU()) + + +def decoder_layer(in_c, + out_c, + k=3, + s=1, + p=1, + mode='nearest', + scale_factor=None, + size=None): + align_corners = False if mode == 'nearest' else True + return nn.Sequential( + nn.Upsample( + size=size, + scale_factor=scale_factor, + mode=mode, + align_corners=align_corners), + nn.Conv2D(in_c, out_c, k, s, p), + nn.BatchNorm2D(out_c), + nn.ReLU()) + + +class PositionAttention(nn.Layer): + def __init__(self, + max_length, + in_channels=512, + num_channels=64, + h=8, + w=32, + mode='nearest', + **kwargs): + super().__init__() + self.max_length = max_length + self.k_encoder = nn.Sequential( + encoder_layer( + in_channels, num_channels, s=(1, 2)), + encoder_layer( + num_channels, num_channels, s=(2, 2)), + encoder_layer( + num_channels, num_channels, s=(2, 2)), + encoder_layer( + num_channels, num_channels, s=(2, 2))) + self.k_decoder = nn.Sequential( + decoder_layer( + num_channels, num_channels, scale_factor=2, mode=mode), + decoder_layer( + num_channels, num_channels, scale_factor=2, mode=mode), + decoder_layer( + num_channels, num_channels, scale_factor=2, mode=mode), + decoder_layer( + num_channels, in_channels, size=(h, w), mode=mode)) + + self.pos_encoder = PositionalEncoding( + dropout=0, dim=in_channels, max_len=max_length) + self.project = nn.Linear(in_channels, in_channels) + + def forward(self, x): + B, C, H, W = x.shape + k, v = x, x + + # calculate key vector + features = [] + for i in range(0, len(self.k_encoder)): + k = self.k_encoder[i](k) + features.append(k) + for i in range(0, len(self.k_decoder) - 1): + k = self.k_decoder[i](k) + # print(k.shape, features[len(self.k_decoder) - 2 - i].shape) + k = k + features[len(self.k_decoder) - 2 - i] + k = self.k_decoder[-1](k) + + # calculate query vector + # TODO q=f(q,k) + zeros = paddle.zeros( + (B, self.max_length, C), dtype=x.dtype) # (T, N, C) + q = self.pos_encoder(zeros) # (B, N, C) + q = self.project(q) # (B, N, C) + + # calculate attention + attn_scores = q @k.flatten(2) # (B, N, (H*W)) + attn_scores = attn_scores / (C**0.5) + attn_scores = F.softmax(attn_scores, axis=-1) + + v = v.flatten(2).transpose([0, 2, 1]) # (B, (H*W), C) + attn_vecs = attn_scores @v # (B, N, C) + + return attn_vecs, attn_scores.reshape([0, self.max_length, H, W]) + + +class ABINetHead(nn.Layer): + def __init__(self, + in_channels, + out_channels, + d_model=512, + nhead=8, + num_layers=3, + dim_feedforward=2048, + dropout=0.1, + max_length=25, + use_lang=False, + iter_size=1): + super().__init__() + self.max_length = max_length + 1 + self.pos_encoder = PositionalEncoding( + dropout=0.1, dim=d_model, max_len=8 * 32) + self.encoder = nn.LayerList([ + TransformerBlock( + d_model=d_model, + nhead=nhead, + dim_feedforward=dim_feedforward, + attention_dropout_rate=dropout, + residual_dropout_rate=dropout, + with_self_attn=True, + with_cross_attn=False) for i in range(num_layers) + ]) + self.decoder = PositionAttention( + max_length=max_length + 1, # additional stop token + mode='nearest', ) + self.out_channels = out_channels + self.cls = nn.Linear(d_model, self.out_channels) + self.use_lang = use_lang + if use_lang: + self.iter_size = iter_size + self.language = BCNLanguage( + d_model=d_model, + nhead=nhead, + num_layers=4, + dim_feedforward=dim_feedforward, + dropout=dropout, + max_length=max_length, + num_classes=self.out_channels) + # alignment + self.w_att_align = nn.Linear(2 * d_model, d_model) + self.cls_align = nn.Linear(d_model, self.out_channels) + + def forward(self, x, targets=None): + x = x.transpose([0, 2, 3, 1]) + _, H, W, C = x.shape + feature = x.flatten(1, 2) + feature = self.pos_encoder(feature) + for encoder_layer in self.encoder: + feature = encoder_layer(feature) + feature = feature.reshape([0, H, W, C]).transpose([0, 3, 1, 2]) + v_feature, attn_scores = self.decoder( + feature) # (B, N, C), (B, C, H, W) + vis_logits = self.cls(v_feature) # (B, N, C) + logits = vis_logits + vis_lengths = _get_length(vis_logits) + if self.use_lang: + align_logits = vis_logits + align_lengths = vis_lengths + all_l_res, all_a_res = [], [] + for i in range(self.iter_size): + tokens = F.softmax(align_logits, axis=-1) + lengths = align_lengths + lengths = paddle.clip( + lengths, 2, self.max_length) # TODO:move to langauge model + l_feature, l_logits = self.language(tokens, lengths) + + # alignment + all_l_res.append(l_logits) + fuse = paddle.concat((l_feature, v_feature), -1) + f_att = F.sigmoid(self.w_att_align(fuse)) + output = f_att * v_feature + (1 - f_att) * l_feature + align_logits = self.cls_align(output) # (B, N, C) + + align_lengths = _get_length(align_logits) + all_a_res.append(align_logits) + if self.training: + return { + 'align': all_a_res, + 'lang': all_l_res, + 'vision': vis_logits + } + else: + logits = align_logits + if self.training: + return logits + else: + return F.softmax(logits, -1) + + +def _get_length(logit): + """ Greed decoder to obtain length from logit""" + out = (logit.argmax(-1) == 0) + abn = out.any(-1) + out_int = out.cast('int32') + out = (out_int.cumsum(-1) == 1) & out + out = out.cast('int32') + out = out.argmax(-1) + out = out + 1 + out = paddle.where(abn, out, paddle.to_tensor(logit.shape[1])) + return out + + +def _get_mask(length, max_length): + """Generate a square mask for the sequence. The masked positions are filled with float('-inf'). + Unmasked positions are filled with float(0.0). + """ + length = length.unsqueeze(-1) + B = paddle.shape(length)[0] + grid = paddle.arange(0, max_length).unsqueeze(0).tile([B, 1]) + zero_mask = paddle.zeros([B, max_length], dtype='float32') + inf_mask = paddle.full([B, max_length], '-inf', dtype='float32') + diag_mask = paddle.diag( + paddle.full( + [max_length], '-inf', dtype=paddle.float32), + offset=0, + name=None) + mask = paddle.where(grid >= length, inf_mask, zero_mask) + mask = mask.unsqueeze(1) + diag_mask + return mask.unsqueeze(1) diff --git a/ppocr/modeling/heads/rec_nrtr_head.py b/ppocr/modeling/heads/rec_nrtr_head.py index 38ba0c9178..bf9ef56145 100644 --- a/ppocr/modeling/heads/rec_nrtr_head.py +++ b/ppocr/modeling/heads/rec_nrtr_head.py @@ -14,20 +14,15 @@ import math import paddle -import copy from paddle import nn import paddle.nn.functional as F from paddle.nn import LayerList -from paddle.nn.initializer import XavierNormal as xavier_uniform_ -from paddle.nn import Dropout, Linear, LayerNorm, Conv2D +# from paddle.nn.initializer import XavierNormal as xavier_uniform_ +from paddle.nn import Dropout, Linear, LayerNorm import numpy as np -from ppocr.modeling.heads.multiheadAttention import MultiheadAttention -from paddle.nn.initializer import Constant as constant_ +from ppocr.modeling.backbones.rec_svtrnet import Mlp, zeros_, ones_ from paddle.nn.initializer import XavierNormal as xavier_normal_ -zeros_ = constant_(value=0.) -ones_ = constant_(value=1.) - class Transformer(nn.Layer): """A transformer model. User is able to modify the attributes as needed. The architechture @@ -45,7 +40,6 @@ class Transformer(nn.Layer): dropout: the dropout value (default=0.1). custom_encoder: custom encoder (default=None). custom_decoder: custom decoder (default=None). - """ def __init__(self, @@ -54,45 +48,49 @@ class Transformer(nn.Layer): num_encoder_layers=6, beam_size=0, num_decoder_layers=6, + max_len=25, dim_feedforward=1024, attention_dropout_rate=0.0, residual_dropout_rate=0.1, - custom_encoder=None, - custom_decoder=None, in_channels=0, out_channels=0, scale_embedding=True): super(Transformer, self).__init__() self.out_channels = out_channels + 1 + self.max_len = max_len self.embedding = Embeddings( d_model=d_model, vocab=self.out_channels, padding_idx=0, scale_embedding=scale_embedding) self.positional_encoding = PositionalEncoding( - dropout=residual_dropout_rate, - dim=d_model, ) - if custom_encoder is not None: - self.encoder = custom_encoder - else: - if num_encoder_layers > 0: - encoder_layer = TransformerEncoderLayer( - d_model, nhead, dim_feedforward, attention_dropout_rate, - residual_dropout_rate) - self.encoder = TransformerEncoder(encoder_layer, - num_encoder_layers) - else: - self.encoder = None + dropout=residual_dropout_rate, dim=d_model) - if custom_decoder is not None: - self.decoder = custom_decoder + if num_encoder_layers > 0: + self.encoder = nn.LayerList([ + TransformerBlock( + d_model, + nhead, + dim_feedforward, + attention_dropout_rate, + residual_dropout_rate, + with_self_attn=True, + with_cross_attn=False) for i in range(num_encoder_layers) + ]) else: - decoder_layer = TransformerDecoderLayer( - d_model, nhead, dim_feedforward, attention_dropout_rate, - residual_dropout_rate) - self.decoder = TransformerDecoder(decoder_layer, num_decoder_layers) + self.encoder = None + + self.decoder = nn.LayerList([ + TransformerBlock( + d_model, + nhead, + dim_feedforward, + attention_dropout_rate, + residual_dropout_rate, + with_self_attn=True, + with_cross_attn=True) for i in range(num_decoder_layers) + ]) - self._reset_parameters() self.beam_size = beam_size self.d_model = d_model self.nhead = nhead @@ -105,7 +103,7 @@ class Transformer(nn.Layer): def _init_weights(self, m): - if isinstance(m, nn.Conv2D): + if isinstance(m, nn.Linear): xavier_normal_(m.weight) if m.bias is not None: zeros_(m.bias) @@ -113,24 +111,20 @@ class Transformer(nn.Layer): def forward_train(self, src, tgt): tgt = tgt[:, :-1] - tgt_key_padding_mask = self.generate_padding_mask(tgt) - tgt = self.embedding(tgt).transpose([1, 0, 2]) + tgt = self.embedding(tgt) tgt = self.positional_encoding(tgt) - tgt_mask = self.generate_square_subsequent_mask(tgt.shape[0]) + tgt_mask = self.generate_square_subsequent_mask(tgt.shape[1]) if self.encoder is not None: - src = self.positional_encoding(src.transpose([1, 0, 2])) - memory = self.encoder(src) + src = self.positional_encoding(src) + for encoder_layer in self.encoder: + src = encoder_layer(src) + memory = src # B N C else: - memory = src.squeeze(2).transpose([2, 0, 1]) - output = self.decoder( - tgt, - memory, - tgt_mask=tgt_mask, - memory_mask=None, - tgt_key_padding_mask=tgt_key_padding_mask, - memory_key_padding_mask=None) - output = output.transpose([1, 0, 2]) + memory = src # B N C + for decoder_layer in self.decoder: + tgt = decoder_layer(tgt, memory, self_mask=tgt_mask) + output = tgt logit = self.tgt_word_prj(output) return logit @@ -140,8 +134,8 @@ class Transformer(nn.Layer): src: the sequence to the encoder (required). tgt: the sequence to the decoder (required). Shape: - - src: :math:`(S, N, E)`. - - tgt: :math:`(T, N, E)`. + - src: :math:`(B, sN, C)`. + - tgt: :math:`(B, tN, C)`. Examples: >>> output = transformer_model(src, tgt) """ @@ -157,36 +151,35 @@ class Transformer(nn.Layer): return self.forward_test(src) def forward_test(self, src): + bs = paddle.shape(src)[0] if self.encoder is not None: - src = self.positional_encoding(paddle.transpose(src, [1, 0, 2])) - memory = self.encoder(src) + src = self.positional_encoding(src) + for encoder_layer in self.encoder: + src = encoder_layer(src) + memory = src # B N C else: - memory = paddle.transpose(paddle.squeeze(src, 2), [2, 0, 1]) + memory = src dec_seq = paddle.full((bs, 1), 2, dtype=paddle.int64) dec_prob = paddle.full((bs, 1), 1., dtype=paddle.float32) - for len_dec_seq in range(1, 25): - dec_seq_embed = paddle.transpose(self.embedding(dec_seq), [1, 0, 2]) + for len_dec_seq in range(1, self.max_len): + dec_seq_embed = self.embedding(dec_seq) dec_seq_embed = self.positional_encoding(dec_seq_embed) tgt_mask = self.generate_square_subsequent_mask( - paddle.shape(dec_seq_embed)[0]) - output = self.decoder( - dec_seq_embed, - memory, - tgt_mask=tgt_mask, - memory_mask=None, - tgt_key_padding_mask=None, - memory_key_padding_mask=None) - dec_output = paddle.transpose(output, [1, 0, 2]) + paddle.shape(dec_seq_embed)[1]) + tgt = dec_seq_embed + for decoder_layer in self.decoder: + tgt = decoder_layer(tgt, memory, self_mask=tgt_mask) + dec_output = tgt dec_output = dec_output[:, -1, :] - word_prob = F.softmax(self.tgt_word_prj(dec_output), axis=1) - preds_idx = paddle.argmax(word_prob, axis=1) + word_prob = F.softmax(self.tgt_word_prj(dec_output), axis=-1) + preds_idx = paddle.argmax(word_prob, axis=-1) if paddle.equal_all( preds_idx, paddle.full( paddle.shape(preds_idx), 3, dtype='int64')): break - preds_prob = paddle.max(word_prob, axis=1) + preds_prob = paddle.max(word_prob, axis=-1) dec_seq = paddle.concat( [dec_seq, paddle.reshape(preds_idx, [-1, 1])], axis=1) dec_prob = paddle.concat( @@ -194,10 +187,10 @@ class Transformer(nn.Layer): return [dec_seq, dec_prob] def forward_beam(self, images): - ''' Translation work in one batch ''' + """ Translation work in one batch """ def get_inst_idx_to_tensor_position_map(inst_idx_list): - ''' Indicate the position of an instance in a tensor. ''' + """ Indicate the position of an instance in a tensor. """ return { inst_idx: tensor_position for tensor_position, inst_idx in enumerate(inst_idx_list) @@ -205,7 +198,7 @@ class Transformer(nn.Layer): def collect_active_part(beamed_tensor, curr_active_inst_idx, n_prev_active_inst, n_bm): - ''' Collect tensor parts associated to active instances. ''' + """ Collect tensor parts associated to active instances. """ beamed_tensor_shape = paddle.shape(beamed_tensor) n_curr_active_inst = len(curr_active_inst_idx) @@ -237,9 +230,8 @@ class Transformer(nn.Layer): return active_src_enc, active_inst_idx_to_position_map def beam_decode_step(inst_dec_beams, len_dec_seq, enc_output, - inst_idx_to_position_map, n_bm, - memory_key_padding_mask): - ''' Decode and update beam status, and then return active beam idx ''' + inst_idx_to_position_map, n_bm): + """ Decode and update beam status, and then return active beam idx """ def prepare_beam_dec_seq(inst_dec_beams, len_dec_seq): dec_partial_seq = [ @@ -249,19 +241,15 @@ class Transformer(nn.Layer): dec_partial_seq = dec_partial_seq.reshape([-1, len_dec_seq]) return dec_partial_seq - def predict_word(dec_seq, enc_output, n_active_inst, n_bm, - memory_key_padding_mask): - dec_seq = paddle.transpose(self.embedding(dec_seq), [1, 0, 2]) + def predict_word(dec_seq, enc_output, n_active_inst, n_bm): + dec_seq = self.embedding(dec_seq) dec_seq = self.positional_encoding(dec_seq) tgt_mask = self.generate_square_subsequent_mask( - paddle.shape(dec_seq)[0]) - dec_output = self.decoder( - dec_seq, - enc_output, - tgt_mask=tgt_mask, - tgt_key_padding_mask=None, - memory_key_padding_mask=memory_key_padding_mask, ) - dec_output = paddle.transpose(dec_output, [1, 0, 2]) + paddle.shape(dec_seq)[1]) + tgt = dec_seq + for decoder_layer in self.decoder: + tgt = decoder_layer(tgt, enc_output, self_mask=tgt_mask) + dec_output = tgt dec_output = dec_output[:, -1, :] # Pick the last step: (bh * bm) * d_h word_prob = F.softmax(self.tgt_word_prj(dec_output), axis=1) @@ -281,8 +269,7 @@ class Transformer(nn.Layer): n_active_inst = len(inst_idx_to_position_map) dec_seq = prepare_beam_dec_seq(inst_dec_beams, len_dec_seq) - word_prob = predict_word(dec_seq, enc_output, n_active_inst, n_bm, - None) + word_prob = predict_word(dec_seq, enc_output, n_active_inst, n_bm) # Update the beam with predicted word prob information and collect incomplete instances active_inst_idx_list = collect_active_inst_idx_list( inst_dec_beams, word_prob, inst_idx_to_position_map) @@ -303,10 +290,10 @@ class Transformer(nn.Layer): with paddle.no_grad(): #-- Encode if self.encoder is not None: - src = self.positional_encoding(images.transpose([1, 0, 2])) + src = self.positional_encoding(images) src_enc = self.encoder(src) else: - src_enc = images.squeeze(2).transpose([0, 2, 1]) + src_enc = images n_bm = self.beam_size src_shape = paddle.shape(src_enc) @@ -317,11 +304,11 @@ class Transformer(nn.Layer): inst_idx_to_position_map = get_inst_idx_to_tensor_position_map( active_inst_idx_list) # Decode - for len_dec_seq in range(1, 25): + for len_dec_seq in range(1, self.max_len): src_enc_copy = src_enc.clone() active_inst_idx_list = beam_decode_step( inst_dec_beams, len_dec_seq, src_enc_copy, - inst_idx_to_position_map, n_bm, None) + inst_idx_to_position_map, n_bm) if not active_inst_idx_list: break # all instances have finished their path to src_enc, inst_idx_to_position_map = collate_active_info( @@ -354,261 +341,124 @@ class Transformer(nn.Layer): shape=[sz, sz], dtype='float32', fill_value='-inf'), diagonal=1) mask = mask + mask_inf - return mask - - def generate_padding_mask(self, x): - padding_mask = paddle.equal(x, paddle.to_tensor(0, dtype=x.dtype)) - return padding_mask - - def _reset_parameters(self): - """Initiate parameters in the transformer model.""" - - for p in self.parameters(): - if p.dim() > 1: - xavier_uniform_(p) + return mask.unsqueeze([0, 1]) -class TransformerEncoder(nn.Layer): - """TransformerEncoder is a stack of N encoder layers - Args: - encoder_layer: an instance of the TransformerEncoderLayer() class (required). - num_layers: the number of sub-encoder-layers in the encoder (required). - norm: the layer normalization component (optional). - """ +class MultiheadAttention(nn.Layer): + """Allows the model to jointly attend to information + from different representation subspaces. + See reference: Attention Is All You Need - def __init__(self, encoder_layer, num_layers): - super(TransformerEncoder, self).__init__() - self.layers = _get_clones(encoder_layer, num_layers) - self.num_layers = num_layers - - def forward(self, src): - """Pass the input through the endocder layers in turn. - Args: - src: the sequnce to the encoder (required). - mask: the mask for the src sequence (optional). - src_key_padding_mask: the mask for the src keys per batch (optional). - """ - output = src - - for i in range(self.num_layers): - output = self.layers[i](output, - src_mask=None, - src_key_padding_mask=None) - - return output - - -class TransformerDecoder(nn.Layer): - """TransformerDecoder is a stack of N decoder layers + .. math:: + \text{MultiHead}(Q, K, V) = \text{Concat}(head_1,\dots,head_h)W^O + \text{where} head_i = \text{Attention}(QW_i^Q, KW_i^K, VW_i^V) Args: - decoder_layer: an instance of the TransformerDecoderLayer() class (required). - num_layers: the number of sub-decoder-layers in the decoder (required). - norm: the layer normalization component (optional). + embed_dim: total dimension of the model + num_heads: parallel attention layers, or heads """ - def __init__(self, decoder_layer, num_layers): - super(TransformerDecoder, self).__init__() - self.layers = _get_clones(decoder_layer, num_layers) - self.num_layers = num_layers + def __init__(self, embed_dim, num_heads, dropout=0., self_attn=False): + super(MultiheadAttention, self).__init__() + self.embed_dim = embed_dim + self.num_heads = num_heads + # self.dropout = dropout + self.head_dim = embed_dim // num_heads + assert self.head_dim * num_heads == self.embed_dim, "embed_dim must be divisible by num_heads" + self.scale = self.head_dim**-0.5 + self.self_attn = self_attn + if self_attn: + self.qkv = nn.Linear(embed_dim, embed_dim * 3) + else: + self.q = nn.Linear(embed_dim, embed_dim) + self.kv = nn.Linear(embed_dim, embed_dim * 2) + self.attn_drop = nn.Dropout(dropout) + self.out_proj = nn.Linear(embed_dim, embed_dim) - def forward(self, - tgt, - memory, - tgt_mask=None, - memory_mask=None, - tgt_key_padding_mask=None, - memory_key_padding_mask=None): - """Pass the inputs (and mask) through the decoder layer in turn. + def forward(self, query, key=None, attn_mask=None): - Args: - tgt: the sequence to the decoder (required). - memory: the sequnce from the last layer of the encoder (required). - tgt_mask: the mask for the tgt sequence (optional). - memory_mask: the mask for the memory sequence (optional). - tgt_key_padding_mask: the mask for the tgt keys per batch (optional). - memory_key_padding_mask: the mask for the memory keys per batch (optional). - """ - output = tgt - for i in range(self.num_layers): - output = self.layers[i]( - output, - memory, - tgt_mask=tgt_mask, - memory_mask=memory_mask, - tgt_key_padding_mask=tgt_key_padding_mask, - memory_key_padding_mask=memory_key_padding_mask) + qN = query.shape[1] - return output + if self.self_attn: + qkv = self.qkv(query).reshape( + (0, qN, 3, self.num_heads, self.head_dim)).transpose( + (2, 0, 3, 1, 4)) + q, k, v = qkv[0], qkv[1], qkv[2] + else: + kN = key.shape[1] + q = self.q(query).reshape( + [0, qN, self.num_heads, self.head_dim]).transpose([0, 2, 1, 3]) + kv = self.kv(key).reshape( + (0, kN, 2, self.num_heads, self.head_dim)).transpose( + (2, 0, 3, 1, 4)) + k, v = kv[0], kv[1] + + attn = (q.matmul(k.transpose((0, 1, 3, 2)))) * self.scale + + if attn_mask is not None: + attn += attn_mask + + attn = F.softmax(attn, axis=-1) + attn = self.attn_drop(attn) + + x = (attn.matmul(v)).transpose((0, 2, 1, 3)).reshape( + (0, qN, self.embed_dim)) + x = self.out_proj(x) + + return x -class TransformerEncoderLayer(nn.Layer): - """TransformerEncoderLayer is made up of self-attn and feedforward network. - This standard encoder layer is based on the paper "Attention Is All You Need". - Ashish Vaswani, Noam Shazeer, Niki Parmar, Jakob Uszkoreit, Llion Jones, Aidan N Gomez, - Lukasz Kaiser, and Illia Polosukhin. 2017. Attention is all you need. In Advances in - Neural Information Processing Systems, pages 6000-6010. Users may modify or implement - in a different way during application. - - Args: - d_model: the number of expected features in the input (required). - nhead: the number of heads in the multiheadattention models (required). - dim_feedforward: the dimension of the feedforward network model (default=2048). - dropout: the dropout value (default=0.1). - - """ - +class TransformerBlock(nn.Layer): def __init__(self, d_model, nhead, dim_feedforward=2048, attention_dropout_rate=0.0, - residual_dropout_rate=0.1): - super(TransformerEncoderLayer, self).__init__() - self.self_attn = MultiheadAttention( - d_model, nhead, dropout=attention_dropout_rate) + residual_dropout_rate=0.1, + with_self_attn=True, + with_cross_attn=False, + epsilon=1e-5): + super(TransformerBlock, self).__init__() + self.with_self_attn = with_self_attn + if with_self_attn: + self.self_attn = MultiheadAttention( + d_model, + nhead, + dropout=attention_dropout_rate, + self_attn=with_self_attn) + self.norm1 = LayerNorm(d_model, epsilon=epsilon) + self.dropout1 = Dropout(residual_dropout_rate) + self.with_cross_attn = with_cross_attn + if with_cross_attn: + self.cross_attn = MultiheadAttention( #for self_attn of encoder or cross_attn of decoder + d_model, + nhead, + dropout=attention_dropout_rate) + self.norm2 = LayerNorm(d_model, epsilon=epsilon) + self.dropout2 = Dropout(residual_dropout_rate) - self.conv1 = Conv2D( - in_channels=d_model, - out_channels=dim_feedforward, - kernel_size=(1, 1)) - self.conv2 = Conv2D( - in_channels=dim_feedforward, - out_channels=d_model, - kernel_size=(1, 1)) + self.mlp = Mlp(in_features=d_model, + hidden_features=dim_feedforward, + act_layer=nn.ReLU, + drop=residual_dropout_rate) - self.norm1 = LayerNorm(d_model) - self.norm2 = LayerNorm(d_model) - self.dropout1 = Dropout(residual_dropout_rate) - self.dropout2 = Dropout(residual_dropout_rate) + self.norm3 = LayerNorm(d_model, epsilon=epsilon) - def forward(self, src, src_mask=None, src_key_padding_mask=None): - """Pass the input through the endocder layer. - Args: - src: the sequnce to the encoder layer (required). - src_mask: the mask for the src sequence (optional). - src_key_padding_mask: the mask for the src keys per batch (optional). - """ - src2 = self.self_attn( - src, - src, - src, - attn_mask=src_mask, - key_padding_mask=src_key_padding_mask) - src = src + self.dropout1(src2) - src = self.norm1(src) - - src = paddle.transpose(src, [1, 2, 0]) - src = paddle.unsqueeze(src, 2) - src2 = self.conv2(F.relu(self.conv1(src))) - src2 = paddle.squeeze(src2, 2) - src2 = paddle.transpose(src2, [2, 0, 1]) - src = paddle.squeeze(src, 2) - src = paddle.transpose(src, [2, 0, 1]) - - src = src + self.dropout2(src2) - src = self.norm2(src) - return src - - -class TransformerDecoderLayer(nn.Layer): - """TransformerDecoderLayer is made up of self-attn, multi-head-attn and feedforward network. - This standard decoder layer is based on the paper "Attention Is All You Need". - Ashish Vaswani, Noam Shazeer, Niki Parmar, Jakob Uszkoreit, Llion Jones, Aidan N Gomez, - Lukasz Kaiser, and Illia Polosukhin. 2017. Attention is all you need. In Advances in - Neural Information Processing Systems, pages 6000-6010. Users may modify or implement - in a different way during application. - - Args: - d_model: the number of expected features in the input (required). - nhead: the number of heads in the multiheadattention models (required). - dim_feedforward: the dimension of the feedforward network model (default=2048). - dropout: the dropout value (default=0.1). - - """ - - def __init__(self, - d_model, - nhead, - dim_feedforward=2048, - attention_dropout_rate=0.0, - residual_dropout_rate=0.1): - super(TransformerDecoderLayer, self).__init__() - self.self_attn = MultiheadAttention( - d_model, nhead, dropout=attention_dropout_rate) - self.multihead_attn = MultiheadAttention( - d_model, nhead, dropout=attention_dropout_rate) - - self.conv1 = Conv2D( - in_channels=d_model, - out_channels=dim_feedforward, - kernel_size=(1, 1)) - self.conv2 = Conv2D( - in_channels=dim_feedforward, - out_channels=d_model, - kernel_size=(1, 1)) - - self.norm1 = LayerNorm(d_model) - self.norm2 = LayerNorm(d_model) - self.norm3 = LayerNorm(d_model) - self.dropout1 = Dropout(residual_dropout_rate) - self.dropout2 = Dropout(residual_dropout_rate) self.dropout3 = Dropout(residual_dropout_rate) - def forward(self, - tgt, - memory, - tgt_mask=None, - memory_mask=None, - tgt_key_padding_mask=None, - memory_key_padding_mask=None): - """Pass the inputs (and mask) through the decoder layer. + def forward(self, tgt, memory=None, self_mask=None, cross_mask=None): + if self.with_self_attn: + tgt1 = self.self_attn(tgt, attn_mask=self_mask) + tgt = self.norm1(tgt + self.dropout1(tgt1)) - Args: - tgt: the sequence to the decoder layer (required). - memory: the sequnce from the last layer of the encoder (required). - tgt_mask: the mask for the tgt sequence (optional). - memory_mask: the mask for the memory sequence (optional). - tgt_key_padding_mask: the mask for the tgt keys per batch (optional). - memory_key_padding_mask: the mask for the memory keys per batch (optional). - - """ - tgt2 = self.self_attn( - tgt, - tgt, - tgt, - attn_mask=tgt_mask, - key_padding_mask=tgt_key_padding_mask) - tgt = tgt + self.dropout1(tgt2) - tgt = self.norm1(tgt) - tgt2 = self.multihead_attn( - tgt, - memory, - memory, - attn_mask=memory_mask, - key_padding_mask=memory_key_padding_mask) - tgt = tgt + self.dropout2(tgt2) - tgt = self.norm2(tgt) - - # default - tgt = paddle.transpose(tgt, [1, 2, 0]) - tgt = paddle.unsqueeze(tgt, 2) - tgt2 = self.conv2(F.relu(self.conv1(tgt))) - tgt2 = paddle.squeeze(tgt2, 2) - tgt2 = paddle.transpose(tgt2, [2, 0, 1]) - tgt = paddle.squeeze(tgt, 2) - tgt = paddle.transpose(tgt, [2, 0, 1]) - - tgt = tgt + self.dropout3(tgt2) - tgt = self.norm3(tgt) + if self.with_cross_attn: + tgt2 = self.cross_attn(tgt, key=memory, attn_mask=cross_mask) + tgt = self.norm2(tgt + self.dropout2(tgt2)) + tgt = self.norm3(tgt + self.dropout3(self.mlp(tgt))) return tgt -def _get_clones(module, N): - return LayerList([copy.deepcopy(module) for i in range(N)]) - - class PositionalEncoding(nn.Layer): """Inject some information about the relative or absolute position of the tokens in the sequence. The positional encodings have the same dimension as @@ -651,8 +501,9 @@ class PositionalEncoding(nn.Layer): Examples: >>> output = pos_encoder(x) """ + x = x.transpose([1, 0, 2]) x = x + self.pe[:paddle.shape(x)[0], :] - return self.dropout(x) + return self.dropout(x).transpose([1, 0, 2]) class PositionalEncoding_2d(nn.Layer): @@ -725,7 +576,7 @@ class PositionalEncoding_2d(nn.Layer): class Embeddings(nn.Layer): - def __init__(self, d_model, vocab, padding_idx, scale_embedding): + def __init__(self, d_model, vocab, padding_idx=None, scale_embedding=True): super(Embeddings, self).__init__() self.embedding = nn.Embedding(vocab, d_model, padding_idx=padding_idx) w0 = np.random.normal(0.0, d_model**-0.5, @@ -742,7 +593,7 @@ class Embeddings(nn.Layer): class Beam(): - ''' Beam search ''' + """ Beam search """ def __init__(self, size, device=False): diff --git a/ppocr/postprocess/__init__.py b/ppocr/postprocess/__init__.py index 4f900ee1fc..2635117c84 100644 --- a/ppocr/postprocess/__init__.py +++ b/ppocr/postprocess/__init__.py @@ -27,7 +27,7 @@ from .sast_postprocess import SASTPostProcess from .fce_postprocess import FCEPostProcess from .rec_postprocess import CTCLabelDecode, AttnLabelDecode, SRNLabelDecode, \ DistillationCTCLabelDecode, TableLabelDecode, NRTRLabelDecode, SARLabelDecode, \ - SEEDLabelDecode, PRENLabelDecode, ViTSTRLabelDecode + SEEDLabelDecode, PRENLabelDecode, ViTSTRLabelDecode, ABINetLabelDecode from .cls_postprocess import ClsPostProcess from .pg_postprocess import PGPostProcess from .vqa_token_ser_layoutlm_postprocess import VQASerTokenLayoutLMPostProcess @@ -42,7 +42,7 @@ def build_post_process(config, global_config=None): 'DistillationDBPostProcess', 'NRTRLabelDecode', 'SARLabelDecode', 'SEEDLabelDecode', 'VQASerTokenLayoutLMPostProcess', 'VQAReTokenLayoutLMPostProcess', 'PRENLabelDecode', - 'DistillationSARLabelDecode', 'ViTSTRLabelDecode' + 'DistillationSARLabelDecode', 'ViTSTRLabelDecode', 'ABINetLabelDecode' ] if config['name'] == 'PSEPostProcess': diff --git a/ppocr/postprocess/rec_postprocess.py b/ppocr/postprocess/rec_postprocess.py index df6203fada..c77420ad19 100644 --- a/ppocr/postprocess/rec_postprocess.py +++ b/ppocr/postprocess/rec_postprocess.py @@ -140,96 +140,6 @@ class DistillationCTCLabelDecode(CTCLabelDecode): return output -class NRTRLabelDecode(BaseRecLabelDecode): - """ Convert between text-label and text-index """ - - def __init__(self, character_dict_path=None, use_space_char=True, **kwargs): - super(NRTRLabelDecode, self).__init__(character_dict_path, - use_space_char) - - def __call__(self, preds, label=None, *args, **kwargs): - - if len(preds) == 2: - preds_id = preds[0] - preds_prob = preds[1] - if isinstance(preds_id, paddle.Tensor): - preds_id = preds_id.numpy() - if isinstance(preds_prob, paddle.Tensor): - preds_prob = preds_prob.numpy() - if preds_id[0][0] == 2: - preds_idx = preds_id[:, 1:] - preds_prob = preds_prob[:, 1:] - else: - preds_idx = preds_id - text = self.decode(preds_idx, preds_prob, is_remove_duplicate=False) - if label is None: - return text - label = self.decode(label[:, 1:]) - else: - if isinstance(preds, paddle.Tensor): - preds = preds.numpy() - preds_idx = preds.argmax(axis=2) - preds_prob = preds.max(axis=2) - text = self.decode(preds_idx, preds_prob, is_remove_duplicate=False) - if label is None: - return text - label = self.decode(label[:, 1:]) - return text, label - - def add_special_char(self, dict_character): - dict_character = ['blank', '', '', ''] + 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 = [] - 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])): - try: - char_idx = self.character[int(text_index[batch_idx][idx])] - except: - continue - if char_idx == '': # end - break - char_list.append(char_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.lower(), np.mean(conf_list).tolist())) - return result_list - - -class ViTSTRLabelDecode(NRTRLabelDecode): - """ Convert between text-label and text-index """ - - def __init__(self, character_dict_path=None, use_space_char=False, - **kwargs): - super(ViTSTRLabelDecode, self).__init__(character_dict_path, - use_space_char) - - def __call__(self, preds, label=None, *args, **kwargs): - if isinstance(preds, paddle.Tensor): - preds = preds[:, 1:].numpy() - else: - preds = preds[:, 1:] - preds_idx = preds.argmax(axis=2) - preds_prob = preds.max(axis=2) - text = self.decode(preds_idx, preds_prob, is_remove_duplicate=False) - if label is None: - return text - label = self.decode(label[:, 1:]) - return text, label - - def add_special_char(self, dict_character): - dict_character = ['', ''] + dict_character - return dict_character - - class AttnLabelDecode(BaseRecLabelDecode): """ Convert between text-label and text-index """ @@ -778,3 +688,122 @@ class PRENLabelDecode(BaseRecLabelDecode): return text label = self.decode(label) return text, label + + +class NRTRLabelDecode(BaseRecLabelDecode): + """ Convert between text-label and text-index """ + + def __init__(self, character_dict_path=None, use_space_char=True, **kwargs): + super(NRTRLabelDecode, self).__init__(character_dict_path, + use_space_char) + + def __call__(self, preds, label=None, *args, **kwargs): + + if len(preds) == 2: + preds_id = preds[0] + preds_prob = preds[1] + if isinstance(preds_id, paddle.Tensor): + preds_id = preds_id.numpy() + if isinstance(preds_prob, paddle.Tensor): + preds_prob = preds_prob.numpy() + if preds_id[0][0] == 2: + preds_idx = preds_id[:, 1:] + preds_prob = preds_prob[:, 1:] + else: + preds_idx = preds_id + text = self.decode(preds_idx, preds_prob, is_remove_duplicate=False) + if label is None: + return text + label = self.decode(label[:, 1:]) + else: + if isinstance(preds, paddle.Tensor): + preds = preds.numpy() + preds_idx = preds.argmax(axis=2) + preds_prob = preds.max(axis=2) + text = self.decode(preds_idx, preds_prob, is_remove_duplicate=False) + if label is None: + return text + label = self.decode(label[:, 1:]) + return text, label + + def add_special_char(self, dict_character): + dict_character = ['blank', '', '', ''] + 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 = [] + 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])): + try: + char_idx = self.character[int(text_index[batch_idx][idx])] + except: + continue + if char_idx == '': # end + break + char_list.append(char_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.lower(), np.mean(conf_list).tolist())) + return result_list + + +class ViTSTRLabelDecode(NRTRLabelDecode): + """ Convert between text-label and text-index """ + + def __init__(self, character_dict_path=None, use_space_char=False, + **kwargs): + super(ViTSTRLabelDecode, self).__init__(character_dict_path, + use_space_char) + + def __call__(self, preds, label=None, *args, **kwargs): + if isinstance(preds, paddle.Tensor): + preds = preds[:, 1:].numpy() + else: + preds = preds[:, 1:] + preds_idx = preds.argmax(axis=2) + preds_prob = preds.max(axis=2) + text = self.decode(preds_idx, preds_prob, is_remove_duplicate=False) + if label is None: + return text + label = self.decode(label[:, 1:]) + return text, label + + def add_special_char(self, dict_character): + dict_character = ['', ''] + dict_character + return dict_character + + +class ABINetLabelDecode(NRTRLabelDecode): + """ Convert between text-label and text-index """ + + def __init__(self, character_dict_path=None, use_space_char=False, + **kwargs): + super(ABINetLabelDecode, self).__init__(character_dict_path, + use_space_char) + + def __call__(self, preds, label=None, *args, **kwargs): + if isinstance(preds, dict): + preds = preds['align'][-1].numpy() + elif isinstance(preds, paddle.Tensor): + preds = preds.numpy() + else: + preds = preds + + preds_idx = preds.argmax(axis=2) + preds_prob = preds.max(axis=2) + text = self.decode(preds_idx, preds_prob, is_remove_duplicate=False) + if label is None: + return text + label = self.decode(label) + return text, label + + def add_special_char(self, dict_character): + dict_character = [''] + dict_character + return dict_character diff --git a/test_tipc/configs/rec_mtb_nrtr/rec_mtb_nrtr.yml b/test_tipc/configs/rec_mtb_nrtr/rec_mtb_nrtr.yml index ba6728d836..8118d58724 100644 --- a/test_tipc/configs/rec_mtb_nrtr/rec_mtb_nrtr.yml +++ b/test_tipc/configs/rec_mtb_nrtr/rec_mtb_nrtr.yml @@ -99,5 +99,5 @@ Eval: shuffle: False drop_last: False batch_size_per_card: 256 - num_workers: 1 + num_workers: 4 use_shared_memory: False diff --git a/test_tipc/configs/rec_r45_abinet/rec_r45_abinet.yml b/test_tipc/configs/rec_r45_abinet/rec_r45_abinet.yml new file mode 100644 index 0000000000..306c49d683 --- /dev/null +++ b/test_tipc/configs/rec_r45_abinet/rec_r45_abinet.yml @@ -0,0 +1,105 @@ +Global: + use_gpu: True + epoch_num: 10 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/r45_abinet/ + save_epoch_step: 1 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + character_type: en + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_abinet.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.99 + clip_norm: 20.0 + lr: + name: Piecewise + decay_epochs: [6] + values: [0.0001, 0.00001] + regularizer: + name: 'L2' + factor: 0. + +Architecture: + model_type: rec + algorithm: ABINet + in_channels: 3 + Transform: + Backbone: + name: ResNet45 + + Head: + name: ABINetHead + use_lang: True + iter_size: 3 + + +Loss: + name: CELoss + ignore_index: &ignore_index 100 # Must be greater than the number of character classes + +PostProcess: + name: ABINetLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data/ + label_file_list: ["./train_data/ic15_data/rec_gt_train.txt"] + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - ABINetLabelEncode: # Class handling label + ignore_index: *ignore_index + - ABINetRecResizeImg: + image_shape: [3, 32, 128] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 96 + drop_last: True + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data + label_file_list: ["./train_data/ic15_data/rec_gt_test.txt"] + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - ABINetLabelEncode: # Class handling label + ignore_index: *ignore_index + - ABINetRecResizeImg: + image_shape: [3, 32, 128] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 4 + use_shared_memory: False diff --git a/test_tipc/configs/rec_r45_abinet/train_infer_python.txt b/test_tipc/configs/rec_r45_abinet/train_infer_python.txt new file mode 100644 index 0000000000..ecab1bcbbd --- /dev/null +++ b/test_tipc/configs/rec_r45_abinet/train_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:rec_abinet +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=2|whole_train_whole_infer=300 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=64 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/rec_r45_abinet/rec_r45_abinet.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c test_tipc/configs/rec_r45_abinet/rec_r45_abinet.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/rec_r45_abinet/rec_r45_abinet.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/rec_r45_abinet_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/rec_r45_abinet/rec_r45_abinet.yml -o +infer_quant:False +inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/ic15_dict.txt --rec_image_shape="3,32,128" --rec_algorithm="ABINet" +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1|6 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +--save_log_path:./test/output/ +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,32,128]}] diff --git a/test_tipc/configs/rec_svtrnet/rec_svtrnet.yml b/test_tipc/configs/rec_svtrnet/rec_svtrnet.yml index 26facca34d..bffd8ac09a 100644 --- a/test_tipc/configs/rec_svtrnet/rec_svtrnet.yml +++ b/test_tipc/configs/rec_svtrnet/rec_svtrnet.yml @@ -26,7 +26,7 @@ Optimizer: name: AdamW beta1: 0.9 beta2: 0.99 - epsilon: 0.00000008 + epsilon: 8.e-8 weight_decay: 0.05 no_weight_decay_name: norm pos_embed one_dim_param_no_weight_decay: true diff --git a/test_tipc/configs/rec_svtrnet/train_infer_python.txt b/test_tipc/configs/rec_svtrnet/train_infer_python.txt index 60fd25ad4f..a7e4a24063 100644 --- a/test_tipc/configs/rec_svtrnet/train_infer_python.txt +++ b/test_tipc/configs/rec_svtrnet/train_infer_python.txt @@ -37,7 +37,7 @@ export2:null train_model:./inference/rec_svtrnet_train/best_accuracy infer_export:tools/export_model.py -c test_tipc/configs/rec_svtrnet/rec_svtrnet.yml -o infer_quant:False -inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/EN_symbol_dict.txt --rec_image_shape="3,64,256" --rec_algorithm="SVTR" +inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/ic15_dict.txt --rec_image_shape="3,64,256" --rec_algorithm="SVTR" --use_gpu:True|False --enable_mkldnn:False --cpu_threads:6 @@ -50,4 +50,4 @@ inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/EN_symbo --benchmark:True null:null ===========================infer_benchmark_params========================== -random_infer_input:[{float32,[1=3,64,256]}] +random_infer_input:[{float32,[3,64,256]}] diff --git a/test_tipc/configs/rec_vitstr_none_ce/rec_vitstr_none_ce.yml b/test_tipc/configs/rec_vitstr_none_ce/rec_vitstr_none_ce.yml index 4e8151f599..a0aed48875 100644 --- a/test_tipc/configs/rec_vitstr_none_ce/rec_vitstr_none_ce.yml +++ b/test_tipc/configs/rec_vitstr_none_ce/rec_vitstr_none_ce.yml @@ -23,7 +23,7 @@ Global: Optimizer: name: Adadelta - epsilon: 0.00000001 + epsilon: 1.e-8 rho: 0.95 clip_norm: 5.0 lr: @@ -46,6 +46,7 @@ Loss: name: CELoss smoothing: False with_all: True + ignore_index: &ignore_index 0 # Must be zero or greater than the number of character classes PostProcess: name: ViTSTRLabelDecode @@ -64,6 +65,7 @@ Train: img_mode: BGR channel_first: False - ViTSTRLabelEncode: # Class handling label + ignore_index: *ignore_index - GrayRecResizeImg: image_shape: [224, 224] # W H resize_type: PIL # PIL or OpenCV @@ -87,6 +89,7 @@ Eval: img_mode: BGR channel_first: False - ViTSTRLabelEncode: # Class handling label + ignore_index: *ignore_index - GrayRecResizeImg: image_shape: [224, 224] # W H resize_type: PIL # PIL or OpenCV diff --git a/tools/export_model.py b/tools/export_model.py index 7ef2c36b12..e2673239b1 100755 --- a/tools/export_model.py +++ b/tools/export_model.py @@ -79,6 +79,19 @@ def export_single_model(model, arch_config, save_path, logger, quanter=None): shape=[None, 1, 224, 224], dtype="float32"), ] model = to_static(model, input_spec=other_shape) + elif arch_config["algorithm"] == "ABINet": + other_shape = [ + paddle.static.InputSpec( + shape=[None, 3, 32, 128], dtype="float32"), + ] + # print([None, 3, 32, 128]) + model = to_static(model, input_spec=other_shape) + elif arch_config["algorithm"] == "NRTR": + other_shape = [ + paddle.static.InputSpec( + shape=[None, 1, 32, 100], dtype="float32"), + ] + model = to_static(model, input_spec=other_shape) else: infer_shape = [3, -1, -1] if arch_config["model_type"] == "rec": @@ -90,8 +103,6 @@ def export_single_model(model, arch_config, save_path, logger, quanter=None): "When there is tps in the network, variable length input is not supported, and the input size needs to be the same as during training" ) infer_shape[-1] = 100 - if arch_config["algorithm"] == "NRTR": - infer_shape = [1, 32, 100] elif arch_config["model_type"] == "table": infer_shape = [3, 488, 488] model = to_static( diff --git a/tools/infer/predict_rec.py b/tools/infer/predict_rec.py index 1945667972..a95f555966 100755 --- a/tools/infer/predict_rec.py +++ b/tools/infer/predict_rec.py @@ -75,6 +75,12 @@ class TextRecognizer(object): "character_dict_path": args.rec_char_dict_path, "use_space_char": args.use_space_char } + elif self.rec_algorithm == 'ABINet': + postprocess_params = { + 'name': 'ABINetLabelDecode', + "character_dict_path": args.rec_char_dict_path, + "use_space_char": args.use_space_char + } self.postprocess_op = build_post_process(postprocess_params) self.predictor, self.input_tensor, self.output_tensors, self.config = \ utility.create_predictor(args, 'rec', logger) @@ -145,17 +151,6 @@ class TextRecognizer(object): padding_im[:, :, 0:resized_w] = resized_image return padding_im - def resize_norm_img_svtr(self, img, image_shape): - - imgC, imgH, imgW = image_shape - resized_image = cv2.resize( - img, (imgW, imgH), interpolation=cv2.INTER_LINEAR) - resized_image = resized_image.astype('float32') - resized_image = resized_image.transpose((2, 0, 1)) / 255 - resized_image -= 0.5 - resized_image /= 0.5 - return resized_image - def resize_norm_img_srn(self, img, image_shape): imgC, imgH, imgW = image_shape @@ -263,6 +258,35 @@ class TextRecognizer(object): return padding_im, resize_shape, pad_shape, valid_ratio + def resize_norm_img_svtr(self, img, image_shape): + + imgC, imgH, imgW = image_shape + resized_image = cv2.resize( + img, (imgW, imgH), interpolation=cv2.INTER_LINEAR) + resized_image = resized_image.astype('float32') + resized_image = resized_image.transpose((2, 0, 1)) / 255 + resized_image -= 0.5 + resized_image /= 0.5 + return resized_image + + def resize_norm_img_abinet(self, img, image_shape): + + imgC, imgH, imgW = image_shape + + resized_image = cv2.resize( + img, (imgW, imgH), interpolation=cv2.INTER_LINEAR) + resized_image = resized_image.astype('float32') + resized_image = resized_image / 255. + + mean = np.array([0.485, 0.456, 0.406]) + std = np.array([0.229, 0.224, 0.225]) + resized_image = ( + resized_image - mean[None, None, ...]) / std[None, None, ...] + resized_image = resized_image.transpose((2, 0, 1)) + resized_image = resized_image.astype('float32') + + return resized_image + def __call__(self, img_list): img_num = len(img_list) # Calculate the aspect ratio of all text bars @@ -313,6 +337,11 @@ class TextRecognizer(object): self.rec_image_shape) norm_img = norm_img[np.newaxis, :] norm_img_batch.append(norm_img) + elif self.rec_algorithm == "ABINet": + norm_img = self.resize_norm_img_abinet( + img_list[indices[ino]], self.rec_image_shape) + norm_img = norm_img[np.newaxis, :] + norm_img_batch.append(norm_img) else: norm_img = self.resize_norm_img(img_list[indices[ino]], max_wh_ratio) diff --git a/tools/program.py b/tools/program.py index 745c28b872..f61d3f9af7 100755 --- a/tools/program.py +++ b/tools/program.py @@ -575,7 +575,7 @@ def preprocess(is_train=False): 'EAST', 'DB', 'SAST', 'Rosetta', 'CRNN', 'STARNet', 'RARE', 'SRN', 'CLS', 'PGNet', 'Distillation', 'NRTR', 'TableAttn', 'SAR', 'PSE', 'SEED', 'SDMGR', 'LayoutXLM', 'LayoutLM', 'PREN', 'FCE', 'SVTR', - 'ViTSTR' + 'ViTSTR', 'ABINet' ] if use_xpu: From 37f22e3e33400599fa0856777b94ab6af0e2f31b Mon Sep 17 00:00:00 2001 From: Topdu <784990967@qq.com> Date: Thu, 23 Jun 2022 05:03:15 +0000 Subject: [PATCH 12/45] add rec_resnet45 --- ppocr/modeling/backbones/__init__.py | 3 +- ppocr/modeling/backbones/rec_resnet.py | 280 ---------------------- ppocr/modeling/backbones/rec_resnet_45.py | 147 ++++++++++++ 3 files changed, 149 insertions(+), 281 deletions(-) delete mode 100644 ppocr/modeling/backbones/rec_resnet.py create mode 100644 ppocr/modeling/backbones/rec_resnet_45.py diff --git a/ppocr/modeling/backbones/__init__.py b/ppocr/modeling/backbones/__init__.py index 56d2c2b9db..f8959e263e 100755 --- a/ppocr/modeling/backbones/__init__.py +++ b/ppocr/modeling/backbones/__init__.py @@ -27,7 +27,8 @@ def build_backbone(config, model_type): from .rec_resnet_fpn import ResNetFPN from .rec_mv1_enhance import MobileNetV1Enhance from .rec_nrtr_mtb import MTB - from .rec_resnet import ResNet31, ResNet45 + from .rec_resnet_31 import ResNet31 + from .rec_resnet_45 import ResNet45 from .rec_resnet_aster import ResNet_ASTER from .rec_micronet import MicroNet from .rec_efficientb3_pren import EfficientNetb3_PREN diff --git a/ppocr/modeling/backbones/rec_resnet.py b/ppocr/modeling/backbones/rec_resnet.py deleted file mode 100644 index ce76b642cc..0000000000 --- a/ppocr/modeling/backbones/rec_resnet.py +++ /dev/null @@ -1,280 +0,0 @@ -# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. -# -# 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. -""" -This code is refer from: -https://github.com/open-mmlab/mmocr/blob/main/mmocr/models/textrecog/layers/conv_layer.py -https://github.com/open-mmlab/mmocr/blob/main/mmocr/models/textrecog/backbones/resnet31_ocr.py -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import paddle -from paddle import ParamAttr -from paddle.nn.initializer import KaimingNormal -import paddle.nn as nn -import paddle.nn.functional as F -import numpy as np -import math - -__all__ = ["ResNet31", "ResNet45"] - - -def conv1x1(in_planes, out_planes, stride=1): - return nn.Conv2D( - in_planes, - out_planes, - kernel_size=1, - stride=stride, - weight_attr=ParamAttr(initializer=KaimingNormal()), - bias_attr=False) - - -def conv3x3(in_channel, out_channel, stride=1): - return nn.Conv2D( - in_channel, - out_channel, - kernel_size=3, - stride=stride, - padding=1, - weight_attr=ParamAttr(initializer=KaimingNormal()), - bias_attr=False) - - -class BasicBlock(nn.Layer): - expansion = 1 - - def __init__(self, in_channels, channels, stride=1, downsample=None): - super().__init__() - self.conv1 = conv1x1(in_channels, channels) - self.bn1 = nn.BatchNorm2D(channels) - self.relu = nn.ReLU() - self.conv2 = conv3x3(channels, channels, stride) - self.bn2 = nn.BatchNorm2D(channels) - self.downsample = downsample - self.stride = stride - - def forward(self, x): - residual = x - - out = self.conv1(x) - out = self.bn1(out) - out = self.relu(out) - - out = self.conv2(out) - out = self.bn2(out) - - if self.downsample is not None: - residual = self.downsample(x) - out += residual - out = self.relu(out) - - return out - - -class ResNet31(nn.Layer): - ''' - Args: - in_channels (int): Number of channels of input image tensor. - layers (list[int]): List of BasicBlock number for each stage. - channels (list[int]): List of out_channels of Conv2d layer. - out_indices (None | Sequence[int]): Indices of output stages. - last_stage_pool (bool): If True, add `MaxPool2d` layer to last stage. - ''' - - def __init__(self, - in_channels=3, - layers=[1, 2, 5, 3], - channels=[64, 128, 256, 256, 512, 512, 512], - out_indices=None, - last_stage_pool=False): - super(ResNet31, self).__init__() - assert isinstance(in_channels, int) - assert isinstance(last_stage_pool, bool) - - self.out_indices = out_indices - self.last_stage_pool = last_stage_pool - - # conv 1 (Conv Conv) - self.conv1_1 = nn.Conv2D( - in_channels, channels[0], kernel_size=3, stride=1, padding=1) - self.bn1_1 = nn.BatchNorm2D(channels[0]) - self.relu1_1 = nn.ReLU() - - self.conv1_2 = nn.Conv2D( - channels[0], channels[1], kernel_size=3, stride=1, padding=1) - self.bn1_2 = nn.BatchNorm2D(channels[1]) - self.relu1_2 = nn.ReLU() - - # conv 2 (Max-pooling, Residual block, Conv) - self.pool2 = nn.MaxPool2D( - kernel_size=2, stride=2, padding=0, ceil_mode=True) - self.block2 = self._make_layer(channels[1], channels[2], layers[0]) - self.conv2 = nn.Conv2D( - channels[2], channels[2], kernel_size=3, stride=1, padding=1) - self.bn2 = nn.BatchNorm2D(channels[2]) - self.relu2 = nn.ReLU() - - # conv 3 (Max-pooling, Residual block, Conv) - self.pool3 = nn.MaxPool2D( - kernel_size=2, stride=2, padding=0, ceil_mode=True) - self.block3 = self._make_layer(channels[2], channels[3], layers[1]) - self.conv3 = nn.Conv2D( - channels[3], channels[3], kernel_size=3, stride=1, padding=1) - self.bn3 = nn.BatchNorm2D(channels[3]) - self.relu3 = nn.ReLU() - - # conv 4 (Max-pooling, Residual block, Conv) - self.pool4 = nn.MaxPool2D( - kernel_size=(2, 1), stride=(2, 1), padding=0, ceil_mode=True) - self.block4 = self._make_layer(channels[3], channels[4], layers[2]) - self.conv4 = nn.Conv2D( - channels[4], channels[4], kernel_size=3, stride=1, padding=1) - self.bn4 = nn.BatchNorm2D(channels[4]) - self.relu4 = nn.ReLU() - - # conv 5 ((Max-pooling), Residual block, Conv) - self.pool5 = None - if self.last_stage_pool: - self.pool5 = nn.MaxPool2D( - kernel_size=2, stride=2, padding=0, ceil_mode=True) - self.block5 = self._make_layer(channels[4], channels[5], layers[3]) - self.conv5 = nn.Conv2D( - channels[5], channels[5], kernel_size=3, stride=1, padding=1) - self.bn5 = nn.BatchNorm2D(channels[5]) - self.relu5 = nn.ReLU() - - self.out_channels = channels[-1] - - def _make_layer(self, input_channels, output_channels, blocks): - layers = [] - for _ in range(blocks): - downsample = None - if input_channels != output_channels: - downsample = nn.Sequential( - nn.Conv2D( - input_channels, - output_channels, - kernel_size=1, - stride=1, - weight_attr=ParamAttr(initializer=KaimingNormal()), - bias_attr=False), - nn.BatchNorm2D(output_channels), ) - - layers.append( - BasicBlock( - input_channels, output_channels, downsample=downsample)) - input_channels = output_channels - return nn.Sequential(*layers) - - def forward(self, x): - x = self.conv1_1(x) - x = self.bn1_1(x) - x = self.relu1_1(x) - - x = self.conv1_2(x) - x = self.bn1_2(x) - x = self.relu1_2(x) - - outs = [] - for i in range(4): - layer_index = i + 2 - pool_layer = getattr(self, f'pool{layer_index}') - block_layer = getattr(self, f'block{layer_index}') - conv_layer = getattr(self, f'conv{layer_index}') - bn_layer = getattr(self, f'bn{layer_index}') - relu_layer = getattr(self, f'relu{layer_index}') - - if pool_layer is not None: - x = pool_layer(x) - x = block_layer(x) - x = conv_layer(x) - x = bn_layer(x) - x = relu_layer(x) - - outs.append(x) - - if self.out_indices is not None: - return tuple([outs[i] for i in self.out_indices]) - - return x - - -class ResNet(nn.Layer): - def __init__(self, block, layers, in_channels=3): - self.inplanes = 32 - super(ResNet, self).__init__() - self.conv1 = nn.Conv2D( - 3, - 32, - kernel_size=3, - stride=1, - padding=1, - weight_attr=ParamAttr(initializer=KaimingNormal()), - bias_attr=False) - self.bn1 = nn.BatchNorm2D(32) - self.relu = nn.ReLU() - - self.layer1 = self._make_layer(block, 32, layers[0], stride=2) - self.layer2 = self._make_layer(block, 64, layers[1], stride=1) - self.layer3 = self._make_layer(block, 128, layers[2], stride=2) - self.layer4 = self._make_layer(block, 256, layers[3], stride=1) - self.layer5 = self._make_layer(block, 512, layers[4], stride=1) - self.out_channels = 512 - - # for m in self.modules(): - # if isinstance(m, nn.Conv2D): - # n = m._kernel_size[0] * m._kernel_size[1] * m._out_channels - # m.weight.data.normal_(0, math.sqrt(2. / n)) - - def _make_layer(self, block, planes, blocks, stride=1): - downsample = None - if stride != 1 or self.inplanes != planes * block.expansion: - # downsample = True - downsample = nn.Sequential( - nn.Conv2D( - self.inplanes, - planes * block.expansion, - kernel_size=1, - stride=stride, - weight_attr=ParamAttr(initializer=KaimingNormal()), - bias_attr=False), - nn.BatchNorm2D(planes * block.expansion), ) - - layers = [] - layers.append(block(self.inplanes, planes, stride, downsample)) - self.inplanes = planes * block.expansion - for i in range(1, blocks): - layers.append(block(self.inplanes, planes)) - - return nn.Sequential(*layers) - - def forward(self, x): - - x = self.conv1(x) - x = self.bn1(x) - x = self.relu(x) - # print(x) - x = self.layer1(x) - x = self.layer2(x) - x = self.layer3(x) - # print(x) - x = self.layer4(x) - x = self.layer5(x) - return x - - -def ResNet45(in_channels=3): - return ResNet(BasicBlock, [3, 4, 6, 6, 3], in_channels=in_channels) diff --git a/ppocr/modeling/backbones/rec_resnet_45.py b/ppocr/modeling/backbones/rec_resnet_45.py new file mode 100644 index 0000000000..9093d0bc99 --- /dev/null +++ b/ppocr/modeling/backbones/rec_resnet_45.py @@ -0,0 +1,147 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# 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. +""" +This code is refer from: +https://github.com/FangShancheng/ABINet/tree/main/modules +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import ParamAttr +from paddle.nn.initializer import KaimingNormal +import paddle.nn as nn +import paddle.nn.functional as F +import numpy as np +import math + +__all__ = ["ResNet45"] + + +def conv1x1(in_planes, out_planes, stride=1): + return nn.Conv2D( + in_planes, + out_planes, + kernel_size=1, + stride=1, + weight_attr=ParamAttr(initializer=KaimingNormal()), + bias_attr=False) + + +def conv3x3(in_channel, out_channel, stride=1): + return nn.Conv2D( + in_channel, + out_channel, + kernel_size=3, + stride=stride, + padding=1, + weight_attr=ParamAttr(initializer=KaimingNormal()), + bias_attr=False) + + +class BasicBlock(nn.Layer): + expansion = 1 + + def __init__(self, in_channels, channels, stride=1, downsample=None): + super().__init__() + self.conv1 = conv1x1(in_channels, channels) + self.bn1 = nn.BatchNorm2D(channels) + self.relu = nn.ReLU() + self.conv2 = conv3x3(channels, channels, stride) + self.bn2 = nn.BatchNorm2D(channels) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + + if self.downsample is not None: + residual = self.downsample(x) + out += residual + out = self.relu(out) + + return out + + +class ResNet45(nn.Layer): + def __init__(self, block=BasicBlock, layers=[3, 4, 6, 6, 3], in_channels=3): + self.inplanes = 32 + super(ResNet45, self).__init__() + self.conv1 = nn.Conv2D( + 3, + 32, + kernel_size=3, + stride=1, + padding=1, + weight_attr=ParamAttr(initializer=KaimingNormal()), + bias_attr=False) + self.bn1 = nn.BatchNorm2D(32) + self.relu = nn.ReLU() + + self.layer1 = self._make_layer(block, 32, layers[0], stride=2) + self.layer2 = self._make_layer(block, 64, layers[1], stride=1) + self.layer3 = self._make_layer(block, 128, layers[2], stride=2) + self.layer4 = self._make_layer(block, 256, layers[3], stride=1) + self.layer5 = self._make_layer(block, 512, layers[4], stride=1) + self.out_channels = 512 + + # for m in self.modules(): + # if isinstance(m, nn.Conv2D): + # n = m._kernel_size[0] * m._kernel_size[1] * m._out_channels + # m.weight.data.normal_(0, math.sqrt(2. / n)) + + def _make_layer(self, block, planes, blocks, stride=1): + downsample = None + if stride != 1 or self.inplanes != planes * block.expansion: + # downsample = True + downsample = nn.Sequential( + nn.Conv2D( + self.inplanes, + planes * block.expansion, + kernel_size=1, + stride=stride, + weight_attr=ParamAttr(initializer=KaimingNormal()), + bias_attr=False), + nn.BatchNorm2D(planes * block.expansion), ) + + layers = [] + layers.append(block(self.inplanes, planes, stride, downsample)) + self.inplanes = planes * block.expansion + for i in range(1, blocks): + layers.append(block(self.inplanes, planes)) + + return nn.Sequential(*layers) + + def forward(self, x): + + x = self.conv1(x) + x = self.bn1(x) + x = self.relu(x) + # print(x) + x = self.layer1(x) + x = self.layer2(x) + x = self.layer3(x) + # print(x) + x = self.layer4(x) + x = self.layer5(x) + return x From 0a343fd3aae14b866281d897474d0669b1b26282 Mon Sep 17 00:00:00 2001 From: Topdu <784990967@qq.com> Date: Thu, 23 Jun 2022 08:17:00 +0000 Subject: [PATCH 13/45] svtr ch large model --- configs/rec/rec_svtrnet.yml | 4 +- configs/rec/rec_svtrnet_ch.yml | 159 ++++++++++++++++++++++++++++ doc/doc_ch/algorithm_rec_svtr.md | 1 - doc/doc_en/algorithm_rec_svtr_en.md | 1 - ppocr/data/imaug/__init__.py | 3 +- ppocr/data/imaug/rec_img_aug.py | 124 +++++++++++++--------- tools/export_model.py | 19 +++- 7 files changed, 252 insertions(+), 59 deletions(-) create mode 100644 configs/rec/rec_svtrnet_ch.yml diff --git a/configs/rec/rec_svtrnet.yml b/configs/rec/rec_svtrnet.yml index a3d292b6ba..5896b10515 100644 --- a/configs/rec/rec_svtrnet.yml +++ b/configs/rec/rec_svtrnet.yml @@ -83,7 +83,7 @@ Train: img_mode: BGR channel_first: False - CTCLabelEncode: # Class handling label - - RecResizeImg: + - SVTRRecResizeImg: character_dict_path: image_shape: [3, 64, 256] padding: False @@ -104,7 +104,7 @@ Eval: img_mode: BGR channel_first: False - CTCLabelEncode: # Class handling label - - RecResizeImg: + - SVTRRecResizeImg: character_dict_path: image_shape: [3, 64, 256] padding: False diff --git a/configs/rec/rec_svtrnet_ch.yml b/configs/rec/rec_svtrnet_ch.yml new file mode 100644 index 0000000000..b327edf499 --- /dev/null +++ b/configs/rec/rec_svtrnet_ch.yml @@ -0,0 +1,159 @@ +Global: + use_gpu: true + epoch_num: 100 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/svtr_ch_all/ + save_epoch_step: 10 + eval_batch_step: + - 0 + - 2000 + cal_metric_during_train: true + pretrained_model: null + checkpoints: null + save_inference_dir: null + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: 25 + infer_mode: false + use_space_char: true + save_res_path: ./output/rec/predicts_svtr_tiny_ch_all.txt +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.99 + epsilon: 8.0e-08 + weight_decay: 0.05 + no_weight_decay_name: norm pos_embed + one_dim_param_no_weight_decay: true + lr: + name: Cosine + learning_rate: 0.0005 + warmup_epoch: 2 +Architecture: + model_type: rec + algorithm: SVTR + Transform: null + Backbone: + name: SVTRNet + img_size: + - 32 + - 320 + out_char_num: 40 + out_channels: 96 + patch_merging: Conv + embed_dim: + - 64 + - 128 + - 256 + depth: + - 3 + - 6 + - 3 + num_heads: + - 2 + - 4 + - 8 + mixer: + - Local + - Local + - Local + - Local + - Local + - Local + - Global + - Global + - Global + - Global + - Global + - Global + local_mixer: + - - 7 + - 11 + - - 7 + - 11 + - - 7 + - 11 + last_stage: true + prenorm: false + Neck: + name: SequenceEncoder + encoder_type: reshape + Head: + name: CTCHead +Loss: + name: CTCLoss +PostProcess: + name: CTCLabelDecode +Metric: + name: RecMetric + main_indicator: acc +Train: + dataset: + name: SimpleDataSet + label_file_list: + - /paddle/data/ocr_all/train_all_list.txt + data_dir: /paddle/data/ocr_all + ext_op_transform_idx: 1 + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: + - 32 + - 320 + - 3 + - RecAug: null + - CTCLabelEncode: null + - SVTRRecResizeImg: + character_dict_path: null + infer_mode: False + image_shape: + - 3 + - 32 + - 320 + padding: true + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: true + batch_size_per_card: 256 + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: /paddle/data/ocr_all + label_file_list: + - /paddle/data/ocr_all/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - CTCLabelEncode: null + - SVTRRecResizeImg: + character_dict_path: null + infer_mode: False + image_shape: + - 3 + - 32 + - 320 + padding: true + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: false + drop_last: false + batch_size_per_card: 256 + num_workers: 2 +profiler_options: null diff --git a/doc/doc_ch/algorithm_rec_svtr.md b/doc/doc_ch/algorithm_rec_svtr.md index 41a22ca65c..c0e26433e9 100644 --- a/doc/doc_ch/algorithm_rec_svtr.md +++ b/doc/doc_ch/algorithm_rec_svtr.md @@ -111,7 +111,6 @@ python3 tools/export_model.py -c ./rec_svtr_tiny_none_ctc_en_train/rec_svtr_tiny **注意:** - 如果您是在自己的数据集上训练的模型,并且调整了字典文件,请注意修改配置文件中的`character_dict_path`是否为所正确的字典文件。 -- 如果您修改了训练时的输入大小,请修改`tools/export_model.py`文件中的对应SVTR的`infer_shape`。 转换成功后,在目录下有三个文件: ``` diff --git a/doc/doc_en/algorithm_rec_svtr_en.md b/doc/doc_en/algorithm_rec_svtr_en.md index d402a6b491..37cd35f35a 100644 --- a/doc/doc_en/algorithm_rec_svtr_en.md +++ b/doc/doc_en/algorithm_rec_svtr_en.md @@ -88,7 +88,6 @@ python3 tools/export_model.py -c configs/rec/rec_svtrnet.yml -o Global.pretraine **Note:** - If you are training the model on your own dataset and have modified the dictionary file, please pay attention to modify the `character_dict_path` in the configuration file to the modified dictionary file. -- If you modified the input size during training, please modify the `infer_shape` corresponding to SVTR in the `tools/export_model.py` file. After the conversion is successful, there are three files in the directory: ``` diff --git a/ppocr/data/imaug/__init__.py b/ppocr/data/imaug/__init__.py index 58e8a5c780..437e0152fa 100644 --- a/ppocr/data/imaug/__init__.py +++ b/ppocr/data/imaug/__init__.py @@ -24,7 +24,8 @@ from .make_pse_gt import MakePseGt from .rec_img_aug import RecAug, RecConAug, RecResizeImg, ClsResizeImg, \ - SRNRecResizeImg, GrayRecResizeImg, SARRecResizeImg, PRENResizeImg, ABINetRecResizeImg + SRNRecResizeImg, GrayRecResizeImg, SARRecResizeImg, PRENResizeImg, \ + ABINetRecResizeImg, SVTRRecResizeImg from .ssl_img_aug import SSLRotateResize from .randaugment import RandAugment from .copy_paste import CopyPaste diff --git a/ppocr/data/imaug/rec_img_aug.py b/ppocr/data/imaug/rec_img_aug.py index a8b3b813b3..874d9aa084 100644 --- a/ppocr/data/imaug/rec_img_aug.py +++ b/ppocr/data/imaug/rec_img_aug.py @@ -148,57 +148,6 @@ class ClsResizeImg(object): return data -class GrayRecResizeImg(object): - def __init__(self, - image_shape, - resize_type, - inter_type='Image.ANTIALIAS', - scale=True, - padding=False, - **kwargs): - self.image_shape = image_shape - self.resize_type = resize_type - self.padding = padding - self.inter_type = eval(inter_type) - self.scale = scale - - def __call__(self, data): - img = data['image'] - img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) - image_shape = self.image_shape - if self.padding: - imgC, imgH, imgW = image_shape - # todo: change to 0 and modified image shape - 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)) - norm_img = np.expand_dims(resized_image, -1) - norm_img = norm_img.transpose((2, 0, 1)) - resized_image = norm_img.astype(np.float32) / 128. - 1. - padding_im = np.zeros((imgC, imgH, imgW), dtype=np.float32) - padding_im[:, :, 0:resized_w] = resized_image - data['image'] = padding_im - return data - if self.resize_type == 'PIL': - image_pil = Image.fromarray(np.uint8(img)) - img = image_pil.resize(self.image_shape, self.inter_type) - img = np.array(img) - if self.resize_type == 'OpenCV': - img = cv2.resize(img, self.image_shape) - norm_img = np.expand_dims(img, -1) - norm_img = norm_img.transpose((2, 0, 1)) - if self.scale: - data['image'] = norm_img.astype(np.float32) / 128. - 1. - else: - data['image'] = norm_img.astype(np.float32) / 255. - return data - - class RecResizeImg(object): def __init__(self, image_shape, @@ -279,6 +228,57 @@ class PRENResizeImg(object): return data +class GrayRecResizeImg(object): + def __init__(self, + image_shape, + resize_type, + inter_type='Image.ANTIALIAS', + scale=True, + padding=False, + **kwargs): + self.image_shape = image_shape + self.resize_type = resize_type + self.padding = padding + self.inter_type = eval(inter_type) + self.scale = scale + + def __call__(self, data): + img = data['image'] + img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + image_shape = self.image_shape + if self.padding: + imgC, imgH, imgW = image_shape + # todo: change to 0 and modified image shape + 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)) + norm_img = np.expand_dims(resized_image, -1) + norm_img = norm_img.transpose((2, 0, 1)) + resized_image = norm_img.astype(np.float32) / 128. - 1. + padding_im = np.zeros((imgC, imgH, imgW), dtype=np.float32) + padding_im[:, :, 0:resized_w] = resized_image + data['image'] = padding_im + return data + if self.resize_type == 'PIL': + image_pil = Image.fromarray(np.uint8(img)) + img = image_pil.resize(self.image_shape, self.inter_type) + img = np.array(img) + if self.resize_type == 'OpenCV': + img = cv2.resize(img, self.image_shape) + norm_img = np.expand_dims(img, -1) + norm_img = norm_img.transpose((2, 0, 1)) + if self.scale: + data['image'] = norm_img.astype(np.float32) / 128. - 1. + else: + data['image'] = norm_img.astype(np.float32) / 255. + return data + + class ABINetRecResizeImg(object): def __init__(self, image_shape, @@ -297,6 +297,28 @@ class ABINetRecResizeImg(object): return data +class SVTRRecResizeImg(object): + def __init__(self, + image_shape, + infer_mode=False, + character_dict_path='./ppocr/utils/ppocr_keys_v1.txt', + padding=True, + **kwargs): + self.image_shape = image_shape + self.infer_mode = infer_mode + self.character_dict_path = character_dict_path + self.padding = padding + + def __call__(self, data): + img = data['image'] + + norm_img, valid_ratio = resize_norm_img(img, self.image_shape, + self.padding) + data['image'] = norm_img + data['valid_ratio'] = valid_ratio + return data + + def resize_norm_img_sar(img, image_shape, width_downsample_ratio=0.25): imgC, imgH, imgW_min, imgW_max = image_shape h = img.shape[0] diff --git a/tools/export_model.py b/tools/export_model.py index e2673239b1..b10d41d5b2 100755 --- a/tools/export_model.py +++ b/tools/export_model.py @@ -31,7 +31,12 @@ from ppocr.utils.logging import get_logger from tools.program import load_config, merge_config, ArgsParser -def export_single_model(model, arch_config, save_path, logger, quanter=None): +def export_single_model(model, + arch_config, + save_path, + logger, + input_shape=None, + quanter=None): if arch_config["algorithm"] == "SRN": max_text_length = arch_config["Head"]["max_text_length"] other_shape = [ @@ -64,7 +69,7 @@ def export_single_model(model, arch_config, save_path, logger, quanter=None): else: other_shape = [ paddle.static.InputSpec( - shape=[None, 3, 64, 256], dtype="float32"), + shape=[None] + input_shape, dtype="float32"), ] model = to_static(model, input_spec=other_shape) elif arch_config["algorithm"] == "PREN": @@ -174,6 +179,13 @@ def main(): arch_config = config["Architecture"] + if arch_config["algorithm"] == "SVTR" and arch_config["Head"][ + "name"] != 'MultiHead': + input_shape = config["Eval"]["dataset"]["transforms"][-2][ + 'SVTRRecResizeImg']['image_shape'] + else: + input_shape = None + if arch_config["algorithm"] in ["Distillation", ]: # distillation model archs = list(arch_config["Models"].values()) for idx, name in enumerate(model.model_name_list): @@ -182,7 +194,8 @@ def main(): sub_model_save_path, logger) else: save_path = os.path.join(save_path, "inference") - export_single_model(model, arch_config, save_path, logger) + export_single_model( + model, arch_config, save_path, logger, input_shape=input_shape) if __name__ == "__main__": From 31ab300ca258243a311ae08e1496faa789567a0c Mon Sep 17 00:00:00 2001 From: Topdu <784990967@qq.com> Date: Thu, 23 Jun 2022 13:09:48 +0000 Subject: [PATCH 14/45] [application] svtr ch model --- applications/高精度中文识别模型.md | 101 +++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 applications/高精度中文识别模型.md diff --git a/applications/高精度中文识别模型.md b/applications/高精度中文识别模型.md new file mode 100644 index 0000000000..27b63abf3c --- /dev/null +++ b/applications/高精度中文识别模型.md @@ -0,0 +1,101 @@ +# 高精度中文场景文本识别模型SVTR + +## 1. 简介 + +PP-OCRv3是百度开源的超轻量级场景文本检测识别模型库,其中超轻量的场景中文识别模型SVTR_LCNet使用了SVTR算法结构。为了保证速度,SVTR_LCNet将SVTR模型的Local Blocks替换为LCNet,使用两层Global Blocks。在中文场景中,经过多次优化,SVTR_LCNet的最终精度为79.4%。具体的: +- GTC:Attention指导CTC训练策略; +- TextConAug:挖掘文字上下文信息的数据增广策略; +- TextRotNet:自监督的预训练模型; +- UDML:联合互学习策略; +- UIM:无标注数据挖掘方案。 + +其中 *UIM:无标注数据挖掘方案* 使用了高精度的SVTR中文模型,该模型在PP-OCRv3识别的数据集上训练,精度为82.5%。本项目主要介绍SVTR中文模型如何使用。 + +aistudio项目链接: [高精度中文场景文本识别模型SVTR](https://aistudio.baidu.com/aistudio/projectdetail/4263032) + +## 2. SVTR中文模型使用 + +### 环境准备 + + +本任务基于Aistudio完成, 具体环境如下: + +- 操作系统: Linux +- PaddlePaddle: 2.3 +- PaddleOCR: dygraph + +下载 PaddleOCR代码 + +```bash +git clone -b dygraph https://github.com/PaddlePaddle/PaddleOCR +``` + +安装依赖库 + +```bash +pip install -r PaddleOCR/requirements.txt -i https://mirror.baidu.com/pypi/simple +``` + +### 快速使用 + +获取SVTR中文模型文件,请扫码填写问卷,加入PaddleOCR官方交流群获取全部OCR垂类模型下载链接、《动手学OCR》电子书等全套OCR学习资料🎁 +

+ +
+ +```bash +# 解压模型文件 +tar xf svtr_ch_high_accuracy.tar +``` + +预测中文文本,以下图为例: +![](https://ai-studio-static-online.cdn.bcebos.com/511f6d65f0e84ccc8c5668eafbe7feb73b26c40befec4bd58dde64b59bf340a1) + +预测命令: + +```bash +# CPU预测 +python tools/infer_rec.py -c configs/rec/rec_svtrnet_ch.yml -o Global.pretrained_model=./svtr_ch_high_accuracy/best_accuracy Global.infer_img=./doc/imgs_words/ch/word_1.jpg Global.use_gpu=False + +# GPU预测 +#python tools/infer_rec.py -c configs/rec/rec_svtrnet_ch.yml -o Global.pretrained_model=./svtr_ch_high_accuracy/best_accuracy Global.infer_img=./doc/imgs_words/ch/word_1.jpg Global.use_gpu=True +``` + +可以看到最后打印结果为 +- result: 韩国小馆 0.9853458404541016 + +0.9853458404541016为预测置信度。 + +### 推理模型导出与预测 + +inference 模型(paddle.jit.save保存的模型) 一般是模型训练,把模型结构和模型参数保存在文件中的固化模型,多用于预测部署场景。 训练过程中保存的模型是checkpoints模型,保存的只有模型的参数,多用于恢复训练等。 与checkpoints模型相比,inference 模型会额外保存模型的结构信息,在预测部署、加速推理上性能优越,灵活方便,适合于实际系统集成。 + +运行识别模型转inference模型命令,如下: + +```bash +python tools/export_model.py -c configs/rec/rec_svtrnet_ch.yml -o Global.pretrained_model=./svtr_ch_high_accuracy/best_accuracy Global.save_inference_dir=./inference/svtr_ch +``` + +转换成功后,在目录下有三个文件: +```shell +inference/svtr_ch/ + ├── inference.pdiparams # 识别inference模型的参数文件 + ├── inference.pdiparams.info # 识别inference模型的参数信息,可忽略 + └── inference.pdmodel # 识别inference模型的program文件 +``` + +inference模型预测,命令如下: + +```bash +# CPU预测 +python3 tools/infer/predict_rec.py --image_dir="./doc/imgs_words/ch/word_1.jpg" --rec_algorithm='SVTR' --rec_model_dir=./inference/svtr_ch/ --rec_image_shape='3, 32, 320' --rec_char_dict_path=ppocr/utils/ppocr_keys_v1.txt --use_gpu=False + +# GPU预测 +#python3 tools/infer/predict_rec.py --image_dir="./doc/imgs_words/ch/word_1.jpg" --rec_algorithm='SVTR' --rec_model_dir=./inference/svtr_ch/ --rec_image_shape='3, 32, 320' --rec_char_dict_path=ppocr/utils/ppocr_keys_v1.txt --use_gpu=True +``` + +**注意** + +- 使用PP-OCRv3识别SVTR_LCNet进行推理时,不需要使用--rec_algorithm指定算法名称,使用默认的推理方式即为PP-OCRv3识别的推理过程。但是使用SVTR算法时,需要指定--rec_algorithm='SVTR' +- 如果使用自定义字典训练的模型,需要将--rec_char_dict_path=ppocr/utils/ppocr_keys_v1.txt修改为自定义的字典 +- --rec_image_shape='3, 32, 320' 该参数不能去掉 From 6ef2c8762ed7ec3a90a6680292a6095236428103 Mon Sep 17 00:00:00 2001 From: Topdu <784990967@qq.com> Date: Thu, 23 Jun 2022 13:25:31 +0000 Subject: [PATCH 15/45] [application] svtr ch model --- applications/高精度中文识别模型.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/applications/高精度中文识别模型.md b/applications/高精度中文识别模型.md index 27b63abf3c..31d68abbff 100644 --- a/applications/高精度中文识别模型.md +++ b/applications/高精度中文识别模型.md @@ -9,7 +9,12 @@ PP-OCRv3是百度开源的超轻量级场景文本检测识别模型库,其中 - UDML:联合互学习策略; - UIM:无标注数据挖掘方案。 -其中 *UIM:无标注数据挖掘方案* 使用了高精度的SVTR中文模型,该模型在PP-OCRv3识别的数据集上训练,精度为82.5%。本项目主要介绍SVTR中文模型如何使用。 +其中 *UIM:无标注数据挖掘方案* 使用了高精度的SVTR中文模型进行无标注文件的刷库,该模型在PP-OCRv3识别的数据集上训练,精度对比如下表。 + +|中文识别算法|模型|精度| +| --- | --- | --- | +|PP-OCRv3|SVTR_LCNet|79.4%| +|SVTR|SVTR-Tiny|82.5%| aistudio项目链接: [高精度中文场景文本识别模型SVTR](https://aistudio.baidu.com/aistudio/projectdetail/4263032) @@ -96,6 +101,6 @@ python3 tools/infer/predict_rec.py --image_dir="./doc/imgs_words/ch/word_1.jpg" **注意** -- 使用PP-OCRv3识别SVTR_LCNet进行推理时,不需要使用--rec_algorithm指定算法名称,使用默认的推理方式即为PP-OCRv3识别的推理过程。但是使用SVTR算法时,需要指定--rec_algorithm='SVTR' +- 使用SVTR算法时,需要指定--rec_algorithm='SVTR' - 如果使用自定义字典训练的模型,需要将--rec_char_dict_path=ppocr/utils/ppocr_keys_v1.txt修改为自定义的字典 - --rec_image_shape='3, 32, 320' 该参数不能去掉 From 1fc0655d857f25704a54e6550a37c7c68f7d5fdd Mon Sep 17 00:00:00 2001 From: Topdu <784990967@qq.com> Date: Thu, 23 Jun 2022 13:31:57 +0000 Subject: [PATCH 16/45] [application] svtr ch model --- applications/高精度中文识别模型.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/高精度中文识别模型.md b/applications/高精度中文识别模型.md index 31d68abbff..e3d0693ece 100644 --- a/applications/高精度中文识别模型.md +++ b/applications/高精度中文识别模型.md @@ -54,7 +54,7 @@ tar xf svtr_ch_high_accuracy.tar ``` 预测中文文本,以下图为例: -![](https://ai-studio-static-online.cdn.bcebos.com/511f6d65f0e84ccc8c5668eafbe7feb73b26c40befec4bd58dde64b59bf340a1) +![](../doc/imgs_words/ch/word_1.jpg) 预测命令: From 773b687f5a5a6d3115e27ac15070fd13ea6bb5ce Mon Sep 17 00:00:00 2001 From: Topdu <784990967@qq.com> Date: Fri, 24 Jun 2022 07:25:50 +0000 Subject: [PATCH 17/45] add abinet_rec_aug and trained model --- configs/rec/rec_r45_abinet.yml | 3 +- doc/doc_ch/algorithm_rec_abinet.md | 4 +- doc/doc_en/algorithm_rec_abinet_en.md | 4 +- ppocr/data/imaug/__init__.py | 2 +- ppocr/data/imaug/abinet_aug.py | 407 ++++++++++++++++++ ppocr/data/imaug/rec_img_aug.py | 27 ++ .../configs/rec_r45_abinet/rec_r45_abinet.yml | 1 + 7 files changed, 442 insertions(+), 6 deletions(-) create mode 100644 ppocr/data/imaug/abinet_aug.py diff --git a/configs/rec/rec_r45_abinet.yml b/configs/rec/rec_r45_abinet.yml index a756fead9f..3cf4cddb04 100644 --- a/configs/rec/rec_r45_abinet.yml +++ b/configs/rec/rec_r45_abinet.yml @@ -8,7 +8,7 @@ Global: # evaluation is run every 2000 iterations eval_batch_step: [0, 2000] cal_metric_during_train: True - pretrained_model: + pretrained_model: ./rec_r45_abinet_train/abinet_vl_pretrained checkpoints: save_inference_dir: use_visualdl: False @@ -67,6 +67,7 @@ Train: - DecodeImage: # load image img_mode: RGB channel_first: False + - ABINetRecAug: - ABINetLabelEncode: # Class handling label ignore_index: *ignore_index - ABINetRecResizeImg: diff --git a/doc/doc_ch/algorithm_rec_abinet.md b/doc/doc_ch/algorithm_rec_abinet.md index d20c703014..47507c36c7 100644 --- a/doc/doc_ch/algorithm_rec_abinet.md +++ b/doc/doc_ch/algorithm_rec_abinet.md @@ -27,7 +27,7 @@ |模型|骨干网络|配置文件|Acc|下载链接| | --- | --- | --- | --- | --- | -|ABINet|ResNet45|[rec_r45_abinet.yml](../../configs/rec/rec_r45_abinet.yml)|90.75%|[训练模型]()/[预训练模型]| +|ABINet|ResNet45|[rec_r45_abinet.yml](../../configs/rec/rec_r45_abinet.yml)|90.75%|[预训练、训练模型](https://paddleocr.bj.bcebos.com/rec_r45_abinet_train.tar)| ## 2. 环境配置 @@ -80,7 +80,7 @@ python3 tools/infer_rec.py -c configs/rec/rec_r45_abinet.yml -o Global.infer_img ### 4.1 Python推理 -首先将训练得到best模型,转换成inference model。这里以训练完成的模型为例([模型下载地址]() ),可以使用如下命令进行转换: +首先将训练得到best模型,转换成inference model。这里以训练完成的模型为例([模型下载地址](https://paddleocr.bj.bcebos.com/rec_r45_abinet_train.tar) ),可以使用如下命令进行转换: ```shell # 注意将pretrained_model的路径设置为本地路径。 diff --git a/doc/doc_en/algorithm_rec_abinet_en.md b/doc/doc_en/algorithm_rec_abinet_en.md index 3b0f6c09a9..767ca65f64 100644 --- a/doc/doc_en/algorithm_rec_abinet_en.md +++ b/doc/doc_en/algorithm_rec_abinet_en.md @@ -25,7 +25,7 @@ Using MJSynth and SynthText two text recognition datasets for training, and eval |Model|Backbone|config|Acc|Download link| | --- | --- | --- | --- | --- | -|ABINet|ResNet45|[rec_r45_abinet.yml](../../configs/rec/rec_r45_abinet.yml)|90.75%|[trained model]()/[pretrained model]()| +|ABINet|ResNet45|[rec_r45_abinet.yml](../../configs/rec/rec_r45_abinet.yml)|90.75%|[pretrained & trained model](https://paddleocr.bj.bcebos.com/rec_r45_abinet_train.tar)| ## 2. Environment @@ -68,7 +68,7 @@ python3 tools/infer_rec.py -c configs/rec/rec_r45_abinet.yml -o Global.infer_img ### 4.1 Python Inference -First, the model saved during the ABINet text recognition training process is converted into an inference model. ( [Model download link]()) ), you can use the following command to convert: +First, the model saved during the ABINet text recognition training process is converted into an inference model. ( [Model download link](https://paddleocr.bj.bcebos.com/rec_r45_abinet_train.tar)) ), you can use the following command to convert: ``` python3 tools/export_model.py -c configs/rec/rec_r45_abinet.yml -o Global.pretrained_model=./rec_r45_abinet_train/best_accuracy Global.save_inference_dir=./inference/rec_r45_abinet diff --git a/ppocr/data/imaug/__init__.py b/ppocr/data/imaug/__init__.py index 437e0152fa..63dfda91f8 100644 --- a/ppocr/data/imaug/__init__.py +++ b/ppocr/data/imaug/__init__.py @@ -25,7 +25,7 @@ from .make_pse_gt import MakePseGt from .rec_img_aug import RecAug, RecConAug, RecResizeImg, ClsResizeImg, \ SRNRecResizeImg, GrayRecResizeImg, SARRecResizeImg, PRENResizeImg, \ - ABINetRecResizeImg, SVTRRecResizeImg + ABINetRecResizeImg, SVTRRecResizeImg, ABINetRecAug from .ssl_img_aug import SSLRotateResize from .randaugment import RandAugment from .copy_paste import CopyPaste diff --git a/ppocr/data/imaug/abinet_aug.py b/ppocr/data/imaug/abinet_aug.py new file mode 100644 index 0000000000..eefdc75d5a --- /dev/null +++ b/ppocr/data/imaug/abinet_aug.py @@ -0,0 +1,407 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# 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. +""" +This code is refer from: +https://github.com/FangShancheng/ABINet/blob/main/transforms.py +""" +import math +import numbers +import random + +import cv2 +import numpy as np +from paddle.vision.transforms import Compose, ColorJitter + + +def sample_asym(magnitude, size=None): + return np.random.beta(1, 4, size) * magnitude + + +def sample_sym(magnitude, size=None): + return (np.random.beta(4, 4, size=size) - 0.5) * 2 * magnitude + + +def sample_uniform(low, high, size=None): + return np.random.uniform(low, high, size=size) + + +def get_interpolation(type='random'): + if type == 'random': + choice = [ + cv2.INTER_NEAREST, cv2.INTER_LINEAR, cv2.INTER_CUBIC, cv2.INTER_AREA + ] + interpolation = choice[random.randint(0, len(choice) - 1)] + elif type == 'nearest': + interpolation = cv2.INTER_NEAREST + elif type == 'linear': + interpolation = cv2.INTER_LINEAR + elif type == 'cubic': + interpolation = cv2.INTER_CUBIC + elif type == 'area': + interpolation = cv2.INTER_AREA + else: + raise TypeError( + 'Interpolation types only nearest, linear, cubic, area are supported!' + ) + return interpolation + + +class CVRandomRotation(object): + def __init__(self, degrees=15): + assert isinstance(degrees, + numbers.Number), "degree should be a single number." + assert degrees >= 0, "degree must be positive." + self.degrees = degrees + + @staticmethod + def get_params(degrees): + return sample_sym(degrees) + + def __call__(self, img): + angle = self.get_params(self.degrees) + src_h, src_w = img.shape[:2] + M = cv2.getRotationMatrix2D( + center=(src_w / 2, src_h / 2), angle=angle, scale=1.0) + abs_cos, abs_sin = abs(M[0, 0]), abs(M[0, 1]) + dst_w = int(src_h * abs_sin + src_w * abs_cos) + dst_h = int(src_h * abs_cos + src_w * abs_sin) + M[0, 2] += (dst_w - src_w) / 2 + M[1, 2] += (dst_h - src_h) / 2 + + flags = get_interpolation() + return cv2.warpAffine( + img, + M, (dst_w, dst_h), + flags=flags, + borderMode=cv2.BORDER_REPLICATE) + + +class CVRandomAffine(object): + def __init__(self, degrees, translate=None, scale=None, shear=None): + assert isinstance(degrees, + numbers.Number), "degree should be a single number." + assert degrees >= 0, "degree must be positive." + self.degrees = degrees + + if translate is not None: + assert isinstance(translate, (tuple, list)) and len(translate) == 2, \ + "translate should be a list or tuple and it must be of length 2." + for t in translate: + if not (0.0 <= t <= 1.0): + raise ValueError( + "translation values should be between 0 and 1") + self.translate = translate + + if scale is not None: + assert isinstance(scale, (tuple, list)) and len(scale) == 2, \ + "scale should be a list or tuple and it must be of length 2." + for s in scale: + if s <= 0: + raise ValueError("scale values should be positive") + self.scale = scale + + if shear is not None: + if isinstance(shear, numbers.Number): + if shear < 0: + raise ValueError( + "If shear is a single number, it must be positive.") + self.shear = [shear] + else: + assert isinstance(shear, (tuple, list)) and (len(shear) == 2), \ + "shear should be a list or tuple and it must be of length 2." + self.shear = shear + else: + self.shear = shear + + def _get_inverse_affine_matrix(self, center, angle, translate, scale, + shear): + # https://github.com/pytorch/vision/blob/v0.4.0/torchvision/transforms/functional.py#L717 + from numpy import sin, cos, tan + + if isinstance(shear, numbers.Number): + shear = [shear, 0] + + if not isinstance(shear, (tuple, list)) and len(shear) == 2: + raise ValueError( + "Shear should be a single value or a tuple/list containing " + + "two values. Got {}".format(shear)) + + rot = math.radians(angle) + sx, sy = [math.radians(s) for s in shear] + + cx, cy = center + tx, ty = translate + + # RSS without scaling + a = cos(rot - sy) / cos(sy) + b = -cos(rot - sy) * tan(sx) / cos(sy) - sin(rot) + c = sin(rot - sy) / cos(sy) + d = -sin(rot - sy) * tan(sx) / cos(sy) + cos(rot) + + # Inverted rotation matrix with scale and shear + # det([[a, b], [c, d]]) == 1, since det(rotation) = 1 and det(shear) = 1 + M = [d, -b, 0, -c, a, 0] + M = [x / scale for x in M] + + # Apply inverse of translation and of center translation: RSS^-1 * C^-1 * T^-1 + M[2] += M[0] * (-cx - tx) + M[1] * (-cy - ty) + M[5] += M[3] * (-cx - tx) + M[4] * (-cy - ty) + + # Apply center translation: C * RSS^-1 * C^-1 * T^-1 + M[2] += cx + M[5] += cy + return M + + @staticmethod + def get_params(degrees, translate, scale_ranges, shears, height): + angle = sample_sym(degrees) + if translate is not None: + max_dx = translate[0] * height + max_dy = translate[1] * height + translations = (np.round(sample_sym(max_dx)), + np.round(sample_sym(max_dy))) + else: + translations = (0, 0) + + if scale_ranges is not None: + scale = sample_uniform(scale_ranges[0], scale_ranges[1]) + else: + scale = 1.0 + + if shears is not None: + if len(shears) == 1: + shear = [sample_sym(shears[0]), 0.] + elif len(shears) == 2: + shear = [sample_sym(shears[0]), sample_sym(shears[1])] + else: + shear = 0.0 + + return angle, translations, scale, shear + + def __call__(self, img): + src_h, src_w = img.shape[:2] + angle, translate, scale, shear = self.get_params( + self.degrees, self.translate, self.scale, self.shear, src_h) + + M = self._get_inverse_affine_matrix((src_w / 2, src_h / 2), angle, + (0, 0), scale, shear) + M = np.array(M).reshape(2, 3) + + startpoints = [(0, 0), (src_w - 1, 0), (src_w - 1, src_h - 1), + (0, src_h - 1)] + project = lambda x, y, a, b, c: int(a * x + b * y + c) + endpoints = [(project(x, y, *M[0]), project(x, y, *M[1])) + for x, y in startpoints] + + rect = cv2.minAreaRect(np.array(endpoints)) + bbox = cv2.boxPoints(rect).astype(dtype=np.int) + max_x, max_y = bbox[:, 0].max(), bbox[:, 1].max() + min_x, min_y = bbox[:, 0].min(), bbox[:, 1].min() + + dst_w = int(max_x - min_x) + dst_h = int(max_y - min_y) + M[0, 2] += (dst_w - src_w) / 2 + M[1, 2] += (dst_h - src_h) / 2 + + # add translate + dst_w += int(abs(translate[0])) + dst_h += int(abs(translate[1])) + if translate[0] < 0: M[0, 2] += abs(translate[0]) + if translate[1] < 0: M[1, 2] += abs(translate[1]) + + flags = get_interpolation() + return cv2.warpAffine( + img, + M, (dst_w, dst_h), + flags=flags, + borderMode=cv2.BORDER_REPLICATE) + + +class CVRandomPerspective(object): + def __init__(self, distortion=0.5): + self.distortion = distortion + + def get_params(self, width, height, distortion): + offset_h = sample_asym( + distortion * height / 2, size=4).astype(dtype=np.int) + offset_w = sample_asym( + distortion * width / 2, size=4).astype(dtype=np.int) + topleft = (offset_w[0], offset_h[0]) + topright = (width - 1 - offset_w[1], offset_h[1]) + botright = (width - 1 - offset_w[2], height - 1 - offset_h[2]) + botleft = (offset_w[3], height - 1 - offset_h[3]) + + startpoints = [(0, 0), (width - 1, 0), (width - 1, height - 1), + (0, height - 1)] + endpoints = [topleft, topright, botright, botleft] + return np.array( + startpoints, dtype=np.float32), np.array( + endpoints, dtype=np.float32) + + def __call__(self, img): + height, width = img.shape[:2] + startpoints, endpoints = self.get_params(width, height, self.distortion) + M = cv2.getPerspectiveTransform(startpoints, endpoints) + + # TODO: more robust way to crop image + rect = cv2.minAreaRect(endpoints) + bbox = cv2.boxPoints(rect).astype(dtype=np.int) + max_x, max_y = bbox[:, 0].max(), bbox[:, 1].max() + min_x, min_y = bbox[:, 0].min(), bbox[:, 1].min() + min_x, min_y = max(min_x, 0), max(min_y, 0) + + flags = get_interpolation() + img = cv2.warpPerspective( + img, + M, (max_x, max_y), + flags=flags, + borderMode=cv2.BORDER_REPLICATE) + img = img[min_y:, min_x:] + return img + + +class CVRescale(object): + def __init__(self, factor=4, base_size=(128, 512)): + """ Define image scales using gaussian pyramid and rescale image to target scale. + + Args: + factor: the decayed factor from base size, factor=4 keeps target scale by default. + base_size: base size the build the bottom layer of pyramid + """ + if isinstance(factor, numbers.Number): + self.factor = round(sample_uniform(0, factor)) + elif isinstance(factor, (tuple, list)) and len(factor) == 2: + self.factor = round(sample_uniform(factor[0], factor[1])) + else: + raise Exception('factor must be number or list with length 2') + # assert factor is valid + self.base_h, self.base_w = base_size[:2] + + def __call__(self, img): + if self.factor == 0: return img + src_h, src_w = img.shape[:2] + cur_w, cur_h = self.base_w, self.base_h + scale_img = cv2.resize( + img, (cur_w, cur_h), interpolation=get_interpolation()) + for _ in range(self.factor): + scale_img = cv2.pyrDown(scale_img) + scale_img = cv2.resize( + scale_img, (src_w, src_h), interpolation=get_interpolation()) + return scale_img + + +class CVGaussianNoise(object): + def __init__(self, mean=0, var=20): + self.mean = mean + if isinstance(var, numbers.Number): + self.var = max(int(sample_asym(var)), 1) + elif isinstance(var, (tuple, list)) and len(var) == 2: + self.var = int(sample_uniform(var[0], var[1])) + else: + raise Exception('degree must be number or list with length 2') + + def __call__(self, img): + noise = np.random.normal(self.mean, self.var**0.5, img.shape) + img = np.clip(img + noise, 0, 255).astype(np.uint8) + return img + + +class CVMotionBlur(object): + def __init__(self, degrees=12, angle=90): + if isinstance(degrees, numbers.Number): + self.degree = max(int(sample_asym(degrees)), 1) + elif isinstance(degrees, (tuple, list)) and len(degrees) == 2: + self.degree = int(sample_uniform(degrees[0], degrees[1])) + else: + raise Exception('degree must be number or list with length 2') + self.angle = sample_uniform(-angle, angle) + + def __call__(self, img): + M = cv2.getRotationMatrix2D((self.degree // 2, self.degree // 2), + self.angle, 1) + motion_blur_kernel = np.zeros((self.degree, self.degree)) + motion_blur_kernel[self.degree // 2, :] = 1 + motion_blur_kernel = cv2.warpAffine(motion_blur_kernel, M, + (self.degree, self.degree)) + motion_blur_kernel = motion_blur_kernel / self.degree + img = cv2.filter2D(img, -1, motion_blur_kernel) + img = np.clip(img, 0, 255).astype(np.uint8) + return img + + +class CVGeometry(object): + def __init__(self, + degrees=15, + translate=(0.3, 0.3), + scale=(0.5, 2.), + shear=(45, 15), + distortion=0.5, + p=0.5): + self.p = p + type_p = random.random() + if type_p < 0.33: + self.transforms = CVRandomRotation(degrees=degrees) + elif type_p < 0.66: + self.transforms = CVRandomAffine( + degrees=degrees, translate=translate, scale=scale, shear=shear) + else: + self.transforms = CVRandomPerspective(distortion=distortion) + + def __call__(self, img): + if random.random() < self.p: + return self.transforms(img) + else: + return img + + +class CVDeterioration(object): + def __init__(self, var, degrees, factor, p=0.5): + self.p = p + transforms = [] + if var is not None: + transforms.append(CVGaussianNoise(var=var)) + if degrees is not None: + transforms.append(CVMotionBlur(degrees=degrees)) + if factor is not None: + transforms.append(CVRescale(factor=factor)) + + random.shuffle(transforms) + transforms = Compose(transforms) + self.transforms = transforms + + def __call__(self, img): + if random.random() < self.p: + + return self.transforms(img) + else: + return img + + +class CVColorJitter(object): + def __init__(self, + brightness=0.5, + contrast=0.5, + saturation=0.5, + hue=0.1, + p=0.5): + self.p = p + self.transforms = ColorJitter( + brightness=brightness, + contrast=contrast, + saturation=saturation, + hue=hue) + + def __call__(self, img): + if random.random() < self.p: return self.transforms(img) + else: return img diff --git a/ppocr/data/imaug/rec_img_aug.py b/ppocr/data/imaug/rec_img_aug.py index 874d9aa084..92f7382724 100644 --- a/ppocr/data/imaug/rec_img_aug.py +++ b/ppocr/data/imaug/rec_img_aug.py @@ -19,6 +19,8 @@ import random import copy from PIL import Image from .text_image_aug import tia_perspective, tia_stretch, tia_distort +from .abinet_aug import CVGeometry, CVDeterioration, CVColorJitter +from paddle.vision.transforms import Compose class RecAug(object): @@ -94,6 +96,31 @@ class BaseDataAugmentation(object): return data +class ABINetRecAug(object): + def __init__(self, **kwargs): + self.transforms = Compose([ + CVGeometry( + degrees=45, + translate=(0.0, 0.0), + scale=(0.5, 2.), + shear=(45, 15), + distortion=0.5, + p=0.5), CVDeterioration( + var=20, degrees=6, factor=4, p=0.25), CVColorJitter( + brightness=0.5, + contrast=0.5, + saturation=0.5, + hue=0.1, + p=0.25) + ]) + + def __call__(self, data): + img = data['image'] + img = self.transforms(img) + data['image'] = img + return data + + class RecConAug(object): def __init__(self, prob=0.5, diff --git a/test_tipc/configs/rec_r45_abinet/rec_r45_abinet.yml b/test_tipc/configs/rec_r45_abinet/rec_r45_abinet.yml index 306c49d683..5b5890e772 100644 --- a/test_tipc/configs/rec_r45_abinet/rec_r45_abinet.yml +++ b/test_tipc/configs/rec_r45_abinet/rec_r45_abinet.yml @@ -68,6 +68,7 @@ Train: - DecodeImage: # load image img_mode: RGB channel_first: False + - ABINetRecAug: - ABINetLabelEncode: # Class handling label ignore_index: *ignore_index - ABINetRecResizeImg: From 1c5e7f2578b8ac1d8b4999782ac0349a0f49781d Mon Sep 17 00:00:00 2001 From: Topdu <784990967@qq.com> Date: Fri, 24 Jun 2022 09:56:27 +0000 Subject: [PATCH 18/45] aug p infe --- applications/高精度中文识别模型.md | 11 ++++++----- configs/rec/rec_svtrnet_ch.yml | 8 ++++---- ppocr/data/imaug/rec_img_aug.py | 21 +++++++++++++-------- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/applications/高精度中文识别模型.md b/applications/高精度中文识别模型.md index e3d0693ece..3c31af42ee 100644 --- a/applications/高精度中文识别模型.md +++ b/applications/高精度中文识别模型.md @@ -2,7 +2,7 @@ ## 1. 简介 -PP-OCRv3是百度开源的超轻量级场景文本检测识别模型库,其中超轻量的场景中文识别模型SVTR_LCNet使用了SVTR算法结构。为了保证速度,SVTR_LCNet将SVTR模型的Local Blocks替换为LCNet,使用两层Global Blocks。在中文场景中,经过多次优化,SVTR_LCNet的最终精度为79.4%。具体的: +PP-OCRv3是百度开源的超轻量级场景文本检测识别模型库,其中超轻量的场景中文识别模型SVTR_LCNet使用了SVTR算法结构。为了保证速度,SVTR_LCNet将SVTR模型的Local Blocks替换为LCNet,使用两层Global Blocks。在中文场景中,PP-OCRv3识别主要使用如下优化策略: - GTC:Attention指导CTC训练策略; - TextConAug:挖掘文字上下文信息的数据增广策略; - TextRotNet:自监督的预训练模型; @@ -11,10 +11,11 @@ PP-OCRv3是百度开源的超轻量级场景文本检测识别模型库,其中 其中 *UIM:无标注数据挖掘方案* 使用了高精度的SVTR中文模型进行无标注文件的刷库,该模型在PP-OCRv3识别的数据集上训练,精度对比如下表。 -|中文识别算法|模型|精度| -| --- | --- | --- | -|PP-OCRv3|SVTR_LCNet|79.4%| -|SVTR|SVTR-Tiny|82.5%| +|中文识别算法|模型|UIM|精度| +| --- | --- | --- |--- | +|PP-OCRv3|SVTR_LCNet| w/o |78.4%| +|PP-OCRv3|SVTR_LCNet| w |79.4%| +|SVTR|SVTR-Tiny|-|82.5%| aistudio项目链接: [高精度中文场景文本识别模型SVTR](https://aistudio.baidu.com/aistudio/projectdetail/4263032) diff --git a/configs/rec/rec_svtrnet_ch.yml b/configs/rec/rec_svtrnet_ch.yml index b327edf499..c4ce4ccc16 100644 --- a/configs/rec/rec_svtrnet_ch.yml +++ b/configs/rec/rec_svtrnet_ch.yml @@ -92,9 +92,9 @@ Metric: Train: dataset: name: SimpleDataSet + data_dir: ./train_data label_file_list: - - /paddle/data/ocr_all/train_all_list.txt - data_dir: /paddle/data/ocr_all + - ./train_data/train_list.txt ext_op_transform_idx: 1 transforms: - DecodeImage: @@ -130,9 +130,9 @@ Train: Eval: dataset: name: SimpleDataSet - data_dir: /paddle/data/ocr_all + data_dir: ./train_data label_file_list: - - /paddle/data/ocr_all/val_list.txt + - ./train_data/val_list.txt transforms: - DecodeImage: img_mode: BGR diff --git a/ppocr/data/imaug/rec_img_aug.py b/ppocr/data/imaug/rec_img_aug.py index 92f7382724..1de8e6ee3c 100644 --- a/ppocr/data/imaug/rec_img_aug.py +++ b/ppocr/data/imaug/rec_img_aug.py @@ -97,7 +97,11 @@ class BaseDataAugmentation(object): class ABINetRecAug(object): - def __init__(self, **kwargs): + def __init__(self, + geometry_p=0.5, + deterioration_p=0.25, + colorjitter_p=0.25, + **kwargs): self.transforms = Compose([ CVGeometry( degrees=45, @@ -105,13 +109,14 @@ class ABINetRecAug(object): scale=(0.5, 2.), shear=(45, 15), distortion=0.5, - p=0.5), CVDeterioration( - var=20, degrees=6, factor=4, p=0.25), CVColorJitter( - brightness=0.5, - contrast=0.5, - saturation=0.5, - hue=0.1, - p=0.25) + p=geometry_p), CVDeterioration( + var=20, degrees=6, factor=4, p=deterioration_p), + CVColorJitter( + brightness=0.5, + contrast=0.5, + saturation=0.5, + hue=0.1, + p=colorjitter_p) ]) def __call__(self, data): From 6a8980e478ccacb7d3dfd9cd093c0c47132e01c9 Mon Sep 17 00:00:00 2001 From: an1018 <614803115@qq.com> Date: Sat, 25 Jun 2022 17:28:34 +0800 Subject: [PATCH 19/45] update --- applications/多模态表单识别.md | 157 ++++++++++++++++----------------- 1 file changed, 77 insertions(+), 80 deletions(-) diff --git a/applications/多模态表单识别.md b/applications/多模态表单识别.md index d47bbe7704..457d397030 100644 --- a/applications/多模态表单识别.md +++ b/applications/多模态表单识别.md @@ -1,4 +1,8 @@ -# 1 项目说明 +# 多模态表单识别 + +[TOC] + +## 1 项目说明 计算机视觉在金融领域的应用覆盖文字识别、图像识别、视频识别等,其中文字识别(OCR)是金融领域中的核心AI能力,其应用覆盖客户服务、风险防控、运营管理等各项业务,针对的对象包括通用卡证票据识别(银行卡、身份证、营业执照等)、通用文本表格识别(印刷体、多语言、手写体等)以及一些金融特色票据凭证。通过因此如果能够在结构化信息提取时同时利用文字、页面布局等信息,便可增强不同版式下的泛化性。 @@ -16,39 +20,37 @@
图1 多模态表单识别流程图
-注:欢迎再AIStudio领取免费算力体验线上实训,项目链接: [多模态表单识别](https://aistudio.baidu.com/aistudio/projectdetail/3884375)(配备Tesla V100、A100等高级算力资源) +注:欢迎再AIStudio领取免费算力体验线上实训,项目链接: [多模态表单识别](https://aistudio.baidu.com/aistudio/projectdetail/3884375?contributionType=1) - - -# 2 安装说明 +## 2 安装说明 下载PaddleOCR源码,上述AIStudio项目中已经帮大家打包好的PaddleOCR(已经修改好配置文件),无需下载解压即可,只需安装依赖环境~ ```python -! unzip -q PaddleOCR.zip +unzip -q PaddleOCR.zip ``` ```python # 如仍需安装or安装更新,可以执行以下步骤 -# ! git clone https://github.com/PaddlePaddle/PaddleOCR.git -b dygraph -# ! git clone https://gitee.com/PaddlePaddle/PaddleOCR +# git clone https://github.com/PaddlePaddle/PaddleOCR.git -b dygraph +# git clone https://gitee.com/PaddlePaddle/PaddleOCR ``` ```python # 安装依赖包 -! pip install -U pip -! pip install -r /home/aistudio/PaddleOCR/requirements.txt -! pip install paddleocr +pip install -U pip +pip install -r /home/aistudio/PaddleOCR/requirements.txt +pip install paddleocr -! pip install yacs gnureadline paddlenlp==2.2.1 -! pip install xlsxwriter +pip install yacs gnureadline paddlenlp==2.2.1 +pip install xlsxwriter ``` -# 3 数据准备 +## 3 数据准备 这里使用[XFUN数据集](https://github.com/doc-analysis/XFUND)做为实验数据集。 XFUN数据集是微软提出的一个用于KIE任务的多语言数据集,共包含七个数据集,每个数据集包含149张训练集和50张验证集 @@ -59,7 +61,7 @@
图2 数据集样例,左中文,右法语
-## 3.1 下载处理好的数据集 +### 3.1 下载处理好的数据集 处理好的XFUND中文数据集下载地址:[https://paddleocr.bj.bcebos.com/dataset/XFUND.tar](https://paddleocr.bj.bcebos.com/dataset/XFUND.tar) ,可以运行如下指令完成中文数据集下载和解压。 @@ -69,13 +71,13 @@ ```python -! wget https://paddleocr.bj.bcebos.com/dataset/XFUND.tar -! tar -xf XFUND.tar +wget https://paddleocr.bj.bcebos.com/dataset/XFUND.tar +tar -xf XFUND.tar # XFUN其他数据集使用下面的代码进行转换 # 代码链接:https://github.com/PaddlePaddle/PaddleOCR/blob/release%2F2.4/ppstructure/vqa/helper/trans_xfun_data.py # %cd PaddleOCR -# !python3 ppstructure/vqa/tools/trans_xfun_data.py --ori_gt_path=path/to/json_path --output_path=path/to/save_path +# python3 ppstructure/vqa/tools/trans_xfun_data.py --ori_gt_path=path/to/json_path --output_path=path/to/save_path # %cd ../ ``` @@ -119,7 +121,7 @@ } ``` -## 3.2 转换为PaddleOCR检测和识别格式 +### 3.2 转换为PaddleOCR检测和识别格式 使用XFUND训练PaddleOCR检测和识别模型,需要将数据集格式改为训练需求的格式。 @@ -147,7 +149,7 @@ train_data/rec/train/word_002.jpg 用科技让复杂的世界更简单 ```python -! unzip -q /home/aistudio/data/data140302/XFUND_ori.zip -d /home/aistudio/data/data140302/ +unzip -q /home/aistudio/data/data140302/XFUND_ori.zip -d /home/aistudio/data/data140302/ ``` 已经提供转换脚本,执行如下代码即可转换成功: @@ -155,21 +157,20 @@ train_data/rec/train/word_002.jpg 用科技让复杂的世界更简单 ```python %cd /home/aistudio/ -! python trans_xfund_data.py +python trans_xfund_data.py ``` -# 4 OCR +## 4 OCR 选用飞桨OCR开发套件[PaddleOCR](https://github.com/PaddlePaddle/PaddleOCR/blob/dygraph/README_ch.md)中的PP-OCRv2模型进行文本检测和识别。PP-OCRv2在PP-OCR的基础上,进一步在5个方面重点优化,检测模型采用CML协同互学习知识蒸馏策略和CopyPaste数据增广策略;识别模型采用LCNet轻量级骨干网络、UDML 改进知识蒸馏策略和[Enhanced CTC loss](https://github.com/PaddlePaddle/PaddleOCR/blob/dygraph/doc/doc_ch/enhanced_ctc_loss.md)损失函数改进,进一步在推理速度和预测效果上取得明显提升。更多细节请参考PP-OCRv2[技术报告](https://arxiv.org/abs/2109.03144)。 - -## 4.1 文本检测 +### 4.1 文本检测 我们使用2种方案进行训练、评估: - **PP-OCRv2中英文超轻量检测预训练模型** - **XFUND数据集+fine-tune** -### **4.1.1 方案1:预训练模型** +#### **4.1.1 方案1:预训练模型** **1)下载预训练模型** @@ -195,8 +196,8 @@ PaddleOCR已经提供了PP-OCR系列模型,部分模型展示如下表所示 ```python %cd /home/aistudio/PaddleOCR/pretrain/ -! wget https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_det_distill_train.tar -! tar -xf ch_PP-OCRv2_det_distill_train.tar && rm -rf ch_PP-OCRv2_det_distill_train.tar +wget https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_det_distill_train.tar +tar -xf ch_PP-OCRv2_det_distill_train.tar && rm -rf ch_PP-OCRv2_det_distill_train.tar % cd .. ``` @@ -226,7 +227,7 @@ Eval.dataset.label_file_list:指向验证集标注文件 ```python %cd /home/aistudio/PaddleOCR -! python tools/eval.py \ +python tools/eval.py \ -c configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_distill.yml \ -o Global.checkpoints="./pretrain_models/ch_PP-OCRv2_det_distill_train/best_accuracy" ``` @@ -237,9 +238,9 @@ Eval.dataset.label_file_list:指向验证集标注文件 | -------- | -------- | | PP-OCRv2中英文超轻量检测预训练模型 | 77.26% | -使用文本检测预训练模型在XFUND验证集上评估,达到77%左右,充分说明ppocr提供的预训练模型有一定的泛化能力。 +使用文本检测预训练模型在XFUND验证集上评估,达到77%左右,充分说明ppocr提供的预训练模型具有泛化能力。 -### **4.1.2 方案2:XFUND数据集+fine-tune** +#### **4.1.2 方案2:XFUND数据集+fine-tune** PaddleOCR提供的蒸馏预训练模型包含了多个模型的参数,我们提取Student模型的参数,在XFUND数据集上进行finetune,可以参考如下代码: @@ -281,7 +282,7 @@ Eval.dataset.transforms.DetResizeForTest:评估尺寸,添加如下参数 ```python -! CUDA_VISIBLE_DEVICES=0 python tools/train.py \ +CUDA_VISIBLE_DEVICES=0 python tools/train.py \ -c configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_student.yml ``` @@ -290,12 +291,12 @@ Eval.dataset.transforms.DetResizeForTest:评估尺寸,添加如下参数
图8 文本检测方案2-模型评估
-使用训练好的模型进行评估,更新模型路径`Global.checkpoints`,这里为大家提供训练好的模型`./pretrain/ch_db_mv3-student1600-finetune/best_accuracy`,[模型下载地址](https://paddleocr.bj.bcebos.com/fanliku/sheet_recognition/ch_db_mv3-student1600-finetune.zip) +使用训练好的模型进行评估,更新模型路径`Global.checkpoints`。 ```python %cd /home/aistudio/PaddleOCR/ -! python tools/eval.py \ +python tools/eval.py \ -c configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_student.yml \ -o Global.checkpoints="pretrain/ch_db_mv3-student1600-finetune/best_accuracy" ``` @@ -305,7 +306,7 @@ Eval.dataset.transforms.DetResizeForTest:评估尺寸,添加如下参数 ```python %cd /home/aistudio/PaddleOCR/ -! python tools/eval.py \ +python tools/eval.py \ -c configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_student.yml \ -o Global.checkpoints="pretrain/ch_db_mv3-student1600/best_accuracy" ``` @@ -331,7 +332,7 @@ Eval.dataset.transforms.DetResizeForTest:评估尺寸,添加如下参数 # 加载配置文件`ch_PP-OCRv2_det_student.yml`,从`pretrain/ch_db_mv3-student1600-finetune`目录下加载`best_accuracy`模型 # inference模型保存在`./output/det_db_inference`目录下 %cd /home/aistudio/PaddleOCR/ -! python tools/export_model.py \ +python tools/export_model.py \ -c configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_student.yml \ -o Global.pretrained_model="pretrain/ch_db_mv3-student1600-finetune/best_accuracy" \ Global.save_inference_dir="./output/det_db_inference/" @@ -374,12 +375,11 @@ use_gpu:是否使用GPU | 方案 | hmeans | 结果分析 | | -------- | -------- | -------- | -| PP-OCRv2中英文超轻量检测预训练模型 | 77.26% | ppocr提供的预训练模型有一定的泛化能力 | +| PP-OCRv2中英文超轻量检测预训练模型 | 77.26% | ppocr提供的预训练模型有泛化能力 | | XFUND数据集 | 79.27% | | | XFUND数据集+finetune | 85.24% | finetune会提升垂类场景效果 | - -## 4.2 文本识别 +### 4.2 文本识别 我们分别使用如下3种方案进行训练、评估: @@ -387,8 +387,7 @@ use_gpu:是否使用GPU - XFUND数据集+fine-tune - XFUND数据集+fine-tune+真实通用识别数据 - -### **4.2.1 方案1:预训练模型** +#### **4.2.1 方案1:预训练模型** **1)下载预训练模型** @@ -401,8 +400,8 @@ use_gpu:是否使用GPU ```python %cd /home/aistudio/PaddleOCR/pretrain/ -! wget https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_rec_train.tar -! tar -xf ch_PP-OCRv2_rec_train.tar && rm -rf ch_PP-OCRv2_rec_train.tar +wget https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_rec_train.tar +tar -xf ch_PP-OCRv2_rec_train.tar && rm -rf ch_PP-OCRv2_rec_train.tar % cd .. ``` @@ -424,7 +423,7 @@ Eval.dataset.label_file_list:指向验证集标注文件 ```python %cd /home/aistudio/PaddleOCR -! CUDA_VISIBLE_DEVICES=0 python tools/eval.py \ +CUDA_VISIBLE_DEVICES=0 python tools/eval.py \ -c configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec_distillation.yml \ -o Global.checkpoints=./pretrain/ch_PP-OCRv2_rec_train/best_accuracy ``` @@ -435,9 +434,9 @@ Eval.dataset.label_file_list:指向验证集标注文件 | -------- | -------- | | PP-OCRv2中英文超轻量识别预训练模型 | 67.48% | -使用文本预训练模型在XFUND验证集上评估,acc达到67%左右,充分说明ppocr提供的预训练模型有一定的泛化能力。 +使用文本预训练模型在XFUND验证集上评估,acc达到67%左右,充分说明ppocr提供的预训练模型具有泛化能力。 -### **4.2.2 方案2:XFUND数据集+finetune** +#### **4.2.2 方案2:XFUND数据集+finetune** 同检测模型,我们提取Student模型的参数,在XFUND数据集上进行finetune,可以参考如下代码: @@ -474,11 +473,9 @@ Eval.dataset.label_file_list:指向验证集标注文件 ``` 执行如下命令启动训练: - - ```python %cd /home/aistudio/PaddleOCR/ -! CUDA_VISIBLE_DEVICES=0 python tools/train.py \ +CUDA_VISIBLE_DEVICES=0 python tools/train.py \ -c configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec.yml ``` @@ -493,7 +490,7 @@ Eval.dataset.label_file_list:指向验证集标注文件 ```python %cd /home/aistudio/PaddleOCR/ -! CUDA_VISIBLE_DEVICES=0 python tools/eval.py \ +CUDA_VISIBLE_DEVICES=0 python tools/eval.py \ -c configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec.yml \ -o Global.checkpoints=./pretrain/rec_mobile_pp-OCRv2-student-finetune/best_accuracy ``` @@ -506,7 +503,7 @@ Eval.dataset.label_file_list:指向验证集标注文件 使用XFUND数据集+finetune训练,在验证集上评估达到72%左右,说明 finetune会提升垂类场景效果。 -### **4.2.3 方案3:XFUND数据集+finetune+真实通用识别数据** +#### **4.2.3 方案3:XFUND数据集+finetune+真实通用识别数据** 接着我们在上述`XFUND数据集+finetune`实验的基础上,添加真实通用识别数据,进一步提升识别效果。首先准备真实通用识别数据,并上传到AIStudio: @@ -528,7 +525,7 @@ Train.dataset.ratio_list:动态采样 ```python %cd /home/aistudio/PaddleOCR/ -! CUDA_VISIBLE_DEVICES=0 python tools/train.py \ +CUDA_VISIBLE_DEVICES=0 python tools/train.py \ -c configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec.yml ``` @@ -538,11 +535,11 @@ Train.dataset.ratio_list:动态采样
图16 文本识别方案3-模型评估
-使用训练好的模型进行评估,更新模型路径`Global.checkpoints`,这里为大家提供训练好的模型`./pretrain/rec_mobile_pp-OCRv2-student-readldata/best_accuracy`,[模型下载地址](https://paddleocr.bj.bcebos.com/fanliku/sheet_recognition/rec_mobile_pp-OCRv2-student-realdata.zip) +使用训练好的模型进行评估,更新模型路径`Global.checkpoints`。 ```python -! CUDA_VISIBLE_DEVICES=0 python tools/eval.py \ +CUDA_VISIBLE_DEVICES=0 python tools/eval.py \ -c configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec.yml \ -o Global.checkpoints=./pretrain/rec_mobile_pp-OCRv2-student-realdata/best_accuracy ``` @@ -580,7 +577,7 @@ Train.dataset.ratio_list:动态采样 ```python -! python tools/infer/predict_system.py \ +python tools/infer/predict_system.py \ --image_dir="./doc/vqa/input/zh_val_21.jpg" \ --det_model_dir="./output/det_db_inference/" \ --rec_model_dir="./output/rec_crnn_inference/" \ @@ -592,11 +589,11 @@ Train.dataset.ratio_list:动态采样 | 方案 | acc | 结果分析 | | -------- | -------- | -------- | -| PP-OCRv2中英文超轻量识别预训练模型 | 67.48% | ppocr提供的预训练模型有一定的泛化能力 | +| PP-OCRv2中英文超轻量识别预训练模型 | 67.48% | ppocr提供的预训练模型具有泛化能力 | | XFUND数据集+fine-tune |72.33% | finetune会提升垂类场景效果 | | XFUND数据集+fine-tune+真实通用识别数据 | 85.29% | 真实通用识别数据对于性能提升很有帮助 | -# 5 文档视觉问答(DOC-VQA) +## 5 文档视觉问答(DOC-VQA) VQA指视觉问答,主要针对图像内容进行提问和回答,DOC-VQA是VQA任务中的一种,DOC-VQA主要针对文本图像的文字内容提出问题。 @@ -608,14 +605,13 @@ PaddleOCR中DOC-VQA系列算法基于PaddleNLP自然语言处理算法库实现L ```python %cd pretrain #下载SER模型 -! wget https://paddleocr.bj.bcebos.com/pplayout/ser_LayoutXLM_xfun_zh.tar && tar -xvf ser_LayoutXLM_xfun_zh.tar +wget https://paddleocr.bj.bcebos.com/pplayout/ser_LayoutXLM_xfun_zh.tar && tar -xvf ser_LayoutXLM_xfun_zh.tar #下载RE模型 -! wget https://paddleocr.bj.bcebos.com/pplayout/re_LayoutXLM_xfun_zh.tar && tar -xvf re_LayoutXLM_xfun_zh.tar +wget https://paddleocr.bj.bcebos.com/pplayout/re_LayoutXLM_xfun_zh.tar && tar -xvf re_LayoutXLM_xfun_zh.tar %cd ../ ``` - -## 5.1 SER +### 5.1 SER SER: 语义实体识别 (Semantic Entity Recognition), 可以完成对图像中的文本识别与分类。 @@ -647,7 +643,7 @@ SER: 语义实体识别 (Semantic Entity Recognition), 可以完成对图像 ```python %cd /home/aistudio/PaddleOCR/ -! CUDA_VISIBLE_DEVICES=0 python tools/train.py -c configs/vqa/ser/layoutxlm.yml +CUDA_VISIBLE_DEVICES=0 python tools/train.py -c configs/vqa/ser/layoutxlm.yml ``` 最终会打印出`precision`, `recall`, `hmean`等指标。 在`./output/ser_layoutxlm/`文件夹中会保存训练日志,最优的模型和最新epoch的模型。 @@ -664,7 +660,7 @@ SER: 语义实体识别 (Semantic Entity Recognition), 可以完成对图像 ```python -! CUDA_VISIBLE_DEVICES=0 python tools/eval.py \ +CUDA_VISIBLE_DEVICES=0 python tools/eval.py \ -c configs/vqa/ser/layoutxlm.yml \ -o Architecture.Backbone.checkpoints=pretrain/ser_LayoutXLM_xfun_zh/ ``` @@ -684,7 +680,7 @@ SER: 语义实体识别 (Semantic Entity Recognition), 可以完成对图像 ```python -! CUDA_VISIBLE_DEVICES=0 python tools/infer_vqa_token_ser.py \ +CUDA_VISIBLE_DEVICES=0 python tools/infer_vqa_token_ser.py \ -c configs/vqa/ser/layoutxlm.yml \ -o Architecture.Backbone.checkpoints=pretrain/ser_LayoutXLM_xfun_zh/ \ Global.infer_img=doc/vqa/input/zh_val_42.jpg @@ -704,7 +700,7 @@ plt.figure(figsize=(48,24)) plt.imshow(img) ``` -## 5.2 RE +### 5.2 RE 基于 RE 任务,可以完成对图象中的文本内容的关系提取,如判断问题对(pair)。 @@ -729,7 +725,7 @@ plt.imshow(img) ```python -! CUDA_VISIBLE_DEVICES=0 python3 tools/train.py -c configs/vqa/re/layoutxlm.yml +CUDA_VISIBLE_DEVICES=0 python3 tools/train.py -c configs/vqa/re/layoutxlm.yml ``` 最终会打印出`precision`, `recall`, `hmean`等指标。 在`./output/re_layoutxlm/`文件夹中会保存训练日志,最优的模型和最新epoch的模型 @@ -744,7 +740,7 @@ plt.imshow(img) ```python -! CUDA_VISIBLE_DEVICES=0 python3 tools/eval.py \ +CUDA_VISIBLE_DEVICES=0 python3 tools/eval.py \ -c configs/vqa/re/layoutxlm.yml \ -o Architecture.Backbone.checkpoints=pretrain/re_LayoutXLM_xfun_zh/ ``` @@ -760,20 +756,14 @@ plt.imshow(img)
图26 RE-模型预测
- 使用OCR引擎 + SER + RE串联预测 - -使用如下命令即可完成OCR引擎 + SER + RE的串联预测, 以预训练SER和RE模型为例: - - +使用如下命令即可完成OCR引擎 + SER + RE的串联预测, 以预训练SER和RE模型为例, 最终会在config.Global.save_res_path字段所配置的目录下保存预测结果可视化图像以及预测结果文本文件,预测结果文本文件名为infer_results.txt。 - - ```python -%cd /home/aistudio/PaddleOCR -! CUDA_VISIBLE_DEVICES=0 python3 tools/infer_vqa_token_ser_re.py \ +cd /home/aistudio/PaddleOCR +CUDA_VISIBLE_DEVICES=0 python3 tools/infer_vqa_token_ser_re.py \ -c configs/vqa/re/layoutxlm.yml \ -o Architecture.Backbone.checkpoints=pretrain/re_LayoutXLM_xfun_zh/ \ Global.infer_img=test_imgs/ \ @@ -787,10 +777,9 @@ plt.imshow(img) test_imgs/t131.jpg {"政治面税": "群众", "性别": "男", "籍贯": "河北省邯郸市", "婚姻状况": "亏末婚口已婚口已娇", "通讯地址": "邯郸市阳光苑7号楼003", "民族": "汉族", "毕业院校": "河南工业大学", "户口性质": "口农村城镇", "户口地址": "河北省邯郸市", "联系电话": "13288888888", "健康状况": "健康", "姓名": "小六", "好高cm": "180", "出生年月": "1996年8月9日", "文化程度": "本科", "身份证号码": "458933777777777777"} ```` - +展示预测结果 ```python -# 展示预测结果 import cv2 from matplotlib import pyplot as plt %matplotlib inline @@ -800,7 +789,7 @@ plt.figure(figsize=(48,24)) plt.imshow(img) ``` -# 6 导出Excel +## 6 导出Excel
图27 导出Excel
@@ -859,7 +848,15 @@ with open('output/re/infer_results.txt', 'r', encoding='utf-8') as fin: workbook.close() ``` -# 更多资源 +## 获得模型 + +如需获取已训练模型,请扫码填写问卷,加入PaddleOCR官方交流群获取全部OCR垂类模型下载链接、《动手学OCR》电子书等全套OCR学习资料🎁 +
+ +
+将下载或训练完成的模型放置在对应目录下即可完成模型推理 + +## 更多资源 - 更多深度学习知识、产业案例、面试宝典等,请参考:[awesome-DeepLearning](https://github.com/paddlepaddle/awesome-DeepLearning) @@ -869,7 +866,7 @@ workbook.close() - 飞桨框架相关资料,请参考:[飞桨深度学习平台](https://www.paddlepaddle.org.cn/?fr=paddleEdu_aistudio) -# 参考链接 +## 参考链接 - LayoutXLM: Multimodal Pre-training for Multilingual Visually-rich Document Understanding, https://arxiv.org/pdf/2104.08836.pdf From d554037ece782db092cb46394ad4aface7834c0c Mon Sep 17 00:00:00 2001 From: an1018 <614803115@qq.com> Date: Mon, 27 Jun 2022 10:04:39 +0800 Subject: [PATCH 20/45] update --- applications/多模态表单识别.md | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/applications/多模态表单识别.md b/applications/多模态表单识别.md index 457d397030..27f981101a 100644 --- a/applications/多模态表单识别.md +++ b/applications/多模态表单识别.md @@ -1,6 +1,31 @@ # 多模态表单识别 - -[TOC] +- [多模态表单识别](#多模态表单识别) + - [1 项目说明](#1-项目说明) + - [2 安装说明](#2-安装说明) + - [3 数据准备](#3-数据准备) + - [3.1 下载处理好的数据集](#31-下载处理好的数据集) + - [3.2 转换为PaddleOCR检测和识别格式](#32-转换为paddleocr检测和识别格式) + - [4 OCR](#4-ocr) + - [4.1 文本检测](#41-文本检测) + - [**4.1.1 方案1:预训练模型**](#411-方案1预训练模型) + - [**4.1.2 方案2:XFUND数据集+fine-tune**](#412-方案2xfund数据集fine-tune) + - [4.2 文本识别](#42-文本识别) + - [**4.2.1 方案1:预训练模型**](#421-方案1预训练模型) + - [**4.2.2 方案2:XFUND数据集+finetune**](#422-方案2xfund数据集finetune) + - [**4.2.3 方案3:XFUND数据集+finetune+真实通用识别数据**](#423-方案3xfund数据集finetune真实通用识别数据) + - [5 文档视觉问答(DOC-VQA)](#5-文档视觉问答doc-vqa) + - [5.1 SER](#51-ser) + - [5.1.1 模型训练](#511-模型训练) + - [5.1.2 模型评估](#512-模型评估) + - [5.1.3 模型预测](#513-模型预测) + - [5.2 RE](#52-re) + - [5.2.1 模型训练](#521-模型训练) + - [5.2.2 模型评估](#522-模型评估) + - [5.2.3 模型预测](#523-模型预测) + - [6 导出Excel](#6-导出excel) + - [获得模型](#获得模型) + - [更多资源](#更多资源) + - [参考链接](#参考链接) ## 1 项目说明 From 1eba8ae3f89ac850fbec35f5d3b1c6e402dfd02a Mon Sep 17 00:00:00 2001 From: xiaoting <31891223+tink2123@users.noreply.github.com> Date: Mon, 27 Jun 2022 14:51:33 +0800 Subject: [PATCH 21/45] =?UTF-8?q?Update=20=E5=85=89=E5=8A=9F=E7=8E=87?= =?UTF-8?q?=E8=AE=A1=E6=95=B0=E7=A0=81=E7=AE=A1=E5=AD=97=E7=AC=A6=E8=AF=86?= =?UTF-8?q?=E5=88=AB.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- applications/光功率计数码管字符识别.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/applications/光功率计数码管字符识别.md b/applications/光功率计数码管字符识别.md index 33c515a5e9..d6c11259bf 100644 --- a/applications/光功率计数码管字符识别.md +++ b/applications/光功率计数码管字符识别.md @@ -21,7 +21,12 @@ 第二步,使用文本识别模型获得准确的数字和单位信息。 -本项目主要介绍如何完成第二步文本识别部分。 +本项目主要介绍如何完成第二步文本识别部分,包括: + +* 真实评估集的建立 +* 训练数据的合成 +* 基于 PP-OCRv3 和 SVTR_Tiny 两个模型进行训练 +* 评估并推理 ## 2. PaddleOCR 快速使用 @@ -400,7 +405,11 @@ python tools/train.py -c rec_svtr_tiny_none_ctc_ch_train/rec_svtr_tiny_6local_6g ### 3.4 验证效果 -**本例中的评估集和模型路径,可通过填写[问卷](todo:add link)获得** +如需获取已训练模型,请扫码填写问卷,加入PaddleOCR官方交流群获取全部OCR垂类模型下载链接、《动手学OCR》电子书等全套OCR学习资料🎁 +
+ +
+将下载或训练完成的模型放置在对应目录下即可完成模型推理 * 指标评估 From 6e6d77432fb2f77af6b30e638f2d6eeef974ee12 Mon Sep 17 00:00:00 2001 From: Topdu <784990967@qq.com> Date: Mon, 27 Jun 2022 09:01:38 +0000 Subject: [PATCH 22/45] fix ci export bug --- configs/rec/rec_r45_abinet.yml | 2 -- configs/rec/rec_svtrnet.yml | 2 -- configs/rec/rec_svtrnet_ch.yml | 4 ---- ppocr/data/imaug/rec_img_aug.py | 17 ++--------------- test_tipc/configs/rec_svtrnet/rec_svtrnet.yml | 6 ++---- 5 files changed, 4 insertions(+), 27 deletions(-) diff --git a/configs/rec/rec_r45_abinet.yml b/configs/rec/rec_r45_abinet.yml index 3cf4cddb04..1e03db8b1b 100644 --- a/configs/rec/rec_r45_abinet.yml +++ b/configs/rec/rec_r45_abinet.yml @@ -72,7 +72,6 @@ Train: ignore_index: *ignore_index - ABINetRecResizeImg: image_shape: [3, 32, 128] - padding: False - KeepKeys: keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order loader: @@ -93,7 +92,6 @@ Eval: ignore_index: *ignore_index - ABINetRecResizeImg: image_shape: [3, 32, 128] - padding: False - KeepKeys: keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order loader: diff --git a/configs/rec/rec_svtrnet.yml b/configs/rec/rec_svtrnet.yml index 5896b10515..c1f5cc380a 100644 --- a/configs/rec/rec_svtrnet.yml +++ b/configs/rec/rec_svtrnet.yml @@ -84,7 +84,6 @@ Train: channel_first: False - CTCLabelEncode: # Class handling label - SVTRRecResizeImg: - character_dict_path: image_shape: [3, 64, 256] padding: False - KeepKeys: @@ -105,7 +104,6 @@ Eval: channel_first: False - CTCLabelEncode: # Class handling label - SVTRRecResizeImg: - character_dict_path: image_shape: [3, 64, 256] padding: False - KeepKeys: diff --git a/configs/rec/rec_svtrnet_ch.yml b/configs/rec/rec_svtrnet_ch.yml index c4ce4ccc16..0d3f63d125 100644 --- a/configs/rec/rec_svtrnet_ch.yml +++ b/configs/rec/rec_svtrnet_ch.yml @@ -110,8 +110,6 @@ Train: - RecAug: null - CTCLabelEncode: null - SVTRRecResizeImg: - character_dict_path: null - infer_mode: False image_shape: - 3 - 32 @@ -139,8 +137,6 @@ Eval: channel_first: false - CTCLabelEncode: null - SVTRRecResizeImg: - character_dict_path: null - infer_mode: False image_shape: - 3 - 32 diff --git a/ppocr/data/imaug/rec_img_aug.py b/ppocr/data/imaug/rec_img_aug.py index 1de8e6ee3c..26773d0a51 100644 --- a/ppocr/data/imaug/rec_img_aug.py +++ b/ppocr/data/imaug/rec_img_aug.py @@ -312,14 +312,8 @@ class GrayRecResizeImg(object): class ABINetRecResizeImg(object): - def __init__(self, - image_shape, - infer_mode=False, - character_dict_path=None, - **kwargs): + def __init__(self, image_shape, **kwargs): self.image_shape = image_shape - self.infer_mode = infer_mode - self.character_dict_path = character_dict_path def __call__(self, data): img = data['image'] @@ -330,15 +324,8 @@ class ABINetRecResizeImg(object): class SVTRRecResizeImg(object): - def __init__(self, - image_shape, - infer_mode=False, - character_dict_path='./ppocr/utils/ppocr_keys_v1.txt', - padding=True, - **kwargs): + def __init__(self, image_shape, padding=True, **kwargs): self.image_shape = image_shape - self.infer_mode = infer_mode - self.character_dict_path = character_dict_path self.padding = padding def __call__(self, data): diff --git a/test_tipc/configs/rec_svtrnet/rec_svtrnet.yml b/test_tipc/configs/rec_svtrnet/rec_svtrnet.yml index bffd8ac09a..140b17e0e7 100644 --- a/test_tipc/configs/rec_svtrnet/rec_svtrnet.yml +++ b/test_tipc/configs/rec_svtrnet/rec_svtrnet.yml @@ -84,8 +84,7 @@ Train: img_mode: BGR channel_first: False - CTCLabelEncode: # Class handling label - - RecResizeImg: - character_dict_path: + - SVTRRecResizeImg: image_shape: [3, 64, 256] padding: False - KeepKeys: @@ -106,8 +105,7 @@ Eval: img_mode: BGR channel_first: False - CTCLabelEncode: # Class handling label - - RecResizeImg: - character_dict_path: + - SVTRRecResizeImg: image_shape: [3, 64, 256] padding: False - KeepKeys: From ec3f8ff7e83d2462ef99b538ff0b5c7311ae992d Mon Sep 17 00:00:00 2001 From: LDOUBLEV Date: Mon, 27 Jun 2022 19:29:33 +0800 Subject: [PATCH 23/45] support PP-OCRv3 for lite demo (arm8) --- deploy/lite/config.txt | 3 ++- deploy/lite/crnn_process.cc | 18 ++++++++-------- deploy/lite/crnn_process.h | 2 +- deploy/lite/ocr_db_crnn.cc | 13 ++++++++---- deploy/lite/readme.md | 36 ++++++++++++++++++-------------- deploy/lite/readme_ch.md | 41 ++++++++++++++++++++----------------- 6 files changed, 65 insertions(+), 48 deletions(-) diff --git a/deploy/lite/config.txt b/deploy/lite/config.txt index 4c68105d39..dda0d2b032 100644 --- a/deploy/lite/config.txt +++ b/deploy/lite/config.txt @@ -4,4 +4,5 @@ det_db_box_thresh 0.5 det_db_unclip_ratio 1.6 det_db_use_dilate 0 det_use_polygon_score 1 -use_direction_classify 1 \ No newline at end of file +use_direction_classify 1 +rec_image_height 32 \ No newline at end of file diff --git a/deploy/lite/crnn_process.cc b/deploy/lite/crnn_process.cc index 7528f36fe6..6d5fc1504e 100644 --- a/deploy/lite/crnn_process.cc +++ b/deploy/lite/crnn_process.cc @@ -19,25 +19,27 @@ const std::vector rec_image_shape{3, 32, 320}; -cv::Mat CrnnResizeImg(cv::Mat img, float wh_ratio) { +cv::Mat CrnnResizeImg(cv::Mat img, float wh_ratio, int rec_image_height) { int imgC, imgH, imgW; imgC = rec_image_shape[0]; + imgH = rec_image_height; imgW = rec_image_shape[2]; - imgH = rec_image_shape[1]; - imgW = int(32 * wh_ratio); + imgW = int(imgH * wh_ratio); - float ratio = static_cast(img.cols) / static_cast(img.rows); + float ratio = float(img.cols) / float(img.rows); int resize_w, resize_h; + if (ceilf(imgH * ratio) > imgW) resize_w = imgW; else - resize_w = static_cast(ceilf(imgH * ratio)); - cv::Mat resize_img; + resize_w = int(ceilf(imgH * ratio)); + cv::resize(img, resize_img, cv::Size(resize_w, imgH), 0.f, 0.f, cv::INTER_LINEAR); - - return resize_img; + cv::copyMakeBorder(resize_img, resize_img, 0, 0, 0, + int(imgW - resize_img.cols), cv::BORDER_CONSTANT, + {127, 127, 127}); } std::vector ReadDict(std::string path) { diff --git a/deploy/lite/crnn_process.h b/deploy/lite/crnn_process.h index 29e6790697..ed7a316706 100644 --- a/deploy/lite/crnn_process.h +++ b/deploy/lite/crnn_process.h @@ -26,7 +26,7 @@ #include "opencv2/imgcodecs.hpp" #include "opencv2/imgproc.hpp" -cv::Mat CrnnResizeImg(cv::Mat img, float wh_ratio); +cv::Mat CrnnResizeImg(cv::Mat img, float wh_ratio, int rec_image_height); std::vector ReadDict(std::string path); diff --git a/deploy/lite/ocr_db_crnn.cc b/deploy/lite/ocr_db_crnn.cc index 1ffbbacb74..cb2bf7791a 100644 --- a/deploy/lite/ocr_db_crnn.cc +++ b/deploy/lite/ocr_db_crnn.cc @@ -162,7 +162,8 @@ void RunRecModel(std::vector>> boxes, cv::Mat img, std::vector charactor_dict, std::shared_ptr predictor_cls, int use_direction_classify, - std::vector *times) { + std::vector *times, + int rec_image_height) { std::vector mean = {0.5f, 0.5f, 0.5f}; std::vector scale = {1 / 0.5f, 1 / 0.5f, 1 / 0.5f}; @@ -183,7 +184,7 @@ void RunRecModel(std::vector>> boxes, cv::Mat img, float wh_ratio = static_cast(crop_img.cols) / static_cast(crop_img.rows); - resize_img = CrnnResizeImg(crop_img, wh_ratio); + resize_img = CrnnResizeImg(crop_img, wh_ratio, rec_image_height); resize_img.convertTo(resize_img, CV_32FC3, 1 / 255.f); const float *dimg = reinterpret_cast(resize_img.data); @@ -444,7 +445,7 @@ void system(char **argv){ //// load config from txt file auto Config = LoadConfigTxt(det_config_path); int use_direction_classify = int(Config["use_direction_classify"]); - + int rec_image_height = int(Config["rec_image_height"]); auto charactor_dict = ReadDict(dict_path); charactor_dict.insert(charactor_dict.begin(), "#"); // blank char for ctc charactor_dict.push_back(" "); @@ -590,12 +591,16 @@ void rec(int argc, char **argv) { std::string batchsize = argv[6]; std::string img_dir = argv[7]; std::string dict_path = argv[8]; + std::string config_path = argv[9]; if (strcmp(argv[4], "FP32") != 0 && strcmp(argv[4], "INT8") != 0) { std::cerr << "Only support FP32 or INT8." << std::endl; exit(1); } + auto Config = LoadConfigTxt(config_path); + int rec_image_height = int(Config["rec_image_height"]); + std::vector cv_all_img_names; cv::glob(img_dir, cv_all_img_names); @@ -630,7 +635,7 @@ void rec(int argc, char **argv) { std::vector rec_text_score; std::vector times; RunRecModel(boxes, srcimg, rec_predictor, rec_text, rec_text_score, - charactor_dict, cls_predictor, 0, ×); + charactor_dict, cls_predictor, 0, ×, rec_image_height); //// print recognized text for (int i = 0; i < rec_text.size(); i++) { diff --git a/deploy/lite/readme.md b/deploy/lite/readme.md index 9926e2dd8c..883aff9890 100644 --- a/deploy/lite/readme.md +++ b/deploy/lite/readme.md @@ -34,7 +34,7 @@ For the compilation process of different development environments, please refer ### 1.2 Prepare Paddle-Lite library There are two ways to obtain the Paddle-Lite library: -- 1. Download directly, the download link of the Paddle-Lite library is as follows: +- 1. [Recommended] Download directly, the download link of the Paddle-Lite library is as follows: | Platform | Paddle-Lite library download link | |---|---| @@ -43,7 +43,9 @@ There are two ways to obtain the Paddle-Lite library: Note: 1. The above Paddle-Lite library is compiled from the Paddle-Lite 2.10 branch. For more information about Paddle-Lite 2.10, please refer to [link](https://github.com/PaddlePaddle/Paddle-Lite/releases/tag/v2.10). -- 2. [Recommended] Compile Paddle-Lite to get the prediction library. The compilation method of Paddle-Lite is as follows: + **Note: It is recommended to use paddlelite>=2.10 version of the prediction library, other prediction library versions [download link](https://github.com/PaddlePaddle/Paddle-Lite/tags)** + +- 2. Compile Paddle-Lite to get the prediction library. The compilation method of Paddle-Lite is as follows: ``` git clone https://github.com/PaddlePaddle/Paddle-Lite.git cd Paddle-Lite @@ -104,20 +106,16 @@ If you directly use the model in the above table for deployment, you can skip th If the model to be deployed is not in the above table, you need to follow the steps below to obtain the optimized model. -The `opt` tool can be obtained by compiling Paddle Lite. +- Step 1: Refer to [document](https://www.paddlepaddle.org.cn/lite/v2.10/user_guides/opt/opt_python.html) to install paddlelite, which is used to convert paddle inference model to paddlelite required for running nb model ``` -git clone https://github.com/PaddlePaddle/Paddle-Lite.git -cd Paddle-Lite -git checkout release/v2.10 -./lite/tools/build.sh build_optimize_tool +pip install paddlelite==2.10 # The paddlelite version should be the same as the prediction library version +``` +After installation, the following commands can view the help information +``` +paddle_lite_opt ``` -After the compilation is complete, the opt file is located under build.opt/lite/api/, You can view the operating options and usage of opt in the following ways: - -``` -cd build.opt/lite/api/ -./opt -``` +Introduction to paddle_lite_opt parameters: |Options|Description| |---|---| @@ -131,6 +129,8 @@ cd build.opt/lite/api/ `--model_dir` is suitable for the non-combined mode of the model to be optimized, and the inference model of PaddleOCR is the combined mode, that is, the model structure and model parameters are stored in a single file. +- Step 2: Use paddle_lite_opt to convert the inference model to the mobile model format. + The following takes the ultra-lightweight Chinese model of PaddleOCR as an example to introduce the use of the compiled opt file to complete the conversion of the inference model to the Paddle-Lite optimized model ``` @@ -240,6 +240,7 @@ det_db_thresh 0.3 # Used to filter the binarized image of DB prediction, det_db_box_thresh 0.5 # DDB post-processing filter box threshold, if there is a missing box detected, it can be reduced as appropriate det_db_unclip_ratio 1.6 # Indicates the compactness of the text box, the smaller the value, the closer the text box to the text use_direction_classify 0 # Whether to use the direction classifier, 0 means not to use, 1 means to use +rec_image_height 32 # The height of the input image of the recognition model, the PP-OCRv3 model needs to be set to 48, and the PP-OCRv2 model needs to be set to 32 ``` 5. Run Model on phone @@ -258,8 +259,9 @@ After the above steps are completed, you can use adb to push the file to the pho cd /data/local/tmp/debug export LD_LIBRARY_PATH=${PWD}:$LD_LIBRARY_PATH # The use of ocr_db_crnn is: - # ./ocr_db_crnn Detection model file Orientation classifier model file Recognition model file Test image path Dictionary file path - ./ocr_db_crnn ch_PP-OCRv2_det_slim_opt.nb ch_PP-OCRv2_rec_slim_opt.nb ch_ppocr_mobile_v2.0_cls_opt.nb ./11.jpg ppocr_keys_v1.txt + # ./ocr_db_crnn Detection model file Orientation classifier model file Recognition model file Hardware Precision Threads Batchsize Test image path Dictionary file path + ./ocr_db_crnn ch_PP-OCRv2_det_slim_opt.nb ch_PP-OCRv2_rec_slim_opt.nb ch_ppocr_mobile_v2.0_cls_slim_opt.nb arm8 INT8 10 1 ./11.jpg config.txt ppocr_keys_v1.txt True +# precision can be INT8 for quantitative model or FP32 for normal model. ``` If you modify the code, you need to recompile and push to the phone. @@ -283,3 +285,7 @@ A2: Replace the .jpg test image under ./debug with the image you want to test, a Q3: How to package it into the mobile APP? A3: This demo aims to provide the core algorithm part that can run OCR on mobile phones. Further, PaddleOCR/deploy/android_demo is an example of encapsulating this demo into a mobile app for reference. + +Q4: When running the demo, an error is reported `Error: This model is not supported, because kernel for 'io_copy' is not supported by Paddle-Lite.` + +A4: The problem is that the installed paddlelite version does not match the downloaded prediction library version. Make sure that the paddleliteopt tool matches your prediction library version, and try to switch to the nb model again. diff --git a/deploy/lite/readme_ch.md b/deploy/lite/readme_ch.md index 99a543d0d6..e238b4ec53 100644 --- a/deploy/lite/readme_ch.md +++ b/deploy/lite/readme_ch.md @@ -8,7 +8,7 @@ - [2.1 模型优化](#21-模型优化) - [2.2 与手机联调](#22-与手机联调) - [FAQ](#faq) - + 本教程将介绍基于[Paddle Lite](https://github.com/PaddlePaddle/Paddle-Lite) 在移动端部署PaddleOCR超轻量中文检测、识别模型的详细步骤。 @@ -32,7 +32,7 @@ Paddle Lite是飞桨轻量化推理引擎,为手机、IOT端提供高效推理 ### 1.2 准备预测库 预测库有两种获取方式: -- 1. 直接下载,预测库下载链接如下: +- 1. [推荐]直接下载,预测库下载链接如下: | 平台 | 预测库下载链接 | |---|---| @@ -41,7 +41,9 @@ Paddle Lite是飞桨轻量化推理引擎,为手机、IOT端提供高效推理 注:1. 上述预测库为PaddleLite 2.10分支编译得到,有关PaddleLite 2.10 详细信息可参考 [链接](https://github.com/PaddlePaddle/Paddle-Lite/releases/tag/v2.10) 。 -- 2. [推荐]编译Paddle-Lite得到预测库,Paddle-Lite的编译方式如下: +**注:建议使用paddlelite>=2.10版本的预测库,其他预测库版本[下载链接](https://github.com/PaddlePaddle/Paddle-Lite/tags)** + +- 2. 编译Paddle-Lite得到预测库,Paddle-Lite的编译方式如下: ``` git clone https://github.com/PaddlePaddle/Paddle-Lite.git cd Paddle-Lite @@ -102,22 +104,16 @@ Paddle-Lite 提供了多种策略来自动优化原始的模型,其中包括 如果要部署的模型不在上述表格中,则需要按照如下步骤获得优化后的模型。 -模型优化需要Paddle-Lite的opt可执行文件,可以通过编译Paddle-Lite源码获得,编译步骤如下: +- 步骤1:参考[文档](https://www.paddlepaddle.org.cn/lite/v2.10/user_guides/opt/opt_python.html)安装paddlelite,用于转换paddle inference model为paddlelite运行所需的nb模型 ``` -# 如果准备环境时已经clone了Paddle-Lite,则不用重新clone Paddle-Lite -git clone https://github.com/PaddlePaddle/Paddle-Lite.git -cd Paddle-Lite -git checkout release/v2.10 -# 启动编译 -./lite/tools/build.sh build_optimize_tool +pip install paddlelite==2.10 # paddlelite版本要与预测库版本一致 ``` - -编译完成后,opt文件位于`build.opt/lite/api/`下,可通过如下方式查看opt的运行选项和使用方式; +安装完后,如下指令可以查看帮助信息 ``` -cd build.opt/lite/api/ -./opt +paddle_lite_opt ``` +paddle_lite_opt 参数介绍: |选项|说明| |---|---| |--model_dir|待优化的PaddlePaddle模型(非combined形式)的路径| @@ -130,6 +126,8 @@ cd build.opt/lite/api/ `--model_dir`适用于待优化的模型是非combined方式,PaddleOCR的inference模型是combined方式,即模型结构和模型参数使用单独一个文件存储。 +- 步骤2:使用paddle_lite_opt将inference模型转换成移动端模型格式。 + 下面以PaddleOCR的超轻量中文模型为例,介绍使用编译好的opt文件完成inference模型到Paddle-Lite优化模型的转换。 ``` @@ -148,7 +146,7 @@ wget https://paddleocr.bj.bcebos.com/dygraph_v2.0/slim/ch_ppocr_mobile_v2.0_cls 转换成功后,inference模型目录下会多出`.nb`结尾的文件,即是转换成功的模型文件。 -注意:使用paddle-lite部署时,需要使用opt工具优化后的模型。 opt 工具的输入模型是paddle保存的inference模型 +注意:使用paddle-lite部署时,需要使用opt工具优化后的模型。 opt工具的输入模型是paddle保存的inference模型 ### 2.2 与手机联调 @@ -234,13 +232,14 @@ ppocr_keys_v1.txt # 中文字典 ... ``` -2. `config.txt` 包含了检测器、分类器的超参数,如下: +2. `config.txt` 包含了检测器、分类器、识别器的超参数,如下: ``` max_side_len 960 # 输入图像长宽大于960时,等比例缩放图像,使得图像最长边为960 det_db_thresh 0.3 # 用于过滤DB预测的二值化图像,设置为0.-0.3对结果影响不明显 -det_db_box_thresh 0.5 # DB后处理过滤box的阈值,如果检测存在漏框情况,可酌情减小 +det_db_box_thresh 0.5 # 检测器后处理过滤box的阈值,如果检测存在漏框情况,可酌情减小 det_db_unclip_ratio 1.6 # 表示文本框的紧致程度,越小则文本框更靠近文本 use_direction_classify 0 # 是否使用方向分类器,0表示不使用,1表示使用 +rec_image_height 32 # 识别模型输入图像的高度,PP-OCRv3模型设置为48,PP-OCRv2模型需要设置为32 ``` 5. 启动调试 @@ -259,8 +258,8 @@ use_direction_classify 0 # 是否使用方向分类器,0表示不使用,1 cd /data/local/tmp/debug export LD_LIBRARY_PATH=${PWD}:$LD_LIBRARY_PATH # 开始使用,ocr_db_crnn可执行文件的使用方式为: - # ./ocr_db_crnn 检测模型文件 方向分类器模型文件 识别模型文件 测试图像路径 字典文件路径 - ./ocr_db_crnn ch_PP-OCRv2_det_slim_opt.nb ch_PP-OCRv2_rec_slim_opt.nb ch_ppocr_mobile_v2.0_cls_slim_opt.nb ./11.jpg ppocr_keys_v1.txt + # ./ocr_db_crnn 检测模型文件 方向分类器模型文件 识别模型文件 运行硬件 运行精度 线程数 batchsize 测试图像路径 参数配置路径 字典文件路径 是否使用benchmark参数 + ./ocr_db_crnn ch_PP-OCRv2_det_slim_opt.nb ch_PP-OCRv2_rec_slim_opt.nb ch_ppocr_mobile_v2.0_cls_slim_opt.nb arm8 INT8 10 1 ./11.jpg config.txt ppocr_keys_v1.txt True ``` 如果对代码做了修改,则需要重新编译并push到手机上。 @@ -284,3 +283,7 @@ A2:替换debug下的.jpg测试图像为你想要测试的图像,adb push 到 Q3:如何封装到手机APP中? A3:此demo旨在提供能在手机上运行OCR的核心算法部分,PaddleOCR/deploy/android_demo是将这个demo封装到手机app的示例,供参考 + +Q4:运行demo时遇到报错`Error: This model is not supported, because kernel for 'io_copy' is not supported by Paddle-Lite.` + +A4:问题是安装的paddlelite版本和下载的预测库版本不匹配,确保paddleliteopt工具和你的预测库版本匹配,重新转nb模型试试。 From d56c0b9b7cea28bca25b5b8720c5eff9453178a3 Mon Sep 17 00:00:00 2001 From: LDOUBLEV Date: Mon, 27 Jun 2022 19:47:07 +0800 Subject: [PATCH 24/45] add mode --- deploy/lite/readme.md | 10 ++++++++-- deploy/lite/readme_ch.md | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/deploy/lite/readme.md b/deploy/lite/readme.md index 883aff9890..a1bef8120e 100644 --- a/deploy/lite/readme.md +++ b/deploy/lite/readme.md @@ -259,9 +259,15 @@ After the above steps are completed, you can use adb to push the file to the pho cd /data/local/tmp/debug export LD_LIBRARY_PATH=${PWD}:$LD_LIBRARY_PATH # The use of ocr_db_crnn is: - # ./ocr_db_crnn Detection model file Orientation classifier model file Recognition model file Hardware Precision Threads Batchsize Test image path Dictionary file path - ./ocr_db_crnn ch_PP-OCRv2_det_slim_opt.nb ch_PP-OCRv2_rec_slim_opt.nb ch_ppocr_mobile_v2.0_cls_slim_opt.nb arm8 INT8 10 1 ./11.jpg config.txt ppocr_keys_v1.txt True + # ./ocr_db_crnn Mode Detection model file Orientation classifier model file Recognition model file Hardware Precision Threads Batchsize Test image path Dictionary file path + ./ocr_db_crnn system ch_PP-OCRv2_det_slim_opt.nb ch_PP-OCRv2_rec_slim_opt.nb ch_ppocr_mobile_v2.0_cls_slim_opt.nb arm8 INT8 10 1 ./11.jpg config.txt ppocr_keys_v1.txt True # precision can be INT8 for quantitative model or FP32 for normal model. + +# Only using detection model +./ocr_db_crnn det ch_PP-OCRv2_det_slim_opt.nb arm8 INT8 10 1 ./11.jpg config.txt + +# Only using recognition model +./ocr_db_crnn rec ch_PP-OCRv2_rec_slim_opt.nb arm8 INT8 10 1 word_1.jpg ppocr_keys_v1.txt config.txt ``` If you modify the code, you need to recompile and push to the phone. diff --git a/deploy/lite/readme_ch.md b/deploy/lite/readme_ch.md index e238b4ec53..0793827fe6 100644 --- a/deploy/lite/readme_ch.md +++ b/deploy/lite/readme_ch.md @@ -258,8 +258,14 @@ rec_image_height 32 # 识别模型输入图像的高度,PP-OCRv3模型 cd /data/local/tmp/debug export LD_LIBRARY_PATH=${PWD}:$LD_LIBRARY_PATH # 开始使用,ocr_db_crnn可执行文件的使用方式为: - # ./ocr_db_crnn 检测模型文件 方向分类器模型文件 识别模型文件 运行硬件 运行精度 线程数 batchsize 测试图像路径 参数配置路径 字典文件路径 是否使用benchmark参数 - ./ocr_db_crnn ch_PP-OCRv2_det_slim_opt.nb ch_PP-OCRv2_rec_slim_opt.nb ch_ppocr_mobile_v2.0_cls_slim_opt.nb arm8 INT8 10 1 ./11.jpg config.txt ppocr_keys_v1.txt True + # ./ocr_db_crnn 预测模式 检测模型文件 方向分类器模型文件 识别模型文件 运行硬件 运行精度 线程数 batchsize 测试图像路径 参数配置路径 字典文件路径 是否使用benchmark参数 + ./ocr_db_crnn system ch_PP-OCRv2_det_slim_opt.nb ch_PP-OCRv2_rec_slim_opt.nb ch_ppocr_mobile_v2.0_cls_slim_opt.nb arm8 INT8 10 1 ./11.jpg config.txt ppocr_keys_v1.txt True + +# 仅使用文本检测模型,使用方式如下: +./ocr_db_crnn det ch_PP-OCRv2_det_slim_opt.nb arm8 INT8 10 1 ./11.jpg config.txt + +# 仅使用文本识别模型,使用方式如下: +./ocr_db_crnn rec ch_PP-OCRv2_rec_slim_opt.nb arm8 INT8 10 1 word_1.jpg ppocr_keys_v1.txt config.txt ``` 如果对代码做了修改,则需要重新编译并push到手机上。 From 4805aa0f100749e82250a78154321b7002e4b812 Mon Sep 17 00:00:00 2001 From: andyjpaddle Date: Tue, 28 Jun 2022 02:08:48 +0000 Subject: [PATCH 25/45] fix amp vqa --- tools/program.py | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/tools/program.py b/tools/program.py index aa0d2698cf..9a90eda2f8 100755 --- a/tools/program.py +++ b/tools/program.py @@ -255,6 +255,8 @@ def train(config, with paddle.amp.auto_cast(): if model_type == 'table' or extra_input: preds = model(images, data=batch[1:]) + elif model_type in ["kie", 'vqa']: + preds = model(batch) else: preds = model(images) else: @@ -307,7 +309,8 @@ def train(config, train_stats.update(stats) if log_writer is not None and dist.get_rank() == 0: - log_writer.log_metrics(metrics=train_stats.get(), prefix="TRAIN", step=global_step) + log_writer.log_metrics( + metrics=train_stats.get(), prefix="TRAIN", step=global_step) if dist.get_rank() == 0 and ( (global_step > 0 and global_step % print_batch_step == 0) or @@ -354,7 +357,8 @@ def train(config, # logger metric if log_writer is not None: - log_writer.log_metrics(metrics=cur_metric, prefix="EVAL", step=global_step) + log_writer.log_metrics( + metrics=cur_metric, prefix="EVAL", step=global_step) if cur_metric[main_indicator] >= best_model_dict[ main_indicator]: @@ -377,11 +381,18 @@ def train(config, logger.info(best_str) # logger best metric if log_writer is not None: - log_writer.log_metrics(metrics={ - "best_{}".format(main_indicator): best_model_dict[main_indicator] - }, prefix="EVAL", step=global_step) - - log_writer.log_model(is_best=True, prefix="best_accuracy", metadata=best_model_dict) + log_writer.log_metrics( + metrics={ + "best_{}".format(main_indicator): + best_model_dict[main_indicator] + }, + prefix="EVAL", + step=global_step) + + log_writer.log_model( + is_best=True, + prefix="best_accuracy", + metadata=best_model_dict) reader_start = time.time() if dist.get_rank() == 0: @@ -413,7 +424,8 @@ def train(config, epoch=epoch, global_step=global_step) if log_writer is not None: - log_writer.log_model(is_best=False, prefix='iter_epoch_{}'.format(epoch)) + log_writer.log_model( + is_best=False, prefix='iter_epoch_{}'.format(epoch)) best_str = 'best metric, {}'.format(', '.join( ['{}: {}'.format(k, v) for k, v in best_model_dict.items()])) @@ -585,7 +597,8 @@ def preprocess(is_train=False): vdl_writer_path = '{}/vdl/'.format(save_model_dir) log_writer = VDLLogger(save_model_dir) loggers.append(log_writer) - if ('use_wandb' in config['Global'] and config['Global']['use_wandb']) or 'wandb' in config: + if ('use_wandb' in config['Global'] and + config['Global']['use_wandb']) or 'wandb' in config: save_dir = config['Global']['save_model_dir'] wandb_writer_path = "{}/wandb".format(save_dir) if "wandb" in config: From adeb8a17c9f67f99f4ed6021a3b50e3b0d6f02e3 Mon Sep 17 00:00:00 2001 From: andyjpaddle Date: Tue, 28 Jun 2022 02:17:53 +0000 Subject: [PATCH 26/45] fix amp vqa --- tools/program.py | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/tools/program.py b/tools/program.py index aa0d2698cf..9a90eda2f8 100755 --- a/tools/program.py +++ b/tools/program.py @@ -255,6 +255,8 @@ def train(config, with paddle.amp.auto_cast(): if model_type == 'table' or extra_input: preds = model(images, data=batch[1:]) + elif model_type in ["kie", 'vqa']: + preds = model(batch) else: preds = model(images) else: @@ -307,7 +309,8 @@ def train(config, train_stats.update(stats) if log_writer is not None and dist.get_rank() == 0: - log_writer.log_metrics(metrics=train_stats.get(), prefix="TRAIN", step=global_step) + log_writer.log_metrics( + metrics=train_stats.get(), prefix="TRAIN", step=global_step) if dist.get_rank() == 0 and ( (global_step > 0 and global_step % print_batch_step == 0) or @@ -354,7 +357,8 @@ def train(config, # logger metric if log_writer is not None: - log_writer.log_metrics(metrics=cur_metric, prefix="EVAL", step=global_step) + log_writer.log_metrics( + metrics=cur_metric, prefix="EVAL", step=global_step) if cur_metric[main_indicator] >= best_model_dict[ main_indicator]: @@ -377,11 +381,18 @@ def train(config, logger.info(best_str) # logger best metric if log_writer is not None: - log_writer.log_metrics(metrics={ - "best_{}".format(main_indicator): best_model_dict[main_indicator] - }, prefix="EVAL", step=global_step) - - log_writer.log_model(is_best=True, prefix="best_accuracy", metadata=best_model_dict) + log_writer.log_metrics( + metrics={ + "best_{}".format(main_indicator): + best_model_dict[main_indicator] + }, + prefix="EVAL", + step=global_step) + + log_writer.log_model( + is_best=True, + prefix="best_accuracy", + metadata=best_model_dict) reader_start = time.time() if dist.get_rank() == 0: @@ -413,7 +424,8 @@ def train(config, epoch=epoch, global_step=global_step) if log_writer is not None: - log_writer.log_model(is_best=False, prefix='iter_epoch_{}'.format(epoch)) + log_writer.log_model( + is_best=False, prefix='iter_epoch_{}'.format(epoch)) best_str = 'best metric, {}'.format(', '.join( ['{}: {}'.format(k, v) for k, v in best_model_dict.items()])) @@ -585,7 +597,8 @@ def preprocess(is_train=False): vdl_writer_path = '{}/vdl/'.format(save_model_dir) log_writer = VDLLogger(save_model_dir) loggers.append(log_writer) - if ('use_wandb' in config['Global'] and config['Global']['use_wandb']) or 'wandb' in config: + if ('use_wandb' in config['Global'] and + config['Global']['use_wandb']) or 'wandb' in config: save_dir = config['Global']['save_model_dir'] wandb_writer_path = "{}/wandb".format(save_dir) if "wandb" in config: From 03a6ef531f82eda961183ba38e7473b8cfc9c3fa Mon Sep 17 00:00:00 2001 From: Topdu <784990967@qq.com> Date: Tue, 28 Jun 2022 03:27:48 +0000 Subject: [PATCH 27/45] fix abinet ci bug --- configs/rec/rec_r45_abinet.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/configs/rec/rec_r45_abinet.yml b/configs/rec/rec_r45_abinet.yml index 1e03db8b1b..e604bcd12d 100644 --- a/configs/rec/rec_r45_abinet.yml +++ b/configs/rec/rec_r45_abinet.yml @@ -8,7 +8,7 @@ Global: # evaluation is run every 2000 iterations eval_batch_step: [0, 2000] cal_metric_during_train: True - pretrained_model: ./rec_r45_abinet_train/abinet_vl_pretrained + pretrained_model: checkpoints: save_inference_dir: use_visualdl: False @@ -41,7 +41,6 @@ Architecture: Transform: Backbone: name: ResNet45 - Head: name: ABINetHead use_lang: True From 671415bbd878bd6d4bc392b22b7a61afa9e6048b Mon Sep 17 00:00:00 2001 From: topduke <784990967@qq.com> Date: Tue, 28 Jun 2022 15:06:53 +0800 Subject: [PATCH 28/45] [New Rec]add rec ViTSTR & ABINet algorithm. (#6414) * add rec vitstr algorithm. * fix cpu_thread and precision * fix svtr tipc * modify vitstr name * modify vitstr config batchsize * [New Rec] add vitstr and ABINet * add rec_resnet45 * svtr ch large model * [application] svtr ch model * [application] svtr ch model * [application] svtr ch model * add abinet_rec_aug and trained model * aug p infe * fix ci export bug * fix abinet ci bug --- applications/高精度中文识别模型.md | 107 ++++ configs/rec/rec_mtb_nrtr.yml | 16 +- configs/rec/rec_r45_abinet.yml | 101 ++++ configs/rec/rec_svtrnet.yml | 12 +- configs/rec/rec_svtrnet_ch.yml | 155 ++++++ configs/rec/rec_vitstr_none_ce.yml | 102 ++++ doc/doc_ch/algorithm_overview.md | 5 +- doc/doc_ch/algorithm_rec_abinet.md | 155 ++++++ doc/doc_ch/algorithm_rec_nrtr.md | 140 ++++- doc/doc_ch/algorithm_rec_svtr.md | 1 - doc/doc_ch/algorithm_rec_vitstr.md | 154 ++++++ doc/doc_en/algorithm_overview_en.md | 5 +- doc/doc_en/algorithm_rec_abinet_en.md | 136 +++++ doc/doc_en/algorithm_rec_nrtr_en.md | 141 ++++- doc/doc_en/algorithm_rec_svtr_en.md | 1 - doc/doc_en/algorithm_rec_vitstr_en.md | 134 +++++ ppocr/data/imaug/__init__.py | 6 +- ppocr/data/imaug/abinet_aug.py | 407 +++++++++++++++ ppocr/data/imaug/label_ops.py | 127 +++-- ppocr/data/imaug/operators.py | 33 -- ppocr/data/imaug/rec_img_aug.py | 170 ++++-- ppocr/losses/__init__.py | 4 +- ppocr/losses/rec_ce_loss.py | 66 +++ ppocr/losses/rec_nrtr_loss.py | 30 -- ppocr/modeling/backbones/__init__.py | 18 +- ppocr/modeling/backbones/rec_resnet_45.py | 147 ++++++ ppocr/modeling/backbones/rec_svtrnet.py | 102 ++-- ppocr/modeling/backbones/rec_vitstr.py | 120 +++++ ppocr/modeling/heads/__init__.py | 3 +- ppocr/modeling/heads/multiheadAttention.py | 163 ------ ppocr/modeling/heads/rec_abinet_head.py | 296 +++++++++++ ppocr/modeling/heads/rec_nrtr_head.py | 489 ++++++------------ ppocr/postprocess/__init__.py | 4 +- ppocr/postprocess/rec_postprocess.py | 183 ++++--- .../configs/rec_mtb_nrtr/rec_mtb_nrtr.yml | 8 +- .../configs/rec_r45_abinet/rec_r45_abinet.yml | 106 ++++ .../rec_r45_abinet/train_infer_python.txt | 53 ++ test_tipc/configs/rec_svtrnet/rec_svtrnet.yml | 117 +++++ .../rec_svtrnet/train_infer_python.txt | 53 ++ .../rec_vitstr_none_ce/rec_vitstr_none_ce.yml | 104 ++++ .../rec_vitstr_none_ce/train_infer_python.txt | 53 ++ tools/export_model.py | 40 +- tools/infer/predict_rec.py | 70 ++- tools/program.py | 3 +- 44 files changed, 3550 insertions(+), 790 deletions(-) create mode 100644 applications/高精度中文识别模型.md create mode 100644 configs/rec/rec_r45_abinet.yml create mode 100644 configs/rec/rec_svtrnet_ch.yml create mode 100644 configs/rec/rec_vitstr_none_ce.yml create mode 100644 doc/doc_ch/algorithm_rec_abinet.md create mode 100644 doc/doc_ch/algorithm_rec_vitstr.md create mode 100644 doc/doc_en/algorithm_rec_abinet_en.md create mode 100644 doc/doc_en/algorithm_rec_vitstr_en.md create mode 100644 ppocr/data/imaug/abinet_aug.py create mode 100644 ppocr/losses/rec_ce_loss.py delete mode 100644 ppocr/losses/rec_nrtr_loss.py create mode 100644 ppocr/modeling/backbones/rec_resnet_45.py create mode 100644 ppocr/modeling/backbones/rec_vitstr.py delete mode 100755 ppocr/modeling/heads/multiheadAttention.py create mode 100644 ppocr/modeling/heads/rec_abinet_head.py create mode 100644 test_tipc/configs/rec_r45_abinet/rec_r45_abinet.yml create mode 100644 test_tipc/configs/rec_r45_abinet/train_infer_python.txt create mode 100644 test_tipc/configs/rec_svtrnet/rec_svtrnet.yml create mode 100644 test_tipc/configs/rec_svtrnet/train_infer_python.txt create mode 100644 test_tipc/configs/rec_vitstr_none_ce/rec_vitstr_none_ce.yml create mode 100644 test_tipc/configs/rec_vitstr_none_ce/train_infer_python.txt diff --git a/applications/高精度中文识别模型.md b/applications/高精度中文识别模型.md new file mode 100644 index 0000000000..3c31af42ee --- /dev/null +++ b/applications/高精度中文识别模型.md @@ -0,0 +1,107 @@ +# 高精度中文场景文本识别模型SVTR + +## 1. 简介 + +PP-OCRv3是百度开源的超轻量级场景文本检测识别模型库,其中超轻量的场景中文识别模型SVTR_LCNet使用了SVTR算法结构。为了保证速度,SVTR_LCNet将SVTR模型的Local Blocks替换为LCNet,使用两层Global Blocks。在中文场景中,PP-OCRv3识别主要使用如下优化策略: +- GTC:Attention指导CTC训练策略; +- TextConAug:挖掘文字上下文信息的数据增广策略; +- TextRotNet:自监督的预训练模型; +- UDML:联合互学习策略; +- UIM:无标注数据挖掘方案。 + +其中 *UIM:无标注数据挖掘方案* 使用了高精度的SVTR中文模型进行无标注文件的刷库,该模型在PP-OCRv3识别的数据集上训练,精度对比如下表。 + +|中文识别算法|模型|UIM|精度| +| --- | --- | --- |--- | +|PP-OCRv3|SVTR_LCNet| w/o |78.4%| +|PP-OCRv3|SVTR_LCNet| w |79.4%| +|SVTR|SVTR-Tiny|-|82.5%| + +aistudio项目链接: [高精度中文场景文本识别模型SVTR](https://aistudio.baidu.com/aistudio/projectdetail/4263032) + +## 2. SVTR中文模型使用 + +### 环境准备 + + +本任务基于Aistudio完成, 具体环境如下: + +- 操作系统: Linux +- PaddlePaddle: 2.3 +- PaddleOCR: dygraph + +下载 PaddleOCR代码 + +```bash +git clone -b dygraph https://github.com/PaddlePaddle/PaddleOCR +``` + +安装依赖库 + +```bash +pip install -r PaddleOCR/requirements.txt -i https://mirror.baidu.com/pypi/simple +``` + +### 快速使用 + +获取SVTR中文模型文件,请扫码填写问卷,加入PaddleOCR官方交流群获取全部OCR垂类模型下载链接、《动手学OCR》电子书等全套OCR学习资料🎁 +
+ +
+ +```bash +# 解压模型文件 +tar xf svtr_ch_high_accuracy.tar +``` + +预测中文文本,以下图为例: +![](../doc/imgs_words/ch/word_1.jpg) + +预测命令: + +```bash +# CPU预测 +python tools/infer_rec.py -c configs/rec/rec_svtrnet_ch.yml -o Global.pretrained_model=./svtr_ch_high_accuracy/best_accuracy Global.infer_img=./doc/imgs_words/ch/word_1.jpg Global.use_gpu=False + +# GPU预测 +#python tools/infer_rec.py -c configs/rec/rec_svtrnet_ch.yml -o Global.pretrained_model=./svtr_ch_high_accuracy/best_accuracy Global.infer_img=./doc/imgs_words/ch/word_1.jpg Global.use_gpu=True +``` + +可以看到最后打印结果为 +- result: 韩国小馆 0.9853458404541016 + +0.9853458404541016为预测置信度。 + +### 推理模型导出与预测 + +inference 模型(paddle.jit.save保存的模型) 一般是模型训练,把模型结构和模型参数保存在文件中的固化模型,多用于预测部署场景。 训练过程中保存的模型是checkpoints模型,保存的只有模型的参数,多用于恢复训练等。 与checkpoints模型相比,inference 模型会额外保存模型的结构信息,在预测部署、加速推理上性能优越,灵活方便,适合于实际系统集成。 + +运行识别模型转inference模型命令,如下: + +```bash +python tools/export_model.py -c configs/rec/rec_svtrnet_ch.yml -o Global.pretrained_model=./svtr_ch_high_accuracy/best_accuracy Global.save_inference_dir=./inference/svtr_ch +``` + +转换成功后,在目录下有三个文件: +```shell +inference/svtr_ch/ + ├── inference.pdiparams # 识别inference模型的参数文件 + ├── inference.pdiparams.info # 识别inference模型的参数信息,可忽略 + └── inference.pdmodel # 识别inference模型的program文件 +``` + +inference模型预测,命令如下: + +```bash +# CPU预测 +python3 tools/infer/predict_rec.py --image_dir="./doc/imgs_words/ch/word_1.jpg" --rec_algorithm='SVTR' --rec_model_dir=./inference/svtr_ch/ --rec_image_shape='3, 32, 320' --rec_char_dict_path=ppocr/utils/ppocr_keys_v1.txt --use_gpu=False + +# GPU预测 +#python3 tools/infer/predict_rec.py --image_dir="./doc/imgs_words/ch/word_1.jpg" --rec_algorithm='SVTR' --rec_model_dir=./inference/svtr_ch/ --rec_image_shape='3, 32, 320' --rec_char_dict_path=ppocr/utils/ppocr_keys_v1.txt --use_gpu=True +``` + +**注意** + +- 使用SVTR算法时,需要指定--rec_algorithm='SVTR' +- 如果使用自定义字典训练的模型,需要将--rec_char_dict_path=ppocr/utils/ppocr_keys_v1.txt修改为自定义的字典 +- --rec_image_shape='3, 32, 320' 该参数不能去掉 diff --git a/configs/rec/rec_mtb_nrtr.yml b/configs/rec/rec_mtb_nrtr.yml index 0426750085..4e5826adc9 100644 --- a/configs/rec/rec_mtb_nrtr.yml +++ b/configs/rec/rec_mtb_nrtr.yml @@ -9,7 +9,7 @@ Global: eval_batch_step: [0, 2000] cal_metric_during_train: True pretrained_model: - checkpoints: + checkpoints: save_inference_dir: use_visualdl: False infer_img: doc/imgs_words_en/word_10.png @@ -49,7 +49,7 @@ Architecture: Loss: - name: NRTRLoss + name: CELoss smoothing: True PostProcess: @@ -68,8 +68,8 @@ Train: img_mode: BGR channel_first: False - NRTRLabelEncode: # Class handling label - - NRTRRecResizeImg: - image_shape: [100, 32] + - GrayRecResizeImg: + image_shape: [100, 32] # W H resize_type: PIL # PIL or OpenCV - KeepKeys: keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order @@ -82,14 +82,14 @@ Train: Eval: dataset: name: LMDBDataSet - data_dir: ./train_data/data_lmdb_release/evaluation/ + data_dir: ./train_data/data_lmdb_release/validation/ transforms: - DecodeImage: # load image img_mode: BGR channel_first: False - NRTRLabelEncode: # Class handling label - - NRTRRecResizeImg: - image_shape: [100, 32] + - GrayRecResizeImg: + image_shape: [100, 32] # W H resize_type: PIL # PIL or OpenCV - KeepKeys: keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order @@ -97,5 +97,5 @@ Eval: shuffle: False drop_last: False batch_size_per_card: 256 - num_workers: 1 + num_workers: 4 use_shared_memory: False diff --git a/configs/rec/rec_r45_abinet.yml b/configs/rec/rec_r45_abinet.yml new file mode 100644 index 0000000000..e604bcd12d --- /dev/null +++ b/configs/rec/rec_r45_abinet.yml @@ -0,0 +1,101 @@ +Global: + use_gpu: True + epoch_num: 10 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/r45_abinet/ + save_epoch_step: 1 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + character_type: en + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_abinet.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.99 + clip_norm: 20.0 + lr: + name: Piecewise + decay_epochs: [6] + values: [0.0001, 0.00001] + regularizer: + name: 'L2' + factor: 0. + +Architecture: + model_type: rec + algorithm: ABINet + in_channels: 3 + Transform: + Backbone: + name: ResNet45 + Head: + name: ABINetHead + use_lang: True + iter_size: 3 + + +Loss: + name: CELoss + ignore_index: &ignore_index 100 # Must be greater than the number of character classes + +PostProcess: + name: ABINetLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - ABINetRecAug: + - ABINetLabelEncode: # Class handling label + ignore_index: *ignore_index + - ABINetRecResizeImg: + image_shape: [3, 32, 128] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 96 + drop_last: True + num_workers: 4 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/validation/ + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - ABINetLabelEncode: # Class handling label + ignore_index: *ignore_index + - ABINetRecResizeImg: + image_shape: [3, 32, 128] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 4 + use_shared_memory: False diff --git a/configs/rec/rec_svtrnet.yml b/configs/rec/rec_svtrnet.yml index 233d5e2765..c1f5cc380a 100644 --- a/configs/rec/rec_svtrnet.yml +++ b/configs/rec/rec_svtrnet.yml @@ -26,7 +26,7 @@ Optimizer: name: AdamW beta1: 0.9 beta2: 0.99 - epsilon: 0.00000008 + epsilon: 8.e-8 weight_decay: 0.05 no_weight_decay_name: norm pos_embed one_dim_param_no_weight_decay: true @@ -77,14 +77,13 @@ Metric: Train: dataset: name: LMDBDataSet - data_dir: ./train_data/data_lmdb_release/training/ + data_dir: ./train_data/data_lmdb_release/training transforms: - DecodeImage: # load image img_mode: BGR channel_first: False - CTCLabelEncode: # Class handling label - - RecResizeImg: - character_dict_path: + - SVTRRecResizeImg: image_shape: [3, 64, 256] padding: False - KeepKeys: @@ -98,14 +97,13 @@ Train: Eval: dataset: name: LMDBDataSet - data_dir: ./train_data/data_lmdb_release/validation/ + data_dir: ./train_data/data_lmdb_release/validation transforms: - DecodeImage: # load image img_mode: BGR channel_first: False - CTCLabelEncode: # Class handling label - - RecResizeImg: - character_dict_path: + - SVTRRecResizeImg: image_shape: [3, 64, 256] padding: False - KeepKeys: diff --git a/configs/rec/rec_svtrnet_ch.yml b/configs/rec/rec_svtrnet_ch.yml new file mode 100644 index 0000000000..0d3f63d125 --- /dev/null +++ b/configs/rec/rec_svtrnet_ch.yml @@ -0,0 +1,155 @@ +Global: + use_gpu: true + epoch_num: 100 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/svtr_ch_all/ + save_epoch_step: 10 + eval_batch_step: + - 0 + - 2000 + cal_metric_during_train: true + pretrained_model: null + checkpoints: null + save_inference_dir: null + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: 25 + infer_mode: false + use_space_char: true + save_res_path: ./output/rec/predicts_svtr_tiny_ch_all.txt +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.99 + epsilon: 8.0e-08 + weight_decay: 0.05 + no_weight_decay_name: norm pos_embed + one_dim_param_no_weight_decay: true + lr: + name: Cosine + learning_rate: 0.0005 + warmup_epoch: 2 +Architecture: + model_type: rec + algorithm: SVTR + Transform: null + Backbone: + name: SVTRNet + img_size: + - 32 + - 320 + out_char_num: 40 + out_channels: 96 + patch_merging: Conv + embed_dim: + - 64 + - 128 + - 256 + depth: + - 3 + - 6 + - 3 + num_heads: + - 2 + - 4 + - 8 + mixer: + - Local + - Local + - Local + - Local + - Local + - Local + - Global + - Global + - Global + - Global + - Global + - Global + local_mixer: + - - 7 + - 11 + - - 7 + - 11 + - - 7 + - 11 + last_stage: true + prenorm: false + Neck: + name: SequenceEncoder + encoder_type: reshape + Head: + name: CTCHead +Loss: + name: CTCLoss +PostProcess: + name: CTCLabelDecode +Metric: + name: RecMetric + main_indicator: acc +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/train_list.txt + ext_op_transform_idx: 1 + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: + - 32 + - 320 + - 3 + - RecAug: null + - CTCLabelEncode: null + - SVTRRecResizeImg: + image_shape: + - 3 + - 32 + - 320 + padding: true + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: true + batch_size_per_card: 256 + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - CTCLabelEncode: null + - SVTRRecResizeImg: + image_shape: + - 3 + - 32 + - 320 + padding: true + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: false + drop_last: false + batch_size_per_card: 256 + num_workers: 2 +profiler_options: null diff --git a/configs/rec/rec_vitstr_none_ce.yml b/configs/rec/rec_vitstr_none_ce.yml new file mode 100644 index 0000000000..b969c83a5d --- /dev/null +++ b/configs/rec/rec_vitstr_none_ce.yml @@ -0,0 +1,102 @@ +Global: + use_gpu: True + epoch_num: 20 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/vitstr_none_ce/ + save_epoch_step: 1 + # evaluation is run every 2000 iterations after the 0th iteration# + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: ppocr/utils/EN_symbol_dict.txt + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_vitstr.txt + + +Optimizer: + name: Adadelta + epsilon: 1.e-8 + rho: 0.95 + clip_norm: 5.0 + lr: + learning_rate: 1.0 + +Architecture: + model_type: rec + algorithm: ViTSTR + in_channels: 1 + Transform: + Backbone: + name: ViTSTR + scale: tiny + Neck: + name: SequenceEncoder + encoder_type: reshape + Head: + name: CTCHead + +Loss: + name: CELoss + with_all: True + ignore_index: &ignore_index 0 # Must be zero or greater than the number of character classes + +PostProcess: + name: ViTSTRLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - ViTSTRLabelEncode: # Class handling label + ignore_index: *ignore_index + - GrayRecResizeImg: + image_shape: [224, 224] # W H + resize_type: PIL # PIL or OpenCV + inter_type: 'Image.BICUBIC' + scale: false + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 48 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/validation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - ViTSTRLabelEncode: # Class handling label + ignore_index: *ignore_index + - GrayRecResizeImg: + image_shape: [224, 224] # W H + resize_type: PIL # PIL or OpenCV + inter_type: 'Image.BICUBIC' + scale: false + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 2 diff --git a/doc/doc_ch/algorithm_overview.md b/doc/doc_ch/algorithm_overview.md index 6227a21498..eb81e4cd6d 100755 --- a/doc/doc_ch/algorithm_overview.md +++ b/doc/doc_ch/algorithm_overview.md @@ -66,6 +66,8 @@ - [x] [SAR](./algorithm_rec_sar.md) - [x] [SEED](./algorithm_rec_seed.md) - [x] [SVTR](./algorithm_rec_svtr.md) +- [x] [ViTSTR](./algorithm_rec_vitstr.md) +- [x] [ABINet](./algorithm_rec_abinet.md) 参考[DTRB](https://arxiv.org/abs/1904.01906)[3]文字识别训练和评估流程,使用MJSynth和SynthText两个文字识别数据集训练,在IIIT, SVT, IC03, IC13, IC15, SVTP, CUTE数据集上进行评估,算法效果如下: @@ -84,7 +86,8 @@ |SAR|Resnet31| 87.20% | rec_r31_sar | [训练模型](https://paddleocr.bj.bcebos.com/dygraph_v2.1/rec/rec_r31_sar_train.tar) | |SEED|Aster_Resnet| 85.35% | rec_resnet_stn_bilstm_att | [训练模型](https://paddleocr.bj.bcebos.com/dygraph_v2.1/rec/rec_resnet_stn_bilstm_att.tar) | |SVTR|SVTR-Tiny| 89.25% | rec_svtr_tiny_none_ctc_en | [训练模型](https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/rec_svtr_tiny_none_ctc_en_train.tar) | - +|ViTSTR|ViTSTR| 79.82% | rec_vitstr_none_ce_en | [训练模型](https://paddleocr.bj.bcebos.com/rec_vitstr_none_ce_train.tar) | +|ABINet|Resnet45| 90.75% | rec_r45_abinet_en | [训练模型](https://paddleocr.bj.bcebos.com/rec_r45_abinet_train.tar) | diff --git a/doc/doc_ch/algorithm_rec_abinet.md b/doc/doc_ch/algorithm_rec_abinet.md new file mode 100644 index 0000000000..47507c36c7 --- /dev/null +++ b/doc/doc_ch/algorithm_rec_abinet.md @@ -0,0 +1,155 @@ +# 场景文本识别算法-ABINet + +- [1. 算法简介](#1) +- [2. 环境配置](#2) +- [3. 模型训练、评估、预测](#3) + - [3.1 训练](#3-1) + - [3.2 评估](#3-2) + - [3.3 预测](#3-3) +- [4. 推理部署](#4) + - [4.1 Python推理](#4-1) + - [4.2 C++推理](#4-2) + - [4.3 Serving服务化部署](#4-3) + - [4.4 更多推理部署](#4-4) +- [5. FAQ](#5) + + +## 1. 算法简介 + +论文信息: +> [ABINet: Read Like Humans: Autonomous, Bidirectional and Iterative Language Modeling for Scene Text Recognition](https://openaccess.thecvf.com/content/CVPR2021/papers/Fang_Read_Like_Humans_Autonomous_Bidirectional_and_Iterative_Language_Modeling_for_CVPR_2021_paper.pdf) +> Shancheng Fang and Hongtao Xie and Yuxin Wang and Zhendong Mao and Yongdong Zhang +> CVPR, 2021 + + + +`ABINet`使用MJSynth和SynthText两个文字识别数据集训练,在IIIT, SVT, IC03, IC13, IC15, SVTP, CUTE数据集上进行评估,算法复现效果如下: + +|模型|骨干网络|配置文件|Acc|下载链接| +| --- | --- | --- | --- | --- | +|ABINet|ResNet45|[rec_r45_abinet.yml](../../configs/rec/rec_r45_abinet.yml)|90.75%|[预训练、训练模型](https://paddleocr.bj.bcebos.com/rec_r45_abinet_train.tar)| + + +## 2. 环境配置 +请先参考[《运行环境准备》](./environment.md)配置PaddleOCR运行环境,参考[《项目克隆》](./clone.md)克隆项目代码。 + + + +## 3. 模型训练、评估、预测 + + +### 3.1 模型训练 + +请参考[文本识别训练教程](./recognition.md)。PaddleOCR对代码进行了模块化,训练`ABINet`识别模型时需要**更换配置文件**为`ABINet`的[配置文件](../../configs/rec/rec_r45_abinet.yml)。 + +#### 启动训练 + + +具体地,在完成数据准备后,便可以启动训练,训练命令如下: +```shell +#单卡训练(训练周期长,不建议) +python3 tools/train.py -c configs/rec/rec_r45_abinet.yml + +#多卡训练,通过--gpus参数指定卡号 +python3 -m paddle.distributed.launch --gpus '0,1,2,3' tools/train.py -c configs/rec/rec_r45_abinet.yml +``` + + +### 3.2 评估 + +可下载已训练完成的[模型文件](#model),使用如下命令进行评估: + +```shell +# 注意将pretrained_model的路径设置为本地路径。 +python3 -m paddle.distributed.launch --gpus '0' tools/eval.py -c configs/rec/rec_r45_abinet.yml -o Global.pretrained_model=./rec_r45_abinet_train/best_accuracy +``` + + +### 3.3 预测 + +使用如下命令进行单张图片预测: +```shell +# 注意将pretrained_model的路径设置为本地路径。 +python3 tools/infer_rec.py -c configs/rec/rec_r45_abinet.yml -o Global.infer_img='./doc/imgs_words_en/word_10.png' Global.pretrained_model=./rec_r45_abinet_train/best_accuracy +# 预测文件夹下所有图像时,可修改infer_img为文件夹,如 Global.infer_img='./doc/imgs_words_en/'。 +``` + + + +## 4. 推理部署 + + +### 4.1 Python推理 +首先将训练得到best模型,转换成inference model。这里以训练完成的模型为例([模型下载地址](https://paddleocr.bj.bcebos.com/rec_r45_abinet_train.tar) ),可以使用如下命令进行转换: + +```shell +# 注意将pretrained_model的路径设置为本地路径。 +python3 tools/export_model.py -c configs/rec/rec_r45_abinet.yml -o Global.pretrained_model=./rec_r45_abinet_train/best_accuracy Global.save_inference_dir=./inference/rec_r45_abinet/ +``` +**注意:** +- 如果您是在自己的数据集上训练的模型,并且调整了字典文件,请注意修改配置文件中的`character_dict_path`是否是所需要的字典文件。 +- 如果您修改了训练时的输入大小,请修改`tools/export_model.py`文件中的对应ABINet的`infer_shape`。 + +转换成功后,在目录下有三个文件: +``` +/inference/rec_r45_abinet/ + ├── inference.pdiparams # 识别inference模型的参数文件 + ├── inference.pdiparams.info # 识别inference模型的参数信息,可忽略 + └── inference.pdmodel # 识别inference模型的program文件 +``` + +执行如下命令进行模型推理: + +```shell +python3 tools/infer/predict_rec.py --image_dir='./doc/imgs_words_en/word_10.png' --rec_model_dir='./inference/rec_r45_abinet/' --rec_algorithm='ABINet' --rec_image_shape='3,32,128' --rec_char_dict_path='./ppocr/utils/ic15_dict.txt' +# 预测文件夹下所有图像时,可修改image_dir为文件夹,如 --image_dir='./doc/imgs_words_en/'。 +``` + +![](../imgs_words_en/word_10.png) + +执行命令后,上面图像的预测结果(识别的文本和得分)会打印到屏幕上,示例如下: +结果如下: +```shell +Predicts of ./doc/imgs_words_en/word_10.png:('pain', 0.9999995231628418) +``` + +**注意**: + +- 训练上述模型采用的图像分辨率是[3,32,128],需要通过参数`rec_image_shape`设置为您训练时的识别图像形状。 +- 在推理时需要设置参数`rec_char_dict_path`指定字典,如果您修改了字典,请修改该参数为您的字典文件。 +- 如果您修改了预处理方法,需修改`tools/infer/predict_rec.py`中ABINet的预处理为您的预处理方法。 + + + +### 4.2 C++推理部署 + +由于C++预处理后处理还未支持ABINet,所以暂未支持 + + +### 4.3 Serving服务化部署 + +暂不支持 + + +### 4.4 更多推理部署 + +暂不支持 + + +## 5. FAQ + +1. MJSynth和SynthText两种数据集来自于[ABINet源repo](https://github.com/FangShancheng/ABINet) 。 +2. 我们使用ABINet作者提供的预训练模型进行finetune训练。 + +## 引用 + +```bibtex +@article{Fang2021ABINet, + title = {ABINet: Read Like Humans: Autonomous, Bidirectional and Iterative Language Modeling for Scene Text Recognition}, + author = {Shancheng Fang and Hongtao Xie and Yuxin Wang and Zhendong Mao and Yongdong Zhang}, + booktitle = {CVPR}, + year = {2021}, + url = {https://arxiv.org/abs/2103.06495}, + pages = {7098-7107} +} +``` diff --git a/doc/doc_ch/algorithm_rec_nrtr.md b/doc/doc_ch/algorithm_rec_nrtr.md index d3b626d024..c619ac1dbc 100644 --- a/doc/doc_ch/algorithm_rec_nrtr.md +++ b/doc/doc_ch/algorithm_rec_nrtr.md @@ -12,6 +12,7 @@ - [4.3 Serving服务化部署](#4-3) - [4.4 更多推理部署](#4-4) - [5. FAQ](#5) +- [6. 发行公告](#6) ## 1. 算法简介 @@ -110,7 +111,7 @@ python3 tools/infer/predict_rec.py --image_dir='./doc/imgs_words_en/word_10.png' 执行命令后,上面图像的预测结果(识别的文本和得分)会打印到屏幕上,示例如下: 结果如下: ```shell -Predicts of ./doc/imgs_words_en/word_10.png:('pain', 0.9265879392623901) +Predicts of ./doc/imgs_words_en/word_10.png:('pain', 0.9465042352676392) ``` **注意**: @@ -140,12 +141,147 @@ Predicts of ./doc/imgs_words_en/word_10.png:('pain', 0.9265879392623901) 1. `NRTR`论文中使用Beam搜索进行解码字符,但是速度较慢,这里默认未使用Beam搜索,以贪婪搜索进行解码字符。 + +## 6. 发行公告 + +1. release/2.6更新NRTR代码结构,新版NRTR可加载旧版(release/2.5及之前)模型参数,使用下面示例代码将旧版模型参数转换为新版模型参数: + +```python + + params = paddle.load('path/' + '.pdparams') # 旧版本参数 + state_dict = model.state_dict() # 新版模型参数 + new_state_dict = {} + + for k1, v1 in state_dict.items(): + + k = k1 + if 'encoder' in k and 'self_attn' in k and 'qkv' in k and 'weight' in k: + + k_para = k[:13] + 'layers.' + k[13:] + q = params[k_para.replace('qkv', 'conv1')].transpose((1, 0, 2, 3)) + k = params[k_para.replace('qkv', 'conv2')].transpose((1, 0, 2, 3)) + v = params[k_para.replace('qkv', 'conv3')].transpose((1, 0, 2, 3)) + + new_state_dict[k1] = np.concatenate([q[:, :, 0, 0], k[:, :, 0, 0], v[:, :, 0, 0]], -1) + + elif 'encoder' in k and 'self_attn' in k and 'qkv' in k and 'bias' in k: + + k_para = k[:13] + 'layers.' + k[13:] + q = params[k_para.replace('qkv', 'conv1')] + k = params[k_para.replace('qkv', 'conv2')] + v = params[k_para.replace('qkv', 'conv3')] + + new_state_dict[k1] = np.concatenate([q, k, v], -1) + + elif 'encoder' in k and 'self_attn' in k and 'out_proj' in k: + + k_para = k[:13] + 'layers.' + k[13:] + new_state_dict[k1] = params[k_para] + + elif 'encoder' in k and 'norm3' in k: + k_para = k[:13] + 'layers.' + k[13:] + new_state_dict[k1] = params[k_para.replace('norm3', 'norm2')] + + elif 'encoder' in k and 'norm1' in k: + k_para = k[:13] + 'layers.' + k[13:] + new_state_dict[k1] = params[k_para] + + + elif 'decoder' in k and 'self_attn' in k and 'qkv' in k and 'weight' in k: + k_para = k[:13] + 'layers.' + k[13:] + q = params[k_para.replace('qkv', 'conv1')].transpose((1, 0, 2, 3)) + k = params[k_para.replace('qkv', 'conv2')].transpose((1, 0, 2, 3)) + v = params[k_para.replace('qkv', 'conv3')].transpose((1, 0, 2, 3)) + new_state_dict[k1] = np.concatenate([q[:, :, 0, 0], k[:, :, 0, 0], v[:, :, 0, 0]], -1) + + elif 'decoder' in k and 'self_attn' in k and 'qkv' in k and 'bias' in k: + k_para = k[:13] + 'layers.' + k[13:] + q = params[k_para.replace('qkv', 'conv1')] + k = params[k_para.replace('qkv', 'conv2')] + v = params[k_para.replace('qkv', 'conv3')] + new_state_dict[k1] = np.concatenate([q, k, v], -1) + + elif 'decoder' in k and 'self_attn' in k and 'out_proj' in k: + + k_para = k[:13] + 'layers.' + k[13:] + new_state_dict[k1] = params[k_para] + + elif 'decoder' in k and 'cross_attn' in k and 'q' in k and 'weight' in k: + k_para = k[:13] + 'layers.' + k[13:] + k_para = k_para.replace('cross_attn', 'multihead_attn') + q = params[k_para.replace('q', 'conv1')].transpose((1, 0, 2, 3)) + new_state_dict[k1] = q[:, :, 0, 0] + + elif 'decoder' in k and 'cross_attn' in k and 'q' in k and 'bias' in k: + k_para = k[:13] + 'layers.' + k[13:] + k_para = k_para.replace('cross_attn', 'multihead_attn') + q = params[k_para.replace('q', 'conv1')] + new_state_dict[k1] = q + + elif 'decoder' in k and 'cross_attn' in k and 'kv' in k and 'weight' in k: + k_para = k[:13] + 'layers.' + k[13:] + k_para = k_para.replace('cross_attn', 'multihead_attn') + k = params[k_para.replace('kv', 'conv2')].transpose((1, 0, 2, 3)) + v = params[k_para.replace('kv', 'conv3')].transpose((1, 0, 2, 3)) + new_state_dict[k1] = np.concatenate([k[:, :, 0, 0], v[:, :, 0, 0]], -1) + + elif 'decoder' in k and 'cross_attn' in k and 'kv' in k and 'bias' in k: + k_para = k[:13] + 'layers.' + k[13:] + k_para = k_para.replace('cross_attn', 'multihead_attn') + k = params[k_para.replace('kv', 'conv2')] + v = params[k_para.replace('kv', 'conv3')] + new_state_dict[k1] = np.concatenate([k, v], -1) + + elif 'decoder' in k and 'cross_attn' in k and 'out_proj' in k: + + k_para = k[:13] + 'layers.' + k[13:] + k_para = k_para.replace('cross_attn', 'multihead_attn') + new_state_dict[k1] = params[k_para] + elif 'decoder' in k and 'norm' in k: + k_para = k[:13] + 'layers.' + k[13:] + new_state_dict[k1] = params[k_para] + elif 'mlp' in k and 'weight' in k: + k_para = k[:13] + 'layers.' + k[13:] + k_para = k_para.replace('fc', 'conv') + k_para = k_para.replace('mlp.', '') + w = params[k_para].transpose((1, 0, 2, 3)) + new_state_dict[k1] = w[:, :, 0, 0] + elif 'mlp' in k and 'bias' in k: + k_para = k[:13] + 'layers.' + k[13:] + k_para = k_para.replace('fc', 'conv') + k_para = k_para.replace('mlp.', '') + w = params[k_para] + new_state_dict[k1] = w + + else: + new_state_dict[k1] = params[k1] + + if list(new_state_dict[k1].shape) != list(v1.shape): + print(k1) + + + for k, v1 in state_dict.items(): + if k not in new_state_dict.keys(): + print(1, k) + elif list(new_state_dict[k].shape) != list(v1.shape): + print(2, k) + + + + model.set_state_dict(new_state_dict) + paddle.save(model.state_dict(), 'nrtrnew_from_old_params.pdparams') + +``` + +2. 新版相比与旧版,代码结构简洁,推理速度有所提高。 + + ## 引用 ```bibtex @article{Sheng2019NRTR, title = {NRTR: A No-Recurrence Sequence-to-Sequence Model For Scene Text Recognition}, - author = {Fenfen Sheng and Zhineng Chen andBo Xu}, + author = {Fenfen Sheng and Zhineng Chen and Bo Xu}, booktitle = {ICDAR}, year = {2019}, url = {http://arxiv.org/abs/1806.00926}, diff --git a/doc/doc_ch/algorithm_rec_svtr.md b/doc/doc_ch/algorithm_rec_svtr.md index 41a22ca65c..c0e26433e9 100644 --- a/doc/doc_ch/algorithm_rec_svtr.md +++ b/doc/doc_ch/algorithm_rec_svtr.md @@ -111,7 +111,6 @@ python3 tools/export_model.py -c ./rec_svtr_tiny_none_ctc_en_train/rec_svtr_tiny **注意:** - 如果您是在自己的数据集上训练的模型,并且调整了字典文件,请注意修改配置文件中的`character_dict_path`是否为所正确的字典文件。 -- 如果您修改了训练时的输入大小,请修改`tools/export_model.py`文件中的对应SVTR的`infer_shape`。 转换成功后,在目录下有三个文件: ``` diff --git a/doc/doc_ch/algorithm_rec_vitstr.md b/doc/doc_ch/algorithm_rec_vitstr.md new file mode 100644 index 0000000000..ab12be7202 --- /dev/null +++ b/doc/doc_ch/algorithm_rec_vitstr.md @@ -0,0 +1,154 @@ +# 场景文本识别算法-ViTSTR + +- [1. 算法简介](#1) +- [2. 环境配置](#2) +- [3. 模型训练、评估、预测](#3) + - [3.1 训练](#3-1) + - [3.2 评估](#3-2) + - [3.3 预测](#3-3) +- [4. 推理部署](#4) + - [4.1 Python推理](#4-1) + - [4.2 C++推理](#4-2) + - [4.3 Serving服务化部署](#4-3) + - [4.4 更多推理部署](#4-4) +- [5. FAQ](#5) + + +## 1. 算法简介 + +论文信息: +> [Vision Transformer for Fast and Efficient Scene Text Recognition](https://arxiv.org/abs/2105.08582) +> Rowel Atienza +> ICDAR, 2021 + + + +`ViTSTR`使用MJSynth和SynthText两个文字识别数据集训练,在IIIT, SVT, IC03, IC13, IC15, SVTP, CUTE数据集上进行评估,算法复现效果如下: + +|模型|骨干网络|配置文件|Acc|下载链接| +| --- | --- | --- | --- | --- | +|ViTSTR|ViTSTR|[rec_vitstr_none_ce.yml](../../configs/rec/rec_vitstr_none_ce.yml)|79.82%|[训练模型](https://paddleocr.bj.bcebos.com/rec_vitstr_none_ce_train.tar)| + + +## 2. 环境配置 +请先参考[《运行环境准备》](./environment.md)配置PaddleOCR运行环境,参考[《项目克隆》](./clone.md)克隆项目代码。 + + + +## 3. 模型训练、评估、预测 + + +### 3.1 模型训练 + +请参考[文本识别训练教程](./recognition.md)。PaddleOCR对代码进行了模块化,训练`ViTSTR`识别模型时需要**更换配置文件**为`ViTSTR`的[配置文件](../../configs/rec/rec_vitstr_none_ce.yml)。 + +#### 启动训练 + + +具体地,在完成数据准备后,便可以启动训练,训练命令如下: +```shell +#单卡训练(训练周期长,不建议) +python3 tools/train.py -c configs/rec/rec_vitstr_none_ce.yml + +#多卡训练,通过--gpus参数指定卡号 +python3 -m paddle.distributed.launch --gpus '0,1,2,3' tools/train.py -c configs/rec/rec_vitstr_none_ce.yml +``` + + +### 3.2 评估 + +可下载已训练完成的[模型文件](#model),使用如下命令进行评估: + +```shell +# 注意将pretrained_model的路径设置为本地路径。 +python3 -m paddle.distributed.launch --gpus '0' tools/eval.py -c configs/rec/rec_vitstr_none_ce.yml -o Global.pretrained_model=./rec_vitstr_none_ce_train/best_accuracy +``` + + +### 3.3 预测 + +使用如下命令进行单张图片预测: +```shell +# 注意将pretrained_model的路径设置为本地路径。 +python3 tools/infer_rec.py -c configs/rec/rec_vitstr_none_ce.yml -o Global.infer_img='./doc/imgs_words_en/word_10.png' Global.pretrained_model=./rec_vitstr_none_ce_train/best_accuracy +# 预测文件夹下所有图像时,可修改infer_img为文件夹,如 Global.infer_img='./doc/imgs_words_en/'。 +``` + + + +## 4. 推理部署 + + +### 4.1 Python推理 +首先将训练得到best模型,转换成inference model。这里以训练完成的模型为例([模型下载地址](https://paddleocr.bj.bcebos.com/rec_vitstr_none_ce_train.tar) ),可以使用如下命令进行转换: + +```shell +# 注意将pretrained_model的路径设置为本地路径。 +python3 tools/export_model.py -c configs/rec/rec_vitstr_none_ce.yml -o Global.pretrained_model=./rec_vitstr_none_ce_train/best_accuracy Global.save_inference_dir=./inference/rec_vitstr/ +``` +**注意:** +- 如果您是在自己的数据集上训练的模型,并且调整了字典文件,请注意修改配置文件中的`character_dict_path`是否是所需要的字典文件。 +- 如果您修改了训练时的输入大小,请修改`tools/export_model.py`文件中的对应ViTSTR的`infer_shape`。 + +转换成功后,在目录下有三个文件: +``` +/inference/rec_vitstr/ + ├── inference.pdiparams # 识别inference模型的参数文件 + ├── inference.pdiparams.info # 识别inference模型的参数信息,可忽略 + └── inference.pdmodel # 识别inference模型的program文件 +``` + +执行如下命令进行模型推理: + +```shell +python3 tools/infer/predict_rec.py --image_dir='./doc/imgs_words_en/word_10.png' --rec_model_dir='./inference/rec_vitstr/' --rec_algorithm='ViTSTR' --rec_image_shape='1,224,224' --rec_char_dict_path='./ppocr/utils/EN_symbol_dict.txt' +# 预测文件夹下所有图像时,可修改image_dir为文件夹,如 --image_dir='./doc/imgs_words_en/'。 +``` + +![](../imgs_words_en/word_10.png) + +执行命令后,上面图像的预测结果(识别的文本和得分)会打印到屏幕上,示例如下: +结果如下: +```shell +Predicts of ./doc/imgs_words_en/word_10.png:('pain', 0.9998350143432617) +``` + +**注意**: + +- 训练上述模型采用的图像分辨率是[1,224,224],需要通过参数`rec_image_shape`设置为您训练时的识别图像形状。 +- 在推理时需要设置参数`rec_char_dict_path`指定字典,如果您修改了字典,请修改该参数为您的字典文件。 +- 如果您修改了预处理方法,需修改`tools/infer/predict_rec.py`中ViTSTR的预处理为您的预处理方法。 + + + +### 4.2 C++推理部署 + +由于C++预处理后处理还未支持ViTSTR,所以暂未支持 + + +### 4.3 Serving服务化部署 + +暂不支持 + + +### 4.4 更多推理部署 + +暂不支持 + + +## 5. FAQ + +1. 在`ViTSTR`论文中,使用在ImageNet1k上的预训练权重进行初始化训练,我们在训练未采用预训练权重,最终精度没有变化甚至有所提高。 +2. 我们仅仅复现了`ViTSTR`中的tiny版本,如果需要使用small、base版本,可将[ViTSTR源repo](https://github.com/roatienza/deep-text-recognition-benchmark) 中的预训练权重转为Paddle权重使用。 + +## 引用 + +```bibtex +@article{Atienza2021ViTSTR, + title = {Vision Transformer for Fast and Efficient Scene Text Recognition}, + author = {Rowel Atienza}, + booktitle = {ICDAR}, + year = {2021}, + url = {https://arxiv.org/abs/2105.08582} +} +``` diff --git a/doc/doc_en/algorithm_overview_en.md b/doc/doc_en/algorithm_overview_en.md index 383cbe39bb..28aca7c0d1 100755 --- a/doc/doc_en/algorithm_overview_en.md +++ b/doc/doc_en/algorithm_overview_en.md @@ -65,6 +65,8 @@ Supported text recognition algorithms (Click the link to get the tutorial): - [x] [SAR](./algorithm_rec_sar_en.md) - [x] [SEED](./algorithm_rec_seed_en.md) - [x] [SVTR](./algorithm_rec_svtr_en.md) +- [x] [ViTSTR](./algorithm_rec_vitstr_en.md) +- [x] [ABINet](./algorithm_rec_abinet_en.md) Refer to [DTRB](https://arxiv.org/abs/1904.01906), the training and evaluation result of these above text recognition (using MJSynth and SynthText for training, evaluate on IIIT, SVT, IC03, IC13, IC15, SVTP, CUTE) is as follow: @@ -83,7 +85,8 @@ Refer to [DTRB](https://arxiv.org/abs/1904.01906), the training and evaluation r |SAR|Resnet31| 87.20% | rec_r31_sar | [trained model](https://paddleocr.bj.bcebos.com/dygraph_v2.1/rec/rec_r31_sar_train.tar) | |SEED|Aster_Resnet| 85.35% | rec_resnet_stn_bilstm_att | [trained model](https://paddleocr.bj.bcebos.com/dygraph_v2.1/rec/rec_resnet_stn_bilstm_att.tar) | |SVTR|SVTR-Tiny| 89.25% | rec_svtr_tiny_none_ctc_en | [trained model](https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/rec_svtr_tiny_none_ctc_en_train.tar) | - +|ViTSTR|ViTSTR| 79.82% | rec_vitstr_none_ce_en | [trained model](https://paddleocr.bj.bcebos.com/rec_vitstr_none_none_train.tar) | +|ABINet|Resnet45| 90.75% | rec_r45_abinet_en | [trained model](https://paddleocr.bj.bcebos.com/rec_r45_abinet_train.tar) | diff --git a/doc/doc_en/algorithm_rec_abinet_en.md b/doc/doc_en/algorithm_rec_abinet_en.md new file mode 100644 index 0000000000..767ca65f64 --- /dev/null +++ b/doc/doc_en/algorithm_rec_abinet_en.md @@ -0,0 +1,136 @@ +# ABINet + +- [1. Introduction](#1) +- [2. Environment](#2) +- [3. Model Training / Evaluation / Prediction](#3) + - [3.1 Training](#3-1) + - [3.2 Evaluation](#3-2) + - [3.3 Prediction](#3-3) +- [4. Inference and Deployment](#4) + - [4.1 Python Inference](#4-1) + - [4.2 C++ Inference](#4-2) + - [4.3 Serving](#4-3) + - [4.4 More](#4-4) +- [5. FAQ](#5) + + +## 1. Introduction + +Paper: +> [ABINet: Read Like Humans: Autonomous, Bidirectional and Iterative Language Modeling for Scene Text Recognition](https://openaccess.thecvf.com/content/CVPR2021/papers/Fang_Read_Like_Humans_Autonomous_Bidirectional_and_Iterative_Language_Modeling_for_CVPR_2021_paper.pdf) +> Shancheng Fang and Hongtao Xie and Yuxin Wang and Zhendong Mao and Yongdong Zhang +> CVPR, 2021 + +Using MJSynth and SynthText two text recognition datasets for training, and evaluating on IIIT, SVT, IC03, IC13, IC15, SVTP, CUTE datasets, the algorithm reproduction effect is as follows: + +|Model|Backbone|config|Acc|Download link| +| --- | --- | --- | --- | --- | +|ABINet|ResNet45|[rec_r45_abinet.yml](../../configs/rec/rec_r45_abinet.yml)|90.75%|[pretrained & trained model](https://paddleocr.bj.bcebos.com/rec_r45_abinet_train.tar)| + + +## 2. Environment +Please refer to ["Environment Preparation"](./environment_en.md) to configure the PaddleOCR environment, and refer to ["Project Clone"](./clone_en.md) to clone the project code. + + + +## 3. Model Training / Evaluation / Prediction + +Please refer to [Text Recognition Tutorial](./recognition_en.md). PaddleOCR modularizes the code, and training different recognition models only requires **changing the configuration file**. + +Training: + +Specifically, after the data preparation is completed, the training can be started. The training command is as follows: + +``` +#Single GPU training (long training period, not recommended) +python3 tools/train.py -c configs/rec/rec_r45_abinet.yml + +#Multi GPU training, specify the gpu number through the --gpus parameter +python3 -m paddle.distributed.launch --gpus '0,1,2,3' tools/train.py -c configs/rec/rec_r45_abinet.yml +``` + +Evaluation: + +``` +# GPU evaluation +python3 -m paddle.distributed.launch --gpus '0' tools/eval.py -c configs/rec/rec_r45_abinet.yml -o Global.pretrained_model={path/to/weights}/best_accuracy +``` + +Prediction: + +``` +# The configuration file used for prediction must match the training +python3 tools/infer_rec.py -c configs/rec/rec_r45_abinet.yml -o Global.infer_img='./doc/imgs_words_en/word_10.png' Global.pretrained_model=./rec_r45_abinet_train/best_accuracy +``` + + +## 4. Inference and Deployment + + +### 4.1 Python Inference +First, the model saved during the ABINet text recognition training process is converted into an inference model. ( [Model download link](https://paddleocr.bj.bcebos.com/rec_r45_abinet_train.tar)) ), you can use the following command to convert: + +``` +python3 tools/export_model.py -c configs/rec/rec_r45_abinet.yml -o Global.pretrained_model=./rec_r45_abinet_train/best_accuracy Global.save_inference_dir=./inference/rec_r45_abinet +``` + +**Note:** +- If you are training the model on your own dataset and have modified the dictionary file, please pay attention to modify the `character_dict_path` in the configuration file to the modified dictionary file. +- If you modified the input size during training, please modify the `infer_shape` corresponding to ABINet in the `tools/export_model.py` file. + +After the conversion is successful, there are three files in the directory: +``` +/inference/rec_r45_abinet/ + ├── inference.pdiparams + ├── inference.pdiparams.info + └── inference.pdmodel +``` + + +For ABINet text recognition model inference, the following commands can be executed: + +``` +python3 tools/infer/predict_rec.py --image_dir='./doc/imgs_words_en/word_10.png' --rec_model_dir='./inference/rec_r45_abinet/' --rec_algorithm='ABINet' --rec_image_shape='3,32,128' --rec_char_dict_path='./ppocr/utils/ic15_dict.txt' +``` + +![](../imgs_words_en/word_10.png) + +After executing the command, the prediction result (recognized text and score) of the image above is printed to the screen, an example is as follows: +The result is as follows: +```shell +Predicts of ./doc/imgs_words_en/word_10.png:('pain', 0.9999995231628418) +``` + + +### 4.2 C++ Inference + +Not supported + + +### 4.3 Serving + +Not supported + + +### 4.4 More + +Not supported + + +## 5. FAQ + +1. Note that the MJSynth and SynthText datasets come from [ABINet repo](https://github.com/FangShancheng/ABINet). +2. We use the pre-trained model provided by the ABINet authors for finetune training. + +## Citation + +```bibtex +@article{Fang2021ABINet, + title = {ABINet: Read Like Humans: Autonomous, Bidirectional and Iterative Language Modeling for Scene Text Recognition}, + author = {Shancheng Fang and Hongtao Xie and Yuxin Wang and Zhendong Mao and Yongdong Zhang}, + booktitle = {CVPR}, + year = {2021}, + url = {https://arxiv.org/abs/2103.06495}, + pages = {7098-7107} +} +``` diff --git a/doc/doc_en/algorithm_rec_nrtr_en.md b/doc/doc_en/algorithm_rec_nrtr_en.md index 40c9b91629..309d7ab123 100644 --- a/doc/doc_en/algorithm_rec_nrtr_en.md +++ b/doc/doc_en/algorithm_rec_nrtr_en.md @@ -12,6 +12,7 @@ - [4.3 Serving](#4-3) - [4.4 More](#4-4) - [5. FAQ](#5) +- [6. Release Note](#6) ## 1. Introduction @@ -25,7 +26,7 @@ Using MJSynth and SynthText two text recognition datasets for training, and eval |Model|Backbone|config|Acc|Download link| | --- | --- | --- | --- | --- | -|NRTR|MTB|[rec_mtb_nrtr.yml](../../configs/rec/rec_mtb_nrtr.yml)|84.21%|[train model](https://paddleocr.bj.bcebos.com/dygraph_v2.0/en/rec_mtb_nrtr_train.tar)| +|NRTR|MTB|[rec_mtb_nrtr.yml](../../configs/rec/rec_mtb_nrtr.yml)|84.21%|[trained model](https://paddleocr.bj.bcebos.com/dygraph_v2.0/en/rec_mtb_nrtr_train.tar)| ## 2. Environment @@ -98,7 +99,7 @@ python3 tools/infer/predict_rec.py --image_dir='./doc/imgs_words_en/word_10.png' After executing the command, the prediction result (recognized text and score) of the image above is printed to the screen, an example is as follows: The result is as follows: ```shell -Predicts of ./doc/imgs_words_en/word_10.png:('pain', 0.9265879392623901) +Predicts of ./doc/imgs_words_en/word_10.png:('pain', 0.9465042352676392) ``` @@ -121,12 +122,146 @@ Not supported 1. In the `NRTR` paper, Beam search is used to decode characters, but the speed is slow. Beam search is not used by default here, and greedy search is used to decode characters. + +## 6. Release Note + +1. The release/2.6 version updates the NRTR code structure. The new version of NRTR can load the model parameters of the old version (release/2.5 and before), and you may use the following code to convert the old version model parameters to the new version model parameters: + +```python + + params = paddle.load('path/' + '.pdparams') # the old version parameters + state_dict = model.state_dict() # the new version model parameters + new_state_dict = {} + + for k1, v1 in state_dict.items(): + + k = k1 + if 'encoder' in k and 'self_attn' in k and 'qkv' in k and 'weight' in k: + + k_para = k[:13] + 'layers.' + k[13:] + q = params[k_para.replace('qkv', 'conv1')].transpose((1, 0, 2, 3)) + k = params[k_para.replace('qkv', 'conv2')].transpose((1, 0, 2, 3)) + v = params[k_para.replace('qkv', 'conv3')].transpose((1, 0, 2, 3)) + + new_state_dict[k1] = np.concatenate([q[:, :, 0, 0], k[:, :, 0, 0], v[:, :, 0, 0]], -1) + + elif 'encoder' in k and 'self_attn' in k and 'qkv' in k and 'bias' in k: + + k_para = k[:13] + 'layers.' + k[13:] + q = params[k_para.replace('qkv', 'conv1')] + k = params[k_para.replace('qkv', 'conv2')] + v = params[k_para.replace('qkv', 'conv3')] + + new_state_dict[k1] = np.concatenate([q, k, v], -1) + + elif 'encoder' in k and 'self_attn' in k and 'out_proj' in k: + + k_para = k[:13] + 'layers.' + k[13:] + new_state_dict[k1] = params[k_para] + + elif 'encoder' in k and 'norm3' in k: + k_para = k[:13] + 'layers.' + k[13:] + new_state_dict[k1] = params[k_para.replace('norm3', 'norm2')] + + elif 'encoder' in k and 'norm1' in k: + k_para = k[:13] + 'layers.' + k[13:] + new_state_dict[k1] = params[k_para] + + + elif 'decoder' in k and 'self_attn' in k and 'qkv' in k and 'weight' in k: + k_para = k[:13] + 'layers.' + k[13:] + q = params[k_para.replace('qkv', 'conv1')].transpose((1, 0, 2, 3)) + k = params[k_para.replace('qkv', 'conv2')].transpose((1, 0, 2, 3)) + v = params[k_para.replace('qkv', 'conv3')].transpose((1, 0, 2, 3)) + new_state_dict[k1] = np.concatenate([q[:, :, 0, 0], k[:, :, 0, 0], v[:, :, 0, 0]], -1) + + elif 'decoder' in k and 'self_attn' in k and 'qkv' in k and 'bias' in k: + k_para = k[:13] + 'layers.' + k[13:] + q = params[k_para.replace('qkv', 'conv1')] + k = params[k_para.replace('qkv', 'conv2')] + v = params[k_para.replace('qkv', 'conv3')] + new_state_dict[k1] = np.concatenate([q, k, v], -1) + + elif 'decoder' in k and 'self_attn' in k and 'out_proj' in k: + + k_para = k[:13] + 'layers.' + k[13:] + new_state_dict[k1] = params[k_para] + + elif 'decoder' in k and 'cross_attn' in k and 'q' in k and 'weight' in k: + k_para = k[:13] + 'layers.' + k[13:] + k_para = k_para.replace('cross_attn', 'multihead_attn') + q = params[k_para.replace('q', 'conv1')].transpose((1, 0, 2, 3)) + new_state_dict[k1] = q[:, :, 0, 0] + + elif 'decoder' in k and 'cross_attn' in k and 'q' in k and 'bias' in k: + k_para = k[:13] + 'layers.' + k[13:] + k_para = k_para.replace('cross_attn', 'multihead_attn') + q = params[k_para.replace('q', 'conv1')] + new_state_dict[k1] = q + + elif 'decoder' in k and 'cross_attn' in k and 'kv' in k and 'weight' in k: + k_para = k[:13] + 'layers.' + k[13:] + k_para = k_para.replace('cross_attn', 'multihead_attn') + k = params[k_para.replace('kv', 'conv2')].transpose((1, 0, 2, 3)) + v = params[k_para.replace('kv', 'conv3')].transpose((1, 0, 2, 3)) + new_state_dict[k1] = np.concatenate([k[:, :, 0, 0], v[:, :, 0, 0]], -1) + + elif 'decoder' in k and 'cross_attn' in k and 'kv' in k and 'bias' in k: + k_para = k[:13] + 'layers.' + k[13:] + k_para = k_para.replace('cross_attn', 'multihead_attn') + k = params[k_para.replace('kv', 'conv2')] + v = params[k_para.replace('kv', 'conv3')] + new_state_dict[k1] = np.concatenate([k, v], -1) + + elif 'decoder' in k and 'cross_attn' in k and 'out_proj' in k: + + k_para = k[:13] + 'layers.' + k[13:] + k_para = k_para.replace('cross_attn', 'multihead_attn') + new_state_dict[k1] = params[k_para] + elif 'decoder' in k and 'norm' in k: + k_para = k[:13] + 'layers.' + k[13:] + new_state_dict[k1] = params[k_para] + elif 'mlp' in k and 'weight' in k: + k_para = k[:13] + 'layers.' + k[13:] + k_para = k_para.replace('fc', 'conv') + k_para = k_para.replace('mlp.', '') + w = params[k_para].transpose((1, 0, 2, 3)) + new_state_dict[k1] = w[:, :, 0, 0] + elif 'mlp' in k and 'bias' in k: + k_para = k[:13] + 'layers.' + k[13:] + k_para = k_para.replace('fc', 'conv') + k_para = k_para.replace('mlp.', '') + w = params[k_para] + new_state_dict[k1] = w + + else: + new_state_dict[k1] = params[k1] + + if list(new_state_dict[k1].shape) != list(v1.shape): + print(k1) + + + for k, v1 in state_dict.items(): + if k not in new_state_dict.keys(): + print(1, k) + elif list(new_state_dict[k].shape) != list(v1.shape): + print(2, k) + + + + model.set_state_dict(new_state_dict) + paddle.save(model.state_dict(), 'nrtrnew_from_old_params.pdparams') + +``` + +2. The new version has a clean code structure and improved inference speed compared with the old version. + ## Citation ```bibtex @article{Sheng2019NRTR, title = {NRTR: A No-Recurrence Sequence-to-Sequence Model For Scene Text Recognition}, - author = {Fenfen Sheng and Zhineng Chen andBo Xu}, + author = {Fenfen Sheng and Zhineng Chen and Bo Xu}, booktitle = {ICDAR}, year = {2019}, url = {http://arxiv.org/abs/1806.00926}, diff --git a/doc/doc_en/algorithm_rec_svtr_en.md b/doc/doc_en/algorithm_rec_svtr_en.md index d402a6b491..37cd35f35a 100644 --- a/doc/doc_en/algorithm_rec_svtr_en.md +++ b/doc/doc_en/algorithm_rec_svtr_en.md @@ -88,7 +88,6 @@ python3 tools/export_model.py -c configs/rec/rec_svtrnet.yml -o Global.pretraine **Note:** - If you are training the model on your own dataset and have modified the dictionary file, please pay attention to modify the `character_dict_path` in the configuration file to the modified dictionary file. -- If you modified the input size during training, please modify the `infer_shape` corresponding to SVTR in the `tools/export_model.py` file. After the conversion is successful, there are three files in the directory: ``` diff --git a/doc/doc_en/algorithm_rec_vitstr_en.md b/doc/doc_en/algorithm_rec_vitstr_en.md new file mode 100644 index 0000000000..a6f9e2f15d --- /dev/null +++ b/doc/doc_en/algorithm_rec_vitstr_en.md @@ -0,0 +1,134 @@ +# ViTSTR + +- [1. Introduction](#1) +- [2. Environment](#2) +- [3. Model Training / Evaluation / Prediction](#3) + - [3.1 Training](#3-1) + - [3.2 Evaluation](#3-2) + - [3.3 Prediction](#3-3) +- [4. Inference and Deployment](#4) + - [4.1 Python Inference](#4-1) + - [4.2 C++ Inference](#4-2) + - [4.3 Serving](#4-3) + - [4.4 More](#4-4) +- [5. FAQ](#5) + + +## 1. Introduction + +Paper: +> [Vision Transformer for Fast and Efficient Scene Text Recognition](https://arxiv.org/abs/2105.08582) +> Rowel Atienza +> ICDAR, 2021 + +Using MJSynth and SynthText two text recognition datasets for training, and evaluating on IIIT, SVT, IC03, IC13, IC15, SVTP, CUTE datasets, the algorithm reproduction effect is as follows: + +|Model|Backbone|config|Acc|Download link| +| --- | --- | --- | --- | --- | +|ViTSTR|ViTSTR|[rec_vitstr_none_ce.yml](../../configs/rec/rec_vitstr_none_ce.yml)|79.82%|[trained model](https://paddleocr.bj.bcebos.com/rec_vitstr_none_none_train.tar)| + + +## 2. Environment +Please refer to ["Environment Preparation"](./environment_en.md) to configure the PaddleOCR environment, and refer to ["Project Clone"](./clone_en.md) to clone the project code. + + + +## 3. Model Training / Evaluation / Prediction + +Please refer to [Text Recognition Tutorial](./recognition_en.md). PaddleOCR modularizes the code, and training different recognition models only requires **changing the configuration file**. + +Training: + +Specifically, after the data preparation is completed, the training can be started. The training command is as follows: + +``` +#Single GPU training (long training period, not recommended) +python3 tools/train.py -c configs/rec/rec_vitstr_none_ce.yml + +#Multi GPU training, specify the gpu number through the --gpus parameter +python3 -m paddle.distributed.launch --gpus '0,1,2,3' tools/train.py -c configs/rec/rec_vitstr_none_ce.yml +``` + +Evaluation: + +``` +# GPU evaluation +python3 -m paddle.distributed.launch --gpus '0' tools/eval.py -c configs/rec/rec_vitstr_none_ce.yml -o Global.pretrained_model={path/to/weights}/best_accuracy +``` + +Prediction: + +``` +# The configuration file used for prediction must match the training +python3 tools/infer_rec.py -c configs/rec/rec_vitstr_none_ce.yml -o Global.infer_img='./doc/imgs_words_en/word_10.png' Global.pretrained_model=./rec_vitstr_none_ce_train/best_accuracy +``` + + +## 4. Inference and Deployment + + +### 4.1 Python Inference +First, the model saved during the ViTSTR text recognition training process is converted into an inference model. ( [Model download link](https://paddleocr.bj.bcebos.com/rec_vitstr_none_none_train.tar)) ), you can use the following command to convert: + +``` +python3 tools/export_model.py -c configs/rec/rec_vitstr_none_ce.yml -o Global.pretrained_model=./rec_vitstr_none_ce_train/best_accuracy Global.save_inference_dir=./inference/rec_vitstr +``` + +**Note:** +- If you are training the model on your own dataset and have modified the dictionary file, please pay attention to modify the `character_dict_path` in the configuration file to the modified dictionary file. +- If you modified the input size during training, please modify the `infer_shape` corresponding to ViTSTR in the `tools/export_model.py` file. + +After the conversion is successful, there are three files in the directory: +``` +/inference/rec_vitstr/ + ├── inference.pdiparams + ├── inference.pdiparams.info + └── inference.pdmodel +``` + + +For ViTSTR text recognition model inference, the following commands can be executed: + +``` +python3 tools/infer/predict_rec.py --image_dir='./doc/imgs_words_en/word_10.png' --rec_model_dir='./inference/rec_vitstr/' --rec_algorithm='ViTSTR' --rec_image_shape='1,224,224' --rec_char_dict_path='./ppocr/utils/EN_symbol_dict.txt' +``` + +![](../imgs_words_en/word_10.png) + +After executing the command, the prediction result (recognized text and score) of the image above is printed to the screen, an example is as follows: +The result is as follows: +```shell +Predicts of ./doc/imgs_words_en/word_10.png:('pain', 0.9998350143432617) +``` + + +### 4.2 C++ Inference + +Not supported + + +### 4.3 Serving + +Not supported + + +### 4.4 More + +Not supported + + +## 5. FAQ + +1. In the `ViTSTR` paper, using pre-trained weights on ImageNet1k for initial training, we did not use pre-trained weights in training, and the final accuracy did not change or even improved. + +## Citation + +```bibtex +@article{Atienza2021ViTSTR, + title = {Vision Transformer for Fast and Efficient Scene Text Recognition}, + author = {Rowel Atienza}, + booktitle = {ICDAR}, + year = {2021}, + url = {https://arxiv.org/abs/2105.08582} +} +``` diff --git a/ppocr/data/imaug/__init__.py b/ppocr/data/imaug/__init__.py index f0fd578f61..63dfda91f8 100644 --- a/ppocr/data/imaug/__init__.py +++ b/ppocr/data/imaug/__init__.py @@ -22,8 +22,10 @@ from .make_shrink_map import MakeShrinkMap from .random_crop_data import EastRandomCropData, RandomCropImgMask from .make_pse_gt import MakePseGt -from .rec_img_aug import BaseDataAugmentation, RecAug, RecConAug, RecResizeImg, ClsResizeImg, \ - SRNRecResizeImg, NRTRRecResizeImg, SARRecResizeImg, PRENResizeImg + +from .rec_img_aug import RecAug, RecConAug, RecResizeImg, ClsResizeImg, \ + SRNRecResizeImg, GrayRecResizeImg, SARRecResizeImg, PRENResizeImg, \ + ABINetRecResizeImg, SVTRRecResizeImg, ABINetRecAug from .ssl_img_aug import SSLRotateResize from .randaugment import RandAugment from .copy_paste import CopyPaste diff --git a/ppocr/data/imaug/abinet_aug.py b/ppocr/data/imaug/abinet_aug.py new file mode 100644 index 0000000000..eefdc75d5a --- /dev/null +++ b/ppocr/data/imaug/abinet_aug.py @@ -0,0 +1,407 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# 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. +""" +This code is refer from: +https://github.com/FangShancheng/ABINet/blob/main/transforms.py +""" +import math +import numbers +import random + +import cv2 +import numpy as np +from paddle.vision.transforms import Compose, ColorJitter + + +def sample_asym(magnitude, size=None): + return np.random.beta(1, 4, size) * magnitude + + +def sample_sym(magnitude, size=None): + return (np.random.beta(4, 4, size=size) - 0.5) * 2 * magnitude + + +def sample_uniform(low, high, size=None): + return np.random.uniform(low, high, size=size) + + +def get_interpolation(type='random'): + if type == 'random': + choice = [ + cv2.INTER_NEAREST, cv2.INTER_LINEAR, cv2.INTER_CUBIC, cv2.INTER_AREA + ] + interpolation = choice[random.randint(0, len(choice) - 1)] + elif type == 'nearest': + interpolation = cv2.INTER_NEAREST + elif type == 'linear': + interpolation = cv2.INTER_LINEAR + elif type == 'cubic': + interpolation = cv2.INTER_CUBIC + elif type == 'area': + interpolation = cv2.INTER_AREA + else: + raise TypeError( + 'Interpolation types only nearest, linear, cubic, area are supported!' + ) + return interpolation + + +class CVRandomRotation(object): + def __init__(self, degrees=15): + assert isinstance(degrees, + numbers.Number), "degree should be a single number." + assert degrees >= 0, "degree must be positive." + self.degrees = degrees + + @staticmethod + def get_params(degrees): + return sample_sym(degrees) + + def __call__(self, img): + angle = self.get_params(self.degrees) + src_h, src_w = img.shape[:2] + M = cv2.getRotationMatrix2D( + center=(src_w / 2, src_h / 2), angle=angle, scale=1.0) + abs_cos, abs_sin = abs(M[0, 0]), abs(M[0, 1]) + dst_w = int(src_h * abs_sin + src_w * abs_cos) + dst_h = int(src_h * abs_cos + src_w * abs_sin) + M[0, 2] += (dst_w - src_w) / 2 + M[1, 2] += (dst_h - src_h) / 2 + + flags = get_interpolation() + return cv2.warpAffine( + img, + M, (dst_w, dst_h), + flags=flags, + borderMode=cv2.BORDER_REPLICATE) + + +class CVRandomAffine(object): + def __init__(self, degrees, translate=None, scale=None, shear=None): + assert isinstance(degrees, + numbers.Number), "degree should be a single number." + assert degrees >= 0, "degree must be positive." + self.degrees = degrees + + if translate is not None: + assert isinstance(translate, (tuple, list)) and len(translate) == 2, \ + "translate should be a list or tuple and it must be of length 2." + for t in translate: + if not (0.0 <= t <= 1.0): + raise ValueError( + "translation values should be between 0 and 1") + self.translate = translate + + if scale is not None: + assert isinstance(scale, (tuple, list)) and len(scale) == 2, \ + "scale should be a list or tuple and it must be of length 2." + for s in scale: + if s <= 0: + raise ValueError("scale values should be positive") + self.scale = scale + + if shear is not None: + if isinstance(shear, numbers.Number): + if shear < 0: + raise ValueError( + "If shear is a single number, it must be positive.") + self.shear = [shear] + else: + assert isinstance(shear, (tuple, list)) and (len(shear) == 2), \ + "shear should be a list or tuple and it must be of length 2." + self.shear = shear + else: + self.shear = shear + + def _get_inverse_affine_matrix(self, center, angle, translate, scale, + shear): + # https://github.com/pytorch/vision/blob/v0.4.0/torchvision/transforms/functional.py#L717 + from numpy import sin, cos, tan + + if isinstance(shear, numbers.Number): + shear = [shear, 0] + + if not isinstance(shear, (tuple, list)) and len(shear) == 2: + raise ValueError( + "Shear should be a single value or a tuple/list containing " + + "two values. Got {}".format(shear)) + + rot = math.radians(angle) + sx, sy = [math.radians(s) for s in shear] + + cx, cy = center + tx, ty = translate + + # RSS without scaling + a = cos(rot - sy) / cos(sy) + b = -cos(rot - sy) * tan(sx) / cos(sy) - sin(rot) + c = sin(rot - sy) / cos(sy) + d = -sin(rot - sy) * tan(sx) / cos(sy) + cos(rot) + + # Inverted rotation matrix with scale and shear + # det([[a, b], [c, d]]) == 1, since det(rotation) = 1 and det(shear) = 1 + M = [d, -b, 0, -c, a, 0] + M = [x / scale for x in M] + + # Apply inverse of translation and of center translation: RSS^-1 * C^-1 * T^-1 + M[2] += M[0] * (-cx - tx) + M[1] * (-cy - ty) + M[5] += M[3] * (-cx - tx) + M[4] * (-cy - ty) + + # Apply center translation: C * RSS^-1 * C^-1 * T^-1 + M[2] += cx + M[5] += cy + return M + + @staticmethod + def get_params(degrees, translate, scale_ranges, shears, height): + angle = sample_sym(degrees) + if translate is not None: + max_dx = translate[0] * height + max_dy = translate[1] * height + translations = (np.round(sample_sym(max_dx)), + np.round(sample_sym(max_dy))) + else: + translations = (0, 0) + + if scale_ranges is not None: + scale = sample_uniform(scale_ranges[0], scale_ranges[1]) + else: + scale = 1.0 + + if shears is not None: + if len(shears) == 1: + shear = [sample_sym(shears[0]), 0.] + elif len(shears) == 2: + shear = [sample_sym(shears[0]), sample_sym(shears[1])] + else: + shear = 0.0 + + return angle, translations, scale, shear + + def __call__(self, img): + src_h, src_w = img.shape[:2] + angle, translate, scale, shear = self.get_params( + self.degrees, self.translate, self.scale, self.shear, src_h) + + M = self._get_inverse_affine_matrix((src_w / 2, src_h / 2), angle, + (0, 0), scale, shear) + M = np.array(M).reshape(2, 3) + + startpoints = [(0, 0), (src_w - 1, 0), (src_w - 1, src_h - 1), + (0, src_h - 1)] + project = lambda x, y, a, b, c: int(a * x + b * y + c) + endpoints = [(project(x, y, *M[0]), project(x, y, *M[1])) + for x, y in startpoints] + + rect = cv2.minAreaRect(np.array(endpoints)) + bbox = cv2.boxPoints(rect).astype(dtype=np.int) + max_x, max_y = bbox[:, 0].max(), bbox[:, 1].max() + min_x, min_y = bbox[:, 0].min(), bbox[:, 1].min() + + dst_w = int(max_x - min_x) + dst_h = int(max_y - min_y) + M[0, 2] += (dst_w - src_w) / 2 + M[1, 2] += (dst_h - src_h) / 2 + + # add translate + dst_w += int(abs(translate[0])) + dst_h += int(abs(translate[1])) + if translate[0] < 0: M[0, 2] += abs(translate[0]) + if translate[1] < 0: M[1, 2] += abs(translate[1]) + + flags = get_interpolation() + return cv2.warpAffine( + img, + M, (dst_w, dst_h), + flags=flags, + borderMode=cv2.BORDER_REPLICATE) + + +class CVRandomPerspective(object): + def __init__(self, distortion=0.5): + self.distortion = distortion + + def get_params(self, width, height, distortion): + offset_h = sample_asym( + distortion * height / 2, size=4).astype(dtype=np.int) + offset_w = sample_asym( + distortion * width / 2, size=4).astype(dtype=np.int) + topleft = (offset_w[0], offset_h[0]) + topright = (width - 1 - offset_w[1], offset_h[1]) + botright = (width - 1 - offset_w[2], height - 1 - offset_h[2]) + botleft = (offset_w[3], height - 1 - offset_h[3]) + + startpoints = [(0, 0), (width - 1, 0), (width - 1, height - 1), + (0, height - 1)] + endpoints = [topleft, topright, botright, botleft] + return np.array( + startpoints, dtype=np.float32), np.array( + endpoints, dtype=np.float32) + + def __call__(self, img): + height, width = img.shape[:2] + startpoints, endpoints = self.get_params(width, height, self.distortion) + M = cv2.getPerspectiveTransform(startpoints, endpoints) + + # TODO: more robust way to crop image + rect = cv2.minAreaRect(endpoints) + bbox = cv2.boxPoints(rect).astype(dtype=np.int) + max_x, max_y = bbox[:, 0].max(), bbox[:, 1].max() + min_x, min_y = bbox[:, 0].min(), bbox[:, 1].min() + min_x, min_y = max(min_x, 0), max(min_y, 0) + + flags = get_interpolation() + img = cv2.warpPerspective( + img, + M, (max_x, max_y), + flags=flags, + borderMode=cv2.BORDER_REPLICATE) + img = img[min_y:, min_x:] + return img + + +class CVRescale(object): + def __init__(self, factor=4, base_size=(128, 512)): + """ Define image scales using gaussian pyramid and rescale image to target scale. + + Args: + factor: the decayed factor from base size, factor=4 keeps target scale by default. + base_size: base size the build the bottom layer of pyramid + """ + if isinstance(factor, numbers.Number): + self.factor = round(sample_uniform(0, factor)) + elif isinstance(factor, (tuple, list)) and len(factor) == 2: + self.factor = round(sample_uniform(factor[0], factor[1])) + else: + raise Exception('factor must be number or list with length 2') + # assert factor is valid + self.base_h, self.base_w = base_size[:2] + + def __call__(self, img): + if self.factor == 0: return img + src_h, src_w = img.shape[:2] + cur_w, cur_h = self.base_w, self.base_h + scale_img = cv2.resize( + img, (cur_w, cur_h), interpolation=get_interpolation()) + for _ in range(self.factor): + scale_img = cv2.pyrDown(scale_img) + scale_img = cv2.resize( + scale_img, (src_w, src_h), interpolation=get_interpolation()) + return scale_img + + +class CVGaussianNoise(object): + def __init__(self, mean=0, var=20): + self.mean = mean + if isinstance(var, numbers.Number): + self.var = max(int(sample_asym(var)), 1) + elif isinstance(var, (tuple, list)) and len(var) == 2: + self.var = int(sample_uniform(var[0], var[1])) + else: + raise Exception('degree must be number or list with length 2') + + def __call__(self, img): + noise = np.random.normal(self.mean, self.var**0.5, img.shape) + img = np.clip(img + noise, 0, 255).astype(np.uint8) + return img + + +class CVMotionBlur(object): + def __init__(self, degrees=12, angle=90): + if isinstance(degrees, numbers.Number): + self.degree = max(int(sample_asym(degrees)), 1) + elif isinstance(degrees, (tuple, list)) and len(degrees) == 2: + self.degree = int(sample_uniform(degrees[0], degrees[1])) + else: + raise Exception('degree must be number or list with length 2') + self.angle = sample_uniform(-angle, angle) + + def __call__(self, img): + M = cv2.getRotationMatrix2D((self.degree // 2, self.degree // 2), + self.angle, 1) + motion_blur_kernel = np.zeros((self.degree, self.degree)) + motion_blur_kernel[self.degree // 2, :] = 1 + motion_blur_kernel = cv2.warpAffine(motion_blur_kernel, M, + (self.degree, self.degree)) + motion_blur_kernel = motion_blur_kernel / self.degree + img = cv2.filter2D(img, -1, motion_blur_kernel) + img = np.clip(img, 0, 255).astype(np.uint8) + return img + + +class CVGeometry(object): + def __init__(self, + degrees=15, + translate=(0.3, 0.3), + scale=(0.5, 2.), + shear=(45, 15), + distortion=0.5, + p=0.5): + self.p = p + type_p = random.random() + if type_p < 0.33: + self.transforms = CVRandomRotation(degrees=degrees) + elif type_p < 0.66: + self.transforms = CVRandomAffine( + degrees=degrees, translate=translate, scale=scale, shear=shear) + else: + self.transforms = CVRandomPerspective(distortion=distortion) + + def __call__(self, img): + if random.random() < self.p: + return self.transforms(img) + else: + return img + + +class CVDeterioration(object): + def __init__(self, var, degrees, factor, p=0.5): + self.p = p + transforms = [] + if var is not None: + transforms.append(CVGaussianNoise(var=var)) + if degrees is not None: + transforms.append(CVMotionBlur(degrees=degrees)) + if factor is not None: + transforms.append(CVRescale(factor=factor)) + + random.shuffle(transforms) + transforms = Compose(transforms) + self.transforms = transforms + + def __call__(self, img): + if random.random() < self.p: + + return self.transforms(img) + else: + return img + + +class CVColorJitter(object): + def __init__(self, + brightness=0.5, + contrast=0.5, + saturation=0.5, + hue=0.1, + p=0.5): + self.p = p + self.transforms = ColorJitter( + brightness=brightness, + contrast=contrast, + saturation=saturation, + hue=hue) + + def __call__(self, img): + if random.random() < self.p: return self.transforms(img) + else: return img diff --git a/ppocr/data/imaug/label_ops.py b/ppocr/data/imaug/label_ops.py index 39c2cbbac8..312d6dc9ad 100644 --- a/ppocr/data/imaug/label_ops.py +++ b/ppocr/data/imaug/label_ops.py @@ -157,37 +157,6 @@ class BaseRecLabelEncode(object): return text_list -class NRTRLabelEncode(BaseRecLabelEncode): - """ Convert between text-label and text-index """ - - def __init__(self, - max_text_length, - character_dict_path=None, - use_space_char=False, - **kwargs): - - super(NRTRLabelEncode, self).__init__( - max_text_length, character_dict_path, use_space_char) - - def __call__(self, data): - text = data['label'] - text = self.encode(text) - if text is None: - return None - if len(text) >= self.max_text_len - 1: - return None - data['length'] = np.array(len(text)) - text.insert(0, 2) - text.append(3) - text = text + [0] * (self.max_text_len - len(text)) - data['label'] = np.array(text) - return data - - def add_special_char(self, dict_character): - dict_character = ['blank', '', '', ''] + dict_character - return dict_character - - class CTCLabelEncode(BaseRecLabelEncode): """ Convert between text-label and text-index """ @@ -1046,3 +1015,99 @@ class MultiLabelEncode(BaseRecLabelEncode): data_out['label_sar'] = sar['label'] data_out['length'] = ctc['length'] return data_out + + +class NRTRLabelEncode(BaseRecLabelEncode): + """ Convert between text-label and text-index """ + + def __init__(self, + max_text_length, + character_dict_path=None, + use_space_char=False, + **kwargs): + + super(NRTRLabelEncode, self).__init__( + max_text_length, character_dict_path, use_space_char) + + def __call__(self, data): + text = data['label'] + text = self.encode(text) + if text is None: + return None + if len(text) >= self.max_text_len - 1: + return None + data['length'] = np.array(len(text)) + text.insert(0, 2) + text.append(3) + text = text + [0] * (self.max_text_len - len(text)) + data['label'] = np.array(text) + return data + + def add_special_char(self, dict_character): + dict_character = ['blank', '', '', ''] + dict_character + return dict_character + + +class ViTSTRLabelEncode(BaseRecLabelEncode): + """ Convert between text-label and text-index """ + + def __init__(self, + max_text_length, + character_dict_path=None, + use_space_char=False, + ignore_index=0, + **kwargs): + + super(ViTSTRLabelEncode, self).__init__( + max_text_length, character_dict_path, use_space_char) + self.ignore_index = ignore_index + + def __call__(self, data): + text = data['label'] + text = self.encode(text) + if text is None: + return None + if len(text) >= self.max_text_len: + return None + data['length'] = np.array(len(text)) + text.insert(0, self.ignore_index) + text.append(1) + text = text + [self.ignore_index] * (self.max_text_len + 2 - len(text)) + data['label'] = np.array(text) + return data + + def add_special_char(self, dict_character): + dict_character = ['', ''] + dict_character + return dict_character + + +class ABINetLabelEncode(BaseRecLabelEncode): + """ Convert between text-label and text-index """ + + def __init__(self, + max_text_length, + character_dict_path=None, + use_space_char=False, + ignore_index=100, + **kwargs): + + super(ABINetLabelEncode, self).__init__( + max_text_length, character_dict_path, use_space_char) + self.ignore_index = ignore_index + + def __call__(self, data): + text = data['label'] + text = self.encode(text) + if text is None: + return None + if len(text) >= self.max_text_len: + return None + data['length'] = np.array(len(text)) + text.append(0) + text = text + [self.ignore_index] * (self.max_text_len + 1 - len(text)) + data['label'] = np.array(text) + return data + + def add_special_char(self, dict_character): + dict_character = [''] + dict_character + return dict_character diff --git a/ppocr/data/imaug/operators.py b/ppocr/data/imaug/operators.py index 09736515e7..5397d71ccb 100644 --- a/ppocr/data/imaug/operators.py +++ b/ppocr/data/imaug/operators.py @@ -67,39 +67,6 @@ class DecodeImage(object): return data -class NRTRDecodeImage(object): - """ decode image """ - - def __init__(self, img_mode='RGB', channel_first=False, **kwargs): - self.img_mode = img_mode - self.channel_first = channel_first - - def __call__(self, data): - img = data['image'] - if six.PY2: - assert type(img) is str and len( - img) > 0, "invalid input 'img' in DecodeImage" - else: - assert type(img) is bytes and len( - img) > 0, "invalid input 'img' in DecodeImage" - img = np.frombuffer(img, dtype='uint8') - - img = cv2.imdecode(img, 1) - - if img is None: - return None - if self.img_mode == 'GRAY': - img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) - elif self.img_mode == 'RGB': - assert img.shape[2] == 3, 'invalid shape of image[%s]' % (img.shape) - img = img[:, :, ::-1] - img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) - if self.channel_first: - img = img.transpose((2, 0, 1)) - data['image'] = img - return data - - class NormalizeImage(object): """ normalize image such as substract mean, divide std """ diff --git a/ppocr/data/imaug/rec_img_aug.py b/ppocr/data/imaug/rec_img_aug.py index 32de2b3fc3..26773d0a51 100644 --- a/ppocr/data/imaug/rec_img_aug.py +++ b/ppocr/data/imaug/rec_img_aug.py @@ -19,6 +19,8 @@ import random import copy from PIL import Image from .text_image_aug import tia_perspective, tia_stretch, tia_distort +from .abinet_aug import CVGeometry, CVDeterioration, CVColorJitter +from paddle.vision.transforms import Compose class RecAug(object): @@ -94,6 +96,36 @@ class BaseDataAugmentation(object): return data +class ABINetRecAug(object): + def __init__(self, + geometry_p=0.5, + deterioration_p=0.25, + colorjitter_p=0.25, + **kwargs): + self.transforms = Compose([ + CVGeometry( + degrees=45, + translate=(0.0, 0.0), + scale=(0.5, 2.), + shear=(45, 15), + distortion=0.5, + p=geometry_p), CVDeterioration( + var=20, degrees=6, factor=4, p=deterioration_p), + CVColorJitter( + brightness=0.5, + contrast=0.5, + saturation=0.5, + hue=0.1, + p=colorjitter_p) + ]) + + def __call__(self, data): + img = data['image'] + img = self.transforms(img) + data['image'] = img + return data + + class RecConAug(object): def __init__(self, prob=0.5, @@ -148,46 +180,6 @@ class ClsResizeImg(object): return data -class NRTRRecResizeImg(object): - def __init__(self, image_shape, resize_type, padding=False, **kwargs): - self.image_shape = image_shape - self.resize_type = resize_type - self.padding = padding - - def __call__(self, data): - img = data['image'] - img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) - image_shape = self.image_shape - if self.padding: - imgC, imgH, imgW = image_shape - # todo: change to 0 and modified image shape - 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)) - norm_img = np.expand_dims(resized_image, -1) - norm_img = norm_img.transpose((2, 0, 1)) - resized_image = norm_img.astype(np.float32) / 128. - 1. - padding_im = np.zeros((imgC, imgH, imgW), dtype=np.float32) - padding_im[:, :, 0:resized_w] = resized_image - data['image'] = padding_im - return data - if self.resize_type == 'PIL': - image_pil = Image.fromarray(np.uint8(img)) - img = image_pil.resize(self.image_shape, Image.ANTIALIAS) - img = np.array(img) - if self.resize_type == 'OpenCV': - img = cv2.resize(img, self.image_shape) - norm_img = np.expand_dims(img, -1) - norm_img = norm_img.transpose((2, 0, 1)) - data['image'] = norm_img.astype(np.float32) / 128. - 1. - return data - - class RecResizeImg(object): def __init__(self, image_shape, @@ -268,6 +260,84 @@ class PRENResizeImg(object): return data +class GrayRecResizeImg(object): + def __init__(self, + image_shape, + resize_type, + inter_type='Image.ANTIALIAS', + scale=True, + padding=False, + **kwargs): + self.image_shape = image_shape + self.resize_type = resize_type + self.padding = padding + self.inter_type = eval(inter_type) + self.scale = scale + + def __call__(self, data): + img = data['image'] + img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + image_shape = self.image_shape + if self.padding: + imgC, imgH, imgW = image_shape + # todo: change to 0 and modified image shape + 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)) + norm_img = np.expand_dims(resized_image, -1) + norm_img = norm_img.transpose((2, 0, 1)) + resized_image = norm_img.astype(np.float32) / 128. - 1. + padding_im = np.zeros((imgC, imgH, imgW), dtype=np.float32) + padding_im[:, :, 0:resized_w] = resized_image + data['image'] = padding_im + return data + if self.resize_type == 'PIL': + image_pil = Image.fromarray(np.uint8(img)) + img = image_pil.resize(self.image_shape, self.inter_type) + img = np.array(img) + if self.resize_type == 'OpenCV': + img = cv2.resize(img, self.image_shape) + norm_img = np.expand_dims(img, -1) + norm_img = norm_img.transpose((2, 0, 1)) + if self.scale: + data['image'] = norm_img.astype(np.float32) / 128. - 1. + else: + data['image'] = norm_img.astype(np.float32) / 255. + return data + + +class ABINetRecResizeImg(object): + def __init__(self, image_shape, **kwargs): + self.image_shape = image_shape + + def __call__(self, data): + img = data['image'] + norm_img, valid_ratio = resize_norm_img_abinet(img, self.image_shape) + data['image'] = norm_img + data['valid_ratio'] = valid_ratio + return data + + +class SVTRRecResizeImg(object): + def __init__(self, image_shape, padding=True, **kwargs): + self.image_shape = image_shape + self.padding = padding + + def __call__(self, data): + img = data['image'] + + norm_img, valid_ratio = resize_norm_img(img, self.image_shape, + self.padding) + data['image'] = norm_img + data['valid_ratio'] = valid_ratio + return data + + def resize_norm_img_sar(img, image_shape, width_downsample_ratio=0.25): imgC, imgH, imgW_min, imgW_max = image_shape h = img.shape[0] @@ -386,6 +456,26 @@ def resize_norm_img_srn(img, image_shape): return np.reshape(img_black, (c, row, col)).astype(np.float32) +def resize_norm_img_abinet(img, image_shape): + imgC, imgH, imgW = image_shape + + resized_image = cv2.resize( + img, (imgW, imgH), interpolation=cv2.INTER_LINEAR) + resized_w = imgW + resized_image = resized_image.astype('float32') + resized_image = resized_image / 255. + + mean = np.array([0.485, 0.456, 0.406]) + std = np.array([0.229, 0.224, 0.225]) + resized_image = ( + resized_image - mean[None, None, ...]) / std[None, None, ...] + resized_image = resized_image.transpose((2, 0, 1)) + resized_image = resized_image.astype('float32') + + valid_ratio = min(1.0, float(resized_w / imgW)) + return resized_image, valid_ratio + + def srn_other_inputs(image_shape, num_heads, max_text_length): imgC, imgH, imgW = image_shape diff --git a/ppocr/losses/__init__.py b/ppocr/losses/__init__.py index de8419b7c1..7bea87f62f 100755 --- a/ppocr/losses/__init__.py +++ b/ppocr/losses/__init__.py @@ -30,7 +30,7 @@ from .det_fce_loss import FCELoss from .rec_ctc_loss import CTCLoss from .rec_att_loss import AttentionLoss from .rec_srn_loss import SRNLoss -from .rec_nrtr_loss import NRTRLoss +from .rec_ce_loss import CELoss from .rec_sar_loss import SARLoss from .rec_aster_loss import AsterLoss from .rec_pren_loss import PRENLoss @@ -60,7 +60,7 @@ def build_loss(config): support_dict = [ 'DBLoss', 'PSELoss', 'EASTLoss', 'SASTLoss', 'FCELoss', 'CTCLoss', 'ClsLoss', 'AttentionLoss', 'SRNLoss', 'PGLoss', 'CombinedLoss', - 'NRTRLoss', 'TableAttentionLoss', 'SARLoss', 'AsterLoss', 'SDMGRLoss', + 'CELoss', 'TableAttentionLoss', 'SARLoss', 'AsterLoss', 'SDMGRLoss', 'VQASerTokenLayoutLMLoss', 'LossFromOutput', 'PRENLoss', 'MultiLoss' ] config = copy.deepcopy(config) diff --git a/ppocr/losses/rec_ce_loss.py b/ppocr/losses/rec_ce_loss.py new file mode 100644 index 0000000000..614384de86 --- /dev/null +++ b/ppocr/losses/rec_ce_loss.py @@ -0,0 +1,66 @@ +import paddle +from paddle import nn +import paddle.nn.functional as F + + +class CELoss(nn.Layer): + def __init__(self, + smoothing=False, + with_all=False, + ignore_index=-1, + **kwargs): + super(CELoss, self).__init__() + if ignore_index >= 0: + self.loss_func = nn.CrossEntropyLoss( + reduction='mean', ignore_index=ignore_index) + else: + self.loss_func = nn.CrossEntropyLoss(reduction='mean') + self.smoothing = smoothing + self.with_all = with_all + + def forward(self, pred, batch): + + if isinstance(pred, dict): # for ABINet + loss = {} + loss_sum = [] + for name, logits in pred.items(): + if isinstance(logits, list): + logit_num = len(logits) + all_tgt = paddle.concat([batch[1]] * logit_num, 0) + all_logits = paddle.concat(logits, 0) + flt_logtis = all_logits.reshape([-1, all_logits.shape[2]]) + flt_tgt = all_tgt.reshape([-1]) + else: + flt_logtis = logits.reshape([-1, logits.shape[2]]) + flt_tgt = batch[1].reshape([-1]) + loss[name + '_loss'] = self.loss_func(flt_logtis, flt_tgt) + loss_sum.append(loss[name + '_loss']) + loss['loss'] = sum(loss_sum) + return loss + else: + if self.with_all: # for ViTSTR + tgt = batch[1] + pred = pred.reshape([-1, pred.shape[2]]) + tgt = tgt.reshape([-1]) + loss = self.loss_func(pred, tgt) + return {'loss': loss} + else: # for NRTR + max_len = batch[2].max() + tgt = batch[1][:, 1:2 + max_len] + pred = pred.reshape([-1, pred.shape[2]]) + tgt = tgt.reshape([-1]) + if self.smoothing: + eps = 0.1 + n_class = pred.shape[1] + one_hot = F.one_hot(tgt, pred.shape[1]) + one_hot = one_hot * (1 - eps) + (1 - one_hot) * eps / ( + n_class - 1) + log_prb = F.log_softmax(pred, axis=1) + non_pad_mask = paddle.not_equal( + tgt, paddle.zeros( + tgt.shape, dtype=tgt.dtype)) + loss = -(one_hot * log_prb).sum(axis=1) + loss = loss.masked_select(non_pad_mask).mean() + else: + loss = self.loss_func(pred, tgt) + return {'loss': loss} diff --git a/ppocr/losses/rec_nrtr_loss.py b/ppocr/losses/rec_nrtr_loss.py deleted file mode 100644 index 200a6d0486..0000000000 --- a/ppocr/losses/rec_nrtr_loss.py +++ /dev/null @@ -1,30 +0,0 @@ -import paddle -from paddle import nn -import paddle.nn.functional as F - - -class NRTRLoss(nn.Layer): - def __init__(self, smoothing=True, **kwargs): - super(NRTRLoss, self).__init__() - self.loss_func = nn.CrossEntropyLoss(reduction='mean', ignore_index=0) - self.smoothing = smoothing - - def forward(self, pred, batch): - pred = pred.reshape([-1, pred.shape[2]]) - max_len = batch[2].max() - tgt = batch[1][:, 1:2 + max_len] - tgt = tgt.reshape([-1]) - if self.smoothing: - eps = 0.1 - n_class = pred.shape[1] - one_hot = F.one_hot(tgt, pred.shape[1]) - one_hot = one_hot * (1 - eps) + (1 - one_hot) * eps / (n_class - 1) - log_prb = F.log_softmax(pred, axis=1) - non_pad_mask = paddle.not_equal( - tgt, paddle.zeros( - tgt.shape, dtype=tgt.dtype)) - loss = -(one_hot * log_prb).sum(axis=1) - loss = loss.masked_select(non_pad_mask).mean() - else: - loss = self.loss_func(pred, tgt) - return {'loss': loss} diff --git a/ppocr/modeling/backbones/__init__.py b/ppocr/modeling/backbones/__init__.py index 072d6e0f84..f8959e263e 100755 --- a/ppocr/modeling/backbones/__init__.py +++ b/ppocr/modeling/backbones/__init__.py @@ -28,35 +28,37 @@ def build_backbone(config, model_type): from .rec_mv1_enhance import MobileNetV1Enhance from .rec_nrtr_mtb import MTB from .rec_resnet_31 import ResNet31 + from .rec_resnet_45 import ResNet45 from .rec_resnet_aster import ResNet_ASTER from .rec_micronet import MicroNet from .rec_efficientb3_pren import EfficientNetb3_PREN from .rec_svtrnet import SVTRNet + from .rec_vitstr import ViTSTR support_dict = [ 'MobileNetV1Enhance', 'MobileNetV3', 'ResNet', 'ResNetFPN', 'MTB', - "ResNet31", "ResNet_ASTER", 'MicroNet', 'EfficientNetb3_PREN', - 'SVTRNet' + 'ResNet31', 'ResNet45', 'ResNet_ASTER', 'MicroNet', + 'EfficientNetb3_PREN', 'SVTRNet', 'ViTSTR' ] - elif model_type == "e2e": + elif model_type == 'e2e': from .e2e_resnet_vd_pg import ResNet support_dict = ['ResNet'] elif model_type == 'kie': from .kie_unet_sdmgr import Kie_backbone support_dict = ['Kie_backbone'] - elif model_type == "table": + elif model_type == 'table': from .table_resnet_vd import ResNet from .table_mobilenet_v3 import MobileNetV3 - support_dict = ["ResNet", "MobileNetV3"] + support_dict = ['ResNet', 'MobileNetV3'] elif model_type == 'vqa': from .vqa_layoutlm import LayoutLMForSer, LayoutLMv2ForSer, LayoutLMv2ForRe, LayoutXLMForSer, LayoutXLMForRe support_dict = [ - "LayoutLMForSer", "LayoutLMv2ForSer", 'LayoutLMv2ForRe', - "LayoutXLMForSer", 'LayoutXLMForRe' + 'LayoutLMForSer', 'LayoutLMv2ForSer', 'LayoutLMv2ForRe', + 'LayoutXLMForSer', 'LayoutXLMForRe' ] else: raise NotImplementedError - module_name = config.pop("name") + module_name = config.pop('name') assert module_name in support_dict, Exception( "when model typs is {}, backbone only support {}".format(model_type, support_dict)) diff --git a/ppocr/modeling/backbones/rec_resnet_45.py b/ppocr/modeling/backbones/rec_resnet_45.py new file mode 100644 index 0000000000..9093d0bc99 --- /dev/null +++ b/ppocr/modeling/backbones/rec_resnet_45.py @@ -0,0 +1,147 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# 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. +""" +This code is refer from: +https://github.com/FangShancheng/ABINet/tree/main/modules +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import ParamAttr +from paddle.nn.initializer import KaimingNormal +import paddle.nn as nn +import paddle.nn.functional as F +import numpy as np +import math + +__all__ = ["ResNet45"] + + +def conv1x1(in_planes, out_planes, stride=1): + return nn.Conv2D( + in_planes, + out_planes, + kernel_size=1, + stride=1, + weight_attr=ParamAttr(initializer=KaimingNormal()), + bias_attr=False) + + +def conv3x3(in_channel, out_channel, stride=1): + return nn.Conv2D( + in_channel, + out_channel, + kernel_size=3, + stride=stride, + padding=1, + weight_attr=ParamAttr(initializer=KaimingNormal()), + bias_attr=False) + + +class BasicBlock(nn.Layer): + expansion = 1 + + def __init__(self, in_channels, channels, stride=1, downsample=None): + super().__init__() + self.conv1 = conv1x1(in_channels, channels) + self.bn1 = nn.BatchNorm2D(channels) + self.relu = nn.ReLU() + self.conv2 = conv3x3(channels, channels, stride) + self.bn2 = nn.BatchNorm2D(channels) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + + if self.downsample is not None: + residual = self.downsample(x) + out += residual + out = self.relu(out) + + return out + + +class ResNet45(nn.Layer): + def __init__(self, block=BasicBlock, layers=[3, 4, 6, 6, 3], in_channels=3): + self.inplanes = 32 + super(ResNet45, self).__init__() + self.conv1 = nn.Conv2D( + 3, + 32, + kernel_size=3, + stride=1, + padding=1, + weight_attr=ParamAttr(initializer=KaimingNormal()), + bias_attr=False) + self.bn1 = nn.BatchNorm2D(32) + self.relu = nn.ReLU() + + self.layer1 = self._make_layer(block, 32, layers[0], stride=2) + self.layer2 = self._make_layer(block, 64, layers[1], stride=1) + self.layer3 = self._make_layer(block, 128, layers[2], stride=2) + self.layer4 = self._make_layer(block, 256, layers[3], stride=1) + self.layer5 = self._make_layer(block, 512, layers[4], stride=1) + self.out_channels = 512 + + # for m in self.modules(): + # if isinstance(m, nn.Conv2D): + # n = m._kernel_size[0] * m._kernel_size[1] * m._out_channels + # m.weight.data.normal_(0, math.sqrt(2. / n)) + + def _make_layer(self, block, planes, blocks, stride=1): + downsample = None + if stride != 1 or self.inplanes != planes * block.expansion: + # downsample = True + downsample = nn.Sequential( + nn.Conv2D( + self.inplanes, + planes * block.expansion, + kernel_size=1, + stride=stride, + weight_attr=ParamAttr(initializer=KaimingNormal()), + bias_attr=False), + nn.BatchNorm2D(planes * block.expansion), ) + + layers = [] + layers.append(block(self.inplanes, planes, stride, downsample)) + self.inplanes = planes * block.expansion + for i in range(1, blocks): + layers.append(block(self.inplanes, planes)) + + return nn.Sequential(*layers) + + def forward(self, x): + + x = self.conv1(x) + x = self.bn1(x) + x = self.relu(x) + # print(x) + x = self.layer1(x) + x = self.layer2(x) + x = self.layer3(x) + # print(x) + x = self.layer4(x) + x = self.layer5(x) + return x diff --git a/ppocr/modeling/backbones/rec_svtrnet.py b/ppocr/modeling/backbones/rec_svtrnet.py index c57bf46345..c2c07f4476 100644 --- a/ppocr/modeling/backbones/rec_svtrnet.py +++ b/ppocr/modeling/backbones/rec_svtrnet.py @@ -147,7 +147,7 @@ class Attention(nn.Layer): dim, num_heads=8, mixer='Global', - HW=[8, 25], + HW=None, local_k=[7, 11], qkv_bias=False, qk_scale=None, @@ -210,7 +210,7 @@ class Block(nn.Layer): num_heads, mixer='Global', local_mixer=[7, 11], - HW=[8, 25], + HW=None, mlp_ratio=4., qkv_bias=False, qk_scale=None, @@ -274,7 +274,9 @@ class PatchEmbed(nn.Layer): img_size=[32, 100], in_channels=3, embed_dim=768, - sub_num=2): + sub_num=2, + patch_size=[4, 4], + mode='pope'): super().__init__() num_patches = (img_size[1] // (2 ** sub_num)) * \ (img_size[0] // (2 ** sub_num)) @@ -282,50 +284,56 @@ class PatchEmbed(nn.Layer): self.num_patches = num_patches self.embed_dim = embed_dim self.norm = None - if sub_num == 2: - self.proj = nn.Sequential( - ConvBNLayer( - in_channels=in_channels, - out_channels=embed_dim // 2, - kernel_size=3, - stride=2, - padding=1, - act=nn.GELU, - bias_attr=None), - ConvBNLayer( - in_channels=embed_dim // 2, - out_channels=embed_dim, - kernel_size=3, - stride=2, - padding=1, - act=nn.GELU, - bias_attr=None)) - if sub_num == 3: - self.proj = nn.Sequential( - ConvBNLayer( - in_channels=in_channels, - out_channels=embed_dim // 4, - kernel_size=3, - stride=2, - padding=1, - act=nn.GELU, - bias_attr=None), - ConvBNLayer( - in_channels=embed_dim // 4, - out_channels=embed_dim // 2, - kernel_size=3, - stride=2, - padding=1, - act=nn.GELU, - bias_attr=None), - ConvBNLayer( - in_channels=embed_dim // 2, - out_channels=embed_dim, - kernel_size=3, - stride=2, - padding=1, - act=nn.GELU, - bias_attr=None)) + if mode == 'pope': + if sub_num == 2: + self.proj = nn.Sequential( + ConvBNLayer( + in_channels=in_channels, + out_channels=embed_dim // 2, + kernel_size=3, + stride=2, + padding=1, + act=nn.GELU, + bias_attr=None), + ConvBNLayer( + in_channels=embed_dim // 2, + out_channels=embed_dim, + kernel_size=3, + stride=2, + padding=1, + act=nn.GELU, + bias_attr=None)) + if sub_num == 3: + self.proj = nn.Sequential( + ConvBNLayer( + in_channels=in_channels, + out_channels=embed_dim // 4, + kernel_size=3, + stride=2, + padding=1, + act=nn.GELU, + bias_attr=None), + ConvBNLayer( + in_channels=embed_dim // 4, + out_channels=embed_dim // 2, + kernel_size=3, + stride=2, + padding=1, + act=nn.GELU, + bias_attr=None), + ConvBNLayer( + in_channels=embed_dim // 2, + out_channels=embed_dim, + kernel_size=3, + stride=2, + padding=1, + act=nn.GELU, + bias_attr=None)) + elif mode == 'linear': + self.proj = nn.Conv2D( + 1, embed_dim, kernel_size=patch_size, stride=patch_size) + self.num_patches = img_size[0] // patch_size[0] * img_size[ + 1] // patch_size[1] def forward(self, x): B, C, H, W = x.shape diff --git a/ppocr/modeling/backbones/rec_vitstr.py b/ppocr/modeling/backbones/rec_vitstr.py new file mode 100644 index 0000000000..d5d7d5148a --- /dev/null +++ b/ppocr/modeling/backbones/rec_vitstr.py @@ -0,0 +1,120 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# 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. +""" +This code is refer from: +https://github.com/roatienza/deep-text-recognition-benchmark/blob/master/modules/vitstr.py +""" + +import numpy as np +import paddle +import paddle.nn as nn +from ppocr.modeling.backbones.rec_svtrnet import Block, PatchEmbed, zeros_, trunc_normal_, ones_ + +scale_dim_heads = {'tiny': [192, 3], 'small': [384, 6], 'base': [768, 12]} + + +class ViTSTR(nn.Layer): + def __init__(self, + img_size=[224, 224], + in_channels=1, + scale='tiny', + seqlen=27, + patch_size=[16, 16], + embed_dim=None, + depth=12, + num_heads=None, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_path_rate=0., + drop_rate=0., + attn_drop_rate=0., + norm_layer='nn.LayerNorm', + act_layer='nn.GELU', + epsilon=1e-6, + out_channels=None, + **kwargs): + super().__init__() + self.seqlen = seqlen + embed_dim = embed_dim if embed_dim is not None else scale_dim_heads[ + scale][0] + num_heads = num_heads if num_heads is not None else scale_dim_heads[ + scale][1] + out_channels = out_channels if out_channels is not None else embed_dim + self.patch_embed = PatchEmbed( + img_size=img_size, + in_channels=in_channels, + embed_dim=embed_dim, + patch_size=patch_size, + mode='linear') + num_patches = self.patch_embed.num_patches + + self.pos_embed = self.create_parameter( + shape=[1, num_patches + 1, embed_dim], default_initializer=zeros_) + self.add_parameter("pos_embed", self.pos_embed) + self.cls_token = self.create_parameter( + shape=[1, 1, embed_dim], default_initializer=zeros_) + self.add_parameter("cls_token", self.cls_token) + + self.pos_drop = nn.Dropout(p=drop_rate) + + dpr = np.linspace(0, drop_path_rate, depth) + self.blocks = nn.LayerList([ + Block( + dim=embed_dim, + num_heads=num_heads, + mlp_ratio=mlp_ratio, + qkv_bias=qkv_bias, + qk_scale=qk_scale, + drop=drop_rate, + attn_drop=attn_drop_rate, + drop_path=dpr[i], + norm_layer=norm_layer, + act_layer=eval(act_layer), + epsilon=epsilon, + prenorm=False) for i in range(depth) + ]) + self.norm = eval(norm_layer)(embed_dim, epsilon=epsilon) + + self.out_channels = out_channels + + trunc_normal_(self.pos_embed) + trunc_normal_(self.cls_token) + self.apply(self._init_weights) + + def _init_weights(self, m): + if isinstance(m, nn.Linear): + trunc_normal_(m.weight) + if isinstance(m, nn.Linear) and m.bias is not None: + zeros_(m.bias) + elif isinstance(m, nn.LayerNorm): + zeros_(m.bias) + ones_(m.weight) + + def forward_features(self, x): + B = x.shape[0] + x = self.patch_embed(x) + cls_tokens = paddle.tile(self.cls_token, repeat_times=[B, 1, 1]) + x = paddle.concat((cls_tokens, x), axis=1) + x = x + self.pos_embed + x = self.pos_drop(x) + for blk in self.blocks: + x = blk(x) + x = self.norm(x) + return x + + def forward(self, x): + x = self.forward_features(x) + x = x[:, :self.seqlen] + return x.transpose([0, 2, 1]).unsqueeze(2) diff --git a/ppocr/modeling/heads/__init__.py b/ppocr/modeling/heads/__init__.py index 1670ea38e6..14e6aab854 100755 --- a/ppocr/modeling/heads/__init__.py +++ b/ppocr/modeling/heads/__init__.py @@ -33,6 +33,7 @@ def build_head(config): from .rec_aster_head import AsterHead from .rec_pren_head import PRENHead from .rec_multi_head import MultiHead + from .rec_abinet_head import ABINetHead # cls head from .cls_head import ClsHead @@ -46,7 +47,7 @@ def build_head(config): 'DBHead', 'PSEHead', 'FCEHead', 'EASTHead', 'SASTHead', 'CTCHead', 'ClsHead', 'AttentionHead', 'SRNHead', 'PGHead', 'Transformer', 'TableAttentionHead', 'SARHead', 'AsterHead', 'SDMGRHead', 'PRENHead', - 'MultiHead' + 'MultiHead', 'ABINetHead' ] #table head diff --git a/ppocr/modeling/heads/multiheadAttention.py b/ppocr/modeling/heads/multiheadAttention.py deleted file mode 100755 index 900865ba1a..0000000000 --- a/ppocr/modeling/heads/multiheadAttention.py +++ /dev/null @@ -1,163 +0,0 @@ -# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. -# -# 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 paddle -from paddle import nn -import paddle.nn.functional as F -from paddle.nn import Linear -from paddle.nn.initializer import XavierUniform as xavier_uniform_ -from paddle.nn.initializer import Constant as constant_ -from paddle.nn.initializer import XavierNormal as xavier_normal_ - -zeros_ = constant_(value=0.) -ones_ = constant_(value=1.) - - -class MultiheadAttention(nn.Layer): - """Allows the model to jointly attend to information - from different representation subspaces. - See reference: Attention Is All You Need - - .. math:: - \text{MultiHead}(Q, K, V) = \text{Concat}(head_1,\dots,head_h)W^O - \text{where} head_i = \text{Attention}(QW_i^Q, KW_i^K, VW_i^V) - - Args: - embed_dim: total dimension of the model - num_heads: parallel attention layers, or heads - - """ - - def __init__(self, - embed_dim, - num_heads, - dropout=0., - bias=True, - add_bias_kv=False, - add_zero_attn=False): - super(MultiheadAttention, self).__init__() - self.embed_dim = embed_dim - self.num_heads = num_heads - self.dropout = dropout - self.head_dim = embed_dim // num_heads - assert self.head_dim * num_heads == self.embed_dim, "embed_dim must be divisible by num_heads" - self.scaling = self.head_dim**-0.5 - self.out_proj = Linear(embed_dim, embed_dim, bias_attr=bias) - self._reset_parameters() - self.conv1 = paddle.nn.Conv2D( - in_channels=embed_dim, out_channels=embed_dim, kernel_size=(1, 1)) - self.conv2 = paddle.nn.Conv2D( - in_channels=embed_dim, out_channels=embed_dim, kernel_size=(1, 1)) - self.conv3 = paddle.nn.Conv2D( - in_channels=embed_dim, out_channels=embed_dim, kernel_size=(1, 1)) - - def _reset_parameters(self): - xavier_uniform_(self.out_proj.weight) - - def forward(self, - query, - key, - value, - key_padding_mask=None, - incremental_state=None, - attn_mask=None): - """ - Inputs of forward function - query: [target length, batch size, embed dim] - key: [sequence length, batch size, embed dim] - value: [sequence length, batch size, embed dim] - key_padding_mask: if True, mask padding based on batch size - incremental_state: if provided, previous time steps are cashed - need_weights: output attn_output_weights - static_kv: key and value are static - - Outputs of forward function - attn_output: [target length, batch size, embed dim] - attn_output_weights: [batch size, target length, sequence length] - """ - q_shape = paddle.shape(query) - src_shape = paddle.shape(key) - q = self._in_proj_q(query) - k = self._in_proj_k(key) - v = self._in_proj_v(value) - q *= self.scaling - q = paddle.transpose( - paddle.reshape( - q, [q_shape[0], q_shape[1], self.num_heads, self.head_dim]), - [1, 2, 0, 3]) - k = paddle.transpose( - paddle.reshape( - k, [src_shape[0], q_shape[1], self.num_heads, self.head_dim]), - [1, 2, 0, 3]) - v = paddle.transpose( - paddle.reshape( - v, [src_shape[0], q_shape[1], self.num_heads, self.head_dim]), - [1, 2, 0, 3]) - if key_padding_mask is not None: - assert key_padding_mask.shape[0] == q_shape[1] - assert key_padding_mask.shape[1] == src_shape[0] - attn_output_weights = paddle.matmul(q, - paddle.transpose(k, [0, 1, 3, 2])) - if attn_mask is not None: - attn_mask = paddle.unsqueeze(paddle.unsqueeze(attn_mask, 0), 0) - attn_output_weights += attn_mask - if key_padding_mask is not None: - attn_output_weights = paddle.reshape( - attn_output_weights, - [q_shape[1], self.num_heads, q_shape[0], src_shape[0]]) - key = paddle.unsqueeze(paddle.unsqueeze(key_padding_mask, 1), 2) - key = paddle.cast(key, 'float32') - y = paddle.full( - shape=paddle.shape(key), dtype='float32', fill_value='-inf') - y = paddle.where(key == 0., key, y) - attn_output_weights += y - attn_output_weights = F.softmax( - attn_output_weights.astype('float32'), - axis=-1, - dtype=paddle.float32 if attn_output_weights.dtype == paddle.float16 - else attn_output_weights.dtype) - attn_output_weights = F.dropout( - attn_output_weights, p=self.dropout, training=self.training) - - attn_output = paddle.matmul(attn_output_weights, v) - attn_output = paddle.reshape( - paddle.transpose(attn_output, [2, 0, 1, 3]), - [q_shape[0], q_shape[1], self.embed_dim]) - attn_output = self.out_proj(attn_output) - - return attn_output - - def _in_proj_q(self, query): - query = paddle.transpose(query, [1, 2, 0]) - query = paddle.unsqueeze(query, axis=2) - res = self.conv1(query) - res = paddle.squeeze(res, axis=2) - res = paddle.transpose(res, [2, 0, 1]) - return res - - def _in_proj_k(self, key): - key = paddle.transpose(key, [1, 2, 0]) - key = paddle.unsqueeze(key, axis=2) - res = self.conv2(key) - res = paddle.squeeze(res, axis=2) - res = paddle.transpose(res, [2, 0, 1]) - return res - - def _in_proj_v(self, value): - value = paddle.transpose(value, [1, 2, 0]) #(1, 2, 0) - value = paddle.unsqueeze(value, axis=2) - res = self.conv3(value) - res = paddle.squeeze(res, axis=2) - res = paddle.transpose(res, [2, 0, 1]) - return res diff --git a/ppocr/modeling/heads/rec_abinet_head.py b/ppocr/modeling/heads/rec_abinet_head.py new file mode 100644 index 0000000000..a0f60f1be1 --- /dev/null +++ b/ppocr/modeling/heads/rec_abinet_head.py @@ -0,0 +1,296 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# 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. +""" +This code is refer from: +https://github.com/FangShancheng/ABINet/tree/main/modules +""" + +import math +import paddle +from paddle import nn +import paddle.nn.functional as F +from paddle.nn import LayerList +from ppocr.modeling.heads.rec_nrtr_head import TransformerBlock, PositionalEncoding + + +class BCNLanguage(nn.Layer): + def __init__(self, + d_model=512, + nhead=8, + num_layers=4, + dim_feedforward=2048, + dropout=0., + max_length=25, + detach=True, + num_classes=37): + super().__init__() + + self.d_model = d_model + self.detach = detach + self.max_length = max_length + 1 # additional stop token + self.proj = nn.Linear(num_classes, d_model, bias_attr=False) + self.token_encoder = PositionalEncoding( + dropout=0.1, dim=d_model, max_len=self.max_length) + self.pos_encoder = PositionalEncoding( + dropout=0, dim=d_model, max_len=self.max_length) + + self.decoder = nn.LayerList([ + TransformerBlock( + d_model=d_model, + nhead=nhead, + dim_feedforward=dim_feedforward, + attention_dropout_rate=dropout, + residual_dropout_rate=dropout, + with_self_attn=False, + with_cross_attn=True) for i in range(num_layers) + ]) + + self.cls = nn.Linear(d_model, num_classes) + + def forward(self, tokens, lengths): + """ + Args: + tokens: (B, N, C) where N is length, B is batch size and C is classes number + lengths: (B,) + """ + if self.detach: tokens = tokens.detach() + embed = self.proj(tokens) # (B, N, C) + embed = self.token_encoder(embed) # (B, N, C) + padding_mask = _get_mask(lengths, self.max_length) + zeros = paddle.zeros_like(embed) # (B, N, C) + qeury = self.pos_encoder(zeros) + for decoder_layer in self.decoder: + qeury = decoder_layer(qeury, embed, cross_mask=padding_mask) + output = qeury # (B, N, C) + + logits = self.cls(output) # (B, N, C) + + return output, logits + + +def encoder_layer(in_c, out_c, k=3, s=2, p=1): + return nn.Sequential( + nn.Conv2D(in_c, out_c, k, s, p), nn.BatchNorm2D(out_c), nn.ReLU()) + + +def decoder_layer(in_c, + out_c, + k=3, + s=1, + p=1, + mode='nearest', + scale_factor=None, + size=None): + align_corners = False if mode == 'nearest' else True + return nn.Sequential( + nn.Upsample( + size=size, + scale_factor=scale_factor, + mode=mode, + align_corners=align_corners), + nn.Conv2D(in_c, out_c, k, s, p), + nn.BatchNorm2D(out_c), + nn.ReLU()) + + +class PositionAttention(nn.Layer): + def __init__(self, + max_length, + in_channels=512, + num_channels=64, + h=8, + w=32, + mode='nearest', + **kwargs): + super().__init__() + self.max_length = max_length + self.k_encoder = nn.Sequential( + encoder_layer( + in_channels, num_channels, s=(1, 2)), + encoder_layer( + num_channels, num_channels, s=(2, 2)), + encoder_layer( + num_channels, num_channels, s=(2, 2)), + encoder_layer( + num_channels, num_channels, s=(2, 2))) + self.k_decoder = nn.Sequential( + decoder_layer( + num_channels, num_channels, scale_factor=2, mode=mode), + decoder_layer( + num_channels, num_channels, scale_factor=2, mode=mode), + decoder_layer( + num_channels, num_channels, scale_factor=2, mode=mode), + decoder_layer( + num_channels, in_channels, size=(h, w), mode=mode)) + + self.pos_encoder = PositionalEncoding( + dropout=0, dim=in_channels, max_len=max_length) + self.project = nn.Linear(in_channels, in_channels) + + def forward(self, x): + B, C, H, W = x.shape + k, v = x, x + + # calculate key vector + features = [] + for i in range(0, len(self.k_encoder)): + k = self.k_encoder[i](k) + features.append(k) + for i in range(0, len(self.k_decoder) - 1): + k = self.k_decoder[i](k) + # print(k.shape, features[len(self.k_decoder) - 2 - i].shape) + k = k + features[len(self.k_decoder) - 2 - i] + k = self.k_decoder[-1](k) + + # calculate query vector + # TODO q=f(q,k) + zeros = paddle.zeros( + (B, self.max_length, C), dtype=x.dtype) # (T, N, C) + q = self.pos_encoder(zeros) # (B, N, C) + q = self.project(q) # (B, N, C) + + # calculate attention + attn_scores = q @k.flatten(2) # (B, N, (H*W)) + attn_scores = attn_scores / (C**0.5) + attn_scores = F.softmax(attn_scores, axis=-1) + + v = v.flatten(2).transpose([0, 2, 1]) # (B, (H*W), C) + attn_vecs = attn_scores @v # (B, N, C) + + return attn_vecs, attn_scores.reshape([0, self.max_length, H, W]) + + +class ABINetHead(nn.Layer): + def __init__(self, + in_channels, + out_channels, + d_model=512, + nhead=8, + num_layers=3, + dim_feedforward=2048, + dropout=0.1, + max_length=25, + use_lang=False, + iter_size=1): + super().__init__() + self.max_length = max_length + 1 + self.pos_encoder = PositionalEncoding( + dropout=0.1, dim=d_model, max_len=8 * 32) + self.encoder = nn.LayerList([ + TransformerBlock( + d_model=d_model, + nhead=nhead, + dim_feedforward=dim_feedforward, + attention_dropout_rate=dropout, + residual_dropout_rate=dropout, + with_self_attn=True, + with_cross_attn=False) for i in range(num_layers) + ]) + self.decoder = PositionAttention( + max_length=max_length + 1, # additional stop token + mode='nearest', ) + self.out_channels = out_channels + self.cls = nn.Linear(d_model, self.out_channels) + self.use_lang = use_lang + if use_lang: + self.iter_size = iter_size + self.language = BCNLanguage( + d_model=d_model, + nhead=nhead, + num_layers=4, + dim_feedforward=dim_feedforward, + dropout=dropout, + max_length=max_length, + num_classes=self.out_channels) + # alignment + self.w_att_align = nn.Linear(2 * d_model, d_model) + self.cls_align = nn.Linear(d_model, self.out_channels) + + def forward(self, x, targets=None): + x = x.transpose([0, 2, 3, 1]) + _, H, W, C = x.shape + feature = x.flatten(1, 2) + feature = self.pos_encoder(feature) + for encoder_layer in self.encoder: + feature = encoder_layer(feature) + feature = feature.reshape([0, H, W, C]).transpose([0, 3, 1, 2]) + v_feature, attn_scores = self.decoder( + feature) # (B, N, C), (B, C, H, W) + vis_logits = self.cls(v_feature) # (B, N, C) + logits = vis_logits + vis_lengths = _get_length(vis_logits) + if self.use_lang: + align_logits = vis_logits + align_lengths = vis_lengths + all_l_res, all_a_res = [], [] + for i in range(self.iter_size): + tokens = F.softmax(align_logits, axis=-1) + lengths = align_lengths + lengths = paddle.clip( + lengths, 2, self.max_length) # TODO:move to langauge model + l_feature, l_logits = self.language(tokens, lengths) + + # alignment + all_l_res.append(l_logits) + fuse = paddle.concat((l_feature, v_feature), -1) + f_att = F.sigmoid(self.w_att_align(fuse)) + output = f_att * v_feature + (1 - f_att) * l_feature + align_logits = self.cls_align(output) # (B, N, C) + + align_lengths = _get_length(align_logits) + all_a_res.append(align_logits) + if self.training: + return { + 'align': all_a_res, + 'lang': all_l_res, + 'vision': vis_logits + } + else: + logits = align_logits + if self.training: + return logits + else: + return F.softmax(logits, -1) + + +def _get_length(logit): + """ Greed decoder to obtain length from logit""" + out = (logit.argmax(-1) == 0) + abn = out.any(-1) + out_int = out.cast('int32') + out = (out_int.cumsum(-1) == 1) & out + out = out.cast('int32') + out = out.argmax(-1) + out = out + 1 + out = paddle.where(abn, out, paddle.to_tensor(logit.shape[1])) + return out + + +def _get_mask(length, max_length): + """Generate a square mask for the sequence. The masked positions are filled with float('-inf'). + Unmasked positions are filled with float(0.0). + """ + length = length.unsqueeze(-1) + B = paddle.shape(length)[0] + grid = paddle.arange(0, max_length).unsqueeze(0).tile([B, 1]) + zero_mask = paddle.zeros([B, max_length], dtype='float32') + inf_mask = paddle.full([B, max_length], '-inf', dtype='float32') + diag_mask = paddle.diag( + paddle.full( + [max_length], '-inf', dtype=paddle.float32), + offset=0, + name=None) + mask = paddle.where(grid >= length, inf_mask, zero_mask) + mask = mask.unsqueeze(1) + diag_mask + return mask.unsqueeze(1) diff --git a/ppocr/modeling/heads/rec_nrtr_head.py b/ppocr/modeling/heads/rec_nrtr_head.py index 38ba0c9178..bf9ef56145 100644 --- a/ppocr/modeling/heads/rec_nrtr_head.py +++ b/ppocr/modeling/heads/rec_nrtr_head.py @@ -14,20 +14,15 @@ import math import paddle -import copy from paddle import nn import paddle.nn.functional as F from paddle.nn import LayerList -from paddle.nn.initializer import XavierNormal as xavier_uniform_ -from paddle.nn import Dropout, Linear, LayerNorm, Conv2D +# from paddle.nn.initializer import XavierNormal as xavier_uniform_ +from paddle.nn import Dropout, Linear, LayerNorm import numpy as np -from ppocr.modeling.heads.multiheadAttention import MultiheadAttention -from paddle.nn.initializer import Constant as constant_ +from ppocr.modeling.backbones.rec_svtrnet import Mlp, zeros_, ones_ from paddle.nn.initializer import XavierNormal as xavier_normal_ -zeros_ = constant_(value=0.) -ones_ = constant_(value=1.) - class Transformer(nn.Layer): """A transformer model. User is able to modify the attributes as needed. The architechture @@ -45,7 +40,6 @@ class Transformer(nn.Layer): dropout: the dropout value (default=0.1). custom_encoder: custom encoder (default=None). custom_decoder: custom decoder (default=None). - """ def __init__(self, @@ -54,45 +48,49 @@ class Transformer(nn.Layer): num_encoder_layers=6, beam_size=0, num_decoder_layers=6, + max_len=25, dim_feedforward=1024, attention_dropout_rate=0.0, residual_dropout_rate=0.1, - custom_encoder=None, - custom_decoder=None, in_channels=0, out_channels=0, scale_embedding=True): super(Transformer, self).__init__() self.out_channels = out_channels + 1 + self.max_len = max_len self.embedding = Embeddings( d_model=d_model, vocab=self.out_channels, padding_idx=0, scale_embedding=scale_embedding) self.positional_encoding = PositionalEncoding( - dropout=residual_dropout_rate, - dim=d_model, ) - if custom_encoder is not None: - self.encoder = custom_encoder - else: - if num_encoder_layers > 0: - encoder_layer = TransformerEncoderLayer( - d_model, nhead, dim_feedforward, attention_dropout_rate, - residual_dropout_rate) - self.encoder = TransformerEncoder(encoder_layer, - num_encoder_layers) - else: - self.encoder = None + dropout=residual_dropout_rate, dim=d_model) - if custom_decoder is not None: - self.decoder = custom_decoder + if num_encoder_layers > 0: + self.encoder = nn.LayerList([ + TransformerBlock( + d_model, + nhead, + dim_feedforward, + attention_dropout_rate, + residual_dropout_rate, + with_self_attn=True, + with_cross_attn=False) for i in range(num_encoder_layers) + ]) else: - decoder_layer = TransformerDecoderLayer( - d_model, nhead, dim_feedforward, attention_dropout_rate, - residual_dropout_rate) - self.decoder = TransformerDecoder(decoder_layer, num_decoder_layers) + self.encoder = None + + self.decoder = nn.LayerList([ + TransformerBlock( + d_model, + nhead, + dim_feedforward, + attention_dropout_rate, + residual_dropout_rate, + with_self_attn=True, + with_cross_attn=True) for i in range(num_decoder_layers) + ]) - self._reset_parameters() self.beam_size = beam_size self.d_model = d_model self.nhead = nhead @@ -105,7 +103,7 @@ class Transformer(nn.Layer): def _init_weights(self, m): - if isinstance(m, nn.Conv2D): + if isinstance(m, nn.Linear): xavier_normal_(m.weight) if m.bias is not None: zeros_(m.bias) @@ -113,24 +111,20 @@ class Transformer(nn.Layer): def forward_train(self, src, tgt): tgt = tgt[:, :-1] - tgt_key_padding_mask = self.generate_padding_mask(tgt) - tgt = self.embedding(tgt).transpose([1, 0, 2]) + tgt = self.embedding(tgt) tgt = self.positional_encoding(tgt) - tgt_mask = self.generate_square_subsequent_mask(tgt.shape[0]) + tgt_mask = self.generate_square_subsequent_mask(tgt.shape[1]) if self.encoder is not None: - src = self.positional_encoding(src.transpose([1, 0, 2])) - memory = self.encoder(src) + src = self.positional_encoding(src) + for encoder_layer in self.encoder: + src = encoder_layer(src) + memory = src # B N C else: - memory = src.squeeze(2).transpose([2, 0, 1]) - output = self.decoder( - tgt, - memory, - tgt_mask=tgt_mask, - memory_mask=None, - tgt_key_padding_mask=tgt_key_padding_mask, - memory_key_padding_mask=None) - output = output.transpose([1, 0, 2]) + memory = src # B N C + for decoder_layer in self.decoder: + tgt = decoder_layer(tgt, memory, self_mask=tgt_mask) + output = tgt logit = self.tgt_word_prj(output) return logit @@ -140,8 +134,8 @@ class Transformer(nn.Layer): src: the sequence to the encoder (required). tgt: the sequence to the decoder (required). Shape: - - src: :math:`(S, N, E)`. - - tgt: :math:`(T, N, E)`. + - src: :math:`(B, sN, C)`. + - tgt: :math:`(B, tN, C)`. Examples: >>> output = transformer_model(src, tgt) """ @@ -157,36 +151,35 @@ class Transformer(nn.Layer): return self.forward_test(src) def forward_test(self, src): + bs = paddle.shape(src)[0] if self.encoder is not None: - src = self.positional_encoding(paddle.transpose(src, [1, 0, 2])) - memory = self.encoder(src) + src = self.positional_encoding(src) + for encoder_layer in self.encoder: + src = encoder_layer(src) + memory = src # B N C else: - memory = paddle.transpose(paddle.squeeze(src, 2), [2, 0, 1]) + memory = src dec_seq = paddle.full((bs, 1), 2, dtype=paddle.int64) dec_prob = paddle.full((bs, 1), 1., dtype=paddle.float32) - for len_dec_seq in range(1, 25): - dec_seq_embed = paddle.transpose(self.embedding(dec_seq), [1, 0, 2]) + for len_dec_seq in range(1, self.max_len): + dec_seq_embed = self.embedding(dec_seq) dec_seq_embed = self.positional_encoding(dec_seq_embed) tgt_mask = self.generate_square_subsequent_mask( - paddle.shape(dec_seq_embed)[0]) - output = self.decoder( - dec_seq_embed, - memory, - tgt_mask=tgt_mask, - memory_mask=None, - tgt_key_padding_mask=None, - memory_key_padding_mask=None) - dec_output = paddle.transpose(output, [1, 0, 2]) + paddle.shape(dec_seq_embed)[1]) + tgt = dec_seq_embed + for decoder_layer in self.decoder: + tgt = decoder_layer(tgt, memory, self_mask=tgt_mask) + dec_output = tgt dec_output = dec_output[:, -1, :] - word_prob = F.softmax(self.tgt_word_prj(dec_output), axis=1) - preds_idx = paddle.argmax(word_prob, axis=1) + word_prob = F.softmax(self.tgt_word_prj(dec_output), axis=-1) + preds_idx = paddle.argmax(word_prob, axis=-1) if paddle.equal_all( preds_idx, paddle.full( paddle.shape(preds_idx), 3, dtype='int64')): break - preds_prob = paddle.max(word_prob, axis=1) + preds_prob = paddle.max(word_prob, axis=-1) dec_seq = paddle.concat( [dec_seq, paddle.reshape(preds_idx, [-1, 1])], axis=1) dec_prob = paddle.concat( @@ -194,10 +187,10 @@ class Transformer(nn.Layer): return [dec_seq, dec_prob] def forward_beam(self, images): - ''' Translation work in one batch ''' + """ Translation work in one batch """ def get_inst_idx_to_tensor_position_map(inst_idx_list): - ''' Indicate the position of an instance in a tensor. ''' + """ Indicate the position of an instance in a tensor. """ return { inst_idx: tensor_position for tensor_position, inst_idx in enumerate(inst_idx_list) @@ -205,7 +198,7 @@ class Transformer(nn.Layer): def collect_active_part(beamed_tensor, curr_active_inst_idx, n_prev_active_inst, n_bm): - ''' Collect tensor parts associated to active instances. ''' + """ Collect tensor parts associated to active instances. """ beamed_tensor_shape = paddle.shape(beamed_tensor) n_curr_active_inst = len(curr_active_inst_idx) @@ -237,9 +230,8 @@ class Transformer(nn.Layer): return active_src_enc, active_inst_idx_to_position_map def beam_decode_step(inst_dec_beams, len_dec_seq, enc_output, - inst_idx_to_position_map, n_bm, - memory_key_padding_mask): - ''' Decode and update beam status, and then return active beam idx ''' + inst_idx_to_position_map, n_bm): + """ Decode and update beam status, and then return active beam idx """ def prepare_beam_dec_seq(inst_dec_beams, len_dec_seq): dec_partial_seq = [ @@ -249,19 +241,15 @@ class Transformer(nn.Layer): dec_partial_seq = dec_partial_seq.reshape([-1, len_dec_seq]) return dec_partial_seq - def predict_word(dec_seq, enc_output, n_active_inst, n_bm, - memory_key_padding_mask): - dec_seq = paddle.transpose(self.embedding(dec_seq), [1, 0, 2]) + def predict_word(dec_seq, enc_output, n_active_inst, n_bm): + dec_seq = self.embedding(dec_seq) dec_seq = self.positional_encoding(dec_seq) tgt_mask = self.generate_square_subsequent_mask( - paddle.shape(dec_seq)[0]) - dec_output = self.decoder( - dec_seq, - enc_output, - tgt_mask=tgt_mask, - tgt_key_padding_mask=None, - memory_key_padding_mask=memory_key_padding_mask, ) - dec_output = paddle.transpose(dec_output, [1, 0, 2]) + paddle.shape(dec_seq)[1]) + tgt = dec_seq + for decoder_layer in self.decoder: + tgt = decoder_layer(tgt, enc_output, self_mask=tgt_mask) + dec_output = tgt dec_output = dec_output[:, -1, :] # Pick the last step: (bh * bm) * d_h word_prob = F.softmax(self.tgt_word_prj(dec_output), axis=1) @@ -281,8 +269,7 @@ class Transformer(nn.Layer): n_active_inst = len(inst_idx_to_position_map) dec_seq = prepare_beam_dec_seq(inst_dec_beams, len_dec_seq) - word_prob = predict_word(dec_seq, enc_output, n_active_inst, n_bm, - None) + word_prob = predict_word(dec_seq, enc_output, n_active_inst, n_bm) # Update the beam with predicted word prob information and collect incomplete instances active_inst_idx_list = collect_active_inst_idx_list( inst_dec_beams, word_prob, inst_idx_to_position_map) @@ -303,10 +290,10 @@ class Transformer(nn.Layer): with paddle.no_grad(): #-- Encode if self.encoder is not None: - src = self.positional_encoding(images.transpose([1, 0, 2])) + src = self.positional_encoding(images) src_enc = self.encoder(src) else: - src_enc = images.squeeze(2).transpose([0, 2, 1]) + src_enc = images n_bm = self.beam_size src_shape = paddle.shape(src_enc) @@ -317,11 +304,11 @@ class Transformer(nn.Layer): inst_idx_to_position_map = get_inst_idx_to_tensor_position_map( active_inst_idx_list) # Decode - for len_dec_seq in range(1, 25): + for len_dec_seq in range(1, self.max_len): src_enc_copy = src_enc.clone() active_inst_idx_list = beam_decode_step( inst_dec_beams, len_dec_seq, src_enc_copy, - inst_idx_to_position_map, n_bm, None) + inst_idx_to_position_map, n_bm) if not active_inst_idx_list: break # all instances have finished their path to src_enc, inst_idx_to_position_map = collate_active_info( @@ -354,261 +341,124 @@ class Transformer(nn.Layer): shape=[sz, sz], dtype='float32', fill_value='-inf'), diagonal=1) mask = mask + mask_inf - return mask - - def generate_padding_mask(self, x): - padding_mask = paddle.equal(x, paddle.to_tensor(0, dtype=x.dtype)) - return padding_mask - - def _reset_parameters(self): - """Initiate parameters in the transformer model.""" - - for p in self.parameters(): - if p.dim() > 1: - xavier_uniform_(p) + return mask.unsqueeze([0, 1]) -class TransformerEncoder(nn.Layer): - """TransformerEncoder is a stack of N encoder layers - Args: - encoder_layer: an instance of the TransformerEncoderLayer() class (required). - num_layers: the number of sub-encoder-layers in the encoder (required). - norm: the layer normalization component (optional). - """ +class MultiheadAttention(nn.Layer): + """Allows the model to jointly attend to information + from different representation subspaces. + See reference: Attention Is All You Need - def __init__(self, encoder_layer, num_layers): - super(TransformerEncoder, self).__init__() - self.layers = _get_clones(encoder_layer, num_layers) - self.num_layers = num_layers - - def forward(self, src): - """Pass the input through the endocder layers in turn. - Args: - src: the sequnce to the encoder (required). - mask: the mask for the src sequence (optional). - src_key_padding_mask: the mask for the src keys per batch (optional). - """ - output = src - - for i in range(self.num_layers): - output = self.layers[i](output, - src_mask=None, - src_key_padding_mask=None) - - return output - - -class TransformerDecoder(nn.Layer): - """TransformerDecoder is a stack of N decoder layers + .. math:: + \text{MultiHead}(Q, K, V) = \text{Concat}(head_1,\dots,head_h)W^O + \text{where} head_i = \text{Attention}(QW_i^Q, KW_i^K, VW_i^V) Args: - decoder_layer: an instance of the TransformerDecoderLayer() class (required). - num_layers: the number of sub-decoder-layers in the decoder (required). - norm: the layer normalization component (optional). + embed_dim: total dimension of the model + num_heads: parallel attention layers, or heads """ - def __init__(self, decoder_layer, num_layers): - super(TransformerDecoder, self).__init__() - self.layers = _get_clones(decoder_layer, num_layers) - self.num_layers = num_layers + def __init__(self, embed_dim, num_heads, dropout=0., self_attn=False): + super(MultiheadAttention, self).__init__() + self.embed_dim = embed_dim + self.num_heads = num_heads + # self.dropout = dropout + self.head_dim = embed_dim // num_heads + assert self.head_dim * num_heads == self.embed_dim, "embed_dim must be divisible by num_heads" + self.scale = self.head_dim**-0.5 + self.self_attn = self_attn + if self_attn: + self.qkv = nn.Linear(embed_dim, embed_dim * 3) + else: + self.q = nn.Linear(embed_dim, embed_dim) + self.kv = nn.Linear(embed_dim, embed_dim * 2) + self.attn_drop = nn.Dropout(dropout) + self.out_proj = nn.Linear(embed_dim, embed_dim) - def forward(self, - tgt, - memory, - tgt_mask=None, - memory_mask=None, - tgt_key_padding_mask=None, - memory_key_padding_mask=None): - """Pass the inputs (and mask) through the decoder layer in turn. + def forward(self, query, key=None, attn_mask=None): - Args: - tgt: the sequence to the decoder (required). - memory: the sequnce from the last layer of the encoder (required). - tgt_mask: the mask for the tgt sequence (optional). - memory_mask: the mask for the memory sequence (optional). - tgt_key_padding_mask: the mask for the tgt keys per batch (optional). - memory_key_padding_mask: the mask for the memory keys per batch (optional). - """ - output = tgt - for i in range(self.num_layers): - output = self.layers[i]( - output, - memory, - tgt_mask=tgt_mask, - memory_mask=memory_mask, - tgt_key_padding_mask=tgt_key_padding_mask, - memory_key_padding_mask=memory_key_padding_mask) + qN = query.shape[1] - return output + if self.self_attn: + qkv = self.qkv(query).reshape( + (0, qN, 3, self.num_heads, self.head_dim)).transpose( + (2, 0, 3, 1, 4)) + q, k, v = qkv[0], qkv[1], qkv[2] + else: + kN = key.shape[1] + q = self.q(query).reshape( + [0, qN, self.num_heads, self.head_dim]).transpose([0, 2, 1, 3]) + kv = self.kv(key).reshape( + (0, kN, 2, self.num_heads, self.head_dim)).transpose( + (2, 0, 3, 1, 4)) + k, v = kv[0], kv[1] + + attn = (q.matmul(k.transpose((0, 1, 3, 2)))) * self.scale + + if attn_mask is not None: + attn += attn_mask + + attn = F.softmax(attn, axis=-1) + attn = self.attn_drop(attn) + + x = (attn.matmul(v)).transpose((0, 2, 1, 3)).reshape( + (0, qN, self.embed_dim)) + x = self.out_proj(x) + + return x -class TransformerEncoderLayer(nn.Layer): - """TransformerEncoderLayer is made up of self-attn and feedforward network. - This standard encoder layer is based on the paper "Attention Is All You Need". - Ashish Vaswani, Noam Shazeer, Niki Parmar, Jakob Uszkoreit, Llion Jones, Aidan N Gomez, - Lukasz Kaiser, and Illia Polosukhin. 2017. Attention is all you need. In Advances in - Neural Information Processing Systems, pages 6000-6010. Users may modify or implement - in a different way during application. - - Args: - d_model: the number of expected features in the input (required). - nhead: the number of heads in the multiheadattention models (required). - dim_feedforward: the dimension of the feedforward network model (default=2048). - dropout: the dropout value (default=0.1). - - """ - +class TransformerBlock(nn.Layer): def __init__(self, d_model, nhead, dim_feedforward=2048, attention_dropout_rate=0.0, - residual_dropout_rate=0.1): - super(TransformerEncoderLayer, self).__init__() - self.self_attn = MultiheadAttention( - d_model, nhead, dropout=attention_dropout_rate) + residual_dropout_rate=0.1, + with_self_attn=True, + with_cross_attn=False, + epsilon=1e-5): + super(TransformerBlock, self).__init__() + self.with_self_attn = with_self_attn + if with_self_attn: + self.self_attn = MultiheadAttention( + d_model, + nhead, + dropout=attention_dropout_rate, + self_attn=with_self_attn) + self.norm1 = LayerNorm(d_model, epsilon=epsilon) + self.dropout1 = Dropout(residual_dropout_rate) + self.with_cross_attn = with_cross_attn + if with_cross_attn: + self.cross_attn = MultiheadAttention( #for self_attn of encoder or cross_attn of decoder + d_model, + nhead, + dropout=attention_dropout_rate) + self.norm2 = LayerNorm(d_model, epsilon=epsilon) + self.dropout2 = Dropout(residual_dropout_rate) - self.conv1 = Conv2D( - in_channels=d_model, - out_channels=dim_feedforward, - kernel_size=(1, 1)) - self.conv2 = Conv2D( - in_channels=dim_feedforward, - out_channels=d_model, - kernel_size=(1, 1)) + self.mlp = Mlp(in_features=d_model, + hidden_features=dim_feedforward, + act_layer=nn.ReLU, + drop=residual_dropout_rate) - self.norm1 = LayerNorm(d_model) - self.norm2 = LayerNorm(d_model) - self.dropout1 = Dropout(residual_dropout_rate) - self.dropout2 = Dropout(residual_dropout_rate) + self.norm3 = LayerNorm(d_model, epsilon=epsilon) - def forward(self, src, src_mask=None, src_key_padding_mask=None): - """Pass the input through the endocder layer. - Args: - src: the sequnce to the encoder layer (required). - src_mask: the mask for the src sequence (optional). - src_key_padding_mask: the mask for the src keys per batch (optional). - """ - src2 = self.self_attn( - src, - src, - src, - attn_mask=src_mask, - key_padding_mask=src_key_padding_mask) - src = src + self.dropout1(src2) - src = self.norm1(src) - - src = paddle.transpose(src, [1, 2, 0]) - src = paddle.unsqueeze(src, 2) - src2 = self.conv2(F.relu(self.conv1(src))) - src2 = paddle.squeeze(src2, 2) - src2 = paddle.transpose(src2, [2, 0, 1]) - src = paddle.squeeze(src, 2) - src = paddle.transpose(src, [2, 0, 1]) - - src = src + self.dropout2(src2) - src = self.norm2(src) - return src - - -class TransformerDecoderLayer(nn.Layer): - """TransformerDecoderLayer is made up of self-attn, multi-head-attn and feedforward network. - This standard decoder layer is based on the paper "Attention Is All You Need". - Ashish Vaswani, Noam Shazeer, Niki Parmar, Jakob Uszkoreit, Llion Jones, Aidan N Gomez, - Lukasz Kaiser, and Illia Polosukhin. 2017. Attention is all you need. In Advances in - Neural Information Processing Systems, pages 6000-6010. Users may modify or implement - in a different way during application. - - Args: - d_model: the number of expected features in the input (required). - nhead: the number of heads in the multiheadattention models (required). - dim_feedforward: the dimension of the feedforward network model (default=2048). - dropout: the dropout value (default=0.1). - - """ - - def __init__(self, - d_model, - nhead, - dim_feedforward=2048, - attention_dropout_rate=0.0, - residual_dropout_rate=0.1): - super(TransformerDecoderLayer, self).__init__() - self.self_attn = MultiheadAttention( - d_model, nhead, dropout=attention_dropout_rate) - self.multihead_attn = MultiheadAttention( - d_model, nhead, dropout=attention_dropout_rate) - - self.conv1 = Conv2D( - in_channels=d_model, - out_channels=dim_feedforward, - kernel_size=(1, 1)) - self.conv2 = Conv2D( - in_channels=dim_feedforward, - out_channels=d_model, - kernel_size=(1, 1)) - - self.norm1 = LayerNorm(d_model) - self.norm2 = LayerNorm(d_model) - self.norm3 = LayerNorm(d_model) - self.dropout1 = Dropout(residual_dropout_rate) - self.dropout2 = Dropout(residual_dropout_rate) self.dropout3 = Dropout(residual_dropout_rate) - def forward(self, - tgt, - memory, - tgt_mask=None, - memory_mask=None, - tgt_key_padding_mask=None, - memory_key_padding_mask=None): - """Pass the inputs (and mask) through the decoder layer. + def forward(self, tgt, memory=None, self_mask=None, cross_mask=None): + if self.with_self_attn: + tgt1 = self.self_attn(tgt, attn_mask=self_mask) + tgt = self.norm1(tgt + self.dropout1(tgt1)) - Args: - tgt: the sequence to the decoder layer (required). - memory: the sequnce from the last layer of the encoder (required). - tgt_mask: the mask for the tgt sequence (optional). - memory_mask: the mask for the memory sequence (optional). - tgt_key_padding_mask: the mask for the tgt keys per batch (optional). - memory_key_padding_mask: the mask for the memory keys per batch (optional). - - """ - tgt2 = self.self_attn( - tgt, - tgt, - tgt, - attn_mask=tgt_mask, - key_padding_mask=tgt_key_padding_mask) - tgt = tgt + self.dropout1(tgt2) - tgt = self.norm1(tgt) - tgt2 = self.multihead_attn( - tgt, - memory, - memory, - attn_mask=memory_mask, - key_padding_mask=memory_key_padding_mask) - tgt = tgt + self.dropout2(tgt2) - tgt = self.norm2(tgt) - - # default - tgt = paddle.transpose(tgt, [1, 2, 0]) - tgt = paddle.unsqueeze(tgt, 2) - tgt2 = self.conv2(F.relu(self.conv1(tgt))) - tgt2 = paddle.squeeze(tgt2, 2) - tgt2 = paddle.transpose(tgt2, [2, 0, 1]) - tgt = paddle.squeeze(tgt, 2) - tgt = paddle.transpose(tgt, [2, 0, 1]) - - tgt = tgt + self.dropout3(tgt2) - tgt = self.norm3(tgt) + if self.with_cross_attn: + tgt2 = self.cross_attn(tgt, key=memory, attn_mask=cross_mask) + tgt = self.norm2(tgt + self.dropout2(tgt2)) + tgt = self.norm3(tgt + self.dropout3(self.mlp(tgt))) return tgt -def _get_clones(module, N): - return LayerList([copy.deepcopy(module) for i in range(N)]) - - class PositionalEncoding(nn.Layer): """Inject some information about the relative or absolute position of the tokens in the sequence. The positional encodings have the same dimension as @@ -651,8 +501,9 @@ class PositionalEncoding(nn.Layer): Examples: >>> output = pos_encoder(x) """ + x = x.transpose([1, 0, 2]) x = x + self.pe[:paddle.shape(x)[0], :] - return self.dropout(x) + return self.dropout(x).transpose([1, 0, 2]) class PositionalEncoding_2d(nn.Layer): @@ -725,7 +576,7 @@ class PositionalEncoding_2d(nn.Layer): class Embeddings(nn.Layer): - def __init__(self, d_model, vocab, padding_idx, scale_embedding): + def __init__(self, d_model, vocab, padding_idx=None, scale_embedding=True): super(Embeddings, self).__init__() self.embedding = nn.Embedding(vocab, d_model, padding_idx=padding_idx) w0 = np.random.normal(0.0, d_model**-0.5, @@ -742,7 +593,7 @@ class Embeddings(nn.Layer): class Beam(): - ''' Beam search ''' + """ Beam search """ def __init__(self, size, device=False): diff --git a/ppocr/postprocess/__init__.py b/ppocr/postprocess/__init__.py index f50b5f1c5f..2635117c84 100644 --- a/ppocr/postprocess/__init__.py +++ b/ppocr/postprocess/__init__.py @@ -27,7 +27,7 @@ from .sast_postprocess import SASTPostProcess from .fce_postprocess import FCEPostProcess from .rec_postprocess import CTCLabelDecode, AttnLabelDecode, SRNLabelDecode, \ DistillationCTCLabelDecode, TableLabelDecode, NRTRLabelDecode, SARLabelDecode, \ - SEEDLabelDecode, PRENLabelDecode + SEEDLabelDecode, PRENLabelDecode, ViTSTRLabelDecode, ABINetLabelDecode from .cls_postprocess import ClsPostProcess from .pg_postprocess import PGPostProcess from .vqa_token_ser_layoutlm_postprocess import VQASerTokenLayoutLMPostProcess @@ -42,7 +42,7 @@ def build_post_process(config, global_config=None): 'DistillationDBPostProcess', 'NRTRLabelDecode', 'SARLabelDecode', 'SEEDLabelDecode', 'VQASerTokenLayoutLMPostProcess', 'VQAReTokenLayoutLMPostProcess', 'PRENLabelDecode', - 'DistillationSARLabelDecode' + 'DistillationSARLabelDecode', 'ViTSTRLabelDecode', 'ABINetLabelDecode' ] if config['name'] == 'PSEPostProcess': diff --git a/ppocr/postprocess/rec_postprocess.py b/ppocr/postprocess/rec_postprocess.py index bf0fd890bf..c77420ad19 100644 --- a/ppocr/postprocess/rec_postprocess.py +++ b/ppocr/postprocess/rec_postprocess.py @@ -140,70 +140,6 @@ class DistillationCTCLabelDecode(CTCLabelDecode): return output -class NRTRLabelDecode(BaseRecLabelDecode): - """ Convert between text-label and text-index """ - - def __init__(self, character_dict_path=None, use_space_char=True, **kwargs): - super(NRTRLabelDecode, self).__init__(character_dict_path, - use_space_char) - - def __call__(self, preds, label=None, *args, **kwargs): - - if len(preds) == 2: - preds_id = preds[0] - preds_prob = preds[1] - if isinstance(preds_id, paddle.Tensor): - preds_id = preds_id.numpy() - if isinstance(preds_prob, paddle.Tensor): - preds_prob = preds_prob.numpy() - if preds_id[0][0] == 2: - preds_idx = preds_id[:, 1:] - preds_prob = preds_prob[:, 1:] - else: - preds_idx = preds_id - text = self.decode(preds_idx, preds_prob, is_remove_duplicate=False) - if label is None: - return text - label = self.decode(label[:, 1:]) - else: - if isinstance(preds, paddle.Tensor): - preds = preds.numpy() - preds_idx = preds.argmax(axis=2) - preds_prob = preds.max(axis=2) - text = self.decode(preds_idx, preds_prob, is_remove_duplicate=False) - if label is None: - return text - label = self.decode(label[:, 1:]) - return text, label - - def add_special_char(self, dict_character): - dict_character = ['blank', '', '', ''] + 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 = [] - 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] == 3: # end - break - try: - char_list.append(self.character[int(text_index[batch_idx][ - idx])]) - except: - continue - 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.lower(), np.mean(conf_list).tolist())) - return result_list - - class AttnLabelDecode(BaseRecLabelDecode): """ Convert between text-label and text-index """ @@ -752,3 +688,122 @@ class PRENLabelDecode(BaseRecLabelDecode): return text label = self.decode(label) return text, label + + +class NRTRLabelDecode(BaseRecLabelDecode): + """ Convert between text-label and text-index """ + + def __init__(self, character_dict_path=None, use_space_char=True, **kwargs): + super(NRTRLabelDecode, self).__init__(character_dict_path, + use_space_char) + + def __call__(self, preds, label=None, *args, **kwargs): + + if len(preds) == 2: + preds_id = preds[0] + preds_prob = preds[1] + if isinstance(preds_id, paddle.Tensor): + preds_id = preds_id.numpy() + if isinstance(preds_prob, paddle.Tensor): + preds_prob = preds_prob.numpy() + if preds_id[0][0] == 2: + preds_idx = preds_id[:, 1:] + preds_prob = preds_prob[:, 1:] + else: + preds_idx = preds_id + text = self.decode(preds_idx, preds_prob, is_remove_duplicate=False) + if label is None: + return text + label = self.decode(label[:, 1:]) + else: + if isinstance(preds, paddle.Tensor): + preds = preds.numpy() + preds_idx = preds.argmax(axis=2) + preds_prob = preds.max(axis=2) + text = self.decode(preds_idx, preds_prob, is_remove_duplicate=False) + if label is None: + return text + label = self.decode(label[:, 1:]) + return text, label + + def add_special_char(self, dict_character): + dict_character = ['blank', '', '', ''] + 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 = [] + 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])): + try: + char_idx = self.character[int(text_index[batch_idx][idx])] + except: + continue + if char_idx == '': # end + break + char_list.append(char_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.lower(), np.mean(conf_list).tolist())) + return result_list + + +class ViTSTRLabelDecode(NRTRLabelDecode): + """ Convert between text-label and text-index """ + + def __init__(self, character_dict_path=None, use_space_char=False, + **kwargs): + super(ViTSTRLabelDecode, self).__init__(character_dict_path, + use_space_char) + + def __call__(self, preds, label=None, *args, **kwargs): + if isinstance(preds, paddle.Tensor): + preds = preds[:, 1:].numpy() + else: + preds = preds[:, 1:] + preds_idx = preds.argmax(axis=2) + preds_prob = preds.max(axis=2) + text = self.decode(preds_idx, preds_prob, is_remove_duplicate=False) + if label is None: + return text + label = self.decode(label[:, 1:]) + return text, label + + def add_special_char(self, dict_character): + dict_character = ['', ''] + dict_character + return dict_character + + +class ABINetLabelDecode(NRTRLabelDecode): + """ Convert between text-label and text-index """ + + def __init__(self, character_dict_path=None, use_space_char=False, + **kwargs): + super(ABINetLabelDecode, self).__init__(character_dict_path, + use_space_char) + + def __call__(self, preds, label=None, *args, **kwargs): + if isinstance(preds, dict): + preds = preds['align'][-1].numpy() + elif isinstance(preds, paddle.Tensor): + preds = preds.numpy() + else: + preds = preds + + preds_idx = preds.argmax(axis=2) + preds_prob = preds.max(axis=2) + text = self.decode(preds_idx, preds_prob, is_remove_duplicate=False) + if label is None: + return text + label = self.decode(label) + return text, label + + def add_special_char(self, dict_character): + dict_character = [''] + dict_character + return dict_character diff --git a/test_tipc/configs/rec_mtb_nrtr/rec_mtb_nrtr.yml b/test_tipc/configs/rec_mtb_nrtr/rec_mtb_nrtr.yml index 15119bb2a9..8118d58724 100644 --- a/test_tipc/configs/rec_mtb_nrtr/rec_mtb_nrtr.yml +++ b/test_tipc/configs/rec_mtb_nrtr/rec_mtb_nrtr.yml @@ -49,7 +49,7 @@ Architecture: Loss: - name: NRTRLoss + name: CELoss smoothing: True PostProcess: @@ -69,7 +69,7 @@ Train: img_mode: BGR channel_first: False - NRTRLabelEncode: # Class handling label - - NRTRRecResizeImg: + - GrayRecResizeImg: image_shape: [100, 32] resize_type: PIL # PIL or OpenCV - KeepKeys: @@ -90,7 +90,7 @@ Eval: img_mode: BGR channel_first: False - NRTRLabelEncode: # Class handling label - - NRTRRecResizeImg: + - GrayRecResizeImg: image_shape: [100, 32] resize_type: PIL # PIL or OpenCV - KeepKeys: @@ -99,5 +99,5 @@ Eval: shuffle: False drop_last: False batch_size_per_card: 256 - num_workers: 1 + num_workers: 4 use_shared_memory: False diff --git a/test_tipc/configs/rec_r45_abinet/rec_r45_abinet.yml b/test_tipc/configs/rec_r45_abinet/rec_r45_abinet.yml new file mode 100644 index 0000000000..5b5890e772 --- /dev/null +++ b/test_tipc/configs/rec_r45_abinet/rec_r45_abinet.yml @@ -0,0 +1,106 @@ +Global: + use_gpu: True + epoch_num: 10 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/r45_abinet/ + save_epoch_step: 1 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + character_type: en + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_abinet.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.99 + clip_norm: 20.0 + lr: + name: Piecewise + decay_epochs: [6] + values: [0.0001, 0.00001] + regularizer: + name: 'L2' + factor: 0. + +Architecture: + model_type: rec + algorithm: ABINet + in_channels: 3 + Transform: + Backbone: + name: ResNet45 + + Head: + name: ABINetHead + use_lang: True + iter_size: 3 + + +Loss: + name: CELoss + ignore_index: &ignore_index 100 # Must be greater than the number of character classes + +PostProcess: + name: ABINetLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data/ + label_file_list: ["./train_data/ic15_data/rec_gt_train.txt"] + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - ABINetRecAug: + - ABINetLabelEncode: # Class handling label + ignore_index: *ignore_index + - ABINetRecResizeImg: + image_shape: [3, 32, 128] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 96 + drop_last: True + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data + label_file_list: ["./train_data/ic15_data/rec_gt_test.txt"] + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - ABINetLabelEncode: # Class handling label + ignore_index: *ignore_index + - ABINetRecResizeImg: + image_shape: [3, 32, 128] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 4 + use_shared_memory: False diff --git a/test_tipc/configs/rec_r45_abinet/train_infer_python.txt b/test_tipc/configs/rec_r45_abinet/train_infer_python.txt new file mode 100644 index 0000000000..ecab1bcbbd --- /dev/null +++ b/test_tipc/configs/rec_r45_abinet/train_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:rec_abinet +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=2|whole_train_whole_infer=300 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=64 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/rec_r45_abinet/rec_r45_abinet.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c test_tipc/configs/rec_r45_abinet/rec_r45_abinet.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/rec_r45_abinet/rec_r45_abinet.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/rec_r45_abinet_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/rec_r45_abinet/rec_r45_abinet.yml -o +infer_quant:False +inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/ic15_dict.txt --rec_image_shape="3,32,128" --rec_algorithm="ABINet" +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1|6 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +--save_log_path:./test/output/ +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,32,128]}] diff --git a/test_tipc/configs/rec_svtrnet/rec_svtrnet.yml b/test_tipc/configs/rec_svtrnet/rec_svtrnet.yml new file mode 100644 index 0000000000..140b17e0e7 --- /dev/null +++ b/test_tipc/configs/rec_svtrnet/rec_svtrnet.yml @@ -0,0 +1,117 @@ +Global: + use_gpu: True + epoch_num: 20 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/svtr/ + save_epoch_step: 1 + # evaluation is run every 2000 iterations after the 0th iteration + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + character_type: en + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_svtr_tiny.txt + + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.99 + epsilon: 8.e-8 + weight_decay: 0.05 + no_weight_decay_name: norm pos_embed + one_dim_param_no_weight_decay: true + lr: + name: Cosine + learning_rate: 0.0005 + warmup_epoch: 2 + +Architecture: + model_type: rec + algorithm: SVTR + Transform: + name: STN_ON + tps_inputsize: [32, 64] + tps_outputsize: [32, 100] + num_control_points: 20 + tps_margins: [0.05,0.05] + stn_activation: none + Backbone: + name: SVTRNet + img_size: [32, 100] + out_char_num: 25 + out_channels: 192 + patch_merging: 'Conv' + embed_dim: [64, 128, 256] + depth: [3, 6, 3] + num_heads: [2, 4, 8] + mixer: ['Local','Local','Local','Local','Local','Local','Global','Global','Global','Global','Global','Global'] + local_mixer: [[7, 11], [7, 11], [7, 11]] + last_stage: True + prenorm: false + Neck: + name: SequenceEncoder + encoder_type: reshape + Head: + name: CTCHead + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data/ + label_file_list: ["./train_data/ic15_data/rec_gt_train.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - SVTRRecResizeImg: + image_shape: [3, 64, 256] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 512 + drop_last: True + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data + label_file_list: ["./train_data/ic15_data/rec_gt_test.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - SVTRRecResizeImg: + image_shape: [3, 64, 256] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 2 diff --git a/test_tipc/configs/rec_svtrnet/train_infer_python.txt b/test_tipc/configs/rec_svtrnet/train_infer_python.txt new file mode 100644 index 0000000000..a7e4a24063 --- /dev/null +++ b/test_tipc/configs/rec_svtrnet/train_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:rec_svtrnet +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=2|whole_train_whole_infer=300 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=64 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/rec_svtrnet/rec_svtrnet.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c test_tipc/configs/rec_svtrnet/rec_svtrnet.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/rec_svtrnet/rec_svtrnet.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/rec_svtrnet_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/rec_svtrnet/rec_svtrnet.yml -o +infer_quant:False +inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/ic15_dict.txt --rec_image_shape="3,64,256" --rec_algorithm="SVTR" +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1|6 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +--save_log_path:./test/output/ +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,64,256]}] diff --git a/test_tipc/configs/rec_vitstr_none_ce/rec_vitstr_none_ce.yml b/test_tipc/configs/rec_vitstr_none_ce/rec_vitstr_none_ce.yml new file mode 100644 index 0000000000..a0aed48875 --- /dev/null +++ b/test_tipc/configs/rec_vitstr_none_ce/rec_vitstr_none_ce.yml @@ -0,0 +1,104 @@ +Global: + use_gpu: True + epoch_num: 20 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/vitstr_none_ce/ + save_epoch_step: 1 + # evaluation is run every 2000 iterations after the 0th iteration# + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: ppocr/utils/EN_symbol_dict.txt + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_vitstr.txt + + +Optimizer: + name: Adadelta + epsilon: 1.e-8 + rho: 0.95 + clip_norm: 5.0 + lr: + learning_rate: 1.0 + +Architecture: + model_type: rec + algorithm: ViTSTR + in_channels: 1 + Transform: + Backbone: + name: ViTSTR + Neck: + name: SequenceEncoder + encoder_type: reshape + Head: + name: CTCHead + +Loss: + name: CELoss + smoothing: False + with_all: True + ignore_index: &ignore_index 0 # Must be zero or greater than the number of character classes + +PostProcess: + name: ViTSTRLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data/ + label_file_list: ["./train_data/ic15_data/rec_gt_train.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - ViTSTRLabelEncode: # Class handling label + ignore_index: *ignore_index + - GrayRecResizeImg: + image_shape: [224, 224] # W H + resize_type: PIL # PIL or OpenCV + inter_type: 'Image.BICUBIC' + scale: false + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 48 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data + label_file_list: ["./train_data/ic15_data/rec_gt_test.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - ViTSTRLabelEncode: # Class handling label + ignore_index: *ignore_index + - GrayRecResizeImg: + image_shape: [224, 224] # W H + resize_type: PIL # PIL or OpenCV + inter_type: 'Image.BICUBIC' + scale: false + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 2 diff --git a/test_tipc/configs/rec_vitstr_none_ce/train_infer_python.txt b/test_tipc/configs/rec_vitstr_none_ce/train_infer_python.txt new file mode 100644 index 0000000000..04c5742ea2 --- /dev/null +++ b/test_tipc/configs/rec_vitstr_none_ce/train_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:rec_vitstr +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=2|whole_train_whole_infer=300 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=64 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/rec_vitstr_none_ce/rec_vitstr_none_ce.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c test_tipc/configs/rec_vitstr_none_ce/rec_vitstr_none_ce.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/rec_vitstr_none_ce/rec_vitstr_none_ce.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/rec_vitstr_none_ce_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/rec_vitstr_none_ce/rec_vitstr_none_ce.yml -o +infer_quant:False +inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/EN_symbol_dict.txt --rec_image_shape="1,224,224" --rec_algorithm="ViTSTR" +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1|6 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +--save_log_path:./test/output/ +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[1,224,224]}] diff --git a/tools/export_model.py b/tools/export_model.py index 3ea0228f85..b10d41d5b2 100755 --- a/tools/export_model.py +++ b/tools/export_model.py @@ -31,7 +31,12 @@ from ppocr.utils.logging import get_logger from tools.program import load_config, merge_config, ArgsParser -def export_single_model(model, arch_config, save_path, logger, quanter=None): +def export_single_model(model, + arch_config, + save_path, + logger, + input_shape=None, + quanter=None): if arch_config["algorithm"] == "SRN": max_text_length = arch_config["Head"]["max_text_length"] other_shape = [ @@ -64,7 +69,7 @@ def export_single_model(model, arch_config, save_path, logger, quanter=None): else: other_shape = [ paddle.static.InputSpec( - shape=[None, 3, 64, 256], dtype="float32"), + shape=[None] + input_shape, dtype="float32"), ] model = to_static(model, input_spec=other_shape) elif arch_config["algorithm"] == "PREN": @@ -73,6 +78,25 @@ def export_single_model(model, arch_config, save_path, logger, quanter=None): shape=[None, 3, 64, 512], dtype="float32"), ] model = to_static(model, input_spec=other_shape) + elif arch_config["algorithm"] == "ViTSTR": + other_shape = [ + paddle.static.InputSpec( + shape=[None, 1, 224, 224], dtype="float32"), + ] + model = to_static(model, input_spec=other_shape) + elif arch_config["algorithm"] == "ABINet": + other_shape = [ + paddle.static.InputSpec( + shape=[None, 3, 32, 128], dtype="float32"), + ] + # print([None, 3, 32, 128]) + model = to_static(model, input_spec=other_shape) + elif arch_config["algorithm"] == "NRTR": + other_shape = [ + paddle.static.InputSpec( + shape=[None, 1, 32, 100], dtype="float32"), + ] + model = to_static(model, input_spec=other_shape) else: infer_shape = [3, -1, -1] if arch_config["model_type"] == "rec": @@ -84,8 +108,6 @@ def export_single_model(model, arch_config, save_path, logger, quanter=None): "When there is tps in the network, variable length input is not supported, and the input size needs to be the same as during training" ) infer_shape[-1] = 100 - if arch_config["algorithm"] == "NRTR": - infer_shape = [1, 32, 100] elif arch_config["model_type"] == "table": infer_shape = [3, 488, 488] model = to_static( @@ -157,6 +179,13 @@ def main(): arch_config = config["Architecture"] + if arch_config["algorithm"] == "SVTR" and arch_config["Head"][ + "name"] != 'MultiHead': + input_shape = config["Eval"]["dataset"]["transforms"][-2][ + 'SVTRRecResizeImg']['image_shape'] + else: + input_shape = None + if arch_config["algorithm"] in ["Distillation", ]: # distillation model archs = list(arch_config["Models"].values()) for idx, name in enumerate(model.model_name_list): @@ -165,7 +194,8 @@ def main(): sub_model_save_path, logger) else: save_path = os.path.join(save_path, "inference") - export_single_model(model, arch_config, save_path, logger) + export_single_model( + model, arch_config, save_path, logger, input_shape=input_shape) if __name__ == "__main__": diff --git a/tools/infer/predict_rec.py b/tools/infer/predict_rec.py index 3664ef2caf..a95f555966 100755 --- a/tools/infer/predict_rec.py +++ b/tools/infer/predict_rec.py @@ -69,6 +69,18 @@ class TextRecognizer(object): "character_dict_path": args.rec_char_dict_path, "use_space_char": args.use_space_char } + elif self.rec_algorithm == 'ViTSTR': + postprocess_params = { + 'name': 'ViTSTRLabelDecode', + "character_dict_path": args.rec_char_dict_path, + "use_space_char": args.use_space_char + } + elif self.rec_algorithm == 'ABINet': + postprocess_params = { + 'name': 'ABINetLabelDecode', + "character_dict_path": args.rec_char_dict_path, + "use_space_char": args.use_space_char + } self.postprocess_op = build_post_process(postprocess_params) self.predictor, self.input_tensor, self.output_tensors, self.config = \ utility.create_predictor(args, 'rec', logger) @@ -96,15 +108,22 @@ class TextRecognizer(object): def resize_norm_img(self, img, max_wh_ratio): imgC, imgH, imgW = self.rec_image_shape - if self.rec_algorithm == 'NRTR': + if self.rec_algorithm == 'NRTR' or self.rec_algorithm == 'ViTSTR': img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # return padding_im image_pil = Image.fromarray(np.uint8(img)) - img = image_pil.resize([100, 32], Image.ANTIALIAS) + if self.rec_algorithm == 'ViTSTR': + img = image_pil.resize([imgW, imgH], Image.BICUBIC) + else: + img = image_pil.resize([imgW, imgH], Image.ANTIALIAS) img = np.array(img) norm_img = np.expand_dims(img, -1) norm_img = norm_img.transpose((2, 0, 1)) - return norm_img.astype(np.float32) / 128. - 1. + if self.rec_algorithm == 'ViTSTR': + norm_img = norm_img.astype(np.float32) / 255. + else: + norm_img = norm_img.astype(np.float32) / 128. - 1. + return norm_img assert imgC == img.shape[2] imgW = int((imgH * max_wh_ratio)) @@ -132,17 +151,6 @@ class TextRecognizer(object): padding_im[:, :, 0:resized_w] = resized_image return padding_im - def resize_norm_img_svtr(self, img, image_shape): - - imgC, imgH, imgW = image_shape - resized_image = cv2.resize( - img, (imgW, imgH), interpolation=cv2.INTER_LINEAR) - resized_image = resized_image.astype('float32') - resized_image = resized_image.transpose((2, 0, 1)) / 255 - resized_image -= 0.5 - resized_image /= 0.5 - return resized_image - def resize_norm_img_srn(self, img, image_shape): imgC, imgH, imgW = image_shape @@ -250,6 +258,35 @@ class TextRecognizer(object): return padding_im, resize_shape, pad_shape, valid_ratio + def resize_norm_img_svtr(self, img, image_shape): + + imgC, imgH, imgW = image_shape + resized_image = cv2.resize( + img, (imgW, imgH), interpolation=cv2.INTER_LINEAR) + resized_image = resized_image.astype('float32') + resized_image = resized_image.transpose((2, 0, 1)) / 255 + resized_image -= 0.5 + resized_image /= 0.5 + return resized_image + + def resize_norm_img_abinet(self, img, image_shape): + + imgC, imgH, imgW = image_shape + + resized_image = cv2.resize( + img, (imgW, imgH), interpolation=cv2.INTER_LINEAR) + resized_image = resized_image.astype('float32') + resized_image = resized_image / 255. + + mean = np.array([0.485, 0.456, 0.406]) + std = np.array([0.229, 0.224, 0.225]) + resized_image = ( + resized_image - mean[None, None, ...]) / std[None, None, ...] + resized_image = resized_image.transpose((2, 0, 1)) + resized_image = resized_image.astype('float32') + + return resized_image + def __call__(self, img_list): img_num = len(img_list) # Calculate the aspect ratio of all text bars @@ -300,6 +337,11 @@ class TextRecognizer(object): self.rec_image_shape) norm_img = norm_img[np.newaxis, :] norm_img_batch.append(norm_img) + elif self.rec_algorithm == "ABINet": + norm_img = self.resize_norm_img_abinet( + img_list[indices[ino]], self.rec_image_shape) + norm_img = norm_img[np.newaxis, :] + norm_img_batch.append(norm_img) else: norm_img = self.resize_norm_img(img_list[indices[ino]], max_wh_ratio) diff --git a/tools/program.py b/tools/program.py index 9a90eda2f8..aa3ba82c44 100755 --- a/tools/program.py +++ b/tools/program.py @@ -576,7 +576,8 @@ def preprocess(is_train=False): assert alg in [ 'EAST', 'DB', 'SAST', 'Rosetta', 'CRNN', 'STARNet', 'RARE', 'SRN', 'CLS', 'PGNet', 'Distillation', 'NRTR', 'TableAttn', 'SAR', 'PSE', - 'SEED', 'SDMGR', 'LayoutXLM', 'LayoutLM', 'PREN', 'FCE', 'SVTR' + 'SEED', 'SDMGR', 'LayoutXLM', 'LayoutLM', 'PREN', 'FCE', 'SVTR', + 'ViTSTR', 'ABINet' ] if use_xpu: From bcd56d0054cd251d9280cc43d10348981e52b783 Mon Sep 17 00:00:00 2001 From: andyjpaddle Date: Tue, 28 Jun 2022 12:31:14 +0000 Subject: [PATCH 29/45] update doc for case --- applications/imgs/det.png | Bin 66713 -> 0 bytes applications/imgs/rec.png | Bin 13765 -> 0 bytes applications/imgs/sys_res.png | Bin 291470 -> 0 bytes applications/液晶屏读数识别.md | 86 +++++++++++++++++++-------------- 4 files changed, 49 insertions(+), 37 deletions(-) delete mode 100644 applications/imgs/det.png delete mode 100644 applications/imgs/rec.png delete mode 100644 applications/imgs/sys_res.png diff --git a/applications/imgs/det.png b/applications/imgs/det.png deleted file mode 100644 index 6f21ee170ed272e6ae9c3ef475f60855a064cbbc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 66713 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 O+|-mcmCB!*h5kPSJgL$E diff --git a/applications/imgs/rec.png b/applications/imgs/rec.png deleted file mode 100644 index c9cd069e42198947cf65aeb14923765be296ab5e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13765 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 Z>2m3xtLkl?K*`lLbrpzm@k@)a{|Bz*xt;(3 diff --git a/applications/imgs/sys_res.png b/applications/imgs/sys_res.png deleted file mode 100644 index 6a9305c62d529f87a9ab063f9933b686c8c63228..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 291470 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

{N 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 + + +将下载或训练完成的模型放置在对应目录下即可完成模型推理。 + +#### 4.3.6 模型导出推理 训练完成后,可以将训练模型转换成inference模型。inference 模型会额外保存模型的结构信息,在预测部署、加速推理上性能优越,灵活方便,适合于实际系统集成。 -#### 4.3.6.1 模型导出 +##### 4.3.6.1 模型导出 导出命令如下: ```python @@ -312,7 +318,7 @@ python tools/export_model.py \ ``` -#### 4.3.6.2 模型推理 +##### 4.3.6.2 模型推理 导出模型后,可以使用如下命令进行推理预测: ```python @@ -320,10 +326,10 @@ python tools/export_model.py \ python tools/infer/predict_det.py --image_dir="train_data/icdar2015/text_localization/test/1.jpg" --det_model_dir="./inference/det_ppocrv3/Student" ``` -# 5. 文字识别 +## 5. 文字识别 文本识别的任务是识别出图像中的文字内容,一般输入来自于文本检测得到的文本框截取出的图像文字区域。文本识别一般可以根据待识别文本形状分为规则文本识别和不规则文本识别两大类。规则文本主要指印刷字体、扫描文本等,文本大致处在水平线位置;不规则文本往往不在水平位置,存在弯曲、遮挡、模糊等问题。不规则文本场景具有很大的挑战性,也是目前文本识别领域的主要研究方向。本项目基于PP-OCRv3算法进行优化。 -## 5.1 PP-OCRv3识别算法介绍 +### 5.1 PP-OCRv3识别算法介绍 PP-OCRv3的识别模块是基于文本识别算法[SVTR](https://arxiv.org/abs/2205.00159)优化。SVTR不再采用RNN结构,通过引入Transformers结构更加有效地挖掘文本行图像的上下文信息,从而提升文本识别能力。如下图所示,PP-OCRv3采用了6个优化策略。 ![](https://ai-studio-static-online.cdn.bcebos.com/d4f5344b5b854d50be738671598a89a45689c6704c4d481fb904dd7cf72f2a1a) @@ -337,7 +343,7 @@ PP-OCRv3的识别模块是基于文本识别算法[SVTR](https://arxiv.org/abs/2 详细优化策略描述请参考[PP-OCRv3优化策略](https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.5/doc/doc_ch/PP-OCRv3_introduction.md#3-%E8%AF%86%E5%88%AB%E4%BC%98%E5%8C%96) -## 5.2 数据准备 +### 5.2 数据准备 [计量设备屏幕字符识别数据集](https://aistudio.baidu.com/aistudio/datasetdetail/128714)数据来源于实际项目中各种计量设备的数显屏,以及在网上搜集的一些其他数显屏,包含训练集19912张,测试集4099张。 ```python @@ -368,10 +374,10 @@ def get_one_image(train): get_one_image(train) ``` -![rec_png](imgs/rec.png) +![rec_png](https://ai-studio-static-online.cdn.bcebos.com/3de0d475c69746d0a184029001ef07c85fd68816d66d4beaa10e6ef60030f9b4) -## 5.3 模型训练 -### 下载预训练模型 +### 5.3 模型训练 +#### 下载预训练模型 下载我们需要的PP-OCRv3识别预训练模型,更多选择请自行选择其他的[文字识别模型](https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.5/doc/doc_ch/models_list.md#2-%E6%96%87%E6%9C%AC%E8%AF%86%E5%88%AB%E6%A8%A1%E5%9E%8B) ```python @@ -381,7 +387,7 @@ wget -P ./pretrained_models/ https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch tar -xf ./pretrained_models/ch_PP-OCRv3_rec_train.tar -C pretrained_models ``` -### 修改配置文件 +#### 修改配置文件 我们使用configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml,主要修改训练轮数和学习率参相关参数,设置预训练模型路径,设置数据集路径。 另外,batch_size可根据自己机器显存大小进行调整。 具体修改如下几个地方: ``` epoch_num: 100 # 训练epoch数 @@ -439,7 +445,7 @@ python tools/eval.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml -o |---|---------------------------|---| | 0 | PP-OCRv3中英文超轻量识别预训练模型直接预测 |70.4%| -### 开始训练 +#### 开始训练 我们使用上面修改好的配置文件configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml,预训练模型,数据集路径,学习率,训练轮数等都已经设置完毕后,可以使用下面命令开始训练。 ```python @@ -460,9 +466,15 @@ python tools/eval.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml -o | 0 | PP-OCRv3中英文超轻量识别预训练模型直接预测 |70.4%| | 1 | PP-OCRv3中英文超轻量识别预训练模型finetune |82.2%| -## 5.4 模型导出推理 +如需获取已训练模型,请扫码填写问卷,加入PaddleOCR官方交流群获取全部OCR垂类模型下载链接、《动手学OCR》电子书等全套OCR学习资料🎁 +

+将下载或训练完成的模型放置在对应目录下即可完成模型推理。 + +### 5.4 模型导出推理 训练完成后,可以将训练模型转换成inference模型。inference 模型会额外保存模型的结构信息,在预测部署、加速推理上性能优越,灵活方便,适合于实际系统集成。 -### 模型导出 +#### 模型导出 导出命令如下: ```python @@ -470,7 +482,7 @@ python tools/eval.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml -o python tools/export_model.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml -o Global.pretrained_model="./output/ch_PP-OCR_v3_rec/best_accuracy" Global.save_inference_dir="./inference/rec_ppocrv3/" ``` -### 模型推理 +#### 模型推理 导出模型后,可以使用如下命令进行推理预测 ```python @@ -478,7 +490,7 @@ python tools/export_model.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillatio python tools/infer/predict_rec.py --image_dir="train_data/ic15_data/test/1_crop_0.jpg" --rec_model_dir="./inference/rec_ppocrv3/Student" ``` -# 6. 系统串联 +## 6. 系统串联 我们将上面训练好的检测和识别模型进行系统串联测试,命令如下: ```python @@ -500,9 +512,9 @@ plt.imshow(img) plt.show() ``` -![sys_res_png](imgs/sys_res.png) +![sys_res_png](https://ai-studio-static-online.cdn.bcebos.com/901ab741cb46441ebec510b37e63b9d8d1b7c95f63cc4e5e8757f35179ae6373) -## 6.1 后处理 +### 6.1 后处理 如果需要获取key-value信息,可以基于启发式的规则,将识别结果与关键字库进行匹配;如果匹配上了,则取该字段为key, 后面一个字段为value。 ```python @@ -523,7 +535,7 @@ def postprocess(rec_res): key_value = postprocess(filter_rec_res) ``` -# 7. PaddleServing部署 +## 7. PaddleServing部署 首先需要安装PaddleServing部署相关的环境 ```python @@ -532,7 +544,7 @@ python -m pip install paddle_serving_client python -m pip install paddle-serving-app ``` -## 7.1 转化检测模型 +### 7.1 转化检测模型 ```python cd deploy/pdserving/ @@ -543,7 +555,7 @@ python -m paddle_serving_client.convert --dirname ../../inference/det_ppocrv3/St --serving_client ./ppocr_det_v3_client/ ``` -## 7.2 转化识别模型 +### 7.2 转化识别模型 ```python python -m paddle_serving_client.convert --dirname ../../inference/rec_ppocrv3/Student \ @@ -554,7 +566,7 @@ python -m paddle_serving_client.convert --dirname ../../inference/rec_ppocrv3/St ``` -## 7.3 启动服务 +### 7.3 启动服务 首先可以将后处理代码加入到web_service.py中,具体修改如下: ``` # 代码153行后面增加下面代码 @@ -582,7 +594,7 @@ res = {"result": str(key_value)} python web_service.py 2>&1 >log.txt ``` -## 7.4 发送请求 +### 7.4 发送请求 然后再开启一个新的终端,运行下面的客户端代码 ```python From 2916625a8f88f1e6d065fbdfe064ee910712c1b4 Mon Sep 17 00:00:00 2001 From: xiaoting <31891223+tink2123@users.noreply.github.com> Date: Wed, 29 Jun 2022 15:31:48 +0800 Subject: [PATCH 30/45] =?UTF-8?q?Update=20=E5=85=89=E5=8A=9F=E7=8E=87?= =?UTF-8?q?=E8=AE=A1=E6=95=B0=E7=A0=81=E7=AE=A1=E5=AD=97=E7=AC=A6=E8=AF=86?= =?UTF-8?q?=E5=88=AB.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- applications/光功率计数码管字符识别.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/applications/光功率计数码管字符识别.md b/applications/光功率计数码管字符识别.md index d6c11259bf..2a35cb170e 100644 --- a/applications/光功率计数码管字符识别.md +++ b/applications/光功率计数码管字符识别.md @@ -21,12 +21,16 @@ 第二步,使用文本识别模型获得准确的数字和单位信息。 -本项目主要介绍如何完成第二步文本识别部分,包括: +本项目主要介绍如何完成第二步文本识别部分,包括:真实评估集的建立、训练数据的合成、基于 PP-OCRv3 和 SVTR_Tiny 两个模型进行训练,以及评估和推理。 -* 真实评估集的建立 -* 训练数据的合成 -* 基于 PP-OCRv3 和 SVTR_Tiny 两个模型进行训练 -* 评估并推理 +本项目难点如下: + +- 光功率计数码管字符数据较少,难以获取。 +- 数码管中小数点占像素较少,容易漏识别。 + +针对以上问题, 本例选用 PP-OCRv3 和 SVTR_Tiny 两个高精度模型训练,同时提供了真实数据挖掘案例和数据合成案例。基于 PP-OCRv3 模型,在构建的真实评估集上精度从 52% 提升至 72%,SVTR_Tiny 模型精度可达到 78.9%。 + +aistudio项目链接: [光功率计数码管字符识别](https://aistudio.baidu.com/aistudio/projectdetail/4049044?contributionType=1) ## 2. PaddleOCR 快速使用 From 46fafa298710cf7b4555a98aa03fefa18fc16c33 Mon Sep 17 00:00:00 2001 From: an1018 <614803115@qq.com> Date: Wed, 29 Jun 2022 17:01:47 +0800 Subject: [PATCH 31/45] update --- applications/PCB字符识别/PCB字符识别.md | 6 ++++- applications/多模态表单识别.md | 36 ++++++++++++------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/applications/PCB字符识别/PCB字符识别.md b/applications/PCB字符识别/PCB字符识别.md index a5052e2897..ee13bacffd 100644 --- a/applications/PCB字符识别/PCB字符识别.md +++ b/applications/PCB字符识别/PCB字符识别.md @@ -206,7 +206,11 @@ Eval.dataset.transforms.DetResizeForTest: 尺寸 limit_type: 'min' ``` -然后执行评估代码 +如需获取已训练模型,请扫码填写问卷,加入PaddleOCR官方交流群获取全部OCR垂类模型下载链接、《动手学OCR》电子书等全套OCR学习资料🎁 +
+ +
+将下载或训练完成的模型放置在对应目录下即可完成模型评估。 ```python diff --git a/applications/多模态表单识别.md b/applications/多模态表单识别.md index 27f981101a..471ca633c1 100644 --- a/applications/多模态表单识别.md +++ b/applications/多模态表单识别.md @@ -7,12 +7,12 @@ - [3.2 转换为PaddleOCR检测和识别格式](#32-转换为paddleocr检测和识别格式) - [4 OCR](#4-ocr) - [4.1 文本检测](#41-文本检测) - - [**4.1.1 方案1:预训练模型**](#411-方案1预训练模型) - - [**4.1.2 方案2:XFUND数据集+fine-tune**](#412-方案2xfund数据集fine-tune) + - [4.1.1 方案1:预训练模型](#411-方案1预训练模型) + - [4.1.2 方案2:XFUND数据集+fine-tune](#412-方案2xfund数据集fine-tune) - [4.2 文本识别](#42-文本识别) - - [**4.2.1 方案1:预训练模型**](#421-方案1预训练模型) - - [**4.2.2 方案2:XFUND数据集+finetune**](#422-方案2xfund数据集finetune) - - [**4.2.3 方案3:XFUND数据集+finetune+真实通用识别数据**](#423-方案3xfund数据集finetune真实通用识别数据) + - [4.2.1 方案1:预训练模型](#421-方案1预训练模型) + - [4.2.2 方案2:XFUND数据集+finetune](#422-方案2xfund数据集finetune) + - [4.2.3 方案3:XFUND数据集+finetune+真实通用识别数据](#423-方案3xfund数据集finetune真实通用识别数据) - [5 文档视觉问答(DOC-VQA)](#5-文档视觉问答doc-vqa) - [5.1 SER](#51-ser) - [5.1.1 模型训练](#511-模型训练) @@ -195,7 +195,7 @@ python trans_xfund_data.py - **PP-OCRv2中英文超轻量检测预训练模型** - **XFUND数据集+fine-tune** -#### **4.1.1 方案1:预训练模型** +#### 4.1.1 方案1:预训练模型 **1)下载预训练模型** @@ -265,7 +265,7 @@ python tools/eval.py \ 使用文本检测预训练模型在XFUND验证集上评估,达到77%左右,充分说明ppocr提供的预训练模型具有泛化能力。 -#### **4.1.2 方案2:XFUND数据集+fine-tune** +#### 4.1.2 方案2:XFUND数据集+fine-tune PaddleOCR提供的蒸馏预训练模型包含了多个模型的参数,我们提取Student模型的参数,在XFUND数据集上进行finetune,可以参考如下代码: @@ -316,7 +316,13 @@ CUDA_VISIBLE_DEVICES=0 python tools/train.py \
图8 文本检测方案2-模型评估
-使用训练好的模型进行评估,更新模型路径`Global.checkpoints`。 +使用训练好的模型进行评估,更新模型路径`Global.checkpoints`。如需获取已训练模型,请扫码填写问卷,加入PaddleOCR官方交流群获取全部OCR垂类模型下载链接、《动手学OCR》电子书等全套OCR学习资料🎁 + +
+ +
+ +将下载或训练完成的模型放置在对应目录下即可完成模型评估 ```python @@ -412,7 +418,7 @@ use_gpu:是否使用GPU - XFUND数据集+fine-tune - XFUND数据集+fine-tune+真实通用识别数据 -#### **4.2.1 方案1:预训练模型** +#### 4.2.1 方案1:预训练模型 **1)下载预训练模型** @@ -461,7 +467,7 @@ CUDA_VISIBLE_DEVICES=0 python tools/eval.py \ 使用文本预训练模型在XFUND验证集上评估,acc达到67%左右,充分说明ppocr提供的预训练模型具有泛化能力。 -#### **4.2.2 方案2:XFUND数据集+finetune** +#### 4.2.2 方案2:XFUND数据集+finetune 同检测模型,我们提取Student模型的参数,在XFUND数据集上进行finetune,可以参考如下代码: @@ -528,7 +534,7 @@ CUDA_VISIBLE_DEVICES=0 python tools/eval.py \ 使用XFUND数据集+finetune训练,在验证集上评估达到72%左右,说明 finetune会提升垂类场景效果。 -#### **4.2.3 方案3:XFUND数据集+finetune+真实通用识别数据** +#### 4.2.3 方案3:XFUND数据集+finetune+真实通用识别数据 接着我们在上述`XFUND数据集+finetune`实验的基础上,添加真实通用识别数据,进一步提升识别效果。首先准备真实通用识别数据,并上传到AIStudio: @@ -873,14 +879,6 @@ with open('output/re/infer_results.txt', 'r', encoding='utf-8') as fin: workbook.close() ``` -## 获得模型 - -如需获取已训练模型,请扫码填写问卷,加入PaddleOCR官方交流群获取全部OCR垂类模型下载链接、《动手学OCR》电子书等全套OCR学习资料🎁 -
- -
-将下载或训练完成的模型放置在对应目录下即可完成模型推理 - ## 更多资源 - 更多深度学习知识、产业案例、面试宝典等,请参考:[awesome-DeepLearning](https://github.com/paddlepaddle/awesome-DeepLearning) From 304162824093060b964b097c4d29012b00b5ed9a Mon Sep 17 00:00:00 2001 From: andyjpaddle Date: Wed, 29 Jun 2022 09:12:27 +0000 Subject: [PATCH 32/45] fix serving log --- test_tipc/test_serving_infer_cpp.sh | 6 ++++-- test_tipc/test_serving_infer_python.sh | 15 ++++++++------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/test_tipc/test_serving_infer_cpp.sh b/test_tipc/test_serving_infer_cpp.sh index 4088c66f57..6e313cd949 100644 --- a/test_tipc/test_serving_infer_cpp.sh +++ b/test_tipc/test_serving_infer_cpp.sh @@ -87,11 +87,13 @@ function func_serving(){ set_image_dir=$(func_set_params "${image_dir_key}" "${image_dir_value}") python_list=(${python_list}) cd ${serving_dir_value} + unset https_proxy + unset http_proxy # cpp serving for gpu_id in ${gpu_value[*]}; do if [ ${gpu_id} = "null" ]; then server_log_path="${LOG_PATH}/cpp_server_cpu.log" - web_service_cpp_cmd="${python_list[0]} ${web_service_py} --model ${det_server_value} ${rec_server_value} ${op_key} ${op_value} ${port_key} ${port_value} > ${server_log_path} 2>&1 " + web_service_cpp_cmd="nohup ${python_list[0]} ${web_service_py} --model ${det_server_value} ${rec_server_value} ${op_key} ${op_value} ${port_key} ${port_value} > ${server_log_path} 2>&1 &" eval $web_service_cpp_cmd last_status=${PIPESTATUS[0]} status_check $last_status "${web_service_cpp_cmd}" "${status_log}" "${model_name}" @@ -105,7 +107,7 @@ function func_serving(){ ps ux | grep -i ${port_value} | awk '{print $2}' | xargs kill -s 9 else server_log_path="${LOG_PATH}/cpp_server_gpu.log" - web_service_cpp_cmd="${python_list[0]} ${web_service_py} --model ${det_server_value} ${rec_server_value} ${op_key} ${op_value} ${port_key} ${port_value} ${gpu_key} ${gpu_id} > ${server_log_path} 2>&1 " + web_service_cpp_cmd="nohup ${python_list[0]} ${web_service_py} --model ${det_server_value} ${rec_server_value} ${op_key} ${op_value} ${port_key} ${port_value} ${gpu_key} ${gpu_id} > ${server_log_path} 2>&1 &" eval $web_service_cpp_cmd sleep 5s _save_log_path="${LOG_PATH}/cpp_client_gpu.log" diff --git a/test_tipc/test_serving_infer_python.sh b/test_tipc/test_serving_infer_python.sh index 57dab6aeb5..b14517265d 100644 --- a/test_tipc/test_serving_infer_python.sh +++ b/test_tipc/test_serving_infer_python.sh @@ -112,7 +112,8 @@ function func_serving(){ cd ${serving_dir_value} python=${python_list[0]} - + unset https_proxy + unset http_proxy # python serving for use_gpu in ${web_use_gpu_list[*]}; do if [ ${use_gpu} = "null" ]; then @@ -123,19 +124,19 @@ function func_serving(){ if [ ${model_name} = "ch_PP-OCRv2" ] || [ ${model_name} = "ch_PP-OCRv3" ] || [ ${model_name} = "ch_ppocr_mobile_v2.0" ] || [ ${model_name} = "ch_ppocr_server_v2.0" ]; then set_det_model_config=$(func_set_params "${det_server_key}" "${det_server_value}") set_rec_model_config=$(func_set_params "${rec_server_key}" "${rec_server_value}") - web_service_cmd="${python} ${web_service_py} ${web_use_gpu_key}="" ${web_use_mkldnn_key}=${use_mkldnn} ${set_cpu_threads} ${set_det_model_config} ${set_rec_model_config} > ${server_log_path} 2>&1 " + web_service_cmd="nohup ${python} ${web_service_py} ${web_use_gpu_key}="" ${web_use_mkldnn_key}=${use_mkldnn} ${set_cpu_threads} ${set_det_model_config} ${set_rec_model_config} > ${server_log_path} 2>&1 &" eval $web_service_cmd last_status=${PIPESTATUS[0]} status_check $last_status "${web_service_cmd}" "${status_log}" "${model_name}" elif [[ ${model_name} =~ "det" ]]; then set_det_model_config=$(func_set_params "${det_server_key}" "${det_server_value}") - web_service_cmd="${python} ${web_service_py} ${web_use_gpu_key}="" ${web_use_mkldnn_key}=${use_mkldnn} ${set_cpu_threads} ${set_det_model_config} > ${server_log_path} 2>&1 " + web_service_cmd="nohup ${python} ${web_service_py} ${web_use_gpu_key}="" ${web_use_mkldnn_key}=${use_mkldnn} ${set_cpu_threads} ${set_det_model_config} > ${server_log_path} 2>&1 &" eval $web_service_cmd last_status=${PIPESTATUS[0]} status_check $last_status "${web_service_cmd}" "${status_log}" "${model_name}" elif [[ ${model_name} =~ "rec" ]]; then set_rec_model_config=$(func_set_params "${rec_server_key}" "${rec_server_value}") - web_service_cmd="${python} ${web_service_py} ${web_use_gpu_key}="" ${web_use_mkldnn_key}=${use_mkldnn} ${set_cpu_threads} ${set_rec_model_config} > ${server_log_path} 2>&1 " + web_service_cmd="nohup ${python} ${web_service_py} ${web_use_gpu_key}="" ${web_use_mkldnn_key}=${use_mkldnn} ${set_cpu_threads} ${set_rec_model_config} > ${server_log_path} 2>&1 &" eval $web_service_cmd last_status=${PIPESTATUS[0]} status_check $last_status "${web_service_cmd}" "${status_log}" "${model_name}" @@ -174,19 +175,19 @@ function func_serving(){ if [ ${model_name} = "ch_PP-OCRv2" ] || [ ${model_name} = "ch_PP-OCRv3" ] || [ ${model_name} = "ch_ppocr_mobile_v2.0" ] || [ ${model_name} = "ch_ppocr_server_v2.0" ]; then set_det_model_config=$(func_set_params "${det_server_key}" "${det_server_value}") set_rec_model_config=$(func_set_params "${rec_server_key}" "${rec_server_value}") - web_service_cmd="${python} ${web_service_py} ${set_tensorrt} ${set_precision} ${set_det_model_config} ${set_rec_model_config} > ${server_log_path} 2>&1 " + web_service_cmd="nohup ${python} ${web_service_py} ${set_tensorrt} ${set_precision} ${set_det_model_config} ${set_rec_model_config} > ${server_log_path} 2>&1 &" eval $web_service_cmd last_status=${PIPESTATUS[0]} status_check $last_status "${web_service_cmd}" "${status_log}" "${model_name}" elif [[ ${model_name} =~ "det" ]]; then set_det_model_config=$(func_set_params "${det_server_key}" "${det_server_value}") - web_service_cmd="${python} ${web_service_py} ${set_tensorrt} ${set_precision} ${set_det_model_config} > ${server_log_path} 2>&1 " + web_service_cmd="nohup ${python} ${web_service_py} ${set_tensorrt} ${set_precision} ${set_det_model_config} > ${server_log_path} 2>&1 &" eval $web_service_cmd last_status=${PIPESTATUS[0]} status_check $last_status "${web_service_cmd}" "${status_log}" "${model_name}" elif [[ ${model_name} =~ "rec" ]]; then set_rec_model_config=$(func_set_params "${rec_server_key}" "${rec_server_value}") - web_service_cmd="${python} ${web_service_py} ${set_tensorrt} ${set_precision} ${set_rec_model_config} > ${server_log_path} 2>&1 " + web_service_cmd="nohup ${python} ${web_service_py} ${set_tensorrt} ${set_precision} ${set_rec_model_config} > ${server_log_path} 2>&1 &" eval $web_service_cmd last_status=${PIPESTATUS[0]} status_check $last_status "${web_service_cmd}" "${status_log}" "${model_name}" From 0aa00f1ce840de5d764a786276eaf12d8a11890d Mon Sep 17 00:00:00 2001 From: andyjpaddle Date: Wed, 29 Jun 2022 09:39:15 +0000 Subject: [PATCH 33/45] update case --- applications/液晶屏读数识别.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/applications/液晶屏读数识别.md b/applications/液晶屏读数识别.md index b85bf56daf..ff2fb2cb48 100644 --- a/applications/液晶屏读数识别.md +++ b/applications/液晶屏读数识别.md @@ -31,6 +31,8 @@ ## 2. 项目内容 本项目基于PaddleOCR开源套件,以PP-OCRv3检测和识别模型为基础,针对液晶屏读数识别场景进行优化。 +Aistudio项目链接:[OCR液晶屏读数识别](https://aistudio.baidu.com/aistudio/projectdetail/4080130) + ## 3. 安装环境 ```python From b998a95c7d8ea24af16b8406d9429885910b4900 Mon Sep 17 00:00:00 2001 From: Evezerest <50011306+Evezerest@users.noreply.github.com> Date: Wed, 29 Jun 2022 18:54:34 +0800 Subject: [PATCH 34/45] add application.md --- doc/doc_ch/application.md | 41 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 doc/doc_ch/application.md diff --git a/doc/doc_ch/application.md b/doc/doc_ch/application.md new file mode 100644 index 0000000000..5135dfac10 --- /dev/null +++ b/doc/doc_ch/application.md @@ -0,0 +1,41 @@ +# 场景应用 + +PaddleOCR场景应用覆盖通用,制造、金融、交通行业的主要OCR垂类应用,在PP-OCR、PP-Structure的通用能力基础之上,以notebook的形式展示利用场景数据微调、模型优化方法、数据增广等内容,为开发者快速落地OCR应用提供示范与启发。 + +> 如需下载全部垂类模型,可以扫描下方二维码,关注公众号填写问卷后,加入PaddleOCR官方交流群获取20G OCR学习大礼包(内含《动手学OCR》电子书、课程回放视频、前沿论文等重磅资料) + +
+ +
+ + +> 如果您是企业开发者且未在下述场景中找到合适的方案,可以填写[OCR应用合作调研问卷](https://paddle.wjx.cn/vj/QwF7GKw.aspx),免费与官方团队展开不同层次的合作,包括但不限于问题抽象、确定技术方案、项目答疑、共同研发等。如果您已经使用PaddleOCR落地项目,也可以填写此问卷,与飞桨平台共同宣传推广,提升企业技术品宣。期待您的提交! + +## 通用 + +| 类别 | 亮点 | 类别 | 亮点 | +| ---------------------- | -------- | ---------- | ------------ | +| 高精度中文识别模型SVTR | 新增模型 | 手写体识别 | 新增字形支持 | + +## 制造 + +| 类别 | 亮点 | 类别 | 亮点 | +| -------------- | ------------------------------ | -------------- | -------------------- | +| 数码管识别 | 数码管数据合成、漏识别调优 | 电表识别 | 大分辨率图像检测调优 | +| 液晶屏读数识别 | 检测模型蒸馏、Serving部署 | PCB文字识别 | 小尺寸文本检测与识别 | +| 包装生产日期 | 点阵字符合成、过曝过暗文字识别 | 液晶屏缺陷检测 | 非文字形态识别 | + +## 金融 + +| 类别 | 亮点 | 类别 | 亮点 | +| -------------- | ------------------------ | ------------ | --------------------- | +| 表单VQA | 多模态通用表单结构化提取 | 通用卡证识别 | 通用结构化提取 | +| 增值税发票 | 尽请期待 | 身份证识别 | 结构化提取、图像阴影 | +| 印章检测与识别 | 端到端弯曲文本识别 | 合同比对 | 密集文本检测、NLP串联 | + +## 交通 + +| 类别 | 亮点 | 类别 | 亮点 | +| ----------------- | ------------------------------ | ---------- | -------- | +| 车牌识别 | 多角度图像、轻量模型、端侧部署 | 快递单识别 | 尽请期待 | +| 驾驶证/行驶证识别 | 尽请期待 | | | \ No newline at end of file From 14dedcacf6dab4eda75927f1feff9177f961c932 Mon Sep 17 00:00:00 2001 From: Evezerest <50011306+Evezerest@users.noreply.github.com> Date: Wed, 29 Jun 2022 18:56:24 +0800 Subject: [PATCH 35/45] move application.md --- applications/application.md | 41 +++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 applications/application.md diff --git a/applications/application.md b/applications/application.md new file mode 100644 index 0000000000..5135dfac10 --- /dev/null +++ b/applications/application.md @@ -0,0 +1,41 @@ +# 场景应用 + +PaddleOCR场景应用覆盖通用,制造、金融、交通行业的主要OCR垂类应用,在PP-OCR、PP-Structure的通用能力基础之上,以notebook的形式展示利用场景数据微调、模型优化方法、数据增广等内容,为开发者快速落地OCR应用提供示范与启发。 + +> 如需下载全部垂类模型,可以扫描下方二维码,关注公众号填写问卷后,加入PaddleOCR官方交流群获取20G OCR学习大礼包(内含《动手学OCR》电子书、课程回放视频、前沿论文等重磅资料) + +
+ +
+ + +> 如果您是企业开发者且未在下述场景中找到合适的方案,可以填写[OCR应用合作调研问卷](https://paddle.wjx.cn/vj/QwF7GKw.aspx),免费与官方团队展开不同层次的合作,包括但不限于问题抽象、确定技术方案、项目答疑、共同研发等。如果您已经使用PaddleOCR落地项目,也可以填写此问卷,与飞桨平台共同宣传推广,提升企业技术品宣。期待您的提交! + +## 通用 + +| 类别 | 亮点 | 类别 | 亮点 | +| ---------------------- | -------- | ---------- | ------------ | +| 高精度中文识别模型SVTR | 新增模型 | 手写体识别 | 新增字形支持 | + +## 制造 + +| 类别 | 亮点 | 类别 | 亮点 | +| -------------- | ------------------------------ | -------------- | -------------------- | +| 数码管识别 | 数码管数据合成、漏识别调优 | 电表识别 | 大分辨率图像检测调优 | +| 液晶屏读数识别 | 检测模型蒸馏、Serving部署 | PCB文字识别 | 小尺寸文本检测与识别 | +| 包装生产日期 | 点阵字符合成、过曝过暗文字识别 | 液晶屏缺陷检测 | 非文字形态识别 | + +## 金融 + +| 类别 | 亮点 | 类别 | 亮点 | +| -------------- | ------------------------ | ------------ | --------------------- | +| 表单VQA | 多模态通用表单结构化提取 | 通用卡证识别 | 通用结构化提取 | +| 增值税发票 | 尽请期待 | 身份证识别 | 结构化提取、图像阴影 | +| 印章检测与识别 | 端到端弯曲文本识别 | 合同比对 | 密集文本检测、NLP串联 | + +## 交通 + +| 类别 | 亮点 | 类别 | 亮点 | +| ----------------- | ------------------------------ | ---------- | -------- | +| 车牌识别 | 多角度图像、轻量模型、端侧部署 | 快递单识别 | 尽请期待 | +| 驾驶证/行驶证识别 | 尽请期待 | | | \ No newline at end of file From 8dd60b566ffa4fc2697bf6b1c3dfcb1d890680d5 Mon Sep 17 00:00:00 2001 From: Evezerest <50011306+Evezerest@users.noreply.github.com> Date: Wed, 29 Jun 2022 18:57:26 +0800 Subject: [PATCH 36/45] update name --- applications/README.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 applications/README.md diff --git a/applications/README.md b/applications/README.md new file mode 100644 index 0000000000..5135dfac10 --- /dev/null +++ b/applications/README.md @@ -0,0 +1,41 @@ +# 场景应用 + +PaddleOCR场景应用覆盖通用,制造、金融、交通行业的主要OCR垂类应用,在PP-OCR、PP-Structure的通用能力基础之上,以notebook的形式展示利用场景数据微调、模型优化方法、数据增广等内容,为开发者快速落地OCR应用提供示范与启发。 + +> 如需下载全部垂类模型,可以扫描下方二维码,关注公众号填写问卷后,加入PaddleOCR官方交流群获取20G OCR学习大礼包(内含《动手学OCR》电子书、课程回放视频、前沿论文等重磅资料) + +
+ +
+ + +> 如果您是企业开发者且未在下述场景中找到合适的方案,可以填写[OCR应用合作调研问卷](https://paddle.wjx.cn/vj/QwF7GKw.aspx),免费与官方团队展开不同层次的合作,包括但不限于问题抽象、确定技术方案、项目答疑、共同研发等。如果您已经使用PaddleOCR落地项目,也可以填写此问卷,与飞桨平台共同宣传推广,提升企业技术品宣。期待您的提交! + +## 通用 + +| 类别 | 亮点 | 类别 | 亮点 | +| ---------------------- | -------- | ---------- | ------------ | +| 高精度中文识别模型SVTR | 新增模型 | 手写体识别 | 新增字形支持 | + +## 制造 + +| 类别 | 亮点 | 类别 | 亮点 | +| -------------- | ------------------------------ | -------------- | -------------------- | +| 数码管识别 | 数码管数据合成、漏识别调优 | 电表识别 | 大分辨率图像检测调优 | +| 液晶屏读数识别 | 检测模型蒸馏、Serving部署 | PCB文字识别 | 小尺寸文本检测与识别 | +| 包装生产日期 | 点阵字符合成、过曝过暗文字识别 | 液晶屏缺陷检测 | 非文字形态识别 | + +## 金融 + +| 类别 | 亮点 | 类别 | 亮点 | +| -------------- | ------------------------ | ------------ | --------------------- | +| 表单VQA | 多模态通用表单结构化提取 | 通用卡证识别 | 通用结构化提取 | +| 增值税发票 | 尽请期待 | 身份证识别 | 结构化提取、图像阴影 | +| 印章检测与识别 | 端到端弯曲文本识别 | 合同比对 | 密集文本检测、NLP串联 | + +## 交通 + +| 类别 | 亮点 | 类别 | 亮点 | +| ----------------- | ------------------------------ | ---------- | -------- | +| 车牌识别 | 多角度图像、轻量模型、端侧部署 | 快递单识别 | 尽请期待 | +| 驾驶证/行驶证识别 | 尽请期待 | | | \ No newline at end of file From ab13b42f91527cf6224ef18e81104105c45a4a73 Mon Sep 17 00:00:00 2001 From: Evezerest <50011306+Evezerest@users.noreply.github.com> Date: Wed, 29 Jun 2022 18:58:05 +0800 Subject: [PATCH 37/45] Delete application.md --- applications/application.md | 41 ------------------------------------- 1 file changed, 41 deletions(-) delete mode 100644 applications/application.md diff --git a/applications/application.md b/applications/application.md deleted file mode 100644 index 5135dfac10..0000000000 --- a/applications/application.md +++ /dev/null @@ -1,41 +0,0 @@ -# 场景应用 - -PaddleOCR场景应用覆盖通用,制造、金融、交通行业的主要OCR垂类应用,在PP-OCR、PP-Structure的通用能力基础之上,以notebook的形式展示利用场景数据微调、模型优化方法、数据增广等内容,为开发者快速落地OCR应用提供示范与启发。 - -> 如需下载全部垂类模型,可以扫描下方二维码,关注公众号填写问卷后,加入PaddleOCR官方交流群获取20G OCR学习大礼包(内含《动手学OCR》电子书、课程回放视频、前沿论文等重磅资料) - -
- -
- - -> 如果您是企业开发者且未在下述场景中找到合适的方案,可以填写[OCR应用合作调研问卷](https://paddle.wjx.cn/vj/QwF7GKw.aspx),免费与官方团队展开不同层次的合作,包括但不限于问题抽象、确定技术方案、项目答疑、共同研发等。如果您已经使用PaddleOCR落地项目,也可以填写此问卷,与飞桨平台共同宣传推广,提升企业技术品宣。期待您的提交! - -## 通用 - -| 类别 | 亮点 | 类别 | 亮点 | -| ---------------------- | -------- | ---------- | ------------ | -| 高精度中文识别模型SVTR | 新增模型 | 手写体识别 | 新增字形支持 | - -## 制造 - -| 类别 | 亮点 | 类别 | 亮点 | -| -------------- | ------------------------------ | -------------- | -------------------- | -| 数码管识别 | 数码管数据合成、漏识别调优 | 电表识别 | 大分辨率图像检测调优 | -| 液晶屏读数识别 | 检测模型蒸馏、Serving部署 | PCB文字识别 | 小尺寸文本检测与识别 | -| 包装生产日期 | 点阵字符合成、过曝过暗文字识别 | 液晶屏缺陷检测 | 非文字形态识别 | - -## 金融 - -| 类别 | 亮点 | 类别 | 亮点 | -| -------------- | ------------------------ | ------------ | --------------------- | -| 表单VQA | 多模态通用表单结构化提取 | 通用卡证识别 | 通用结构化提取 | -| 增值税发票 | 尽请期待 | 身份证识别 | 结构化提取、图像阴影 | -| 印章检测与识别 | 端到端弯曲文本识别 | 合同比对 | 密集文本检测、NLP串联 | - -## 交通 - -| 类别 | 亮点 | 类别 | 亮点 | -| ----------------- | ------------------------------ | ---------- | -------- | -| 车牌识别 | 多角度图像、轻量模型、端侧部署 | 快递单识别 | 尽请期待 | -| 驾驶证/行驶证识别 | 尽请期待 | | | \ No newline at end of file From 427628bb3f5e726df5f531fed78b5101b2edb93c Mon Sep 17 00:00:00 2001 From: Leif <4603009@qq.com> Date: Wed, 29 Jun 2022 19:19:39 +0800 Subject: [PATCH 38/45] Update readme of applications Update readme of applications --- applications/README.md | 34 +++++++++--------- .../corpus/digital.txt | 0 .../fonts/DS-DIGI.TTF | Bin .../fonts/DS-DIGIB.TTF | Bin .../光功率计数码管字符识别.md | 0 5 files changed, 17 insertions(+), 17 deletions(-) rename applications/{ => 光功率计数码管字符识别}/corpus/digital.txt (100%) rename applications/{ => 光功率计数码管字符识别}/fonts/DS-DIGI.TTF (100%) rename applications/{ => 光功率计数码管字符识别}/fonts/DS-DIGIB.TTF (100%) rename applications/{ => 光功率计数码管字符识别}/光功率计数码管字符识别.md (100%) diff --git a/applications/README.md b/applications/README.md index 5135dfac10..eba1e205dc 100644 --- a/applications/README.md +++ b/applications/README.md @@ -13,29 +13,29 @@ PaddleOCR场景应用覆盖通用,制造、金融、交通行业的主要OCR ## 通用 -| 类别 | 亮点 | 类别 | 亮点 | -| ---------------------- | -------- | ---------- | ------------ | -| 高精度中文识别模型SVTR | 新增模型 | 手写体识别 | 新增字形支持 | +| 类别 | 亮点 | 类别 | 亮点 | +| ------------------------------------------------- | -------- | ---------- | ------------ | +| [高精度中文识别模型SVTR](./高精度中文识别模型.md) | 新增模型 | 手写体识别 | 新增字形支持 | ## 制造 -| 类别 | 亮点 | 类别 | 亮点 | -| -------------- | ------------------------------ | -------------- | -------------------- | -| 数码管识别 | 数码管数据合成、漏识别调优 | 电表识别 | 大分辨率图像检测调优 | -| 液晶屏读数识别 | 检测模型蒸馏、Serving部署 | PCB文字识别 | 小尺寸文本检测与识别 | -| 包装生产日期 | 点阵字符合成、过曝过暗文字识别 | 液晶屏缺陷检测 | 非文字形态识别 | +| 类别 | 亮点 | 类别 | 亮点 | +| ------------------------------------------------------------ | ------------------------------ | ------------------------------------------- | -------------------- | +| [数码管识别](./光功率计数码管字符识别/光功率计数码管字符识别.md) | 数码管数据合成、漏识别调优 | 电表识别 | 大分辨率图像检测调优 | +| [液晶屏读数识别](./液晶屏读数识别.md) | 检测模型蒸馏、Serving部署 | [PCB文字识别](./PCB字符识别/PCB字符识别.md) | 小尺寸文本检测与识别 | +| [包装生产日期](./包装生产日期识别.md) | 点阵字符合成、过曝过暗文字识别 | 液晶屏缺陷检测 | 非文字字符识别 | ## 金融 -| 类别 | 亮点 | 类别 | 亮点 | -| -------------- | ------------------------ | ------------ | --------------------- | -| 表单VQA | 多模态通用表单结构化提取 | 通用卡证识别 | 通用结构化提取 | -| 增值税发票 | 尽请期待 | 身份证识别 | 结构化提取、图像阴影 | -| 印章检测与识别 | 端到端弯曲文本识别 | 合同比对 | 密集文本检测、NLP串联 | +| 类别 | 亮点 | 类别 | 亮点 | +| ------------------------------ | ------------------------ | ------------ | --------------------- | +| [表单VQA](./多模态表单识别.md) | 多模态通用表单结构化提取 | 通用卡证识别 | 通用结构化提取 | +| 增值税发票 | 尽请期待 | 身份证识别 | 结构化提取、图像阴影 | +| 印章检测与识别 | 端到端弯曲文本识别 | 合同比对 | 密集文本检测、NLP串联 | ## 交通 -| 类别 | 亮点 | 类别 | 亮点 | -| ----------------- | ------------------------------ | ---------- | -------- | -| 车牌识别 | 多角度图像、轻量模型、端侧部署 | 快递单识别 | 尽请期待 | -| 驾驶证/行驶证识别 | 尽请期待 | | | \ No newline at end of file +| 类别 | 亮点 | 类别 | 亮点 | +| ------------------------------- | ------------------------------ | ---------- | -------- | +| [车牌识别](./轻量级车牌识别.md) | 多角度图像、轻量模型、端侧部署 | 快递单识别 | 尽请期待 | +| 驾驶证/行驶证识别 | 尽请期待 | | | \ No newline at end of file diff --git a/applications/corpus/digital.txt b/applications/光功率计数码管字符识别/corpus/digital.txt similarity index 100% rename from applications/corpus/digital.txt rename to applications/光功率计数码管字符识别/corpus/digital.txt diff --git a/applications/fonts/DS-DIGI.TTF b/applications/光功率计数码管字符识别/fonts/DS-DIGI.TTF similarity index 100% rename from applications/fonts/DS-DIGI.TTF rename to applications/光功率计数码管字符识别/fonts/DS-DIGI.TTF diff --git a/applications/fonts/DS-DIGIB.TTF b/applications/光功率计数码管字符识别/fonts/DS-DIGIB.TTF similarity index 100% rename from applications/fonts/DS-DIGIB.TTF rename to applications/光功率计数码管字符识别/fonts/DS-DIGIB.TTF diff --git a/applications/光功率计数码管字符识别.md b/applications/光功率计数码管字符识别/光功率计数码管字符识别.md similarity index 100% rename from applications/光功率计数码管字符识别.md rename to applications/光功率计数码管字符识别/光功率计数码管字符识别.md From 894221e105bfda4bb576f2396954108a9d95c86d Mon Sep 17 00:00:00 2001 From: Evezerest <50011306+Evezerest@users.noreply.github.com> Date: Wed, 29 Jun 2022 19:29:20 +0800 Subject: [PATCH 39/45] Update README_ch.md --- README_ch.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README_ch.md b/README_ch.md index f837980498..e801ce561c 100755 --- a/README_ch.md +++ b/README_ch.md @@ -71,6 +71,8 @@ PaddleOCR旨在打造一套丰富、领先、且实用的OCR工具库,助力 ## 《动手学OCR》电子书 - [《动手学OCR》电子书📚](./doc/doc_ch/ocr_book.md) +## 场景应用 +- PaddleOCR场景应用覆盖通用,制造、金融、交通行业的主要OCR垂类应用,在PP-OCR、PP-Structure的通用能力基础之上,以notebook的形式展示利用场景数据微调、模型优化方法、数据增广等内容,为开发者快速落地OCR应用提供示范与启发。详情可查看[README](./applications)。
## 开源社区 From 4959c1c2720da9cfe9ea59b6a7825ec31a196ada Mon Sep 17 00:00:00 2001 From: huangjun12 <12272008@bjtu.edu.cn> Date: Wed, 29 Jun 2022 11:56:55 +0000 Subject: [PATCH 40/45] update url --- applications/包装生产日期识别.md | 174 ++++--------------------------- 1 file changed, 18 insertions(+), 156 deletions(-) diff --git a/applications/包装生产日期识别.md b/applications/包装生产日期识别.md index 4f74a4a148..a0fec65f31 100644 --- a/applications/包装生产日期识别.md +++ b/applications/包装生产日期识别.md @@ -40,6 +40,8 @@ | 真实数据finetune | 71.33| | 真实+合成数据finetune | 86.99| +AIStudio项目链接: [一种基于PaddleOCR的包装生产日期识别方法](https://aistudio.baidu.com/aistudio/projectdetail/4287736) + ## 2. 环境搭建 本任务基于Aistudio完成, 具体环境如下: @@ -96,7 +98,6 @@ PaddleOCR │ └── val.list # 测试集数据文件列表 | ├── bg # 合成数据所需背景图像 │ └── corpus # 合成数据所需语料 -│ └── rec_vit_sub_64_363_all/ # svtr_tiny高精度识别模型 ``` ## 4. 直接使用PP-OCRv3模型评估 @@ -417,160 +418,9 @@ Eval.loader.batch_size_per_card: 评估单卡batch size ### 6.1 python爬虫获取数据 -本节介绍如何使用python脚本爬取网络图片,这里以爬取百度图片为例。首先将图片链接都爬取下来,然后用多进程下载图片。使用到的工具有requests库和谷歌浏览器(抓包工具)。 +- 推荐使用[爬虫工具](https://github.com/Joeclinton1/google-images-download)获取无标签图片。 -准备工作: -1. 获取请求头需要的的设备信息: -(1) 使用谷歌浏览器,打开百度图片,搜索关键词【包装生产日期】 -(2) 右键,选择【检查】,打开抓包工具 -(3) Network >> XHR >> 选定网址 >> Headers >> RequestHeaders >> UserAgents -![](https://ai-studio-static-online.cdn.bcebos.com/715fa3f1255544789ddf5699084783b6b88055835a7049e3b588f4f564e14972) - -2. 获取网页参数 -用类似的方式,在XHR中选择Payload获取网页参数 -![](https://ai-studio-static-online.cdn.bcebos.com/2a8b91ed854a40e589cc9a9596873192cc730c60d5754373a403f395a81ec44f) - - -接下来编写代码进行图片抓取: - -```python -import requests -import json -import tqdm -import os - -img_num = 0 # 记录保存下来的图片数量 -num_per_page = 50 # 每一页展示的图片数量 -page_num = 40 # 页数 -page_id = 0 # 每一页开始索引 - -# 图片链接保存文件路径 -list_path = '/home/aistudio/sprider' -if not os.path.exists(list_path): - os.mkdir(list_path) -f_list = open(os.path.join(list_path, 'file.list'), 'w') - - -img_dict = {} # 图片链接字典,去重 - -# 搜索关键词 -key_word = ['包装产品生产日期', '包装日期', '生产日期', '保质期', '纸箱生产日期', '商品日期', '黑色生产日期', '黑底生产日期'] - -for key in key_word: - bar = tqdm.tqdm(total=page_num*num_per_page) - for page_id in range(page_num): - # 使用准备好的请求头设备信息 - header = { - 'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36' - } - - # 使用准备好的网页参数 - url = 'https://image.baidu.com/search/acjson?' - param = { - 'tn': 'resultjson_com', - 'logid': '8931113288182398301', - 'ipn': 'rj', - 'ct': '201326592', - 'is': '', - 'fp': 'result', - 'fr':'', - 'word': key, # 搜索关键词 - 'queryWord': key, # 搜索关键词 - 'cl': '2', - 'lm': '-1', - 'ie': 'utf-8', - 'oe': 'utf-8', - 'adpicid': '', - 'st': '-1', - 'z': '', - 'ic': '0', - 'hd': '', - 'latest': '', - 'copyright': '', - 's': '', - 'se': '', - 'tab': '', - 'width': '', - 'height': '', - 'face': '0', - 'istype': '2', - 'qc': '', - 'nc': '1', - 'expermode': '', - 'nojc': '', - 'isAsync': '', - 'pn': str(page_id*num_per_page), # 每一页开始索引 - 'rn': str(num_per_page), # 每一页爬取的图片数量 - 'gsm': '1e', - '1652689395185':'' - } - - # 获取网页信息 - page_text = requests.get(url=url, headers=header, params=param, timeout=20) - - try: - page_text = page_text.json() # 网页信息转成json字典 - except: - continue - - info_list = page_text['data'] - - # 获取不到有效信息后,退出搜索 - if info_list == [{}]: - print('===break===') - break - - del info_list[-1] - - img_path_list = [] - for info in info_list: - img_url = info['thumbURL'] # 图片源地址 - #bar.update(1) - if img_url not in img_dict: - img_dict[img_url] = 1 - f_list.write(img_url + '\n') - -f_list.close() -print('saved image number:', img_num+1) -``` - -```python -# 使用多进程下载网页图片 - -import requests -from concurrent.futures import ProcessPoolExecutor - -file_path = '/home/aistudio/sprider/file.list' -image_save_path = '/home/aistudio/sprider/data' - -if not os.path.exists(image_save_path): - os.mkdir(image_save_path) - -f = open(file_path, 'r') -lines = f.readlines() - -header = { - 'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36' -} - -def process(img_url, img_num): - try: - img_data = requests.get(url=img_url, headers=header, timeout=20).content - img_path = os.path.join(image_save_path, '{:05d}.jpg'.format(img_num)) - with open(img_path, 'wb') as fp: - fp.write(img_data) - img_num += 1 - except Exception as e: - print('connect Failed, just continue') - -with ProcessPoolExecutor(max_workers=10) as executer: - for idx, line in enumerate(lines): - executer.submit(process, line.strip(), idx) - -print('done') -``` - -完成以上步骤,便可得到获取的图片,目录格式如下: +图片获取后,可按如下目录格式组织: ```txt sprider @@ -591,7 +441,13 @@ sprider 首先下载预训练模型,PP-OCRv3检测模型下载链接:https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_det_infer.tar -svtr-tiny识别模型,我们使用的是在内部数据上训练的高精度模型,本项目提供下载链接,完成步骤3的数据准备后,可得数据路径位于: +svtr-tiny识别模型,我们使用的是在内部数据上训练的高精度模型。如需获取该训练模型,请扫码填写问卷,加入PaddleOCR官方交流群获取全部OCR垂类模型下载链接、《动手学OCR》电子书等全套OCR学习资料🎁 +
+ +
+ + +完成下载后,可将模型存储于如下位置: ```shell PaddleOCR @@ -824,4 +680,10 @@ python tools/train.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml \ 可以看到,相较于原始PP-OCRv3的识别精度62.99%,使用合成数据+真实数据finetune后,识别精度能提升24%。 -训练完成后,PP-OCRv3模型的推理部署方法可以参考repo文档: https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.5/deploy/README_ch.md +如需获取已训练模型,请扫码填写问卷,加入PaddleOCR官方交流群获取全部OCR垂类模型下载链接、《动手学OCR》电子书等全套OCR学习资料🎁 +
+ +
+将下载或训练完成的模型放置在对应目录下即可完成模型推理。 + +模型的推理部署方法可以参考repo文档: https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.5/deploy/README_ch.md From 3ae853f070e0cc64fc263afa2d6b021421ae2494 Mon Sep 17 00:00:00 2001 From: Evezerest <50011306+Evezerest@users.noreply.github.com> Date: Wed, 29 Jun 2022 20:03:34 +0800 Subject: [PATCH 41/45] =?UTF-8?q?Update=20=E5=8C=85=E8=A3=85=E7=94=9F?= =?UTF-8?q?=E4=BA=A7=E6=97=A5=E6=9C=9F=E8=AF=86=E5=88=AB.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- applications/包装生产日期识别.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/包装生产日期识别.md b/applications/包装生产日期识别.md index a0fec65f31..d24bacd584 100644 --- a/applications/包装生产日期识别.md +++ b/applications/包装生产日期识别.md @@ -441,7 +441,7 @@ sprider 首先下载预训练模型,PP-OCRv3检测模型下载链接:https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_det_infer.tar -svtr-tiny识别模型,我们使用的是在内部数据上训练的高精度模型。如需获取该训练模型,请扫码填写问卷,加入PaddleOCR官方交流群获取全部OCR垂类模型下载链接、《动手学OCR》电子书等全套OCR学习资料🎁 +如需获取svtr-tiny高精度中文识别预训练模型,请扫码填写问卷,加入PaddleOCR官方交流群获取全部OCR垂类模型下载链接、《动手学OCR》电子书等全套OCR学习资料🎁
From 5bf0bd870929b2f40227b802dc3b90ef6e0225e5 Mon Sep 17 00:00:00 2001 From: Evezerest <50011306+Evezerest@users.noreply.github.com> Date: Wed, 29 Jun 2022 20:07:24 +0800 Subject: [PATCH 42/45] =?UTF-8?q?Update=20=E5=8C=85=E8=A3=85=E7=94=9F?= =?UTF-8?q?=E4=BA=A7=E6=97=A5=E6=9C=9F=E8=AF=86=E5=88=AB.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- applications/包装生产日期识别.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/applications/包装生产日期识别.md b/applications/包装生产日期识别.md index d24bacd584..73c174c4f9 100644 --- a/applications/包装生产日期识别.md +++ b/applications/包装生产日期识别.md @@ -680,10 +680,6 @@ python tools/train.py -c configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml \ 可以看到,相较于原始PP-OCRv3的识别精度62.99%,使用合成数据+真实数据finetune后,识别精度能提升24%。 -如需获取已训练模型,请扫码填写问卷,加入PaddleOCR官方交流群获取全部OCR垂类模型下载链接、《动手学OCR》电子书等全套OCR学习资料🎁 -
- -
-将下载或训练完成的模型放置在对应目录下即可完成模型推理。 +如需获取已训练模型,可以同样扫描上方二维码下载,将下载或训练完成的模型放置在对应目录下即可完成模型推理。 模型的推理部署方法可以参考repo文档: https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.5/deploy/README_ch.md From 9da6038214ce90094ebb09c88f84fe73f4845092 Mon Sep 17 00:00:00 2001 From: Topdu <784990967@qq.com> Date: Wed, 29 Jun 2022 12:08:45 +0000 Subject: [PATCH 43/45] fix BaseAug --- doc/doc_ch/algorithm_overview.md | 4 ++-- doc/doc_en/algorithm_overview_en.md | 4 ++-- ppocr/data/imaug/__init__.py | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/doc_ch/algorithm_overview.md b/doc/doc_ch/algorithm_overview.md index eb81e4cd6d..fbf0129ddd 100755 --- a/doc/doc_ch/algorithm_overview.md +++ b/doc/doc_ch/algorithm_overview.md @@ -86,8 +86,8 @@ |SAR|Resnet31| 87.20% | rec_r31_sar | [训练模型](https://paddleocr.bj.bcebos.com/dygraph_v2.1/rec/rec_r31_sar_train.tar) | |SEED|Aster_Resnet| 85.35% | rec_resnet_stn_bilstm_att | [训练模型](https://paddleocr.bj.bcebos.com/dygraph_v2.1/rec/rec_resnet_stn_bilstm_att.tar) | |SVTR|SVTR-Tiny| 89.25% | rec_svtr_tiny_none_ctc_en | [训练模型](https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/rec_svtr_tiny_none_ctc_en_train.tar) | -|ViTSTR|ViTSTR| 79.82% | rec_vitstr_none_ce_en | [训练模型](https://paddleocr.bj.bcebos.com/rec_vitstr_none_ce_train.tar) | -|ABINet|Resnet45| 90.75% | rec_r45_abinet_en | [训练模型](https://paddleocr.bj.bcebos.com/rec_r45_abinet_train.tar) | +|ViTSTR|ViTSTR| 79.82% | rec_vitstr_none_ce | [训练模型](https://paddleocr.bj.bcebos.com/rec_vitstr_none_ce_train.tar) | +|ABINet|Resnet45| 90.75% | rec_r45_abinet | [训练模型](https://paddleocr.bj.bcebos.com/rec_r45_abinet_train.tar) | diff --git a/doc/doc_en/algorithm_overview_en.md b/doc/doc_en/algorithm_overview_en.md index 28aca7c0d1..a3fa7a2d91 100755 --- a/doc/doc_en/algorithm_overview_en.md +++ b/doc/doc_en/algorithm_overview_en.md @@ -85,8 +85,8 @@ Refer to [DTRB](https://arxiv.org/abs/1904.01906), the training and evaluation r |SAR|Resnet31| 87.20% | rec_r31_sar | [trained model](https://paddleocr.bj.bcebos.com/dygraph_v2.1/rec/rec_r31_sar_train.tar) | |SEED|Aster_Resnet| 85.35% | rec_resnet_stn_bilstm_att | [trained model](https://paddleocr.bj.bcebos.com/dygraph_v2.1/rec/rec_resnet_stn_bilstm_att.tar) | |SVTR|SVTR-Tiny| 89.25% | rec_svtr_tiny_none_ctc_en | [trained model](https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/rec_svtr_tiny_none_ctc_en_train.tar) | -|ViTSTR|ViTSTR| 79.82% | rec_vitstr_none_ce_en | [trained model](https://paddleocr.bj.bcebos.com/rec_vitstr_none_none_train.tar) | -|ABINet|Resnet45| 90.75% | rec_r45_abinet_en | [trained model](https://paddleocr.bj.bcebos.com/rec_r45_abinet_train.tar) | +|ViTSTR|ViTSTR| 79.82% | rec_vitstr_none_ce | [trained model](https://paddleocr.bj.bcebos.com/rec_vitstr_none_none_train.tar) | +|ABINet|Resnet45| 90.75% | rec_r45_abinet | [trained model](https://paddleocr.bj.bcebos.com/rec_r45_abinet_train.tar) | diff --git a/ppocr/data/imaug/__init__.py b/ppocr/data/imaug/__init__.py index 63dfda91f8..c958c7ae4e 100644 --- a/ppocr/data/imaug/__init__.py +++ b/ppocr/data/imaug/__init__.py @@ -23,9 +23,9 @@ from .random_crop_data import EastRandomCropData, RandomCropImgMask from .make_pse_gt import MakePseGt -from .rec_img_aug import RecAug, RecConAug, RecResizeImg, ClsResizeImg, \ - SRNRecResizeImg, GrayRecResizeImg, SARRecResizeImg, PRENResizeImg, \ - ABINetRecResizeImg, SVTRRecResizeImg, ABINetRecAug +from .rec_img_aug import BaseDataAugmentation, RecAug, RecConAug, RecResizeImg, ClsResizeImg, \ + SRNRecResizeImg, GrayRecResizeImg, SARRecResizeImg, PRENResizeImg, \ + ABINetRecResizeImg, SVTRRecResizeImg, ABINetRecAug from .ssl_img_aug import SSLRotateResize from .randaugment import RandAugment from .copy_paste import CopyPaste From e13ec733a628006cf7295652600bbf8c1604755f Mon Sep 17 00:00:00 2001 From: littletomatodonkey Date: Thu, 30 Jun 2022 15:23:31 +0800 Subject: [PATCH 44/45] unify kie and ser for vqa data format (#6704) * unify kie and ser for vqa data format * fix config and label ops * fix doc * add distort bbox --- configs/kie/kie_unet_sdmgr.yml | 4 +-- configs/vqa/ser/layoutlm.yml | 6 ++-- configs/vqa/ser/layoutlmv2.yml | 6 ++-- configs/vqa/ser/layoutxlm.yml | 6 ++-- ppocr/data/imaug/label_ops.py | 46 +++++++++++++++--------- ppocr/data/imaug/vqa/__init__.py | 7 +++- ppocr/data/imaug/vqa/augment.py | 37 +++++++++++++++++++ ppocr/utils/utility.py | 25 ++++++------- ppocr/utils/visual.py | 28 +++++++++++---- ppstructure/docs/kie.md | 2 +- ppstructure/docs/kie_en.md | 2 +- ppstructure/vqa/README.md | 4 +-- ppstructure/vqa/README_ch.md | 4 +-- ppstructure/vqa/labels/labels_ser.txt | 3 -- ppstructure/vqa/tools/trans_xfun_data.py | 18 ++++------ tools/infer_kie.py | 14 ++++---- tools/infer_vqa_token_ser.py | 30 ++++++++++++---- tools/program.py | 4 +-- 18 files changed, 161 insertions(+), 85 deletions(-) create mode 100644 ppocr/data/imaug/vqa/augment.py delete mode 100644 ppstructure/vqa/labels/labels_ser.txt diff --git a/configs/kie/kie_unet_sdmgr.yml b/configs/kie/kie_unet_sdmgr.yml index a6968aaa3a..da2e4fda50 100644 --- a/configs/kie/kie_unet_sdmgr.yml +++ b/configs/kie/kie_unet_sdmgr.yml @@ -17,7 +17,7 @@ Global: checkpoints: save_inference_dir: use_visualdl: False - class_path: ./train_data/wildreceipt/class_list.txt + class_path: &class_path ./train_data/wildreceipt/class_list.txt infer_img: ./train_data/wildreceipt/1.txt save_res_path: ./output/sdmgr_kie/predicts_kie.txt img_scale: [ 1024, 512 ] @@ -72,6 +72,7 @@ Train: order: 'hwc' - KieLabelEncode: # Class handling label character_dict_path: ./train_data/wildreceipt/dict.txt + class_path: *class_path - KieResize: - ToCHWImage: - KeepKeys: @@ -88,7 +89,6 @@ Eval: data_dir: ./train_data/wildreceipt label_file_list: - ./train_data/wildreceipt/wildreceipt_test.txt - # - /paddle/data/PaddleOCR/train_data/wildreceipt/1.txt transforms: - DecodeImage: # load image img_mode: RGB diff --git a/configs/vqa/ser/layoutlm.yml b/configs/vqa/ser/layoutlm.yml index 87131170c9..47ab093e1f 100644 --- a/configs/vqa/ser/layoutlm.yml +++ b/configs/vqa/ser/layoutlm.yml @@ -43,7 +43,7 @@ Optimizer: PostProcess: name: VQASerTokenLayoutLMPostProcess - class_path: &class_path ppstructure/vqa/labels/labels_ser.txt + class_path: &class_path train_data/XFUND/class_list_xfun.txt Metric: name: VQASerTokenMetric @@ -54,7 +54,7 @@ Train: name: SimpleDataSet data_dir: train_data/XFUND/zh_train/image label_file_list: - - train_data/XFUND/zh_train/xfun_normalize_train.json + - train_data/XFUND/zh_train/train.json transforms: - DecodeImage: # load image img_mode: RGB @@ -89,7 +89,7 @@ Eval: name: SimpleDataSet data_dir: train_data/XFUND/zh_val/image label_file_list: - - train_data/XFUND/zh_val/xfun_normalize_val.json + - train_data/XFUND/zh_val/val.json transforms: - DecodeImage: # load image img_mode: RGB diff --git a/configs/vqa/ser/layoutlmv2.yml b/configs/vqa/ser/layoutlmv2.yml index 33406252b3..d6a9c03e5e 100644 --- a/configs/vqa/ser/layoutlmv2.yml +++ b/configs/vqa/ser/layoutlmv2.yml @@ -44,7 +44,7 @@ Optimizer: PostProcess: name: VQASerTokenLayoutLMPostProcess - class_path: &class_path ppstructure/vqa/labels/labels_ser.txt + class_path: &class_path train_data/XFUND/class_list_xfun.txt Metric: name: VQASerTokenMetric @@ -55,7 +55,7 @@ Train: name: SimpleDataSet data_dir: train_data/XFUND/zh_train/image label_file_list: - - train_data/XFUND/zh_train/xfun_normalize_train.json + - train_data/XFUND/zh_train/train.json transforms: - DecodeImage: # load image img_mode: RGB @@ -90,7 +90,7 @@ Eval: name: SimpleDataSet data_dir: train_data/XFUND/zh_val/image label_file_list: - - train_data/XFUND/zh_val/xfun_normalize_val.json + - train_data/XFUND/zh_val/val.json transforms: - DecodeImage: # load image img_mode: RGB diff --git a/configs/vqa/ser/layoutxlm.yml b/configs/vqa/ser/layoutxlm.yml index eb1cca5a21..3686989ccf 100644 --- a/configs/vqa/ser/layoutxlm.yml +++ b/configs/vqa/ser/layoutxlm.yml @@ -11,7 +11,7 @@ Global: save_inference_dir: use_visualdl: False seed: 2022 - infer_img: doc/vqa/input/zh_val_42.jpg + infer_img: ppstructure/docs/vqa/input/zh_val_42.jpg save_res_path: ./output/ser Architecture: @@ -54,7 +54,7 @@ Train: name: SimpleDataSet data_dir: train_data/XFUND/zh_train/image label_file_list: - - train_data/XFUND/zh_train/xfun_normalize_train.json + - train_data/XFUND/zh_train/train.json ratio_list: [ 1.0 ] transforms: - DecodeImage: # load image @@ -90,7 +90,7 @@ Eval: name: SimpleDataSet data_dir: train_data/XFUND/zh_val/image label_file_list: - - train_data/XFUND/zh_val/xfun_normalize_val.json + - train_data/XFUND/zh_val/val.json transforms: - DecodeImage: # load image img_mode: RGB diff --git a/ppocr/data/imaug/label_ops.py b/ppocr/data/imaug/label_ops.py index 312d6dc9ad..c95b326224 100644 --- a/ppocr/data/imaug/label_ops.py +++ b/ppocr/data/imaug/label_ops.py @@ -259,15 +259,26 @@ class E2ELabelEncodeTrain(object): class KieLabelEncode(object): - def __init__(self, character_dict_path, norm=10, directed=False, **kwargs): + def __init__(self, + character_dict_path, + class_path, + norm=10, + directed=False, + **kwargs): super(KieLabelEncode, self).__init__() self.dict = dict({'': 0}) + self.label2classid_map = dict() with open(character_dict_path, 'r', encoding='utf-8') as fr: idx = 1 for line in fr: char = line.strip() self.dict[char] = idx idx += 1 + with open(class_path, "r") as fin: + lines = fin.readlines() + for idx, line in enumerate(lines): + line = line.strip("\n") + self.label2classid_map[line] = idx self.norm = norm self.directed = directed @@ -408,7 +419,7 @@ class KieLabelEncode(object): text_ind = [self.dict[c] for c in text if c in self.dict] text_inds.append(text_ind) if 'label' in ann.keys(): - labels.append(ann['label']) + labels.append(self.label2classid_map[ann['label']]) elif 'key_cls' in ann.keys(): labels.append(ann['key_cls']) else: @@ -876,15 +887,16 @@ class VQATokenLabelEncode(object): for info in ocr_info: if train_re: # for re - if len(info["text"]) == 0: + if len(info["transcription"]) == 0: empty_entity.add(info["id"]) continue id2label[info["id"]] = info["label"] relations.extend([tuple(sorted(l)) for l in info["linking"]]) # smooth_box + info["bbox"] = self.trans_poly_to_bbox(info["points"]) bbox = self._smooth_box(info["bbox"], height, width) - text = info["text"] + text = info["transcription"] encode_res = self.tokenizer.encode( text, pad_to_max_seq_len=False, return_attention_mask=True) @@ -900,7 +912,7 @@ class VQATokenLabelEncode(object): label = info['label'] gt_label = self._parse_label(label, encode_res) - # construct entities for re +# construct entities for re if train_re: if gt_label[0] != self.label2id_map["O"]: entity_id_to_index_map[info["id"]] = len(entities) @@ -944,29 +956,29 @@ class VQATokenLabelEncode(object): data['entity_id_to_index_map'] = entity_id_to_index_map return data - def _load_ocr_info(self, data): - def trans_poly_to_bbox(poly): - x1 = np.min([p[0] for p in poly]) - x2 = np.max([p[0] for p in poly]) - y1 = np.min([p[1] for p in poly]) - y2 = np.max([p[1] for p in poly]) - return [x1, y1, x2, y2] + def trans_poly_to_bbox(self, poly): + x1 = np.min([p[0] for p in poly]) + x2 = np.max([p[0] for p in poly]) + y1 = np.min([p[1] for p in poly]) + y2 = np.max([p[1] for p in poly]) + return [x1, y1, x2, y2] + def _load_ocr_info(self, data): if self.infer_mode: ocr_result = self.ocr_engine.ocr(data['image'], cls=False) ocr_info = [] for res in ocr_result: ocr_info.append({ - "text": res[1][0], - "bbox": trans_poly_to_bbox(res[0]), - "poly": res[0], + "transcription": res[1][0], + "bbox": self.trans_poly_to_bbox(res[0]), + "points": res[0], }) return ocr_info else: info = data['label'] # read text info info_dict = json.loads(info) - return info_dict["ocr_info"] + return info_dict def _smooth_box(self, bbox, height, width): bbox[0] = int(bbox[0] * 1000.0 / width) @@ -977,7 +989,7 @@ class VQATokenLabelEncode(object): def _parse_label(self, label, encode_res): gt_label = [] - if label.lower() == "other": + if label.lower() in ["other", "others", "ignore"]: gt_label.extend([0] * len(encode_res["input_ids"])) else: gt_label.append(self.label2id_map[("b-" + label).upper()]) diff --git a/ppocr/data/imaug/vqa/__init__.py b/ppocr/data/imaug/vqa/__init__.py index a5025e7985..bde1751155 100644 --- a/ppocr/data/imaug/vqa/__init__.py +++ b/ppocr/data/imaug/vqa/__init__.py @@ -13,7 +13,12 @@ # limitations under the License. from .token import VQATokenPad, VQASerTokenChunk, VQAReTokenChunk, VQAReTokenRelation +from .augment import DistortBBox __all__ = [ - 'VQATokenPad', 'VQASerTokenChunk', 'VQAReTokenChunk', 'VQAReTokenRelation' + 'VQATokenPad', + 'VQASerTokenChunk', + 'VQAReTokenChunk', + 'VQAReTokenRelation', + 'DistortBBox', ] diff --git a/ppocr/data/imaug/vqa/augment.py b/ppocr/data/imaug/vqa/augment.py new file mode 100644 index 0000000000..fcdc9685e9 --- /dev/null +++ b/ppocr/data/imaug/vqa/augment.py @@ -0,0 +1,37 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# 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 os +import sys +import numpy as np +import random + + +class DistortBBox: + def __init__(self, prob=0.5, max_scale=1, **kwargs): + """Random distort bbox + """ + self.prob = prob + self.max_scale = max_scale + + def __call__(self, data): + if random.random() > self.prob: + return data + bbox = np.array(data['bbox']) + rnd_scale = (np.random.rand(*bbox.shape) - 0.5) * 2 * self.max_scale + bbox = np.round(bbox + rnd_scale).astype(bbox.dtype) + data['bbox'] = np.clip(data['bbox'], 0, 1000) + data['bbox'] = bbox.tolist() + sys.stdout.flush() + return data diff --git a/ppocr/utils/utility.py b/ppocr/utils/utility.py index 4a25ff8b2f..b881fcab20 100755 --- a/ppocr/utils/utility.py +++ b/ppocr/utils/utility.py @@ -91,18 +91,19 @@ def check_and_read_gif(img_path): def load_vqa_bio_label_maps(label_map_path): with open(label_map_path, "r", encoding='utf-8') as fin: lines = fin.readlines() - lines = [line.strip() for line in lines] - if "O" not in lines: - lines.insert(0, "O") - labels = [] - for line in lines: - if line == "O": - labels.append("O") - else: - labels.append("B-" + line) - labels.append("I-" + line) - label2id_map = {label: idx for idx, label in enumerate(labels)} - id2label_map = {idx: label for idx, label in enumerate(labels)} + old_lines = [line.strip() for line in lines] + lines = ["O"] + for line in old_lines: + # "O" has already been in lines + if line.upper() in ["OTHER", "OTHERS", "IGNORE"]: + continue + lines.append(line) + labels = ["O"] + for line in lines[1:]: + labels.append("B-" + line) + labels.append("I-" + line) + label2id_map = {label.upper(): idx for idx, label in enumerate(labels)} + id2label_map = {idx: label.upper() for idx, label in enumerate(labels)} return label2id_map, id2label_map diff --git a/ppocr/utils/visual.py b/ppocr/utils/visual.py index 7a8c1674a7..235eb572a3 100644 --- a/ppocr/utils/visual.py +++ b/ppocr/utils/visual.py @@ -19,7 +19,7 @@ from PIL import Image, ImageDraw, ImageFont def draw_ser_results(image, ocr_results, font_path="doc/fonts/simfang.ttf", - font_size=18): + font_size=14): np.random.seed(2021) color = (np.random.permutation(range(255)), np.random.permutation(range(255)), @@ -40,9 +40,15 @@ def draw_ser_results(image, if ocr_info["pred_id"] not in color_map: continue color = color_map[ocr_info["pred_id"]] - text = "{}: {}".format(ocr_info["pred"], ocr_info["text"]) + text = "{}: {}".format(ocr_info["pred"], ocr_info["transcription"]) - draw_box_txt(ocr_info["bbox"], text, draw, font, font_size, color) + if "bbox" in ocr_info: + # draw with ocr engine + bbox = ocr_info["bbox"] + else: + # draw with ocr groundtruth + bbox = trans_poly_to_bbox(ocr_info["points"]) + draw_box_txt(bbox, text, draw, font, font_size, color) img_new = Image.blend(image, img_new, 0.5) return np.array(img_new) @@ -62,6 +68,14 @@ def draw_box_txt(bbox, text, draw, font, font_size, color): draw.text((bbox[0][0] + 1, start_y), text, fill=(255, 255, 255), font=font) +def trans_poly_to_bbox(poly): + x1 = np.min([p[0] for p in poly]) + x2 = np.max([p[0] for p in poly]) + y1 = np.min([p[1] for p in poly]) + y2 = np.max([p[1] for p in poly]) + return [x1, y1, x2, y2] + + def draw_re_results(image, result, font_path="doc/fonts/simfang.ttf", @@ -80,10 +94,10 @@ def draw_re_results(image, color_line = (0, 255, 0) for ocr_info_head, ocr_info_tail in result: - draw_box_txt(ocr_info_head["bbox"], ocr_info_head["text"], draw, font, - font_size, color_head) - draw_box_txt(ocr_info_tail["bbox"], ocr_info_tail["text"], draw, font, - font_size, color_tail) + draw_box_txt(ocr_info_head["bbox"], ocr_info_head["transcription"], + draw, font, font_size, color_head) + draw_box_txt(ocr_info_tail["bbox"], ocr_info_tail["transcription"], + draw, font, font_size, color_tail) center_head = ( (ocr_info_head['bbox'][0] + ocr_info_head['bbox'][2]) // 2, diff --git a/ppstructure/docs/kie.md b/ppstructure/docs/kie.md index 35498b3347..315dd9f7ba 100644 --- a/ppstructure/docs/kie.md +++ b/ppstructure/docs/kie.md @@ -16,7 +16,7 @@ SDMGR是一个关键信息提取算法,将每个检测到的文本区域分类 训练和测试的数据采用wildreceipt数据集,通过如下指令下载数据集: ``` -wget https://paddleocr.bj.bcebos.com/dygraph_v2.1/kie/wildreceipt.tar && tar xf wildreceipt.tar +wget https://paddleocr.bj.bcebos.com/ppstructure/dataset/wildreceipt.tar && tar xf wildreceipt.tar ``` 执行预测: diff --git a/ppstructure/docs/kie_en.md b/ppstructure/docs/kie_en.md index 1fe38b0b39..7b3752223d 100644 --- a/ppstructure/docs/kie_en.md +++ b/ppstructure/docs/kie_en.md @@ -15,7 +15,7 @@ This section provides a tutorial example on how to quickly use, train, and evalu [Wildreceipt dataset](https://paperswithcode.com/dataset/wildreceipt) is used for this tutorial. It contains 1765 photos, with 25 classes, and 50000 text boxes, which can be downloaded by wget: ```shell -wget https://paddleocr.bj.bcebos.com/dygraph_v2.1/kie/wildreceipt.tar && tar xf wildreceipt.tar +wget https://paddleocr.bj.bcebos.com/ppstructure/dataset/wildreceipt.tar && tar xf wildreceipt.tar ``` Download the pretrained model and predict the result: diff --git a/ppstructure/vqa/README.md b/ppstructure/vqa/README.md index e3a10671dd..711ffa3138 100644 --- a/ppstructure/vqa/README.md +++ b/ppstructure/vqa/README.md @@ -125,13 +125,13 @@ If you want to experience the prediction process directly, you can download the * Download the processed dataset -The download address of the processed XFUND Chinese dataset: [https://paddleocr.bj.bcebos.com/dataset/XFUND.tar](https://paddleocr.bj.bcebos.com/dataset/XFUND.tar). +The download address of the processed XFUND Chinese dataset: [link](https://paddleocr.bj.bcebos.com/ppstructure/dataset/XFUND.tar). Download and unzip the dataset, and place the dataset in the current directory after unzipping. ```shell -wget https://paddleocr.bj.bcebos.com/dataset/XFUND.tar +wget https://paddleocr.bj.bcebos.com/ppstructure/dataset/XFUND.tar ```` * Convert the dataset diff --git a/ppstructure/vqa/README_ch.md b/ppstructure/vqa/README_ch.md index b677dc07bc..297ba64f82 100644 --- a/ppstructure/vqa/README_ch.md +++ b/ppstructure/vqa/README_ch.md @@ -122,13 +122,13 @@ python3 -m pip install -r ppstructure/vqa/requirements.txt * 下载处理好的数据集 -处理好的XFUND中文数据集下载地址:[https://paddleocr.bj.bcebos.com/dataset/XFUND.tar](https://paddleocr.bj.bcebos.com/dataset/XFUND.tar)。 +处理好的XFUND中文数据集下载地址:[链接](https://paddleocr.bj.bcebos.com/ppstructure/dataset/XFUND.tar)。 下载并解压该数据集,解压后将数据集放置在当前目录下。 ```shell -wget https://paddleocr.bj.bcebos.com/dataset/XFUND.tar +wget https://paddleocr.bj.bcebos.com/ppstructure/dataset/XFUND.tar ``` * 转换数据集 diff --git a/ppstructure/vqa/labels/labels_ser.txt b/ppstructure/vqa/labels/labels_ser.txt deleted file mode 100644 index 508e481124..0000000000 --- a/ppstructure/vqa/labels/labels_ser.txt +++ /dev/null @@ -1,3 +0,0 @@ -QUESTION -ANSWER -HEADER diff --git a/ppstructure/vqa/tools/trans_xfun_data.py b/ppstructure/vqa/tools/trans_xfun_data.py index 93ec98163c..11d221bea4 100644 --- a/ppstructure/vqa/tools/trans_xfun_data.py +++ b/ppstructure/vqa/tools/trans_xfun_data.py @@ -21,26 +21,22 @@ def transfer_xfun_data(json_path=None, output_file=None): json_info = json.loads(lines[0]) documents = json_info["documents"] - label_info = {} with open(output_file, "w", encoding='utf-8') as fout: for idx, document in enumerate(documents): + label_info = [] img_info = document["img"] document = document["document"] image_path = img_info["fname"] - label_info["height"] = img_info["height"] - label_info["width"] = img_info["width"] - - label_info["ocr_info"] = [] - for doc in document: - label_info["ocr_info"].append({ - "text": doc["text"], + x1, y1, x2, y2 = doc["box"] + points = [[x1, y1], [x2, y1], [x2, y2], [x1, y2]] + label_info.append({ + "transcription": doc["text"], "label": doc["label"], - "bbox": doc["box"], + "points": points, "id": doc["id"], - "linking": doc["linking"], - "words": doc["words"] + "linking": doc["linking"] }) fout.write(image_path + "\t" + json.dumps( diff --git a/tools/infer_kie.py b/tools/infer_kie.py index 0cb0b8702c..346e2e0aee 100755 --- a/tools/infer_kie.py +++ b/tools/infer_kie.py @@ -39,13 +39,12 @@ import time def read_class_list(filepath): - dict = {} + ret = {} with open(filepath, "r") as f: lines = f.readlines() - for line in lines: - key, value = line.split(" ") - dict[key] = value.rstrip() - return dict + for idx, line in enumerate(lines): + ret[idx] = line.strip("\n") + return ret def draw_kie_result(batch, node, idx_to_cls, count): @@ -71,7 +70,7 @@ def draw_kie_result(batch, node, idx_to_cls, count): x_min = int(min([point[0] for point in new_box])) y_min = int(min([point[1] for point in new_box])) - pred_label = str(node_pred_label[i]) + pred_label = node_pred_label[i] if pred_label in idx_to_cls: pred_label = idx_to_cls[pred_label] pred_score = '{:.2f}'.format(node_pred_score[i]) @@ -109,8 +108,7 @@ def main(): save_res_path = config['Global']['save_res_path'] class_path = config['Global']['class_path'] idx_to_cls = read_class_list(class_path) - if not os.path.exists(os.path.dirname(save_res_path)): - os.makedirs(os.path.dirname(save_res_path)) + os.makedirs(os.path.dirname(save_res_path), exist_ok=True) model.eval() diff --git a/tools/infer_vqa_token_ser.py b/tools/infer_vqa_token_ser.py index 83ed72b392..39ada64a99 100755 --- a/tools/infer_vqa_token_ser.py +++ b/tools/infer_vqa_token_ser.py @@ -86,15 +86,16 @@ class SerPredictor(object): ] transforms.append(op) - global_config['infer_mode'] = True + if config["Global"].get("infer_mode", None) is None: + global_config['infer_mode'] = True self.ops = create_operators(config['Eval']['dataset']['transforms'], global_config) self.model.eval() - def __call__(self, img_path): - with open(img_path, 'rb') as f: + def __call__(self, data): + with open(data["img_path"], 'rb') as f: img = f.read() - data = {'image': img} + data["image"] = img batch = transform(data, self.ops) batch = to_tensor(batch) preds = self.model(batch) @@ -112,20 +113,35 @@ if __name__ == '__main__': ser_engine = SerPredictor(config) - infer_imgs = get_image_file_list(config['Global']['infer_img']) + if config["Global"].get("infer_mode", None) is False: + data_dir = config['Eval']['dataset']['data_dir'] + with open(config['Global']['infer_img'], "rb") as f: + infer_imgs = f.readlines() + else: + infer_imgs = get_image_file_list(config['Global']['infer_img']) + with open( os.path.join(config['Global']['save_res_path'], "infer_results.txt"), "w", encoding='utf-8') as fout: - for idx, img_path in enumerate(infer_imgs): + for idx, info in enumerate(infer_imgs): + if config["Global"].get("infer_mode", None) is False: + data_line = info.decode('utf-8') + substr = data_line.strip("\n").split("\t") + img_path = os.path.join(data_dir, substr[0]) + data = {'img_path': img_path, 'label': substr[1]} + else: + img_path = info + data = {'img_path': img_path} + save_img_path = os.path.join( config['Global']['save_res_path'], os.path.splitext(os.path.basename(img_path))[0] + "_ser.jpg") logger.info("process: [{}/{}], save result to {}".format( idx, len(infer_imgs), save_img_path)) - result, _ = ser_engine(img_path) + result, _ = ser_engine(data) result = result[0] fout.write(img_path + "\t" + json.dumps( { diff --git a/tools/program.py b/tools/program.py index aa3ba82c44..f598feb7e8 100755 --- a/tools/program.py +++ b/tools/program.py @@ -576,8 +576,8 @@ def preprocess(is_train=False): assert alg in [ 'EAST', 'DB', 'SAST', 'Rosetta', 'CRNN', 'STARNet', 'RARE', 'SRN', 'CLS', 'PGNet', 'Distillation', 'NRTR', 'TableAttn', 'SAR', 'PSE', - 'SEED', 'SDMGR', 'LayoutXLM', 'LayoutLM', 'PREN', 'FCE', 'SVTR', - 'ViTSTR', 'ABINet' + 'SEED', 'SDMGR', 'LayoutXLM', 'LayoutLM', 'LayoutLMv2', 'PREN', 'FCE', + 'SVTR', 'ViTSTR', 'ABINet' ] if use_xpu: From b1ee778fb367b960c7dd13a15cf6b90518ccc3c0 Mon Sep 17 00:00:00 2001 From: andyjpaddle Date: Thu, 30 Jun 2022 09:10:23 +0000 Subject: [PATCH 45/45] rm proxy --- test_tipc/test_serving_infer_cpp.sh | 3 +-- test_tipc/test_serving_infer_python.sh | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/test_tipc/test_serving_infer_cpp.sh b/test_tipc/test_serving_infer_cpp.sh index 6e313cd949..0be6a45adf 100644 --- a/test_tipc/test_serving_infer_cpp.sh +++ b/test_tipc/test_serving_infer_cpp.sh @@ -87,8 +87,7 @@ function func_serving(){ set_image_dir=$(func_set_params "${image_dir_key}" "${image_dir_value}") python_list=(${python_list}) cd ${serving_dir_value} - unset https_proxy - unset http_proxy + # cpp serving for gpu_id in ${gpu_value[*]}; do if [ ${gpu_id} = "null" ]; then diff --git a/test_tipc/test_serving_infer_python.sh b/test_tipc/test_serving_infer_python.sh index b14517265d..4ccccc06e2 100644 --- a/test_tipc/test_serving_infer_python.sh +++ b/test_tipc/test_serving_infer_python.sh @@ -112,8 +112,7 @@ function func_serving(){ cd ${serving_dir_value} python=${python_list[0]} - unset https_proxy - unset http_proxy + # python serving for use_gpu in ${web_use_gpu_list[*]}; do if [ ${use_gpu} = "null" ]; then