Blog de développement

OCR et Reconstruction

Dernière Modification le :
2024-02-19

au XXI ème siècle, envoyer un bon de commande sous le format le plus difficile à lire pour une machine, franchement!!! On se demande ce que les gens ont dans la tête!Pourtant, c'est l'usage retenu!

On a vu que le Listener synchronise la boite mail, distribue le courrier aux fonctions de l'entreprise et au Service Informatique qui va mainenant convertir les pièces jointes (PdF ou DOC) en texte pour reconstituer un mail complet, en mode texte, comprenant ce qui est caché dans les pièces jointes. Pour la plupart, les pièces passent à l'OCR

On n'OCR pas tout!

Le passage à l'OCR gros consommateur de temps et de CPU: parfois plusieurs minutes pour un PDF...

Notre première mesure, très efficace, consiste à n'envoyer au module de transformation que les mails assignés à une tâche (ex: bon de commande). Ensuite on dispose d'une cascade d'actions qui vont réduire le nombre de solliciations de l'OCR

  1. Détection et mise à l'écart des PDFs corrompus
  2. Transformation en mode texte des fichiers DOC
  3. Eviction des fichiers parasites obligés tels que conditions générales d'achat
  4. Identification des fichiers pour lesquels nous disposons déjà d'un fichier texte (ex: nos offres)
  5. Utilisation de l'extraction de texte plutôt que de l'OCR, quand c'est possible
  6. Restriction du nombre de pages à traiter
  7. Enfin... Création d'image et OCR (au plus rapide)
  8. Reconstitution du mail entiément en mode texte

Les Fichiers corrompus

C'est plutôt rare, et, en plus, j'ai abandonné l'usage de Munpack qui en générait. On ne cherche même pas à corriger... Comme on va utiliser amplement la suite Xpdf, autant se servir de PDFInfo pour trier ce qui peut être lu ou pas. Lire et Télécharger → On en profitera pour récupérer le nombre de pages et les autres info, à toutes fins utiles.

On utilisera systématiquement PDFtoText, qui lui aussi fourni un code d'erreur si le fichier ne peut être lu, donc on pourrait éventuellement faire l'économie de PDFInfo. Comme il est rapide, on l'utilise néanmoins...


