Comment améliorer la reconnaissance numérique d'un modèle formé au MNIST?

12

Je travaille sur la reconnaissance multi-chiffres manuscrite avec Java, en utilisant la OpenCVbibliothèque pour le prétraitement et la segmentation, et un Kerasmodèle formé sur MNIST (avec une précision de 0,98) pour la reconnaissance.

La reconnaissance semble fonctionner assez bien, à part une chose. Le réseau ne reconnaît pas souvent ceux-là (numéro "un"). Je ne peux pas savoir si cela se produit en raison d'un prétraitement / d'une implémentation incorrecte de la segmentation, ou si un réseau formé sur le MNIST standard n'a tout simplement pas vu le numéro un qui ressemble à mes cas de test.

Voici à quoi ressemblent les chiffres problématiques après le prétraitement et la segmentation:

entrez la description de l'image icidevient entrez la description de l'image iciet est classé comme 4.

entrez la description de l'image icidevient entrez la description de l'image iciet est classé comme 7.

entrez la description de l'image icidevient entrez la description de l'image iciet est classé comme 4. Etc...

Est-ce quelque chose qui pourrait être corrigé en améliorant le processus de segmentation? Ou plutôt en améliorant l'ensemble de formation?

Edit: Améliorer l'ensemble de formation (augmentation des données) aiderait certainement, ce que je teste déjà, la question du prétraitement correct reste toujours.

Mon prétraitement consiste à redimensionner, à convertir en niveaux de gris, à binariser, à inverser et à dilater. Voici le code:

Mat resized = new Mat();
Imgproc.resize(image, resized, new Size(), 8, 8, Imgproc.INTER_CUBIC);

Mat grayscale = new Mat();
Imgproc.cvtColor(resized, grayscale, Imgproc.COLOR_BGR2GRAY);

Mat binImg = new Mat(grayscale.size(), CvType.CV_8U);
Imgproc.threshold(grayscale, binImg, 0, 255, Imgproc.THRESH_OTSU);

Mat inverted = new Mat();
Core.bitwise_not(binImg, inverted);

Mat dilated = new Mat(inverted.size(), CvType.CV_8U);
int dilation_size = 5;
Mat kernel = Imgproc.getStructuringElement(Imgproc.CV_SHAPE_CROSS, new Size(dilation_size, dilation_size));
Imgproc.dilate(inverted, dilated, kernel, new Point(-1,-1), 1);

L'image prétraitée est ensuite segmentée en chiffres individuels comme suit:

List<Mat> digits = new ArrayList<>();
List<MatOfPoint> contours = new ArrayList<>();
Imgproc.findContours(preprocessed.clone(), contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);

// code to sort contours
// code to check that contour is a valid char

List rects = new ArrayList<>();

for (MatOfPoint contour : contours) {
     Rect boundingBox = Imgproc.boundingRect(contour);
     Rect rectCrop = new Rect(boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height);

     rects.add(rectCrop);
}

for (int i = 0; i < rects.size(); i++) {
    Rect x = (Rect) rects.get(i);
    Mat digit = new Mat(preprocessed, x);

    int border = 50;
    Mat result = digit.clone();
    Core.copyMakeBorder(result, result, border, border, border, border, Core.BORDER_CONSTANT, new Scalar(0, 0, 0));

    Imgproc.resize(result, result, new Size(28, 28));
    digits.add(result);
}
youngpanda
la source
1
vous vous utilisez le masque ou les pixels (masqués?) d'origine en niveaux de gris comme entrée pour votre classification?
Micka
@Micka J'utilise la version prétraitée (binarisée, inversée, dilatée). Ceux qui correspondent à l'ensemble de formation MNIST. Il y a des exemples de nombre "1" après prétraitement dans mon message.
youngpanda

Réponses:

5

