Je travaille sur la reconnaissance multi-chiffres manuscrite avec Java
, en utilisant la bibliothèque OpenCV
pour le prétraitement et la segmentation, et un modèle Keras
formé sur MNIST (avec une précision de 0,98) pour reconnaissance.
La reconnaissance semble fonctionner assez bien, à part une chose. Le réseau ne reconnaît pas souvent ceux-ci (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);
}
Après quelques recherches et expériences, je suis arrivé à la conclusion que le prétraitement d'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). Ce qui a toutefois aidé, ce sont 2 choses suivantes:
Comme @ f4f l'a remarqué, j'avais besoin de 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 qu'elles tiennent dans un 20x20
zone de pixels (comme ils sont dans MNIST
). Après cela, je centre la boîte au milieu de 28x28
image utilisant le centre de masse (qui pour les images binaires est la valeur moyenne sur 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.