Je travaille sur la reconnaissance multi-chiffres manuscrite avec Java
, en utilisant la OpenCV
bibliothèque pour le prétraitement et la segmentation, et un Keras
modè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:
devient et est classé comme 4
.
devient et est classé comme 7
.
devient et 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);
}
la source
Réponses:
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)
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
la source
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?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:
fermeture:
J'espère que cela aide.
la source
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:
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.
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.
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.
la source
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:
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é.
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
20x20
qu'elles s'insèrent dans une zone de pixels (telles qu'elles sontMNIST
). Après cela, je centre la boîte au milieu de l'28x28
image 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.
la source