Je crois que votre problème est le processus de dilatation. Je comprends que vous souhaitez normaliser les tailles d'image, mais vous ne devez pas rompre les proportions, vous devez redimensionner au maximum souhaité par un axe (celui qui permet la plus grande redimensionnement sans laisser une autre dimension d'axe dépasser la taille maximale) et remplir avec la couleur de fond le reste de l'image. Ce n'est pas que "le MNIST standard n'a tout simplement pas vu le numéro un qui ressemble à vos cas de test", vous faites ressembler vos images à différents numéros formés (ceux qui sont reconnus)

Chevauchement des images source et traitées

Si vous avez conservé le rapport d'aspect correct de vos images (source et post-traitées), vous pouvez voir que vous n'avez pas seulement redimensionné l'image mais que vous l'avez "déformée". Cela peut être le résultat d'une dilatation non homogène ou d'un redimensionnement incorrect

Monsieur
la source
Je crois que @SiR a un certain poids, essayez de ne pas changer le rapport d'aspect des littéraux numériques.
ZdaR
Désolé, je ne suis pas tout à fait suivre. Pensez-vous que mon processus de dilatation ou de redimensionnement est le problème? Je ne redimensionne l'image qu'au début avec cette ligne Imgproc.resize(image, resized, new Size(), 8, 8, Imgproc.INTER_CUBIC);. Ici, la ration d'aspect reste la même, où puis-je casser les proportions?
youngpanda
@SiR en réponse à vos modifications ci-dessus: oui, je ne redimensionne pas seulement l'image, j'applique différentes opérations, l'une d'entre elles est la dilatation, qui est morphologique, ce qui provoque une légère "distorsion" car elle provoque des régions lumineuses dans un image pour "grandir". Ou voulez-vous dire redimensionner à la fin, où je fais les images 28x28?
youngpanda
@youngpanda, vous pourriez trouver la discussion ici stackoverflow.com/questions/28525436/… intéressante. Cela pourrait vous donner une idée de pourquoi votre approche n'apporte pas de bons résultats
SiR
@SiR merci pour le lien, je connais LeNet, mais c'est agréable à relire
youngpanda
5

Il y a déjà des réponses publiées, mais aucune ne répond à votre question réelle sur le prétraitement des images .

À mon tour, je ne vois pas de problèmes importants avec votre mise en œuvre tant qu'il s'agit d'un projet d'étude, bien fait.

Mais une chose à remarquer que vous pourriez manquer. Il existe des opérations de base en morphologie mathématique: l'érosion et la dilatation (utilisées par vous). Et il y a des opérations complexes: diverses combinaisons de celles de base (par exemple, ouverture et fermeture). Le lien Wikipédia n'est pas la meilleure référence de CV, mais vous pouvez commencer avec lui pour avoir l'idée.

Habituellement, il vaut mieux utiliser l' ouverture au lieu de l'érosion et la fermeture au lieu de la dilatation, car dans ce cas, l'image binaire d'origine change beaucoup moins (mais l'effet souhaité de nettoyage des arêtes vives ou de remplissage des espaces est atteint). Dans votre cas, vous devez donc vérifier la fermeture (dilatation de l'image suivie d'une érosion avec le même noyau). Dans le cas où une image extra-petite 8 * 8 est considérablement modifiée lorsque vous dilatez même avec un noyau 1 * 1 (1 pixel représente plus de 16% de l'image), ce qui est moins sur des images plus grandes).

Pour visualiser l'idée voir les photos suivantes (des tutoriels OpenCV: 1 , 2 ):

dilatation: symbole d'origine et dilaté

fermeture: symbole d'origine et fermé

J'espère que cela aide.

