Vous êtes sur la page 1sur 40

Rapport de projet

Projet: apOCRyphe

Un logiciel de reconnaissance optique des caractères

Groupe: Shut up and calculate!

Guerville ’Kennox’ Thomas (guervi_t)


Ferveur ’Hellmound’ Corentin (ferveu_c)
Marques ’Tauril’ Guillaume (marque_o)

2ème année en École d’Ingénieurs en Informatique

remis à Monsieur Burelle Marwan le 10 décembre 2014


apOCRyphe Shut up and calculate!

Sommaire

1. Introduction

2. Pré-traitement de l’image

3. Extraction des caractères

4. Redimensionnement des caractères

5. Réseau de neurones

6. Interface graphique

7. Site web

8. Conclusion

9. Annexes

10. Table des matières

2 10 décembre 2014
apOCRyphe Shut up and calculate!

Introduction
Voici notre premier rapport concernant le projet du premier semestre
de la 2ème année à l’EPITA. Après avoir réalisé l’année dernière un jeu
vidéo en C#, nous allons devoir cette année implémenter un logiciel de
Reconnaissance Optique de Caractères (R.O.C.), plus connu sous le nom
de Optical Character Recognition (O.C.R.) en anglais, servant à recopier
automatiquement, dans un fichier texte, une page scannée.

Les membres du groupe sont MARQUES Guillaume, FERVEUR


Corentin et GUERVILLE Thomas (étant le chef de projet). Thomas et
Guillaume, ayant été dans la même classe et dans le même groupe de
projet lors de la première année, ont décidé de s’associer une nouvelle
fois pour réaliser ce projet. Lorsque la répartition des classes fut an-
noncée, ils se sont retrouvé dans une classe d’une vingtaine d’étudiants
parmi laquelle d’autres groupes s’étaient formés. Devant comporter au
minimum trois membres pour être validé, chaque groupe s’est empressé
d’acquérir d’autres membres. C’est ainsi que Corentin rejoint le groupe.

Concernant le nom de projet, nous avons opté pour "apOCRyphe"


car nous cherchions un mot dans lequel se trouvent les trois lettres "O",
"C" et "R" relativement à notre sujet de projet. De plus, nous trou-
vions cela amusant car, par définition, apocryphe désigne un texte dont
l’authenticité n’est pas vérifiée et un O.C.R., réalisant une copie d’un
texte scanné, fait plus ou moins perdre l’authenticité de ces textes. Pour
continuer dans cette lancée, nous avons décidé de prendre comme nom
de groupe une citation apocryphe : "Shut up and calculate!". En effet,
cette citation a été attribuée au physicien Richard Feynman, mais est
problablement apocryphe car un physicien moins connu revendique être
l’auteur de cette citation.

Le groupe étant créé, nous avons donc entrepris la répartition des


rôles. Thomas s’est porté volontaire pour réaliser le réseau de neurones
et Guillaume s’est chargé d’implémenter la détection des caractères. Con-
cernant la partie de pré-traitement, Guillaume, avec l’aide de Thomas,
avait déjà réalisé la plupart des fonctions demandées avant la formation
définitive du groupe. Pendant ce temps, Thomas se documentait concer-
nant les réseaux artificiels de neurones. Une fois que Corentin a rejoint
le groupe, il s’est vu attribué la création de l’interface graphique.

3 10 décembre 2014
apOCRyphe Shut up and calculate!

Nous allons donc présenter, à travers ce rapport, notre avancement


général depuis la première soutenance jusqu’à la date limite du projet
; ce que nous avons réussi à faire et les points que nous n’avons pas
réussi à finaliser. Le rapport est divisé en quatre grandes parties : le pré-
traitement de l’image, la segmentation des caractères, le réseau neuronal
et l’interface graphique.

4 10 décembre 2014
apOCRyphe Shut up and calculate!

Pré-traitement de l’image
Avant qu’il soit possible de travailler sur l’image, il faut que celle-ci
soit pré-traitée pour faciliter l’identification des caractères. En effet, il
faut binariser l’image et supprimer le bruit de l’image ainsi qu’effectuer
une rotation de l’image si le texte n’est pas droit. Cette partie a été
réalisée par Guillaume avec l’aide de Thomas.

1. Débruitage
Afin d’effectuer la suppression du bruit de l’image, nous utilisons une
combinaison de trois méthodes de filtrage d’image : le passage en niveau
de gris, la binarisation Sauvola et le filtre Gaussien.

Passage en niveau de gris


La méthode pour passer une image en niveau de gris est très simple.
Il suffit de récupérer les composantes R, G et B du pixel, de multiplier
chacune de ces composantes par une valeur (0.3 pour le R, 0.59 pour le
G et 0.11 pour le B) puis d’additionner ces valeurs et de les remplacer
à la place des anciennes. Une fois l’action effectuée sur chaque pixel de
l’image, nous obtenons une image en niveau de gris.

Binarisation
La méthode de binarisation choisie est une méthode adaptative et
plus précisément à seuil local. En effet, la méthode choisie est celle de
Sauvola qui s’adapte à l’image et qui requiert une image en niveau de
gris. Le principal objectif est de déterminer un seuil. Pour ce faire, il
faut créer une variable qui servira de seuil (nommons la average). Lors
du parcours de l’image, nous ajoutons à average la somme des valeurs
de gris des pixels présents dans un rayon prédéfini que nous divisons
par le nombre de pixels présents. Par la même occasion, nous calculons
la déviation (que nous plaçons dans une variable deviation) qui est la
somme des valeurs de gris des pixels voisins soustrait à la moyenne av-
erage divisé par le nombre de pixels voisins présents. Ensuite, le seuil
final est égal à T = m × 1 + k × Rd − 1 où T représente le nouveau


seuil, m représente average, k est égale à 0.2, d représente deviation et


R est égale à 128 (valeur pour un niveau de gris en 8-bit). Enfin, si une

5 10 décembre 2014
apOCRyphe Shut up and calculate!

des composantes du pixel (peu importe laquelle étant donné que le pixel
est gris (donc ses composantes ont la même valeur)) est plus grande que
le seuil, nous la mettons en blanc, sinon en noir. L’avantage principal
de cette méthode est qu’elle peut être utilisée sur les images à fond non
blanc.

Voici un exemple typique de l’utilité d’une telle binarisation :

Image d’orgine

Passage en niveau de gris

Ajout de la binarisation Sauvola

6 10 décembre 2014
apOCRyphe Shut up and calculate!

Filtre Gaussien
Le filtre Gaussien est la méthode permettant de supprimer le bruit et
est à utiliser avant la binarisation lorsqu’il est nécessaire. Cette méthode
utilise une matrice de convolution 3 * 3 sur chaque pixel de l’image. Le
principe est le suivant : chaque composante de couleur des pixels voisins
et du pixel actuel est multipliée par la valeur de la matrice de convolu-
tion correspondante et la somme est divisée par 16 (qui est la somme des
valeurs présentes dans la matrice de convolution). Le tout est disposé
dans une variable qui correspond à la nouvelle composante R, G et B du
pixel actuel.
1 2 1
La matrice de convolution est la suivante : 2 4 2
1 2 1

