Using PIL and pytesseract

This is the first blogpost of a three to four (I haven't decided yet) part project. The main idea is that I want to create a model which will tell me how much I would like the book, given an image of a page as in input.

In this part, I will show you how to turn a image of text into actual text, using pytesseract. So let's first get our packages.

Import packages

!apt-get update
!apt-get install libleptonica-dev -y
!apt-get install tesseract-ocr tesseract-ocr-dev -y
!apt-get install libtesseract-dev -y
!apt-get install tesseract-ocr-deu
Ign:1 http://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu1604/x86_64  InRelease
Hit:2 http://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu1604/x86_64  Release
Hit:3 http://security.ubuntu.com/ubuntu xenial-security InRelease              
Ign:5 https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1604/x86_64  InRelease
Hit:6 https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1604/x86_64  Release
Hit:7 http://archive.ubuntu.com/ubuntu xenial InRelease
Hit:9 http://archive.ubuntu.com/ubuntu xenial-updates InRelease
Hit:10 http://archive.ubuntu.com/ubuntu xenial-backports InRelease
Reading package lists... Done
Reading package lists... Done
Building dependency tree       
Reading state information... Done
libleptonica-dev is already the newest version (1.73-1).
0 upgraded, 0 newly installed, 0 to remove and 39 not upgraded.
Reading package lists... Done
Building dependency tree       
Reading state information... Done
tesseract-ocr is already the newest version (3.04.01-4).
tesseract-ocr-dev is already the newest version (3.04.01-4).
0 upgraded, 0 newly installed, 0 to remove and 39 not upgraded.
Reading package lists... Done
Building dependency tree       
Reading state information... Done
libtesseract-dev is already the newest version (3.04.01-4).
0 upgraded, 0 newly installed, 0 to remove and 39 not upgraded.
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following NEW packages will be installed:
  tesseract-ocr-deu
0 upgraded, 1 newly installed, 0 to remove and 39 not upgraded.
Need to get 4153 kB of archives.
After this operation, 13.4 MB of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu xenial/universe amd64 tesseract-ocr-deu all 3.04.00-1 [4153 kB]
Fetched 4153 kB in 1s (2333 kB/s)            
debconf: delaying package configuration, since apt-utils is not installed
Selecting previously unselected package tesseract-ocr-deu.
(Reading database ... 18655 files and directories currently installed.)
Preparing to unpack .../tesseract-ocr-deu_3.04.00-1_all.deb ...
Unpacking tesseract-ocr-deu (3.04.00-1) ...
Setting up tesseract-ocr-deu (3.04.00-1) ...
!pip install pytesseract
Requirement already satisfied: pytesseract in /opt/conda/envs/fastai/lib/python3.8/site-packages (0.3.6)
Requirement already satisfied: Pillow in /opt/conda/envs/fastai/lib/python3.8/site-packages (from pytesseract) (7.2.0)
from fastai.vision.all import *
from fastai.vision.widgets import *
import numpy as np
from pathlib import Path
import pytesseract
import re
from PIL import Image, ImageFilter 
import pandas as pd

Define PosixPath to data

I took about 100 images of pages from books I own (all in German). I then put them in an image folder, let's have a look at the directory.

p = Path.cwd()/'images'
img_paths = [x for x in p.iterdir()]
img_paths[0].parts[5]
'IMG_5123_gegendenStrich.jpg'

Use regex to extract title of book

Next to the text from the image, I would like to extract the title of the book so I can later easily join my ratings to the texts. I therefore use a regex.

title_list = [re.match("^.*\_(.*)\..*$",img_paths[i].parts[5]).group(1) for i in range(len(img_paths))]

Automatically read in images, transform them and get text

We use pytesseract for extracting the text from the images. To improve the performance I tried a lot of data transformation: cropping, binarizing and a lot more. The only thing that worked for me was to first rotate the image and then use a MedianFilter.

def proc_img(img_path):
    im1 = Image.open(img_path) 

    im1 = im1.rotate(angle=270, resample=0, expand=10, center=None, translate=None, fillcolor=None)

    im1 = im1.filter(ImageFilter.MedianFilter)
    
    return im1

All of my input images are from german books, so I need to use lang="deu".

def get_text(img):
    return pytesseract.image_to_string(img, lang="deu")

I again use a regular expression to get rid of common mistakes pytesseract does: putting a \n somewhere or confusing a s for a 5.

def use_pattern(text):
    return pattern.sub(lambda m: rep[re.escape(m.group(0))], text)
rep = {"\n": "", "`": "", '%':"", '°': '', '&':'', '‘':'', '€':'e', '®':'', '\\': '', '5':'s', '1':'i', '_':'', '-':''} # define desired replacements here

# use these three lines to do the replacement
rep = dict((re.escape(k), v) for k, v in rep.items()) 
#Python 3 renamed dict.iteritems to dict.items so use rep.items() for latest versions
pattern = re.compile("|".join(rep.keys()))

Use tesseract to make image into text

Finally, we use a list comprehension (they're super useful) to put all of the text into a list of texts.

text_list = [use_pattern(get_text(proc_img(str(img_paths[i])))) for i in range(len(img_paths))]

Combine into Dataframe

And now let's put that into a pandas dataframe.

d = {'text':text_list,'title':title_list}
df = pd.DataFrame(d) 
df.head()
text title
0 war ein schrecklicher Rückfall eingetreten.In dem »verheirateten Priester« wurde das LobChristi von Barbey d’Aurévilly gesungen; in »LesDiaboliques« hatte sich der Verfasser dem Teufel ergeben, den er pries; und jetzt erschien der Sadismus,dieser Bastard des Katholizismus, den die Religion inallen Formen mit Exorzismen und Scheiterhaufendurch alle Jahrhunderte verfolgt hat.Mit Barbey d’Aurévilly nahm die Serie der reli—giösen Schriftsteller ein Ende. Eigentlich gehörte dieser Paria in jeder Hinsicht mehr zur weltlichen Lite—ratur als zu jener andern, bei der er einen Platzbeanspruchte, den... gegendenStrich
1 höchst moralischer Akt, die Welt von einem solchen Ungeheuerzu befreien? Die Menschheit wäre dann besser und glücklicherund das Leben schöner und süßer.Ich dachte lange darüber nach. Schlaflos lag ich in meinerKoj e und hielt mir die Fakten vor Augen. Mit Johnson und Leachredete ich während der Nachtwachen, wenn Wolf Larsen unterDeck war. Beide Männer hatten die Hoffnung verloren — Johnson wegen seiner grundlegenden Schwermut, Leach, weil ersich in seinen vergeblichen Kämpfen erschöpft hatte. Aber eines Nachts ergriff er leidenschaftlich meine Hand und sagte:»Ich glaube, Sie sind in Ordnung... derSeewolf
2 deutsches Luder nehmen. Und sollten Sie es dann allzu eilighaben, Mr. Andrews, werden Sie mich wohl von ihr runterziehen müssen.«Andrews wartete, dass die beiden Männer fortritten, undsah ihnen nach, wie sie sich durch das dämmrige Tal entfern—ten und ihre auf und ab wippenden Gestalten mit der dunkleren Schraffur der westlichen Berghänge verschmelzen. Dannaufs Neue überraschte, lenkte die Hände ab und sorgte dafür,dass ihm die eigenen Gesichtszüge fremd vorkamen; er fragtesich, wie er wohl aussah, fragte sich, ob Francine ihn wiederkennen würde, wenn sie ihn jetzt sehen könnte.Seit dem Ab... ButchersCrossing
3 Doktor, das wissen Sie so gut wie ich. Vor hundert Jah—ren hat eine Pestepidemie in Persien alle Bewohner einerStadt getötet und ausgerechnet den Totenwäscher nicht,der nie aufgehört hatte, seine Arbeit zu verrichten.»müssen.»Sie kamen jetzt in die Vorstadt. Die Scheinwerfer beleuchteten die menschenleeren Straßen. Sie hielten an.Vor dem Auto fragte Rieux Tarrou, ob er mitkommenwolle, und der sagte ja. Ein Schimmer vom Himmel er—hellte ihre Gesichter. Rieux lachte plötzlich freundschaftlich.« Sagen Sie, Tarrou, was treibt Sie dazu, sich damit zubefassen? »<< Ich weiß nicht. Meine Moral vie... diePest
4 ins Gesicht, wandte sich von ihrem traurigen Ausdruckangewidert ab, und nachdem er zum hundertsten Maldie Aushängeschilder der gegenüberliegenden Geschäfte, die Werbung für die schon nicht mehr erhält—lichen bekannten Aperitifmarken gelesen hatte, stander auf und lief ziellos durch die gelben Straßen derStadt. Er schleppte sich von einsamen Spaziergängen inCafés und von Cafés in Restaurants und erreichte soden Abend. An einem solchen Abend sah Rieux denJournalisten zögernd vor der Tür eines Cafés stehen. Erschien sich zu entschließen, ging hinein und setzte sichhinten hin. Es war um die Ze... diePest
df.text[3]
'Doktor, das wissen Sie so gut wie ich. Vor hundert Jah—ren hat eine Pestepidemie in Persien alle Bewohner einerStadt getötet und ausgerechnet den Totenwäscher nicht,der nie aufgehört hatte, seine Arbeit zu verrichten.»müssen.»Sie kamen jetzt in die Vorstadt. Die Scheinwerfer beleuchteten die menschenleeren Straßen. Sie hielten an.Vor dem Auto fragte Rieux Tarrou, ob er mitkommenwolle, und der sagte ja. Ein Schimmer vom Himmel er—hellte ihre Gesichter. Rieux lachte plötzlich freundschaftlich.« Sagen Sie, Tarrou, was treibt Sie dazu, sich damit zubefassen? »<< Ich weiß nicht. Meine Moral vielleicht.»«Und die wäre? >>«Verständnis.»Tarrou wandte sich dem Haus zu, und Rieux sah erstwieder sein Gesicht, als sie bei dem alten Asthmatikerwaren.'

Looking good! For this project I also need my ratings for each of the books. I use a dictionary and the map function to easily create a column with my ratings.

Use Dictionary to map my ratings

rating_lasse = {'derPate': 5,
                 'ButchersCrossing': 4,
                 'derSeewolf': 5,
                 'JekyllandHyde': 4,
                 'gegendenStrich': 1,
                 'FruestueckmitKaengurus': 5,
                 'HuckleberryFinn': 4,
                 'diePest': 2,
                 'HerzderFinsternis': 3,
                 'derSpieler': 4}
df['rating'] = df['title'].map(rating_lasse)
df.head()
text title rating
0 war ein schrecklicher Rückfall eingetreten.In dem »verheirateten Priester« wurde das LobChristi von Barbey d’Aurévilly gesungen; in »LesDiaboliques« hatte sich der Verfasser dem Teufel ergeben, den er pries; und jetzt erschien der Sadismus,dieser Bastard des Katholizismus, den die Religion inallen Formen mit Exorzismen und Scheiterhaufendurch alle Jahrhunderte verfolgt hat.Mit Barbey d’Aurévilly nahm die Serie der reli—giösen Schriftsteller ein Ende. Eigentlich gehörte dieser Paria in jeder Hinsicht mehr zur weltlichen Lite—ratur als zu jener andern, bei der er einen Platzbeanspruchte, den... gegendenStrich 1
1 höchst moralischer Akt, die Welt von einem solchen Ungeheuerzu befreien? Die Menschheit wäre dann besser und glücklicherund das Leben schöner und süßer.Ich dachte lange darüber nach. Schlaflos lag ich in meinerKoj e und hielt mir die Fakten vor Augen. Mit Johnson und Leachredete ich während der Nachtwachen, wenn Wolf Larsen unterDeck war. Beide Männer hatten die Hoffnung verloren — Johnson wegen seiner grundlegenden Schwermut, Leach, weil ersich in seinen vergeblichen Kämpfen erschöpft hatte. Aber eines Nachts ergriff er leidenschaftlich meine Hand und sagte:»Ich glaube, Sie sind in Ordnung... derSeewolf 5
2 deutsches Luder nehmen. Und sollten Sie es dann allzu eilighaben, Mr. Andrews, werden Sie mich wohl von ihr runterziehen müssen.«Andrews wartete, dass die beiden Männer fortritten, undsah ihnen nach, wie sie sich durch das dämmrige Tal entfern—ten und ihre auf und ab wippenden Gestalten mit der dunkleren Schraffur der westlichen Berghänge verschmelzen. Dannaufs Neue überraschte, lenkte die Hände ab und sorgte dafür,dass ihm die eigenen Gesichtszüge fremd vorkamen; er fragtesich, wie er wohl aussah, fragte sich, ob Francine ihn wiederkennen würde, wenn sie ihn jetzt sehen könnte.Seit dem Ab... ButchersCrossing 4
3 Doktor, das wissen Sie so gut wie ich. Vor hundert Jah—ren hat eine Pestepidemie in Persien alle Bewohner einerStadt getötet und ausgerechnet den Totenwäscher nicht,der nie aufgehört hatte, seine Arbeit zu verrichten.»müssen.»Sie kamen jetzt in die Vorstadt. Die Scheinwerfer beleuchteten die menschenleeren Straßen. Sie hielten an.Vor dem Auto fragte Rieux Tarrou, ob er mitkommenwolle, und der sagte ja. Ein Schimmer vom Himmel er—hellte ihre Gesichter. Rieux lachte plötzlich freundschaftlich.« Sagen Sie, Tarrou, was treibt Sie dazu, sich damit zubefassen? »<< Ich weiß nicht. Meine Moral vie... diePest 2
4 ins Gesicht, wandte sich von ihrem traurigen Ausdruckangewidert ab, und nachdem er zum hundertsten Maldie Aushängeschilder der gegenüberliegenden Geschäfte, die Werbung für die schon nicht mehr erhält—lichen bekannten Aperitifmarken gelesen hatte, stander auf und lief ziellos durch die gelben Straßen derStadt. Er schleppte sich von einsamen Spaziergängen inCafés und von Cafés in Restaurants und erreichte soden Abend. An einem solchen Abend sah Rieux denJournalisten zögernd vor der Tür eines Cafés stehen. Erschien sich zu entschließen, ging hinein und setzte sichhinten hin. Es war um die Ze... diePest 2

And that's it, let's save this dataframe and we're ready to move on to the model training!

df.to_csv(p/'datasets/text_df.csv', encoding='utf8', index=False)

I hope you enjoyed this blogpost and stay tuned for the next one!

Lasse