f4f
la source
Merci de votre contribution! En fait ce n'est pas un projet d'étude, alors quel serait le problème alors? .. Mon image est assez grande quand j'applique la dilatation, 8x8 n'est pas la taille de l'image, c'est le facteur de redimensionnement pour la hauteur et la largeur. Mais il peut toujours être une option d'amélioration pour essayer différentes opérations mathématiques. Je ne connaissais pas l'ouverture et la fermeture, je vais l'essayer! Je vous remercie.
youngpanda
Ma faute, j'ai mal lu le redimensionnement de l'appel tel qu'il était avec 8 * 8 comme nouvelle taille. Si vous souhaitez utiliser l'OCR dans le monde réel, vous envisagez une option de transfert d'apprentissage de votre réseau d'origine sur des données typiques de votre domaine d'utilisation. Vérifiez au moins si cela améliore la précision, ce qui devrait généralement être le cas.
f4f
Une autre chose à vérifier est l'ordre de prétraitement: niveaux de gris-> binaire-> inverse-> redimensionner. Le redimensionnement est une opération coûteuse et je ne vois pas la nécessité de l'appliquer à l'image couleur. Et la segmentation des symboles peut être effectuée sans détection de contour (avec quelque chose de moins coûteux) si vous avez un format d'entrée spécifique, mais il peut être difficile à mettre en œuvre.
f4f
Si j'avais un autre ensemble de données en dehors de MNIST, je pourrais essayer de transférer l'apprentissage :) J'essaierai de changer l'ordre de prétraitement et je vous répondrai. Je vous remercie! Je n'ai pas encore trouvé d'option plus simple que la détection de contour pour mon problème ...
youngpanda
1
D'accord. Vous pouvez collecter vous-même l'ensemble de données à partir des images sur lesquelles vous utiliserez l'OCR. C'est une pratique courante.
f4f
4

Vous avez donc besoin d'une approche complexe à chaque étape de votre cascade informatique basée sur les résultats précédents. Dans votre algorithme, vous disposez des fonctionnalités suivantes:

  1. Prétraitement d'image

Comme mentionné précédemment, si vous appliquez le redimensionnement, vous perdez des informations sur les proportions de l'image. Vous devez effectuer le même retraitement des images numériques pour obtenir les mêmes résultats que ceux impliqués dans le processus de formation.

Meilleur moyen si vous recadrez simplement l'image par des images de taille fixe. Dans cette variante, vous n'aurez pas besoin de trouver et de redimensionner l'image numérique dans les contours avant le processus de formation. Ensuite, vous pouvez faire un petit changement dans votre algorithme de recadrage pour une meilleure reconnaissance: trouvez simplement le contour et placez votre chiffre sans redimensionner au centre du cadre d'image pertinent pour la reconnaissance.

Vous devriez également prêter plus d'attention à l'algorithme de binarisation. J'ai de l'expérience dans l'étude de l'effet des valeurs de seuil de binarisation sur l'erreur d'apprentissage: je peux dire que c'est un facteur très important. Vous pouvez essayer d'autres algorithmes de binarisation pour vérifier cette idée. Par exemple, vous pouvez utiliser cette bibliothèque pour tester d'autres algorithmes de binarisation.

  1. Algorithme d'apprentissage

Pour améliorer la qualité de la reconnaissance, vous utilisez la validation croisée lors du processus de formation. Cela vous aide à éviter le problème de sur- ajustement pour vos données d'entraînement. Par exemple, vous pouvez lire cet article où expliqué comment l'utiliser avec Keras.

Parfois, des taux plus élevés de mesure de précision ne disent rien sur la qualité réelle de la reconnaissance, car le RNA formé n'a pas trouvé le modèle dans les données de formation. Il peut être connecté au processus de formation ou au jeu de données d'entrée comme expliqué ci-dessus, ou il peut être provoqué par l'architecture ANN choisie.

  1. Architecture ANN

C'est un gros problème. Comment définir la meilleure architecture ANN pour résoudre la tâche? Il n'y a pas de façon courante de faire cette chose. Mais il existe plusieurs façons de se rapprocher de l'idéal. Par exemple, vous pouvez lire ce livre . Cela vous aide à mieux voir votre problème. Vous pouvez également trouver ici quelques formules heuristiques pour adapter le nombre de couches / éléments cachés pour votre ANN. Ici aussi , vous trouverez un petit aperçu de cela.

