Home
Softono
CRNN-OCR-lite

CRNN-OCR-lite

Open source Python
152
Stars
29
Forks
1
Issues
9
Watchers
7 years
Last Commit

About CRNN-OCR-lite

CRNN-OCR-lite is a lightweight deep learning model for optical character recognition, built with Keras and TensorFlow. It is designed for word-level text recognition, including handwritten text transcription. The architecture is based on the standard CRNN model combined with CTC loss, enhanced with a spatial transformer module to correct text slope and distortion, and depthwise separable convolutions to reduce complexity and parameter count. The model uses a two-stage transfer learning approach: initial pretraining on the large synthetic mjsynth dataset, followed by fine-tuning on the handwritten IAM dataset. This produces two models: one optimized for scene text in the wild and another for handwritten text. On a validation set of 8000 images, the mjsynth model achieves a 0.09 normalized edit distance, while the IAM model achieves 0.08. The project includes training scripts, preprocessing utilities for the IAM dataset, and a prediction script for inference. It serves as a practical demonstration of building a

Platforms

Web Self-hosted

Languages

Python

Links

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 model with CTC-loss and a couple augmentations:

The training process consists of the following steps:

  • train model with mjsynth 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.  
<img src="https://github.com/gasparian/CRNN_OCR_lite/blob/master/imgs/IAM_losses_mjsynth.png" height=384>  

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  
:-------------------------:|:-------------------------:
<img src="https://github.com/gasparian/CRNN_OCR_lite/blob/master/imgs/STN_examples/mjsynth_1.png" height=225> | <img src="https://github.com/gasparian/CRNN_OCR_lite/blob/master/imgs/STN_examples/IAM_6.png" height=225>  
<img src="https://github.com/gasparian/CRNN_OCR_lite/blob/master/imgs/STN_examples/mjsynth_2.png" height=225> | <img src="https://github.com/gasparian/CRNN_OCR_lite/blob/master/imgs/STN_examples/IAM_2.png" height=225>  

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.