Concernant le filtre gaussien lors de la première soutenance, nous


avions le même problème que nous avons actuellement. En effet, lorsque
nous appliquons le filtre gaussien sur une image, il nous est impossible
d’y appliquer la détection des blocs car, pour des raisons qui nous sont
inconnues, lorsque nous le faisons, la détection détecte des caractères
noirs aux extrémitiés de l’image rendant ainsi l’image intraitable.

7 10 décembre 2014
apOCRyphe Shut up and calculate!

Ci-dessous se trouve le résultat des différentes étapes du pré-traitement


appliqué à une image où la suppression du bruit était nécessaire.

Image initiale Image après le filtre Gaussien

Passage en niveau de gris Ajout binarisation Sauvola

8 10 décembre 2014
apOCRyphe Shut up and calculate!

2. Rotation

La méthode utilisée pour effectuer la rotation est une méthode trigono-


métrique ce qui permet de travailler pixel par pixel augmentant ainsi la
précision de la rotation. Le principe est le suivant : nous créons une nou-
velle surface dans laquelle se trouve une image blanche où, à chaque pixel
rencontré, nous y ajoutons le pixel correspondant issu de la rotation de
l’ancienne image. Lors de cette insertion des pixels, il faut être vigilant à
ce que les nouvelles coordonnées du pixel soient positives et plus petites
que les dimensions de la nouvelle surface.

La méthode utilise plusieurs données : les coordonnées actuelles (x


et y), les nouvelles coordonnées résultantes de la rotation (new_x et
new_y), l’angle désiré (θ) ainsi que le centre de la feuille (center_x et
center_y) pour augmenter encore plus la précision du calcul.

Une fois tous les élément réunis, nous effectuons les deux calculs des
nouvelles coordonnées :

