Anima Mundi

Un moteur d'OCR ultra-performant & gratuit en quelques lignes de code R

Cet article s'adresse aux utilisateurs ayant besoin d'extraire du texte depuis un lot d'images ou de scans, dans une optique d'archivage ou de récolte de données dans le cadre d'un projet Data Science. Les données textuelles sont une mine d'or d'informations, mais souvent peu disponibles car beaucoup de process se font encore sur papier, d'où l'intérêt de pouvoir les extraire pour les rendre exploitables. Une toute petite connaissance de R est un plus, mais pas nécessaire pour comprendre ce qui suit.

L'OCR, pourquoi faire ?

L'Optical Character Recognition (Reconnaissance Optique des Caractères ou Océrisation) est un procédé permettant d'extraire du texte depuis une image dans un fichier texte. L'OCR est particulièrement utile pour dématérialiser des documents papiers imprimés et les remplacer par des bases de données exploitables par ordinateur.

Les entreprises ayant énormémént d'archives papier coûteuses à stocker, pas mal de boîtes se sont donc lancées dans le créneau et proposent des solutions permettant d'aller dans le sens de la dématérialisation.

Et si on faisait notre propre moteur d'OCR pour 0€, uniquement à base d'Open Source ?

Une des forces de R est le fait qu'il ait accés à nombre d'API lui permettant de s'interfacer et d'exploiter des milliers de services divers et variés.

Parmi ces services se trouve l'outil Tesseract, lancé par Hewlett-Packard Co en 1985 puis open sourcé en 2005. Depuis, il est maintenu et mis à jour par Google et disponible gratuitement.

Par souci de simplicité, nous utiliserons simplement un package R qui comprend Tesseract. Pas besoin d'installation supplémentaire, donc.

En complément, on utilisera le package magick, qui permet de faire de la manipulation d'image (un peu comme Paint, mais moins rigolo et avec plus de code)

C'est parti ! On met en place ce qu'il faut

1. Installation & chargement des packages R :

# install.packages('tesseract')
# install.packages('magick')
# install.packages('magrittr')

library(tesseract) # API R vers le moteur d'OCR Tesseract (Google)
library(magick) # Ensemble d'outils d'image processing
library(magrittr) # Pour chaîner les opérations avec %>%

Puis on choisit son répertoire de travail (mettez le votre pour tester)

wd <- "/chemin/vers/mon/dossier/de/travail"

On récupère un scan, j'ai choisi un thème d'actualité :

Prenons ce scan de la préface d'un livre au sujet du compost :

Téléchargeons cette image dans notre répertoire de travail et appelons-là scan.png :

 # download.file(url = "https://bernardkmartin.files.wordpress.com/2016/01/scan04.png", # URL de l'image
 #              destfile = paste0(wd,'scan.png'), # Fichier de destination 
 #              mode = 'wb')

Okay on a tout, il est temps de faire appel à Tesseract !

On utlise image_read() pour lire l'image et la lier l'objet R que l'on appelle img_raw Pour appeler Tesseract et extraire le texte de l'image, rien de plus simple, on appelle la fonction ocr() : cat() permet ensuite d'afficher le texte extrait. Attention :

imgpath <- paste0(wd,"scan.png")
img_raw <- image_read(imgpath) 

txt_raw <- ocr(img_raw)

cat(txt_raw)