J'espère que cela vous aidera.

Egor Zamotaev
la source
1. Si je vous comprends bien, je ne peux pas recadrer à une taille fixe, c'est une image d'un nombre à plusieurs chiffres, et tous les cas sont de taille / emplacement différents, etc. Ou vouliez-vous dire quelque chose de différent? Oui, j'ai essayé différentes méthodes de binarisation et paramètres modifiés, si c'est ce que vous voulez dire. 2. En fait, la reconnaissance sur MNIST est excellente, il n'y a pas de sur-ajustement, la précision que j'ai mentionnée est la précision du test. Ni le réseau ni sa formation ne sont le problème. 3. Merci pour tous les liens, je suis assez satisfait de mon architecture, bien sûr, il y a toujours place à l'amélioration.
youngpanda
Oui, vous comprenez. Mais vous avez toujours la possibilité de rendre votre ensemble de données plus unifié. Dans votre cas, mieux vaut recadrer les images des chiffres par les contours comme vous le faites déjà. Mais après cela, il sera préférable de simplement étendre vos images numériques à une taille unifiée en fonction de la taille maximale d'une image numérique à l'échelle x et y. Vous pourriez préférer le centre de la zone de contour des chiffres pour faire cette chose. Il vous donnera des données d'entrée plus propres pour votre algorithme d'entraînement.
Egor Zamotaev
Voulez-vous dire que je dois sauter la dilatation? Au final, je centre déjà l'image lorsque j'applique la bordure (50 px de chaque côté). Après cela, je redimensionne chaque chiffre en 28x28, car c'est la taille dont nous avons besoin pour MNIST. Voulez-vous dire que je peux redimensionner en 28x28 différemment?
youngpanda
1
Oui, la dilatation n'est pas souhaitable. Vos contours peuvent avoir des rapports différents en hauteur et en largeur, c'est pourquoi vous avez besoin d'améliorer votre algorithme ici. Au moins, vous devez créer des tailles d'images avec les mêmes ratios. Étant donné que vous avez des tailles d'image d'entrée 28x28, vous devez préparer des images avec le même rapport 1: 1 par des échelles x et y. Vous ne devriez pas obtenir une bordure de 50 px pour chaque côté de l'image, mais des bordures X, Y px qui satisfont à la condition: contourSizeX + borderSizeX == contourSizeY + borderSizeY. C'est tout.
Egor Zamotaev
J'ai déjà essayé sans dilatation (oublié de mentionner dans le post). Cela n'a changé aucun résultat ... Mon numéro de frontière était expérimental. Idéalement, j'aurais besoin de mes chiffres pour tenir dans une boîte 20x20 (normalisée en tant que telle dans le jeu de données) et après cela, la déplacer en utilisant le centre de masse ...
youngpanda
1

Après quelques recherches et expériences, je suis arrivé à la conclusion que le prétraitement de l'image lui-même n'était pas le problème (j'ai changé certains paramètres suggérés, comme par exemple la taille et la forme de la dilatation, mais ils n'étaient pas cruciaux pour les résultats). Cependant, ce qui a aidé, ce sont 2 choses suivantes:

  1. Comme @ f4f l'a remarqué, je devais collecter mon propre ensemble de données avec des données du monde réel. Cela a déjà énormément aidé.

  2. J'ai apporté des modifications importantes à mon prétraitement de segmentation. Après avoir obtenu les contours individuels, je normalise d'abord la taille des images pour 20x20qu'elles s'insèrent dans une zone de pixels (telles qu'elles sont MNIST). Après cela, je centre la boîte au milieu de l' 28x28image en utilisant le centre de masse (qui pour les images binaires est la valeur moyenne dans les deux dimensions).

Bien sûr, il existe encore des cas de segmentation difficiles, tels que des chiffres qui se chevauchent ou connectés, mais les modifications ci-dessus ont répondu à ma question initiale et amélioré mes performances de classification.

youngpanda
la source