new_x = ((cos(θ) × (x − center_x) + (sin(θ) × (y − center_y)) + center_x

new_y = ((cos(θ) × (y − center_y) − (sin(θ) × (x − center_x)) + center_y

Nous nous sommes rendus compte, pour la deuxième soutenance, que


nous avions un problème pour détecter les caractères avec cet algorithme
de rotation. En effet, la rotation est effectuée sur l’ensemble des pixels de
l’image. Ainsi, nous obtenons une image bancale sur laquelle la segmen-
tation ne fonctionne pas. Pour remédier à ce problème, nous avons décidé
de rotater seulement les pixels noirs (ceux qui composent les caractères
après la binarisation). Il a été très simple de réaliser cette modification
; un simple test au moment des calculs des nouvelles coordonnées était
nécessaire. En effet, si le pixel que nous voulons rotater est noir, nous le
faisons rotater, sinon, nous remettons le pixel à sa place initiale dans la
nouvelle image.

9 10 décembre 2014
apOCRyphe Shut up and calculate!

Différences avec la première soutenance


Au sujet de la rotation, nous rotatons, à présent, seulement le texte
au lieu de l’image toute entière, ce qui est plus cohérent avec le résultat
voulu. Cependant, nous avons le même problème que lors de la pre-
mière soutenance. En effet, lorsque nous rotatons certaines images, le
résutlat est tout noir. En revanche, nous avons pu localiser le problème
étant donné qu’il ne semble survenir que lorsque nous voulons rotater des
images qui le sont déjà.

10 10 décembre 2014
apOCRyphe Shut up and calculate!

Extraction des caractères


Une fois l’image pré-traitée, il est désormais possible de travailler
dessus et de récupérer les caractères. L’extraction des caractères se di-
vise en trois parties : la détection des blocs, la détection des lignes et
la détection des caractères. Cette partie a été entièrement réalisée par
Guillaume.

1. Détection des blocs


Lors du commencement du travail sur la segmentation, nous avons
voulu directement détecter les lignes pensant que la détection des blocs
n’était pas nécessaire. Nous nous sommes rendu compte de notre er-
reur lorsque nous avons traité des images contenant plusieurs blocs de
texte sur la longueur1 . Par conséquent, nous avons utilisé un algorithme
permettant de séparer, visuellement, les différents blocs du texte. Cet
algorithme est le "Run Length Smoothing Algorithm" (RLSA).

Principe
Cet algorithme se divise en quatre parties : une découpe verticale
sur l’image d’origine, une découpe horizontale sur l’image d’origine, une
opération logique AND sur les deux images résultantes et le calcul des
coordonnées des blocs obtenus. Grâce à cet algorithme, nous obtenons
des blocs noirs qui représentent les paragraphes ce qui nous facilite le
calcul des coordonnées de ces blocs pour pouvoir ensuite y détecter les
lignes et enfin les caractères.

Découpe verticale
Pour effectuer la découpe verticale, nous parcourons notre feuille de
haut en bas et de gauche à droite. Lorsque nous rencontrons un pixel
noir, nous passons un booléen countWhitePixel à true pour indiquer que,
à partir de maintenant, nous voulons connaître le nombre de pixels blancs
qui séparent deux pixels noirs. À la nouvelle rencontre d’un pixel noir,
nous regardons le nombre de pixels blancs que nous avons rencontré et si
ce nombre est plus petit qu’un seuil (l’espace moyen entre deux lettres),
nous transformons les pixels blancs situés entre ces deux pixels noirs en
1
Voir Annexes

11 10 décembre 2014
apOCRyphe Shut up and calculate!

noir et nous passons notre booléen countWhitePixel à false. À chaque


nouvelle itération des abscisses, nous mettons la valeur du nombre de
pixels blancs à 0 et nous passons le booléen countWhitePixel à false.

Une fois l’image traitée dans son intégralité, nous créons un tableau
binaire de taille égale à la dimension de la feuille et qui contient un 0 si
le pixel est blanc et un 1 s’il est noir et nous renvoyons ce tableau.

Découpe horizontale
La découpe horizontale est, dans sa majorité, similaire à la découpe
verticale. En effet, le seul changement est le parcours de l’image qui
s’effectue de gauche à droite et de haut en bas.

Une fois l’image traitée dans son intégralité, nous renvoyons égale-
ment un tableau binaire.

Découpe finale
La découpe finale est, comme son nom le laisse supposer, l’étape
finale de la découpe des blocs. En effet, elle se sert des deux tableaux
obtenus avec la découpe verticale et horizontale pour effectuer une opéra-
tion logique AND dessus. Une fois fait, chaque ligne de notre texte est
représentée par une ligne noire. Cependant, nous devons obtenir un
gros bloc pour chaque paragraphe pour pouvoir appliquer la détection
des lignes dessus. Par conséquent, nous réalisons de nouveau une dé-
coupe verticale puis une découpe horizontale sur la même image et nous
obtenons ainsi des blocs représentants les différents paragraphes et par-
ties du texte.

Calcul des coordonnées


Maintenant que nous avons nos blocs, nous devons récupérer leurs
coordonnées. Par conséquent, nous avons créé un tableau à deux dimen-
sions qui contient un nombre de ligne variable car nous ne savons pas
encore combien de blocs nous avons dans le texte et quatre colonnes qui
contiennent les variables (x_min, y_min, x_max et y_max ) qui per-
mettent d’encadrer les blocs. Nous avons également créé un tableau de
booléen à deux dimensions de la taille de la feuille initialisé à faux et qui
permettra de savoir si le pixel sur lequel nous nous trouvons a déjà été

12 10 décembre 2014
apOCRyphe Shut up and calculate!

visité ou non.

Pour obtenir les coordonnées, nous parcourons le texte et lorsque nous


rencontrons un pixel noir, nous regardons s’il n’a pas déjà été visité et
si c’est le cas, nous prenons ses coordonnées et nous appelons une fonc-
tion itérative de propagation, ayant comme paramètres les coordonnées
actuels du pixel noir non visité et le tableau de booléens, utilisant des
piles afin d’empiler et de traiter tous les pixels voisins noirs non visités
pour, au final, renvoyer les coordonnées minimums et maximums du bloc.
Lorsque nous rencontrons un nouveau pixel noir dans la fonction princi-
pale, nous incrémentons le nombre de blocs et recommençons.

Différences avec la première soutenance


À la différence de la première soutenance, nous utilisons un seuil
qui s’adapte à chaque abscisse (respectivement ordonnée) en calculant
l’espace moyen de la longueur (respectivement hauteur) actuelle entre
deux lettres pour la découpe horizontale (respectivement verticale). Nous
avons choisi d’utiliser un seuil variable car lorsque nous avions un seuil
fixe, ce seuil aboutissait à un rendu utilisable seulement soit sur les images
très petites, soit sur les images très grandes mais pas les deux en même
temps. Avec cette méthode, le seuil s’adapte en fonction des caractères ce
qui améliore grandement le rendu final de la détection des bloc et permet
ainsi de travailler sur les images contenant plusieurs colonnes de texte2
ce qui n’était pas le cas lors de la première soutenance.

De plus, concernant la découpe finale, nous effectuons, à présent, la


dernière découpe verticale et horizontale en appelant les fonctions déjà
créées au lieu de les faire dans la fonction de la découpe finale. Nous
avons choisi d’opter pour cette méthode car, pour la première soutenance,
notre segmentation avait des segmentation fault pour les images d’une
taille trop grande à cause de cela.

Au sujet du calcul des coordonnées, lors de la première soutenance,


nous utilisions une fonction récursive afin de réaliser le calcul des coor-
données. Cependant, lorsque les blocs étaient trop gros, un stack overflow
se produisait du fait du trop grand nombre d’appels récursifs sur le même
bloc. Par conséquent, nous avons opté pour une fonction itérative util-
isant des piles.
2
Voir Annexes

13 10 décembre 2014
apOCRyphe Shut up and calculate!

2. Détection des lignes

La détection des lignes utilise un principe très simple : tant que nous
sommes sur la même ligne, nous prenons les coordonnées de chaque pixel
noir pour, au final, obtenir l’encadrement de la ligne. Pour savoir si nous
nous trouvons toujours sur la même ligne ou si nous sommes sur une
nouvelle ligne, à chaque itération des abscisses, nous incrémentons un
compteur de pixel noir si le pixel sur lequel nous nous trouvons est noir.

Si à la fin de la boucle des abscisses nous voyons que le nombre de


pixels noirs est nul, alors nous ne sommes plus sur la même ligne et devons
donc mettre à true un booléen qui détermine si une ligne est blanche ou
non.

Si le booléen est à true et que nous avons un pixel noir, alors nous
sommes sur une nouvelle ligne. Par conséquent, nous incrémentons le
nombre de ligne et répétons le même principe que précédemment pour
encadrer cette ligne.

Une fois l’image parcourue dans son intégralité, nous renvoyons notre
tableau à deux dimensions pour l’utiliser dans la détection des caractères.

Différences avec la première soutenance


La seule modification effectuée depuis la première soutenance est le
fait de détecter les lignes dans chaque bloc et non dans l’image entière
directement. Nous avons effectué ce changement car notre détection des
blocs est à présent fonctionnelle.

14 10 décembre 2014
apOCRyphe Shut up and calculate!

3. Détection des caractères

La détection des caractères nécessite un peu plus de rigueur que la


détection des lignes. En effet, nous parcourons chaque ligne de haut en
bas et de gauche à droite grâce au tableau renvoyé par la détection des
lignes. Nous avons deux booléens borderLeft et borderRight. Lorsque
l’un d’entre eux est à true cela signifie que nous devons tracer le bord du
caractère.

Le principe se sépare en deux parties : le bord gauche du caractère,


son bord gauche. Si la colonne à droite du pixel blanc sur lequel nous
nous trouvons possède des pixels noirs et que borderLeft est à true, cela
signifie qu’il faut tracer un trait vertical car nous sommes à l’extrémité
gauche du caractère. De la même manière, si la colonne à gauche du
pixel blanc sur lequel nous nous trouvons possède des pixels noirs et que
borderRight est à true, cela signifie qu’il faut tracer un trait vertical car
nous sommes à l’extrémité droite du caractère.
De plus, afin de représenter les espaces, nous remplissons une matrice
de 0 afin de les symboliser. Pour trouver un espace, nous prenons le
même principe que pour calculer le seuil pour les découpes verticales et
horizontales. En effet, à chaque ligne, nous parcourons la longueur et
nous calculons l’espace moyen entre les lettres. Si, lorsque nous créons
le bord gauche d’un caractère, l’espace entre ce bord gauche et le bord
droit précédent est plus grand que la moyenne, alors c’est un espace.
Par conséquent, nous remplissons une matrice avec des 0 de la taille de
l’espace.

En ce qui concerne l’ajout des caractères dans un tableau de matrice


pour ensuite pouvoir les envoyer au réseau de neurone, le principe est
le suivant : nous avons un entier l_ref qui représente le bord gauche
des caractères. Au début de la double boucle, nous initalisons l_ref
à x. Lorsque nous traçons un trait pour le bord droit d’un caractère,
cela signifie que le caractère est encadré par l_ref et le x actuel. Par
conséquent, nous allons, à l’aide d’une double boucle, mettre le caractère
dans un tableau de matrice binaire (si le pixel est noir, nous mettons un
1 sinon un 0).

15 10 décembre 2014
apOCRyphe Shut up and calculate!

Différences avec la première soutenance


Comparé à la première soutenance, notre détection a beaucoup évolué.
Nous avons ajouté la détection des espaces et l’extraction des caractères
est fonctionnelle !

En revanche, un problème qui persiste depuis la première soutenance


est le problème des caractères collés. En effet, ceux-ci empêchent la dé-
tection des caractères de fonctionner correctement car un mot est extrait
au lieu d’un caractère lorsque les caractères sont collés et, ainsi, la recon-
naissance des caractères ne peut reconnaitre ce groupe de caractère. Par
conséquent, nous avons décidé de mettre un point d’interrogation lorsque
c’est le cas.

16 10 décembre 2014
apOCRyphe Shut up and calculate!

Ci-dessous se trouve le résultat des différentes étapes de la segmenta-


tion appliquée à un texte complexe.

Image initiale Découpe horizontale Découpe verticale

Découpe finale Détection des lignes Détection des caractères

Comme nous pouvons le voir, il arrive que plusieurs caractères soient


regroupés. Ceci se produit car les lettres sont trop collées. Cependant,
lorsque les caractères sont écartés, il n’y a aucun problème3 .

3
Voir Annexes

17 10 décembre 2014
apOCRyphe Shut up and calculate!

Redimensionnement des
caractères
Afin de pouvoir utiliser le réseau de neurone et d’apprendre plus pré-
cisément et plus rapidement les différents caractères qui existent, nous
avons décidé de redimensionner tous les caractères que le réseau trait-
era en 16x16 pixels. Pour cela, nous avons implémenté une fonction de
redimensionnement matriciel basée sur le principe de l’interpolation bil-
inéaire. Cette méhode fontionne un peu comme une règle de trois, mais
sur deux dimensions. Cette méthode très simple nous permet de ren-
dre l’implémentation de la reconnaissance des caractères beaucoup plus
facile.

Le concept est de créer une nouvelle matrice de 16x16 afin de fa-


ciliter la navigation dans la matrice. Pour chaque pixel aux coordonnées
(x,y) dans la matrice de départ, nous calculons les nouvelles coordonnées
xmin, xmax, ymin, ymax du pixel dans la matrice résultat en fonction
du rapport entre la taille de la matrice d’arrivée et celle de départ. Si
cette dernière est de dimension inférieure à 16x16, les pixels de la ma-
trices de départs occuperont plusieurs pixels dans la matrice d’arrivée.
Inversement, si elle est supérieure à 16x16, certains pixels n’auront pas
leur place dans la matrice d’arrivée.

Il s’est avéré que cette méthode déréglait totalement l’apprentissage


du réseau neuronal introduit ci-après. En effet, une fois le redimension-
nement fini, certaines lignes ou colonnes de la matrice étaient totalement
blanches (que des 0). En réalité, elle ne servait à rien. Nous avons donc
décidé d’implémenter deux nouvelles fonctions : une qui supprime les
lignes blanches de la matrice, et l’autre, les colonnes blanches. Nous
appliquons ensuite ces deux fonctions à la matrice avant de la redimen-
sionner. Nous obtenons donc un caractère condensé prèt à être traité par
le réseau neuronal.

18 10 décembre 2014
apOCRyphe Shut up and calculate!

Réseau de neurones

1. Principe

L’apprentissage autonome en informatique est un procédé très délicat


nécessitant ce que nous appelons des réseaux artificiels de neurones. Le
principe de ce genre d’architecture est de permettre d’apprendre un mé-
canisme à un programme sans avoir à repérer tous les cas possibles. Dans
notre cas, l’O.C.R. doit pouvoir reconnaître n’importe quel caractère. La
solution facile, très longue mais pas du tout optimisée serait d’imbriquer
une succession de conditions basiques pour détecter à quel caractère celui
que nous cherchons ressemble le plus. Cette méthode est, bien évidem-
ment, impensable. Nous allons donc "intuiter" au programme, via un
réseau de neurones, à quoi ressemble chaque caractère. Pour cela, deux
phases sont nécessaires : la phase d’apprentissage et la phase d’exécution,
sur lesquelles nous reviendrons plus tard. Pour le moment, nous allons
nous concentrer sur la représentation d’un réseau neuronal.

Comme son nom l’indique, le concept du réseau de neurone est in-


spiré du mécanisme des neurones biologiques. Il va, dans un premier
temps, recevoir une série d’entrées à traiter, va ensuite les faire circuler
dans le réseau pour le traitement pour ensuite donner une valeur de sor-
tie. Ce procédé est appelé la propagation. Il est important de noter
que chaque étape de propagation se fait entre deux couches de neurone,
chacune composée d’un certain nombre de neurones. La couche d’entrée
est généralement représentée par un nombre de neurone égal au nombre
d’entrée, contrairement à la couche de sortie, qui dépend simplement du
nombre de sortie que l’utilisateur souhaite. Le cas du réseau neuronal le
plus simple est donc celui-ci : une couche de neurones en entrée, et une
en sortie. Ce simple réseau est désigné par le terme de réseau neuronal
monocouche ou perceptron.

Nous allons maintenant voir le fonctionnement du perceptron. Com-


mençons par la phase d’apprentissage. Au préalable, les entrées sont
stockées dans chaque neurone d’entrée. Nous associons alors un vecteur
de poids, défini aléatoirement (généralement un réel compris entre 0 et
1), à chaque neurone. Ces poids représentent l’importance de chaque
valeur d’entrée pour l’exécution du réseau. Cela étant fait, le réseau ef-

19 10 décembre 2014
apOCRyphe Shut up and calculate!

fectue, pour chaque neurone de la couche suivante, la somme pondérée


des entrées avec les poids sur chaque neurone. Cette somme est ensuite
passée dans une fonction dite d’activation. Certaines choses sont à pré-
ciser : premièrement, cette fonction devrait être, en théorie, différente
sur chaque neurone. En pratique, ce n’est pas vraiment nécessaire. De
plus, cette fonction, aussi appelée fonction de transfert, doit pouvoir ren-
voyer un réel proche de 1 quand les entrées sont "bonnes" et proche de
0 quand elles sont "mauvaises". Quand la valeur donnée par la fonction
d’activation est plus proche de 1 que de 0, nous disons que le neurone est
actif, et est donc inactif dans le cas contraire. En réalité, nous pourrions
très bien utiliser des fonctions linéaires comme fonction d’activation, mais
le réseau neuronal serait alors réduit à une simple régression linéaire. Il
est donc beaucoup plus intéressant et précis de prendre des fonctions
non-linéaires. Ceci nous amène sur les deux fonctions de transfert les
plus utilisées : la fonction
( de Heaviside et la fonction sigmoïde. La pre-
x ≥ 0 ⇒ f (x) = 1
mière est définie par : . La deuxième, que nous
x < 0 ⇒ f (x) = 0
utilisons pour notre réseau, est définie par : f (x) = 1+e1−x . Il faut pré-
ciser que la fonction sigmoïde est beaucoup plus pratique car elle peut
renvoyer des réels compris entre 0 et 1 ce qui n’est pas le cas de celle
de Heaviside, mais elle est également infiniment dérivable, ce qui va être
utile pour la suite.

Voici un exemple de perceptron monocouche comportant n entrées et un


neurone sur sa couche de sortie :

20 10 décembre 2014
apOCRyphe Shut up and calculate!

Le résultat de la fonction d’activation correspond donc à la sortie


de chaque neurone. C’est là que la phase d’apprentissage intervient. Il
faut maintenant, pour chaque neurone, effectuer une sorte de compara-
ison entre la sortie obtenue et celle désirée afin de rééquilibrer les poids
des entrées et ainsi se rapprocher, à la prochaine itération, du résultat
escompté. Plusieurs méthodes peuvent être utilisées, mais nous allons
simplement détailler celle que nous utilisons : l’apprentissage par de-
scente de gradient. Nous reviendrons sur son principe plus tard. Bien
évidemment, en fonction de la complexité du problème à résoudre, il fau-
dra plus ou moins d’itérations d’apprentissage pour que l’ensemble des
poids corresponde au résultat escompté. Cependant, certains problèmes
ne sont pas résolvables avec un perceptron simple (notamment une porte
XOR) car ces problèmes ne sont pas linéairement séparables. Il faut donc
utiliser un perceptron multicouche.

21 10 décembre 2014
apOCRyphe Shut up and calculate!

2. Perceptron multicouche

Ce type de réseau neuronal va donc permettre d’apprendre à notre


programme des problèmes non-linéairement séparables. Cela est en effet
possible grâce à une ou plusieurs couches de neurones cachées entre la
couche d’entrée et la sortie du réseau. Au lieu d’essayer de résoudre le
problème en une seule fois, le réseau le subdivise en plusieurs problèmes
plus simples à résoudre (souvent linéaires) et va ensuite, en fonction des
sorties de ces neurones cachés, calculer la sortie finale. Il est possible
d’avoir plusieurs couches cachées mais la plupart des problèmes non-
linéairement séparables ne sont résolvables qu’avec une seule.

Voici un exemple de perceptron multicouche avec 5 neurones sur sa


couche d’entrée, 3 sur sa couche cachée et 1 sur sa couche de sortie :

Concernant l’apprentissage sur ce type de réseau, la méthode que nous


avons utilisé est très similaire à celle du perceptron. La seule différence
est que la couche de sortie n’est pas la seule à effectuer l’apprentissage
par descente de gradient. L’ensemble des neurones des différentes couches
cachées l’effectue également.

22 10 décembre 2014
apOCRyphe Shut up and calculate!

3. Réalisation du réseau reconnaissant un XOR

La porte XOR étant un problème non-linéairement séparable, nous


avons dû utiliser un perceptron multicouche pour la reconnaissance de
cette porte. Le principe est le suivant : le réseau est composé de deux
neurones d’entrées, deux neurones cachés et un neurone de sortie. Les
deux neurones cachés sont en réalité les deux portes AND et OR. Le
réseau calcule le AND des deux entrées, puis le OR. Le neurone de sortie
prend ensuite les deux sorties du neurone AND et OR, puis calcule, en
fonction de ces résultats, le XOR des deux entrées initiales.

Voici une représentation simplifiée d’un réseau neuronal reconnaissant le


XOR de deux entrées i1 et i2 :

Après quelques modifications, nous avons également réussi à faire en


sorte que le réseau puisse résoudre un XOR en cascade. En effet, il
suffit de calculer, via le réseau neuronal, le XOR entre les deux premières
entrées, puis nous réinjectons le résultat en entrée du réseau avec l’entrée
initiale suivante.

23 10 décembre 2014
apOCRyphe Shut up and calculate!

4. Les caractères

Pour la deuxième et dernière soutenance, notre réseau neuronal devait


être en mesure d’apprendre l’ensemble des caractères de la table ASCII,
et donc de les détecter. Pour ce faire, nous avons commencé à chercher
plusieurs méthodes. Le principe de celle que nous avons préalablement
choisi est le suivant : nous calculons sur chaque ligne et colonne de la
matrice le taux de pixels noirs, représentés par des 1. Pour une matrice
de 16x16 pixels, nous obtenons donc 32 valeurs, 16 pour les lignes et
16 pour les colonnes. Bien évidemment, chacune de ces 32 valeurs sera
associée à un poids. Le réseau est composé de deux neurones cachés : un
pour les lignes et un autre pour les colonnes. La somme pondérée des 16
valeurs des lignes est passée dans la fonction d’activation sigmoïde, puis
est rangée dans le premier neurone. Nous faisions ensuite la même chose
pour les 16 valeurs des colonnes dans le deuxième neurone. Le neurone
de sortie prenait en entrée les deux valeurs des deux neurones cachés
comme expliqué dans la partie précédente. Concernant l’apprentissage,
les valeurs de sorties désirées sur ces trois neurones étaient les valeurs
ASCII des caractères.

En réalité, cette méthode ne fonctionne absolument pas et ce prin-


cipalement car ce n’est pas réellement un réseau neuronal. En effet, la
méthode décrite ci-dessus que nous avons tenté d’utiliser n’est qu’une
sorte de détection par reconnaissance de schéma. Ceci aurait pu marché
mais nous devions avoir un vrai réseau neuronal. Nous avons donc cher-
ché une autre solution.

Après quelques recherches, nous avons décidé de procéder différement.


Nous avons totalement réécrit le code du réseau neuronal. Celui-ci est
donc maintenant défini ainsi : une couche d’entrée, une couche cachée
et une couche de sortie composée chacune de 256 neurones, soit un neu-
rone pour chaque pixel de la matrice du caractère. Chaque neurone de la
couche d’entrée et de la couche cachée est composé de 256 poids, soit un
pour chaque neurone de la couche suivante. Nous avons cependant gardé
la fonction d’activation sigmoïde pour la propagation dans le réseau.
Nous pouvons noter que la couche de sortie contient deux fois plus de
neurones que nécessaire car la table ASCII standard est composée seule-
ment de 127 caractères. Cepandant, il existe une table d’extension allant
jusqu’à 256 caractères. Ainsi, il serait possible, avec notre réseau, de

24 10 décembre 2014
apOCRyphe Shut up and calculate!

reconnaître les caractères, dits spéciaux, à la condition de les lui appren-


dre. De plus, nous pourrions penser qu’un neurone en sortie récupérant
le code ASCII du caractère aurait été suffisant, mais nous avons procédé
autrement. Chaque neurone de sortie, après la propagation, est défini par
une valeur. Le réseau parcours alors le vecteur de neurones en recher-
chant le neurone comportant la valeur la plus haute. Il retourne alors
l’indice de ce neurone correspondant avec la valeur ASCII du caractère
détecté.

Concernant l’apprentissage, nous avons créé nous-même des images


sur lesquelles sont inscrits tous les caractères de la table ASCII. Toutes les
images sont bien évidemment créées avec des polices d’écriture différentes
afin de diversifier l’apprentissage. Les images sont ensuite passées au
réseau de neurones afin qu’il apprenne les différents caractères. Pour cela,
nous lui donnons, pour chaque caractère qu’il doit apprendre, la solution.
Ainsi, le réseau régule ses poids grâce à la méthode de rétropropagation
par descente de gradient.

Cette technique consiste à réguler l’ensemble des poids en fonction


d’un calcul entre la sortie de chaque neurone de la couche de sortie et de
la solution attendue. Ainsi, pour les poids reliant la couche cachée et celle
de sortie wji , nous obtenons le calcul suivant : nous calculons le degrès
d’erreur di pour chaque neurone ni de la couche de sortie par rapport à la
sortie si et la valeur désirée ri tel que di = si ×(1−si )×(ri −si ). Ensuite,
pour chaque neurones nj de la couche cachée, nous régulons les poids wji
tel que wji = wij + ε × di × xj , avec ε un coefficient d’apprentissage défini
en fonction du réseau et du problème à résoudre et xj les valeurs des
neurones de la couche de sortie. Nous effectuons un calcul similaire, mais
pas exactement égal sur les poids reliant la couche d’entrée à celle cachée :
nous calculons le degrès d’erreur di pour chaque neurone ni de la couche
cachée par rapport aux degrès d’erreurs de la couche de sortie. Nous
obtenons ainsi la formule suivante : di = si × (1 − si ) × ( nk=1 dk × wki ).
P

Nous rétablissons ensuite les poids entre la couche d’entrée et la couche


cachée avec la même formule que précédemment.

25 10 décembre 2014
apOCRyphe Shut up and calculate!

5. Les problèmes recontrés

Il s’avère que l’architecture d’un réseau neuronal dépend totalement


du problème à résoudre. Le seul problème est qu’il n’existe pas de règle
ou de théorème pour régler le réseau. Tous les paramètres se règlent
de façon empirique. Voici quelques points pouvant poser problème sur
l’apprentissage auxquels nous nous sommes heurtés. Premièrement, pour
l’apprentissage, il faut répéter l’algorithme de rétropropagation par de-
scente de gradient. Le problème est la condition pour arrêter cette répéti-
tion. Si nous ne répètons pas assez la rétropropagation, l’apprentissage
n’est pas assez précis alors qu’au contraire, si nous la répètons trop
souvent, nous pouvons être confronté à ce que nous appelons le sur-
apprentissage. Ceci se caractérise par le fait que le réseau ne pourra pas
reconnaitre les images autres que celle sur lesquelles il aura effectué son
apprentissage. Pour régler ce problème, nous avons décidé de réitérer
l’apprentissage sur mille itérations ou tant que l’erreur quadratique de
réseau est trop grande, l’erreur quadratique E étant calculée de cette
manière : E = ni=1 (ri − si )2 ,
P

ri étant la sortie désirée du ième neurone et si la sortie actuelle.

Un autre problème qui nous a ralenti est le choix de la taille des


couches de neurones, et donc de la taille des matrices représentant les car-
actères. En effet, si les couches ne sont pas assez grandes, l’apprentissage
ne va pas arriver à bout car il va osciller à cause du manque de données
en entrée et donc de précision. Au contraire, s’il y a trop de données en
entrées, autrement dit si les matrices sont trop grandes, l’apprentissage
prendra beaucoup trop de temps. Comme il a été mentionné précédem-
ment, nous avons opté pour des matrices de 16x16 pixels et donc des
couches de 256 neurones.

26 10 décembre 2014
apOCRyphe Shut up and calculate!

Interface graphique
Lors de la précédente soutenance, nous avons voulu faire une ébauche
d’interface graphique, répondant aux besoin de notre O.C.R.. Concer-
nant l’interface graphique, nous avons essayé d’en créer une qui ne soit
pas en reste. L’interface graphique, étant une façon rapide et intuitive
d’utiliser le logiciel, doit être assez optimisée : une apparence simple,
peu de boutons, pas de couleurs exotiques. L’interface se divise en deux
zones :

• La première étant une zone de texte afin que l’utilisateur puisse


modifier le texte du document numérisé.

• La seconde affiche une visualisation du document d’origine afin que


l’utilisateur puisse comparer la similarité des deux textes.

Pour créer cette interface graphique, nous avons utilisé la bibliothèque


GTK+. En effet, GTK+ permet (et c’est son but principal) de créer une
interface graphique pour faire intéragir l’utilisateur. Nous pouvons donc
ajouter/enlever à volonté différents éléments dans notre fenêtre. Grâce à
de nombreux tutoriels sur internet, nous avions une ébauche d’interface
graphique plutôt satisfaisante. Aux vus des résultats de l’ébauche, le
principal challenge de cette soutenance devait être de relier l’interface
aux fonctions de traitement.

La création de l’interface se déroule en 5 étapes :

• La définition des variables

• L’initialisation de GTK+

• La définition des objets appartenant à l’interface

• La définition de leur effets (par exemple cliquer sur un bouton)

• L’affichage de l’interface

27 10 décembre 2014
apOCRyphe Shut up and calculate!

1. Aspect graphique

La définition d’un objet commence comme la définition d’une variable.


Nous créons un widge basique, nous le nommons et si nous ne faisons rien
de plus, GTK+ l’initialise à NULL. Ensuite, il faut préciser de quel type
de widget il s’agira. Une fois défini, il reste juste à définir où il se situe
et puis l’afficher.

En premier lieu, nous avons créé la fenêtre pour pouvoir y mettre tous
les autres widgets. Vu que nous allons afficher une image et un texte, il
nous faut de la place. Nous avons alors définit sa taille de façon à ce que
l’interface occupe tout l’écran. Enuite il nous faut des boutons. Nous
avons donc créé un premier bouton, nouvs l’avons définit et nous l’avons
placé dans la fenêtre. Lorsque nous compilons, nous voyons une fenêtre
prenant tout l’écran et un bouton au milieu en haut s’étendant sur toute
la largeur. Pour une économie de lignes de code, au lieu d’afficher chaque
widget un par un, nous affichons tous les widgets contenu dans la fenêtre
grâce à la fonction show_all.

Ensuite, nous avons créé un second bouton, nous l’avons définit, nous
l’avons placé, lui aussi, dans la fenêtre. Nous avons compilé et le premier
problème est survenu. Nous avons eu une erreur comme quoi la fenêtre
n’accepte pas plus d’un objet à l’intérieur.

Premier problème
Nous avions une fenêtre qui n’acceptait de contenir qu’un seul widget,
or nous avions au moins 4 widgets à utiliser.
Pour cela, il existe un widget appelé vbox, servant à contenir d’autres
widgets. Ainsi, la fenêtre contient la boîte et la boîte contiendra les
autres widgets nécessaires à l’interface graphique.

Une fois ce problème règlé, nous avons compilé et nous avons obtenu
une fenêtre blanche avec en haut 2 boutons superposés horizontalement,
ce qui, pour être honnête, était assez moche et réduisait aussi la taille
de l’interface. Avec 2 boutons, c’était encore passable mais avec plus de
boutons ce ne serait plus possible d’avoir de la place pour l’affichage de
l’image et du texte.

28 10 décembre 2014
apOCRyphe Shut up and calculate!

Deuxième problème
Afin d’afficher les boutons de manière plus optimisée, il existe un
widget, appelé button_box, qui permet d’afficher les boutons sur la même
ligne. Ainsi, la boîte contient la boîte à boutons qui contient à son tour
tous les boutons actuels et futurs.

Lorsque nous compilons, nous voyons désormais les 2 boutons se


partageant le haut de la fenêtre.
Une fois fait, il nous faut la zone pour l’image et la zone de texte. Pour
cela, nous allons utiliser les widgets gtk_text_view et gtk_image. En
modifiant les paramètres, nous pouvons attribuer l’ensemble de la fenêtre
(excepté les boutons) à ces 2 widgets.

Ainsi, lorsque nous compilons, nous voyons, de haut en bas, le cadre


de la fenêtre, les boutons, la zone de texte et la zone où l’image s’affichera.

Troisième problème
Les 2 zones étant l’une en dessous de l’autre, cela donne un rendu
peu pratique et assez inhabituel.

En fait, comme nous avions pu le voir avec les boutons, la vbox


superpose les widgets les uns en dessous des autres, les premiers widgets
déclarés étant les plus hauts. Heureusement, le semblable de vbox, hbox,
existe. En effet, celui-ci dispose les widgets verticalement : le bord gauche
contient désormais les boutons, en inversant la déclaration des zones,
nous avons la zone de l’image puis la zone de texte.

Cette finalité conclut ainsi l’aspect graphique.

29 10 décembre 2014
apOCRyphe Shut up and calculate!

2. Aspect fonctionnel

Maintenant que l’aspect graphique est fini, il nous faut rendre les
boutons fonctionnels et les zones capables d’afficher l’image et le texte.

Tout d’abord, il nous faut capter les actions de l’utilisateur. S’il clique
sur un bouton, logiquement une action est normalement censée se pro-
duire. Pour ce faire, nous avons utilisé la fonction g_signal_connect,
qui prend en paramètre l’objet ciblé, l’action, la conséquence et l’objet
auquel celle-ci s’applique.
Par exemple, pour fermer la fenêtre, nous avons : g_signal_connect
(G_OBJECT (p_window), "destroy", G_CALLBACK (gtk_main_quit),
NULL);

L’utilisateur intéragit avec la fenêtre p_window, l’action effectuée


par la croix rouge est de détruire l’interface destroy, la fonction appelée
gtk_main_quit est relativement explicite. Quant au dernier paramètre,
n’en ayant pas besoin dans ce cas précis, nous l’avons mis à NULL.
Vu que G_CALLBACK appelle des fonctions que nous allons devoir
définir, un avons créé un fichier contenant toutes nos fonctions appelées.

Ensuite, dans le bouton quitter, nous allons mettre : g_signal_connect


(G_OBJECT (p_button), "clicked", G_CALLBACK (cb_quit), NULL);,
cb_quit étant une fonction définie dans le fichier callback revenant à faire
gtk_main_quit.

À présent, nous allons écrire du texte dans la text_view. Comme


son nom l’indique, elle ne sert qu’à voir le texte ce qui veut dire que,
même si nous écrivons dedans, nous ne pourrons rien faire du texte.
Pour cela, il nous faut un text_buffer capable de récupérer le texte de
la text_view. Il nous faut donc modifier quelques paramètres pour que,
lorsque l’utilisateur modifie le texte, le buffer soit à son tour modifié.

À ce moment là, il ne nous reste que 4 choses à faire : sélectionner


une image, la charger dans le widget correspondant, traiter l’image et
écrire le résultat dans le buffer.

30 10 décembre 2014
apOCRyphe Shut up and calculate!

Quatrième problème
Afin de permettre à l’utilisateur de mettre le chemin de l’image à
charger, il existe un widget qui ouvre une fenêtre de dialogue. L’utilisateur
choisi le fichier à ouvrir et clique sur le bouton ouvrir. À ce moment là,
en théorie, nous stockons le nom du fichier, nous fermons la fenêtre de
dialogue, nous chargeons l’image à sa place et nous chargeons le résultat
du traitement dans la text_view. Mais, malheureusement, ce n’est pas
ce qui s’est produit.

Nous avons commencé par ne pas pouvoir charger l’image. Quoique


nous fassions, les seuls résultats étaient une segmentation fault ou une
assertion failed voire rien du tout. Nous avons essayé de changer les
paramètres des fonctions appelées, rien à faire, aucune image affichée.
Nous avons tenté de détruire le widget image et d’en recréer une autre
dans la boîte ; le seul résultat visible a été la destruction du widget image.
Nous avons donc laissé tomber l’affichage de l’image. Cependant, nous
avons quand même récupéré son chemin.

Par la suite, nous avons voulu charger un texte dans la text_view. En


théorie, en appuyant sur un bouton, le programme devait nous charger
un fichier spécifié au préalable mais, une fois de plus, comme l’image, le
texte refusait de se charger dans la text_view.

À ce moment là, nous n’avions ni l’image, ni le texte qui s’affichait.


Nous avons alors essayé d’ouvrir une boîte de dialogue et de charger un
fichier manuellement. Cela a fonctionné donc nous l’avons, bien évidem-
ment, gardé.

Vu que l’image ne fonctionnait pas, nous avons décidé de supprimer


la zone d’image et d’afficher le texte sur tout l’écran. Du coup, nous
avons changé à nouveau le type de boîte (vbox) car les boutons, sur la
hauteur, ne faisaient pas très esthétiques.. Nous les avons donc mis en
bas sur la largeur.

Au final, grâce au chemin de l’image, nous avons créé un fichier texte,


nous y avons mis le contenu du traitement de l’image et nous l’avons
chargé manuellement. Grâce à un tutoriel, nous avons modifié le code
de façon à avoir un bouton sauvegarder et un sauvegarder sous. Si le
fichier à sauvegarder n’a jamais été sauvegardé auparavant, une boite de
dialogue s’ouvre, demandant où enregistrer le fichier et sous quel nom,
puis sauvegarde à chaque fois dans ce fichier.

31 10 décembre 2014
apOCRyphe Shut up and calculate!

Voici le résultat final de notre interface graphique :

3. Résultat

Au final, nous avons une interface graphique ne pouvant pas charger


d’image, étant ainsi inutile, mais qui, malgré tout, peut afficher le texte
du résultat de la segmentation.

Cependant, pour des raisons de cohésion, nous avons préféré aban-


donner l’interface graphique et ne rester que sur la console pour exécuter
le projet.

32 10 décembre 2014
apOCRyphe Shut up and calculate!

4. Bilan GTK+

Si au premier abord GTK+ semblait bien expliqué par de nombreux


tutoriels, il s’est avéré plus compliqué que prévu de conrétiser l’interface
graphique.

Au final, le bilan de GTK+ est très négatif, beaucoup d’heures passées


pour un résultat qui, même en étant capable d’être relié aux fonctions de
l’O.C.R., n’aurait été que moyen. Cela nous a fait regretter le C# et sa
facilité d’utilisation.

33 10 décembre 2014
apOCRyphe Shut up and calculate!

Site web

Nous avons décidé, sur ce projet, de ne pas trop nous concentrer sur
la création du site web. En effet, ayant que trois mois pour réaliser notre
O.C.R., nous ne voulions pas être retardés par l’aspect communication du
projet. C’est pour cela que nous avons décidé de créer un site web de type
wordpress, un éditeur de site web, nous permettant de le confectionner
facilement. Notre site est en apparence plus un blog qu’autre chose, mais
nous avons jugé que celà était suffisant pour le moment.

Nous avons décidé de créer un site très sobre, de qualité profession-


nelle, et totalement en anglais. Il est possible d’y trouver l’avancement
de notre travail, une présentation du groupe ainsi que de ses mem-
bres, des contenus téléchargeables comme les rapports ou l’exécutable et
les contacts nécessaires. L’adresse du site est http://epitapocryphe.
wordpress.com/.

34 10 décembre 2014
apOCRyphe Shut up and calculate!

Conclusion
1. Personnelle

Thomas
Ce projet m’a beaucoup apporté, autant en expérience de program-
mation avec la découverte du C, qu’en organisation de projet avec un
sujet beaucoup plus complexe que le jeu vidéo que nous devions créer
l’année précédente. Je pense m’être beaucoup impliqué dans ce projet
mais je suis moyennement fier du résultat, surtout concernant ma partie
du réseau neuronal. J’ai beaucoup travailler dessus afin de l’optimiser le
plus possible, je l’ai ré-implémenté plusieurs fois, tout ceci pour arriver
à un réseau ne pouvant pas reconnaître tous les caractères. Ceci est en
partie dû au fait que je n’ai pas réussi à finir l’implémentation du réseau
assez rapidement pour avoir le temps de lui faire apprendre les caractères.
Je pense que c’est une grande leçon sur l’organisation de travail : toujours
essayer de finir son travail un peu en avance et non juste pour la deadline.

Corentin
Ce projet m’a aidé a découvrir le C et la manipulation de fichiers, de
façon plus poussée que dans les TP. Bien que les bases de GTK soient
assez simples à comprendre, la manipulationde fichiers était sensiblement
plus difficile et à conduit à de nombreux échecs. Au final, pour ce qui est
de ma partie, je suis assez déçu. L’interface fonctionne à moitié, n’a pas
été utilisée ce qui rend mon travail totalement inutile. Cependant, cela
m’a permis de faire un choix, si à l’avenir, j’ai le choix entre programmer
en C et en C#, je choisirai la seconde option.

35 10 décembre 2014
apOCRyphe Shut up and calculate!

Guillaume
Ce projet fut un grand plaisir personnel et m’a permis de découvrir
des qualités que je pensais ne pas avoir en programmation. En effet,
j’ai su tenir mes engagements en ce qui concerne ma partie de l’O.C.R.
voire plus que ce que j’étais censé faire. À l’opposé de l’an dernier, où
nous devions coder un jeu vidéo, j’ai su me débrouiller seul et réaliser
des tâches assez complexes.

De plus, j’ai pu renforcer mes liens avec Thomas avec qui je travaille
depuis maintenant deux ans rendant le travail en groupe encore plus
plaisant qu’il ne l’est déjà.

D’une manière générale, je retiens de ce projet des deadlines assez


difficiles à tenir mais un grand apprentissage au niveau du langage de
programmation qu’est le C.

2. Globale

Au final, nous avons un O.C.R. plus ou moins optionnel. En effet,


avec un peu plus de temps au niveau de l’apprentissage avec le réseau
de neurone et notre O.C.R. serait vraiment optionnel étant donné que
l’apprentissage fonctionne.
Le seul vrai bémol est l’interface graphique qui, malheureusement, n’a
pas pu être finie à temps.

36 10 décembre 2014
apOCRyphe Shut up and calculate!

Annexes

Image d’orgine Détection des caractères

1 : Type de texte où la détection des lignes ne fonctionne que partielle-


ment si nous ne détectons pas les blocs au préalable.

Image d’origine Détection des blocs

37 10 décembre 2014
apOCRyphe Shut up and calculate!

Détection des lignes fonctionnelle

2 : Type de texte où la détection des lignes est fonctionnelle avec la dé-


tection des blocs

3 : Exemple où la détection des caractères se fait parfaitement.

38 10 décembre 2014
apOCRyphe Shut up and calculate!

Table des matières


1. Sommaire 2

2. Introduction 3

3. Pré-traitement de l’image 5

1. Débruitage . . . . . . . . . . . . . . . . . . . . . . . . . . . 5

1. Passage en niveau de gris . . . . . . . . . . . . . . . . 5

2. Binarisation . . . . . . . . . . . . . . . . . . . . . . . 5

3. Filtre Gaussien . . . . . . . . . . . . . . . . . . . . . . 7

2. Rotation . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

1. Différences avec la première soutenance . . . . . . . . 10

4. Extraction des caractères 11

1. Détection des blocs . . . . . . . . . . . . . . . . . . . . . . 11

1. Principe . . . . . . . . . . . . . . . . . . . . . . . . . 11

2. Découpe verticale . . . . . . . . . . . . . . . . . . . . 11

3. Découpe horizontale . . . . . . . . . . . . . . . . . . . 12

4. Découpe finale . . . . . . . . . . . . . . . . . . . . . . 12

5. Calcul des coordonnées . . . . . . . . . . . . . . . . . 12

6. Différences avec la première soutenance . . . . . . . . 13

2. Détection des lignes . . . . . . . . . . . . . . . . . . . . . . 14

1. Différences avec la première soutenance . . . . . . . . 14

3. Détection des caractères . . . . . . . . . . . . . . . . . . . . 15

1. Différences avec la première soutenance . . . . . . . . 15

5. Redimensionnement des caractères 18

6. Réseau de neurones 19

39 10 décembre 2014
apOCRyphe Shut up and calculate!

1. Principe . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

2. Perceptron multicouche . . . . . . . . . . . . . . . . . . . . 22

3. Réalisation du réseau reconnaissant un XOR . . . . . . . . 23

4. Les caractères . . . . . . . . . . . . . . . . . . . . . . . . . 24

5. Les problèmes recontrés . . . . . . . . . . . . . . . . . . . . 26

7. Interface graphique 27

1. Aspect graphique . . . . . . . . . . . . . . . . . . . . . . . 28

1. Premier problème . . . . . . . . . . . . . . . . . . . . 28

2. Deuxième problème . . . . . . . . . . . . . . . . . . . 29

3. Troisième problème . . . . . . . . . . . . . . . . . . . 29

2. Aspect fonctionnel . . . . . . . . . . . . . . . . . . . . . . . 30

1. Quatrième problème . . . . . . . . . . . . . . . . . . . 31

3. Résultat . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32

4. Bilan GTK+ . . . . . . . . . . . . . . . . . . . . . . . . . . 33

8. Site web 34

9. Conclusion 35

1. Personnelle . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

1. Thomas . . . . . . . . . . . . . . . . . . . . . . . . . . 35

2. Corentin . . . . . . . . . . . . . . . . . . . . . . . . . 35

3. Guillaume . . . . . . . . . . . . . . . . . . . . . . . . 36

2. Globale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36

10. Annexes 37

11. Tables des matières 39

40 10 décembre 2014

Vous aimerez peut-être aussi