Webux Lab

Par Studio Webux

Extraire le contenu de notes manuscrites

TG
Tommy Gingras Studio Webux S.E.N.C 2021-08-02

Expérimentations avec Tesseract OCR

Requis

Création de la lambda et layer

J’utilise AWS SAM,

layers/
  lambda-tesseract-api/
lambda/
  src/handlers/index.py
  README.md
  requirements.txt
  template.yml

Layers

J’ai cloné le projet https://github.com/amtam0/lambda-tesseract-api comme base, puis modifié quelques trucs pour que le tout fonctionne.


Le lambda-tesseract-api/buildpy/requirements.txt

Voir le requirements.txt
NoteShrinker==0.2.0
Pillow==8.3.1
PyWavelets==1.1.1
cycler==0.10.0
deskew==0.10.33
imageio==2.9.0
kiwisolver==1.3.1
matplotlib==3.4.2
networkx==2.6.2
numpy==1.21.1
opencv-python==4.5.3.56
pyparsing==2.4.7
pytesseract==0.3.8
python-dateutil==2.8.2
scikit-image==0.18.2
scipy==1.7.0
six==1.16.0
tifffile==2021.7.30

Le lambda-tesseract-api/buildtesseract/Dockerfile-tess4

Voir le Dockerfile
FROM lambci/lambda-base:build
#FROM lambci/lambda:build-python3.7

#Proxy setup if exists
#ENV http_proxy 'http://ip:port'
#ENV https_proxy 'https://ip:port'

ARG LEPTONICA_VERSION=1.78.0
ARG TESSERACT_VERSION=4.1.0-rc4
ARG AUTOCONF_ARCHIVE_VERSION=2017.09.28
ARG TMP_BUILD=/tmp
ARG TESSERACT=/opt/tesseract
ARG LEPTONICA=/opt/leptonica
ARG DIST=/opt/build-dist
# change OCR_LANG to enable the layer for different languages
ARG OCR_LANG=fra
# change TESSERACT_DATA_SUFFIX to use different datafiles (options: "_best", "_fast" and "")
ARG TESSERACT_DATA_SUFFIX=_best
ARG TESSERACT_DATA_VERSION=4.0.0

RUN yum makecache fast; yum clean all && yum -y update && yum -y upgrade; yum clean all && \
    yum install -y yum-plugin-ovl; yum clean all && yum -y groupinstall "Development Tools"; yum clean all

RUN yum -y install gcc gcc-c++ make autoconf aclocal automake libtool \
    libjpeg-devel libpng-devel libtiff-devel zlib-devel \
    libzip-devel freetype-devel lcms2-devel libwebp-devel \
    libicu-devel tcl-devel tk-devel pango-devel cairo-devel; yum clean all

WORKDIR ${TMP_BUILD}/leptonica-build
RUN curl -L https://github.com/DanBloomberg/leptonica/releases/download/${LEPTONICA_VERSION}/leptonica-${LEPTONICA_VERSION}.tar.gz | tar xz && cd ${TMP_BUILD}/leptonica-build/leptonica-${LEPTONICA_VERSION} && \
    ./configure --prefix=${LEPTONICA} && make && make install && cp -r ./src/.libs /opt/liblept

RUN echo "/opt/leptonica/lib" > /etc/ld.so.conf.d/leptonica.conf && ldconfig