ReturnValue = ""
' // sDossierLogiciels est le dossier où se trouve pdfinfo.exe
' // la chaine qui contient le chemin vers le fichier de configuration pour la suite XPDF
Command = """" & sDossierLogiciels & "pdfinfo.exe""  " & sArgConfig & " """ & sFilePath & """ " '> """ & sTextFilePath & """ "
' // on recupère le return value	
ReturnValue = oShell.Run (Command, 0, True)
if ReturnValue <> 0 then 
	' // Le fichier, et mis en reserve et archive, avant d'etre éliminé du dossier contenant les fichiers à OCERISER
	if objFSO.FileExists(sFilePath) then
		' // on determine la date de dernière modificationdu fichier: elle nous servira de TimeStamp (pour eviter les doublons il faut que la date soit unique)
		dtDateLastModified = CDate(Int(objFSO.GetFile(sFilePath).DateLastModified))					
		sDateLastModified  = ToIsoDateSimplified (dtDateLastModified)
		sForAnalysisFileName = Replace(sFileName, ".pdf", "_" & sDateLastModified & ".pdf", 1, -1, 1)
		sForAnalysisFilePath = sDossierCorruptedInfosPourAnalyse & sForAnalysisFileName
		If NOT objFSO.FileExists (sForAnalysisFilePath) then objFSO.CopyFile sFilePath , sForAnalysisFilePath
		objFSO.CopyFile sFilePath , sDossierOCRCorrupted & sFileName
		objFSO.DeleteFile sFilePath
		sCorruptedWarning = sFileName & " : Marque comme corrupted par PdfInfo"
		'Il faut aussi le supprimer de là où il vient, sinon, il revient toujours!
		'For iFonction = 0 to 2
		'if objFSO.FileExists(ArOCRFonction(iFonction, 2) & sFileName) then  objFSO.DeleteFile ArOCRFonction(iFonction, 2) & sFileName
		'Next
		'on cree un fichier bidon, il sera utilisé lors de la reconstitution du mail en mode texte
		Set objFile = objFSO.OpenTextFile(sDossierOCRTextes & sTextFileName, ForWriting, true)
		objFile.Write sCorruptedWarning
		objFile.Close
		Set objFile = nothing
	End If
Else
	'if NOT objFSO.FileExists(sTextFilePath) then objFSO.CreateTextFile sTextFilePath
	Command = """" & sDossierLogiciels & "pdfinfo.exe""  " & sArgConfig & " """ & sFilePath & """ > """ & sTextFilePath & """ "
	Set objFile = objFSO.OpenTextFile(sDossierCurl & "Infos.bat", ForWriting, true)
	objFile.Write command
	objFile.Close
	Set objFile = nothing
	Command_Bat = """" & sDossierCurl & "Infos.bat" & """" 				
	oShell.Run Command_Bat, 0, True
	Command = """" & sDossierLogiciels & "pdfinfo.exe""  " & sArgConfig & " """ & sFilePath & """ | Find /i ""Pages:"" > """ & sPagesFilePath  & """ "
	Set objFile = objFSO.OpenTextFile(sDossierCurl & "Pages.bat", ForWriting, true)
	objFile.Write command
	objFile.Close
	Set objFile = nothing
	Command_Bat = """" & sDossierCurl & "Pages.bat" & """" 				
	oShell.Run Command_Bat, 0, True	
End If			

Si le fichier est corrompu, on l'élimine tout en gardant une copie d'archive pour analyse ultérieure. Sinon, on enregistre les infos et on extrait le nombre de pages par un pipe, technique décrite ici: Lire →

Command = """" & sDossierLogiciels & "pdfinfo.exe"" " & sArgConfig & " """ & sFilePath & """ | Find /i ""Pages:"" > """ & sPagesFilePath & """ "

Les Fichiers parasites (clandestins)

Les mails de commande sont parfois accompagnés de "conditions générales d'achat", document qu'on prendra soin de ne pas envoyer à l'OCR. On constitue une liste de mots clés, tels que "CGA." , "k-bis" , "kbis" , "conditions" , "RIB" , "Facture_" Un fichier identifé comme tel est mis à l'ecart, archivé pour analyse ultérieure. Une liste de noms de fichiers OCRisés est conservée en vue d'améliorer éventuellement la liste des parasites.

Les fichiers dont on a déjà une version texte

Lors de la création d'un devis, le serveur fait une version texte, envoyée au client en même temps que le PDF. L'offre, en version texte, est dûment archivée sur le serveur, donc, il nous suffit d'aller retrouver ce fichier et nous ferons ainsi l'économie d'un passage à l'OCR.

Les fichiers Word (Doc ou Docx)

A l'heure où une meilleure productivité en back office est rendue nécessaire par la complexification administrative sans fin, rares sont le clients à envoyer une commande sous un format intelligible. Et pourtant d'exiger que l'on reprenne , sans erreur s'il vous plait, des numeros de commandes formés en dépit du bon sens, des adresses de facturation invérifiables et inutiles à l'heure de la facturation électronique, etc. Et d'utiliser, quasi systématiquement le PDF, format dont le seul intéret, outre la sécurité, est d'être portable sur tous les OS utilisés par... des humains de chair et d'os! Un simple format texte, voire Word, serait le bienvenu! Et bien non! La productivité c'est pour le client, pour le fournisseur, on s'en moque!

Les fichiers Word sont donc rares dans les échanges client/fournisseur. Ils donnent pourtant la possibilité d'une lecture exacte. A noter que le format (trop) récent Order-x sera intéressant.

