# CRNN-OCR-lite
**Repository Path**: atari/CRNN-OCR-lite
## Basic Information
- **Project Name**: CRNN-OCR-lite
- **Description**: 同步 https://github.com/gasparian/CRNN-OCR-lite
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 1
- **Created**: 2020-08-21
- **Last Updated**: 2023-10-26
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# CRNN_OCR_lite
*Disclaimer: This is not a production-ready solution, this repo was created to just show an approach*
## Idea
Train a light-weight network to solve word-level handwritten text recognition on images.
## Training
I decided to use common [CRNN](https://github.com/keras-team/keras/blob/master/examples/image_ocr.py) model with [CTC-loss](https://distill.pub/2017/ctc/) and a couple augmentations:
- use [spatial transformer module](https://github.com/oarriaga/STN.keras) to adjust text slope;
- replace convolution layers in CRNN with [depthwise-separable convolutions](https://github.com/keras-team/keras-applications/blob/master/keras_applications/mobilenet.py);
- use transfer learning: train OCR model on large synthetic dataset and then tune it's weights with "real" handwritten-text data.
The training process consists of the following steps:
- train model with [mjsynth](http://www.robots.ox.ac.uk/~vgg/data/text/mjsynth.tar.gz) dataset in two steps:
```
python3 train.py --G 1 --path %PATH_TO_IMAGES% --training_fname annotation_train.txt \
--val_fname annotation_test.txt --save_path %NEW_PATH% --model_name %OUTPUT_MODEL_NAME% --nbepochs 1 \
--norm --mjsynth --opt adam --time_dense_size 128 --lr .0001 --batch_size 64 --early_stopping 5000
python3 train.py --G 1 --path %PATH_TO_IMAGES% --training_fname annotation_train.txt \
--val_fname annotation_test.txt --save_path %NEW_PATH% --model_name %OUTPUT_MODEL_NAME% --nbepochs 1 \
--norm --mjsynth --opt adam --time_dense_size 128 --lr .0001 --batch_size 64 --early_stopping 20 \
%PATH_TO_PRETRAINED_MODEL%/checkpoint_weights.h5
```
- prepare [IAM](http://www.fki.inf.unibe.ch/databases/iam-handwriting-database) dataset:
```
python3 IAM_preprocessing.py -p %PATH_TO_DATA% -np %PATH_TO_PROCESSED_DATA%
```
- initialize new model with weights obtained in the previous step and continue training with IAM dataset:
```
python3 train.py --G 1 --path %PATH_TO_PROCESSED_DATA% --train_portion 0.9 --save_path %NEW_PATH% \
--model_name %OUTPUT_MODEL_NAME% --nbepochs 200 --norm --opt adam --time_dense_size 128 --lr .0001 \
--batch_size 64 --pretrained_path %PATH_TO_PRETRAINED_MODEL%/final_weights.h5
```
## Results
After full training we've got two models: one for "reading text in the wild" and another for handwritten text transcription (you can find it in `/models`).
I use the lowest-loss model checkpoint.
I've tested both models with random samples of 8000 images from validation sets:
- mjsynth-model gives predictions with **.71** mean edit distance or **.09** if we normilize it by words lengths;
- IAM-model gives **.35** mean edit distance or **.08** if we normalize it by words lengths.
Actually, the majority of errors comes from repeated characters in true labels.
Here are transformed images examples with transcription results:
mjsynth | IAM
:-------------------------:|:-------------------------:
|
|
For inference you can use `prediction.py` or create you own script using functions from `utils.py`:
- mjsynth
```
python3 predict.py --G 0 --model_path %PATH_TO_MODEL% \
--image_path %PATH_TO_IMAGES% \
--val_fname annotation_test.txt --mjsynth \
--validate --num_instances 512 --max_len 23
```
- IAM
```
python3 predict.py --G 0 --model_path %PATH_TO_MODEL% \
--image_path %PATH_TO_IMAGES% \
--validate --num_instances 512 --max_len 21
```
For example, this script will make prediction on images from `%PATH_TO_IMAGES%` and save results in `%PATH_TO_ANSWER%/*.csv`:
```
python3 predict.py --G 0 --model_path %PATH_TO_MODEL% \
--image_path %PATH_TO_IMAGES% \
--result_path %PATH_TO_ANSWER% \
--max_len %MAX_STRING_LENGTH%
```
On average, prediction on one text-box image costs us **~100-150 ms** regardless of using GPU. And **>95%** of that time consumes beam-search on LSTM output (even with fairly low beam widths: 3...10) which computes on CPU-side.
## Reproducibility
At first, install [docker](https://docs.docker.com/install/) and [nvidia-docker](https://github.com/NVIDIA/nvidia-docker).
Pull image from Dockerhub:
```
docker pull gasparjan/crnn_ocr:latest
```
or with CPU support only (just change tag):
```
docker pull gasparjan/crnn_ocr:cpu
```
Or build it locally:
```
docker build -t crnn_ocr:latest -f Dockerfile .
```
Run it via `nvidia-docker`, mounting volumes:
```
nvidia-docker run --rm -it -v /home:/data \
-p 8004:8000 gasparjan/crnn_ocr:latest
```
or just `docker` for CPU-only build:
```
docker run --rm -it -v /home:/data \
-p 8004:8000 gasparjan/crnn_ocr:cpu
```
...and then run scripts in shell as usual.
**The global goal is to make end-to-end pipeline for robust detection and recognition.**
- [x] CRNN trained on mjsynth. Training from scratch;
- [x] CRNN trained on IAM. Initial weights - from model trained on mjsynth;
- [x] CRNN trained on hand-written text "from the wild". Initial weights - from model trained on mjsynth & IAM;
- with the help of recently available [azure ocr api](https://azure.microsoft.com/en-us/services/cognitive-services/computer-vision/) (check out `azure_ocr.py`) I've labeled a small dataset (148 large images) of flipcharts / whiteboards photos with a lot of handwritten text;
- dataset contains ~12k tokens for training and ~2k for validation. [Here is a model](https://github.com/gasparian/CRNN_OCR_lite/tree/master/models/OCR_Stickies_ver1);
- the results are not so convincing: **~1.6** mean edit distance and **~.3** normalized distance. To improve the recognition quality, it makes sense to apply augmentations on images / expand dataset.
- [ ] Text binarizing model (binary segmentation)
- [ ] Word-level text boxes detector
## P.S.
The main usecase can be indexing recognized text on images in search: for example you've got bazillion photos of whiteboards / handwritten notes and etc. And you will be really bad at searching particullar photos with needed topic. So if the all photos had some text annotation - the problem disappears.
Why do I think so? Clearly, it's super-hard to get 0% error rate on real-world photos. So if you want to use "hand-made" detection+recognition pipeline to "digitize" text on photos, in the end, you'll most likely need to check and correct all recognized words or add non-recognized ones. This is pretty same expirience to the current "pdf-scanners" (which is painful). And on the other side, if the model can detect and recognize even 20% of words on image, you can still find something using text search.