WORKDIR ${TMP_BUILD}/autoconf-build
RUN curl https://ftp.gnu.org/gnu/autoconf-archive/autoconf-archive-${AUTOCONF_ARCHIVE_VERSION}.tar.xz | tar xJ && \
    cd autoconf-archive-${AUTOCONF_ARCHIVE_VERSION} && ./configure && make && make install && cp ./m4/* /usr/share/aclocal/

WORKDIR ${TMP_BUILD}/tesseract-build
RUN curl -L https://github.com/tesseract-ocr/tesseract/archive/${TESSERACT_VERSION}.tar.gz | tar xz && \
    cd tesseract-${TESSERACT_VERSION} && ./autogen.sh  && PKG_CONFIG_PATH=/opt/leptonica/lib/pkgconfig LIBLEPT_HEADERSDIR=/opt/leptonica/include \
    ./configure --prefix=${TESSERACT} --with-extra-includes=/opt/leptonica/include --with-extra-libraries=/opt/leptonica/lib && make && make install

WORKDIR /opt
RUN mkdir -p ${DIST}/lib && mkdir -p ${DIST}/bin && \
    cp ${TESSERACT}/bin/tesseract ${DIST}/bin/ && \
    cp ${TESSERACT}/lib/libtesseract.so.4  ${DIST}/lib/ && \
    cp ${LEPTONICA}/lib/liblept.so.5 ${DIST}/lib/liblept.so.5 && \
    cp /usr/lib64/libwebp.so.4 ${DIST}/lib/ && \
    cp /usr/lib64/libpng12.so.0 ${DIST}/lib/ && \
    cp /usr/lib64/libjpeg.so.62 ${DIST}/lib/ && \
    cp /usr/lib64/libtiff.so.5 ${DIST}/lib/ && \
    cp /usr/lib64/libgomp.so.1 ${DIST}/lib/ && \
    cp /usr/lib64/libjbig.so.2.0 ${DIST}/lib/ && \
    cp /usr/lib64/libGL.so.1 ${DIST}/lib/ && \
    cp /usr/lib64/libgthread-2.0.so.0 ${DIST}/lib/ && \
    cp /usr/lib64/libglib-2.0.so.0 ${DIST}/lib/ && \
    cp /usr/lib64/libGLX.so.0 ${DIST}/lib/ && \
    cp /usr/lib64/libX11.so.6 ${DIST}/lib/ && \
    cp /usr/lib64/libXext.so.6 ${DIST}/lib/ && \
    cp /usr/lib64/libxcb.so.1 ${DIST}/lib/ && \
    cp /usr/lib64/libGLdispatch.so.0 ${DIST}/lib/ && \
    cp /usr/lib64/libXau.so.6 ${DIST}/lib/ && \
    echo -e "LEPTONICA_VERSION=${LEPTONICA_VERSION}\nTESSERACT_VERSION=${TESSERACT_VERSION}\nTESSERACT_DATA_FILES=tessdata${TESSERACT_DATA_SUFFIX}/${TESSERACT_DATA_VERSION}" > ${DIST}/TESSERACT-README.md && \
    find ${DIST}/lib -name '*.so*' | xargs strip -s

WORKDIR ${DIST}/tesseract/share/tessdata
RUN curl -L https://github.com/tesseract-ocr/tessdata${TESSERACT_DATA_SUFFIX}/raw/${TESSERACT_DATA_VERSION}/osd.traineddata > osd.traineddata && \
    curl -L https://github.com/tesseract-ocr/tessdata${TESSERACT_DATA_SUFFIX}/raw/${TESSERACT_DATA_VERSION}/eng.traineddata > eng.traineddata && \
    curl -L https://github.com/tesseract-ocr/tessdata${TESSERACT_DATA_SUFFIX}/raw/${TESSERACT_DATA_VERSION}/${OCR_LANG}.traineddata > ${OCR_LANG}.traineddata

COPY . ${DIST}/tesseract/share/tessdata/

RUN rm Dockerfile*; exit 0
RUN rm READM*; exit 0
RUN rm build_*; exit 0
RUN rm *.zip; exit 0
RUN rm -rf example; exit 0

WORKDIR /var/task

Lambda

Le fichier src/handlers/index.py

Voir le index.py
import json
import pytesseract
from PIL import Image, ImageFilter
from NoteShrinker import NoteShrinker
import base64

import math
from typing import Tuple, Union

import cv2
import numpy as np

from deskew import determine_skew


def rotate(
        image: np.ndarray, angle: float, background: Union[int, Tuple[int, int, int]]
) -> np.ndarray:
    old_width, old_height = image.shape[:2]
    angle_radian = math.radians(angle)
    width = abs(np.sin(angle_radian) * old_height) + \
        abs(np.cos(angle_radian) * old_width)
    height = abs(np.sin(angle_radian) * old_width) + \
        abs(np.cos(angle_radian) * old_height)

    image_center = tuple(np.array(image.shape[1::-1]) / 2)
    rot_mat = cv2.getRotationMatrix2D(image_center, angle, 1.0)
    rot_mat[1, 2] += (width - old_width) / 2
    rot_mat[0, 2] += (height - old_height) / 2
    return cv2.warpAffine(image, rot_mat, (int(round(height)), int(round(width))), borderValue=background)


def handler(event, context):

    # Create a NoteShrink object full of images, either an array of filepaths, PIL images or numpy arrays
    ns = NoteShrinker(['test_note.png'])

    # Shrink the images by calling the shrink method, this returns an array of PIL images encoded as RGB
    shrunk = ns.shrink()

    # Carry on with your image processing....
    for img in shrunk:
        img.save('/tmp/example.png')

    image = cv2.imread("/tmp/example.png")
    grayscale = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    angle = determine_skew(grayscale)
    rotated = rotate(image, angle, (0, 0, 0))
    cv2.imwrite('/tmp/output.png', rotated)

    im = Image.open("/tmp/output.png")
    gr_image = im.convert('L')  # grayscale
    gr_image = gr_image.filter(ImageFilter.MedianFilter())

    # SHould be saved in S3
    encoded = f'data:image/png;base64,' + \
        base64.b64encode(open("/tmp/output.png", "rb").read()).decode("utf-8")

    body = {
        "text_eng": pytesseract.image_to_string(gr_image, lang="eng", config="pcm=3 oem=1"),
        "text_fra": pytesseract.image_to_string(gr_image, lang="fra", config="pcm=3 oem=1"),
        "text": pytesseract.image_to_string(gr_image,),
        # "output": encoded
    }

    response = {
        "statusCode": 200,
        "body": json.dumps(body)
    }

    return response

Ce script fait 4 choses,

  1. Convertir l’image avec NoteShrink, j’utilise cette étape pour convertir mes notes écrites à la main.
  2. Renvoyer le base64 de l’image convertie, cette étape devrait utiliser quelque chose comme S3 pour sauvegarder l’image et mettre les metadata dans DynamoDB (non couvert dans cet article).
  3. Extraire le texte de l’image en utilisant le data “fra” en “eng”, ce metadata devrait être sauvegardé dans DynamoDB avec l’image originale.
  4. Renvoyer l’information au client.

Outils et debug

Comment valider l’image en base64 avec le CLI,

L’image est renvoyée en utilisant le champ output,

vous devez seulement garder le base64 (enlever : data:image/png;base64,), enregistrer le contenu comme suit : image.base64

ensuite avec le CLI, entrer la commande suivante:

base64 -i image.base64 -o image.png --decode
file image.png
image.png: PNG image data, 3024 x 4032, 8-bit/color RGB, non-interlaced

La commande file permet de vérifier que le tout a fonctionné.

Configurations et résultats

J’ai testé seulement une image. Puis voici les résultats:


pcm=13 & oem=0

pytesseract.image_to_string(Image.open('/tmp/test_note.png'), lang="eng", config="pcm=13 oem=0"),
pytesseract.image_to_string(Image.open('/tmp/test_note.png'), lang="fra", config="pcm=13 oem=0"),
-\\n\\n \\n\\n\\u00ab\\n\\n \\n\\n1 3 3 1 3 A ra Se a\\n\\n  \\n \\n\\nEy a Cormnt fice garde 1:\\n\\n: i :\\nv . { 3 T ' -\\n1) |\\n\\\\ | ! i : : ; :\\nAY SE SR ee\\n| Ch es\\ni | f i al i ,\\n! : |\\nr \\\" . i\\nPo ;\\n|\\n! } } :\\n\\n  \\n\\nPa ings pp ib\\n0 Chaph PI Tom pes enlace.\\nlant | yet Lizzi\\n\\n) oo a\\nMe\\n=\\n\\nCorn nTnannasfaeestfereiadi\\n\\n \\n\\n    \\n\\f

\\u00e0\\n\\n \\n\\n\\u00ab\\n\\n \\n\\n2, 3 \\u00e0 4, \\u00c0. \\u00c0. La 1\\n\\n  \\n \\n\\nete Thot cormat foire Srandir H\\n\\n! j :\\n\\u2018 . | \\u00bb \\u00ef \\u2018 =\\nL ;\\n, | ! ! | : ; ; ;\\n-\\u2014\\u2018Ay. pr er Lecce dei 1 5\\nPP 0 TA T -\\ni | f j \\u00b0 | ; \\u2018\\n| {\\n\\u201d - - *\\n| \\u20ac ; |\\n! :\\n: } ; :\\n\\n  \\n\\na imp ipe Piges de\\n\\u00a9. chophe; 4 Pt Cdn * eee meuler\\nlent { ee Lessration\\n\\n\\u2018 2e\\n| oe\\na\\n\\nPL RE EEE EEE EN TERRE\\n\\n \\n\\n    \\n\\f

pcm=3 & oem=1

pytesseract.image_to_string(Image.open('/tmp/test_note.png'), lang="eng", config="pcm=3 oem=1"),
pytesseract.image_to_string(Image.open('/tmp/test_note.png'), lang="fra", config="pcm=3 oem=1"),
CC\\n\\nPPP OPIS EII ECCT OEY 4 4 hb 11 da\\n\\n \\n\\n. ;\\nf\\ni\\n\\n  \\n\\nEy \\u201cled | Conmnt fire grondic u\\n\\n-\\n\\n \\n\\n  \\n\\n    \\n\\nNe pparsonre N\\n\\n \\n\\nCy\\n\\n~Cogengo PR\\n\\nole Kinplo J .\\u00a5\\n7 - rg pp 1ene\\n\\n\\u00ae ox. Chepl A food. \\u201d ie ce.\\n\\nlh | yet Lenigpein \\\\\\n\\n   \\n\\n  \\n\\n \\n\\n \\n\\f

\\u20ac\\n\\nLT TS # & k \\u00c0 1 3 114 LA\\n\\n \\n\\ne |\\nf\\ni\\n\\n  \\n\\nete ll | Cornmnt Taie = gnaie \\u0178\\n\\nVe\\n\\n \\n\\n  \\n\\n    \\n\\n1 \\u00c0 Ye fRETSONNE 51 =\\n\\n \\n\\ntb\\n\\nBpargo I\\n\\nOle \\u00ab amples 1 M\\n? roc ptiere\\n\\n=. chophe A dure \\u2026 ie os\\n\\nee { ee Leniguetion ;\\n\\n   \\n\\n  \\n\\n \\n\\n \\n\\f

Conclusion

Définitivement, il y a beaucoup d’optimisation à faire pour obtenir un meilleur résultat.

Je vais peut-être essayer quelque chose comme : https://tesseract-ocr.github.io/tessdoc/tess4/TrainingTesseract-4.00.html, par contre je n’ai pas encore de dataset.

Sources


Recherche