Quand on en trouve un on fait 2 transformations: l'une vers Texte, l'autre vers PDF. On installe LibreOffice sur le serveur (et non pas open office). Lire et Télécharger →.
La commande est :
"C:\Program Files\LibreOffice\program\soffice.exe"" --headless --convert-to pdf --outdir "C:\...\DossierCible_sans_Slash" "C:\...\MonDocument.doc"

Il faut mettre le dossier de destination sans slash (\) à la fin. On fait de même avec --convert-to txt

A priori, on pourrait penser qu'une transformation digitale est supérieure à une lecture OCR. Toutefois... La conversion de --convert-to txt ne convertit PAS les pieds de pages (footer), alors que --convert-to pdf rend bien compte de l'existence du pied de page. Or, des infomations importantes y sont parfois... En fin de compte, on fera donc tout de même l'Extraction et l'OCR sur le PDF, et on utilisera aussi le résultat pour la recherche des coordonnées du client (TVA, SIRET...). Cf discussion sur les Hybrides, plus bas

On peut toujours craindre qu'un logiciel 'lourd' puisse planter, bloquer le serveur, rester en mémoire plus que souhaitable. Juste avant l'execution de soffice, on marque un petit fichier init qui indique q'un redémarrage serveur est souhaité.
Un script 'superviseur', indépendant du Listener vient vérifier le bon déroulement du Listener (par interrogation du log), jusqu'à son terme et envoie un message au manager si tel n'est pas le cas. Le Superviseur va lire ce petit fichier marquer et redémarrer le serveur.

Extraction de texte (ou OCR?)

L'OCR est inévitable pour un PDF numérisé (scan). Pour un PDF généré digitalement par un logiciel on peut practiquer l'Extraction de Texte, un peu comme quand on sélectionne une zone de texte sur un PDF et que le texte apparait en sus. J'ai une page consacrée à ces techniques. Lire →.
A priori, on pourrait croire que l'Extraction est plus fiable. Hum... Pas sûr. C'est beaucoup plus rapide et économe. Chorus PRO ne fait QUE de l'Extraction de texte, et ne prend donc pas les PDF numérisés. L'OCR est lent et fastidieux. Dans notre cas on pourra quand même s'en sortir en quelques dizaines de secondes.