Super ! malgré quelques erreurs de lecture ("L"" minuscule lu comme "I" majuscule, confusion entre accent grave et aigue, etc), nous avons extrait le texte de l'image avec succés en quelques secondes seulement.

Voyons voir si l'on peut faire mieux. Je vais à présent appliquer quelques traitements à notre image pour la rendre plus lisible pour Tesseract, et utiliser un moteur OCR de Tesseract entraîné sur du texte en Français :

img_proc <- image_read(imgpath) %>%
  image_resize("2000") %>%
  image_convert(colorspace = 'gray') %>%
  image_trim() %>%
  image_chop("150x0") %>%
  image_chop("0x550") %>%
  image_deskew(threshold = 40) %>%
  image_background("white", flatten = TRUE) %>%   
  image_noise() %>%                               
  image_enhance() %>%                             
  # image_normalize() %>%    
  image_trim(fuzz = 20) %>%  
  image_deskew() 
print(img_proc)

Allons-y pour l'extraction :

fr_engine <- tesseract(language = 'fra')
txt_proc_fr <- ocr(img_proc, engine = fr_engine)
cat(txt_proc_fr)

C'est un tout petit peu mieux !

Faisons une comparaison de qualité des deux opérations d'OCR :

# install.packages(c("tidytext", "SnowballC"))
library(tidytext)
library(SnowballC)

Pour ma comparaison, j'utilise un dictionnaire gratuit en français que j'ai trouvé sur le net. Ce dictionnaire contient 28997 radicaux de mots, et permet d'évaluer rapidement la qualité de notre océrisation. Si la racine d'un mot extrait de l'image est dans le dictionnaire, c'est que l'OCR a fonctionné.

library(readr)
library(dplyr)
dico <- data.frame(words = read_lines(paste0(wd,"francais.txt"))) %>%
  mutate(words = as.character(words)) %>%
  unnest_tokens(word, words) %>%
  mutate(word = wordStem(word, language = "fra")) %>%
  distinct()

Voici un aperçu du contenu du dictionnaire :

library(DT)
datatable(data.frame('index' = seq(1:28997), racine = dico[1])[1000:1100,])

Afin de faire nos calculs de performance et de faciliter les comparaisons avec le dictionnaire, mettons le text extrait des deux images au format tableau, après avoir transformé tous les mots en leur radical (stem) :

text_1_df <- data.frame(text = read.delim(textConnection(txt_proc_fr),    # on met le texte dans un dataframe
                                          header = FALSE, 
                                          sep = "\n", 
                                          strip.white = TRUE)) %>%
  mutate(text = as.character(V1)) %>%
  unnest_tokens(word, text) %>%                                      # on sépare les mots
  mutate(word = wordStem(word, language = "fra"))                    # on remplace chaque mot par son radical

txt_raw <- gsub(pattern = '"',x = txt_raw,replacement = "”")  
txt_proc_fr <- gsub(pattern = '"',x = txt_proc_fr,replacement = "”")

text_2_df <- data.frame(text = read.delim(textConnection(txt_raw),   
                                          header = FALSE, 
                                          sep = "\n", 
                                          strip.white = TRUE)) %>%
  mutate(text = as.character(V1)) %>%
  unnest_tokens(word, text) %>%                                     
  mutate(word = wordStem(word, language = "fra"))                 

Puis calculons un indicateur de performance : parmi tous les mots extraits par Tesseract, quel pourcentage se retrouve dans le dictionnaire ?

res1 <- text_1_df %>%
    mutate(in_dict = ifelse(word %in% dico$word, TRUE, FALSE)) %>%
    count(in_dict) %>%
    mutate(percent = n / nrow(text_1_df) * 100,
           image = "image 1")

res2 <- text_2_df %>%
     mutate(in_dict = ifelse(word %in% dico$word, TRUE, FALSE)) %>%
     count(in_dict) %>%
     mutate(percent = n / nrow(text_2_df) * 100,
            image = "image 2")
library(plotly)

p <- plot_ly(
  x = c("Non traité","Traité"),
  y = as.numeric(c(res1[2,3],res2[2,3])),
  name = "SF Zoo",
  type = "bar")

p

Nous sommes autour de 77.5% de performance, dans les deux cas. Ici, le traitement n'a pas apporté grand chose car l'image de départ était de très grande qualité : centrée, droite, pas de bruit, excellent niveau de netteté. Il n'y avait donc pas vraiment besoin de traitement. Dans la réalité, il en est tout autrement, et c'est toujours une bonne idée de rajouter une étape permettant de gérer les ratés de numérisation. Il peut également arriver que le papier soit sale, froissé, jauni, etc.

Maintenant, que penser d'une performance de 77.5% ? Selon moi, c'est pas mal du tout pour si peu d'effort. D'autant plus que j'ai pris le premier dictionnaire trouvé sur le net. De plus, ce chiffre est sous-estimé du fait de la présence de noms propres et de sigles dans le texte. Avec un dictionnaire bien choisi, si possible amélioré pour inclure les termes spécifiques au corpus de documents que vous traitez et à votre secteur d'activité/métier, vous pouvez faire bien mieux.

De plus, vue la rapidité à laquelle se fait l'OCR avec Tesseract, on peut tout à fait envisager de l'inclure dans un processus temps réél (eh oui c'est possible avec R !). Il faut également savoir que l'on peut travailler avec des documents en PDF, JPEG, et tous les autres formats.

Merci Tesseract !

Sources :

#AI #Tech #french