Les PDFs hybrides: (Ici, c'est la page qui est hybride) c'est le cas où une cliente numérise (scan) son papier à entête (là où il y a le numéro de TVA), l'utilise comme fond d'une commande (là où il y a le numéro de Commande) en mode digital! On est alors obligés de faire les 2! Une méthode simple et robuste pour distinguer une PDF digital d'un scan constite à faire l'Extraction (c'est immédiat), de constater éventeullement que l'extraction ne produit rien (ou trop peu), et de faire alors l'OCR. Mais quid des hybrides ?

En definitive, je fais les 2 systématiquement et crée 2 versions du texte: l'une avec l'Extraction voire l'OCR, si besoin (pour identification du numero de commande), l'autre avec les 2 (donc en double, plus ou moins) pour l'identification des identifiants légaux (TVA, SIREN, SIRET).

Un autre cas de fichier hybride: (Ici, c'est l'assemblage des pages qui est hybrides) le PDF est constitué d'une page numérisée (la commande) et de pages digitales (issues d'un logiciel, par exemple notre devis), ou l'inverse. On peut, bien sûr, transformer page à page et reconstituer. Pour l'instant on va se limiter à convertir uniquement la première page, ce qui rend les choses plus faciles.

Extraction de texte avec XPDF PDFtoText

PDFtoText est facile à mettre en oeuvre, il est multiplateforme. En même temps que la distribution du courrier, le Listener envoie les PDF dans un répertoire OCR/Sources, puis nous faisons l'Extraction ET l'OCR.

Outre le log, qui enregistre le déroulement général du Listener, on dispose d'un Minilog, spécifique à chaque document.

En principe, seule nous intéresse la première page... Pour l'OCR, cela vaut la peine de se restreindre à la premièrepage. Pour l'extraction de texte... pas vraiement.


sArgFromTo = " -f 1 -l 1 "		' // On peut ajouter cet Argument pour ne faire l'Extraction que de la première page... Si on veut...
Command = """" & sDossierLogiciels & "pdftotext.exe"" -bom -table -enc UTF-8 " & sArgConfig & """" & sDossierOCRSources & sFileName & """ """ & sPdfToTextFilePath & """"
Call Log ("Command : " & Command )	
ReturnValue = oShell.Run (Command, 0, True)		' // pdftotext.exe fournit des valeurs de sortie que l'on intercepte et garde au fins de diagnostique
	'S'il n'a pas pu etre cree on va cree un fichier bidon, pour forcer l'OCR
sReturnValue = ""
if ReturnValue = 0 then sReturnValue = "No error"
if ReturnValue = 1 then sReturnValue = "Error opening a PDF file"
if ReturnValue = 2 then sReturnValue = "Error opening an output file"
if ReturnValue = 3 then sReturnValue = "Error related to PDF permissions"
if ReturnValue = 99 then sReturnValue = "Other error"			
if ReturnValue <> 0 then 
	Is_Need_OCR = True
	Call Log ("Erreur PdfToText: Pour sFileName : " & sFileName & "  la valeur retournee est : " & ReturnValue)
	Call MiniLog ("ReturnValue for PDFtoText = " & ReturnValue & " (" & sReturnValue & ")", sMiniLogFilePath)
Else
	Call MiniLog ("Tranformation reussie par PDFtoText : ", sMiniLogFilePath)
End If
	' // Si besoin on cree un dossier bidon...
If NOT objFSO.FileExists (sPdfToTextFilePath) OR ReturnValue <> 0 then 
	Set objOpen = objFSO.OpenTextFile(sPdfToTextFilePath, ForWriting,true)	' // true veut dire que le fichier sera crée si besoin
	objOpen.Write "PdFToText n'a rien donne: on cree un fichier par defaut"
	objOpen.Close
	Set objOpen = nothing
ELSE 			
	'on verifie le resultat du passage par PdfToText
	nCompteur = 0
	nMax = 5
	Set oStream = CreateObject("ADODB.Stream")
	oStream.Charset = "utf-8"
	oStream.Type = 2
	oStream.Open
	oStream.LoadFromFile (sPdfToTextFilePath)
	oStream.LineSeparator = 10
	Do Until oStream.EOS
		nCompteur = nCompteur + 1
		aLigne = oStream.ReadText(-2)  'Il faut mettre une action, meme bidon pour forcer a boucler
	Loop
	if nCompteur > nMax then 
		Is_Need_OCR = False
		if objFSO.FileExists (sPdfToTextFilePath) then 
			objFSO.CopyFile sPdfToTextFilePath, sTextFilePath
			Call Log ("On valide l'extraction de texte par PDFtoText pour : " & sTextFileName)
			Call MiniLog ("On valide l'extraction de texte par PDFtoText pour : " & sTextFileName & " en : " & DateDiff("s", Now_Old, Now()) & " secondes", sMiniLogFilePath)
		End If
	Else
		Call MiniLog ("L'extraction de texte par PDFtoText n'est pas satisfaisante", sMiniLogFilePath)
		Is_Need_OCR = True
	End If
End If
if nCompteur > nMax then Is_Need_OCR = False

OCR avec Tesseract

En pratique, tous les OCR performants en OpenSource dérivent de Tesseract (HP puis Google). Il ne travaille que sur des images, pages par page.

D'une façon générale, L'OCR fonctionne mieux avec 300 ppi (pixels par pouce) ou plus. Les scans doivent avoir au moins cette qualité ou, si ce n'est pas possible, nous devons envisager de les remettre à l'échelle.
Des polices adaptées à l'OCR doivent être utilisées si possible.
Les délimiteurs de champs de texte sont plus faciles à détecter automatiquement (les bordures et les positions sont plus difficiles).
Les effets d'érosion et de dilatation peuvent être utilisés dans le prétraitement. Ils peuvent
- supprimer le bruit des images
- isoler des éléments individuels et joindre des éléments disparates dans une image
- trouver des bosses ou des trous d'intensité dans une image

Le flou et le filtre médian pour adoucir les bords peuvent être utilisés sur l'image. La binarisation, qui permet d'identifier facilement le texte, peut être utilisée sur l'image. Le traitement par réseau neuronal peut être utilisé pour détecter des zones de texte et les soumettre au moteur d'OCR. Un document papier peut être endommagé (tache, pli, etc.) et il sera plus difficile de faire l'ocr.

Réorientation de la page pour l'OCR

La question du redressement de la page se pose de 2 façons:
- La page peut arriver droite, en mode portrait ouu avec une rotation de + 90, 180, et 270 degrés...
- Pour un scan, elle peut arriver avec une désorientation de quelques degrés (ex: 2 degrés)

Au delà du bénéfice pour l'OCErisation, redresser l'image la rend plus agréable à l'opérateur lors de la saisie ou vérifcation d'une commande...

Même si Tesseract dipose d'un redressement automatique (mode --psm 6), on constate que le redressement préalable de l'image, avant l'OCR, est favorable. L'outil OpenCV convient bien à la correction des petits angles, mais pas à la détection des 'grandes' rotations. L'outil qui le permet n'est autre que... Tesseract soit même. L'idée est de faire une première passe par Tesseract pour mesurer l'orientation générale, la corriger avec Image Magick (ou OpenCV ?), détecter le petit angle éventuel avec OpenCV, puis corriger avec Image Magick (ou OpenCV ?)

Tesseract, avec l'option --psm 0, effectue une analyse d'orientation et de script (OSD - Orientation and Script Detection). Cette commande renvoie plusieurs informations, parmi lesquelles :

  • Orientation de l'image : Les valeurs possibles sont généralement 0, 90, 180, et 270 degrés.
  • Rotate ! : c'est la rotation qu'il est suggéré de faire: très utile!...
  • Confiance de l'orientation : Tesseract indique, en pourcentage, à quel point il est sûr de l'orientation détectée.
  • Détection du script : Tesseract essaie d'identifier le script (langue) du texte.
  • Confiance du script : Tesseract indique, en pourcentage, à quel point il est sûr de la langue détectée.
Voici un exemple de sortie typique de tesseract image.jpg stdout --psm 0 :

Page number: 0
Orientation in degrees: 270
Rotate: 90
Orientation confidence: 30.02
Script: Latin
Script confidence: 10.32

Redimensionnement après Réorientation de la page

La commande convert de ImageMagick, pour effectuer une rotation de 90 degrés, peut créer des bords blancs autour de l'image. Pour retirer ces bords blancs et redimensionner l'image, vous pouvez utiliser ImageMagick :

  • Rotation de l'image : D'abord, faites pivoter l'image de 90 degrés avec convert -rotate.
  • Suppression des bords blancs : Ensuite, utilisez la commande -trim pour supprimer automatiquement les bords blancs.
  • Redimensionnement (optionnel) : Si vous souhaitez également redimensionner l'image après la suppression des bords, vous pouvez utiliser l'option -resize.
Voici un exemple de commande qui combine ces étapes :

convert original.jpg -rotate 90 -trim +repage -resize 700x resized.jpg

Dans cet exemple :
-rotate 90 fait pivoter l'image de 90 degrés.
-trim supprime les bords blancs autour de l'image.
+repage après -trim nettoie le canevas et met à jour la taille de l'image.
-resize 700x redimensionne pour une largeur de 700 pixels, en conservant sa forme (aspect ratio).

Après Rotation : la correction angulaire...