Quel est un moyen rapide et fiable de seuiller des images avec un flou possible et une luminosité non uniforme?
Exemple (flou mais luminosité uniforme):
Parce que l'image n'est pas garantie d'avoir une luminosité uniforme, il n'est pas possible d'utiliser un seuil fixe. Un seuil adaptatif fonctionne bien, mais en raison du flou, il crée des ruptures et des distorsions dans les fonctionnalités (ici, les fonctionnalités importantes sont les chiffres de Sudoku):
J'ai également essayé d'utiliser l'égalisation d'histogramme (en utilisant la fonction equalizeHist
d'OpenCV). Il augmente le contraste sans réduire les différences de luminosité.
La meilleure solution que j'ai trouvée est de diviser l'image par sa fermeture morphologique (crédit à ce post ) pour uniformiser la luminosité, puis renormaliser, puis utiliser un seuil fixe (en utilisant l'algorithme d'Otsu pour choisir le niveau de seuil optimal):
Voici le code pour cela dans OpenCV pour Android:
Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, new Size(19,19));
Mat closed = new Mat(); // closed will have type CV_32F
Imgproc.morphologyEx(image, closed, Imgproc.MORPH_CLOSE, kernel);
Core.divide(image, closed, closed, 1, CvType.CV_32F);
Core.normalize(closed, image, 0, 255, Core.NORM_MINMAX, CvType.CV_8U);
Imgproc.threshold(image, image, -1, 255, Imgproc.THRESH_BINARY_INV
+Imgproc.THRESH_OTSU);
Cela fonctionne très bien mais l'opération de fermeture est très lente. La réduction de la taille de l'élément structurant augmente la vitesse mais réduit la précision.
Edit: basé sur la suggestion de DCS, j'ai essayé d'utiliser un filtre passe-haut. J'ai choisi le filtre laplacien, mais je m'attendrais à des résultats similaires avec les filtres Sobel et Scharr. Le filtre capte le bruit haute fréquence dans les zones qui ne contiennent pas de caractéristiques et souffre d'une distorsion similaire au seuil adaptatif en raison du flou. cela prend aussi environ aussi longtemps que l'opération de fermeture. Voici un exemple avec un filtre 15x15:
Edit 2: Sur la base de la réponse d'AruniRC, j'ai utilisé la détection Canny Edge sur l'image avec les paramètres suggérés:
double mean = Core.mean(image).val[0];
Imgproc.Canny(image, image, 0.66*mean, 1.33*mean);
Je ne sais pas comment fiable automatiquement affiner les paramètres pour obtenir les chiffres connectés.
En utilisant les suggestions de Vaughn Cato et Theraot, j'ai réduit l'image avant de la fermer, puis j'ai redimensionné l'image fermée jusqu'à sa taille normale. J'ai également réduit la taille du noyau proportionnellement.
Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, new Size(5,5));
Mat temp = new Mat();
Imgproc.resize(image, temp, new Size(image.cols()/4, image.rows()/4));
Imgproc.morphologyEx(temp, temp, Imgproc.MORPH_CLOSE, kernel);
Imgproc.resize(temp, temp, new Size(image.cols(), image.rows()));
Core.divide(image, temp, temp, 1, CvType.CV_32F); // temp will now have type CV_32F
Core.normalize(temp, image, 0, 255, Core.NORM_MINMAX, CvType.CV_8U);
Imgproc.threshold(image, image, -1, 255,
Imgproc.THRESH_BINARY_INV+Imgproc.THRESH_OTSU);
L'image ci-dessous montre les résultats côte à côte pour 3 méthodes différentes:
Gauche - fermeture de taille normale (432 pixels), noyau de taille 19
Moyen - fermeture demi-taille (216 pixels), noyau de taille 9
Droite - fermeture quart de taille (108 pixels), noyau de taille 5
La qualité de l'image se détériore à mesure que la taille de l'image utilisée pour la fermeture diminue, mais la détérioration n'est pas suffisamment importante pour affecter les algorithmes de reconnaissance des fonctionnalités. La vitesse augmente légèrement plus de 16 fois pour la fermeture quart de taille, même avec le redimensionnement, ce qui suggère que le temps de fermeture est à peu près proportionnel au nombre de pixels dans l'image.
Toutes les suggestions sur la façon d'améliorer encore cette idée (soit en réduisant davantage la vitesse, soit en réduisant la détérioration de la qualité de l'image) sont les bienvenues.
Nous utilisons l'algorithme Bradleys pour un problème très similaire (pour segmenter les lettres de l'arrière-plan, avec une lumière inégale et une couleur d'arrière-plan inégale), décrit ici: http://people.scs.carleton.ca:8008/~roth/iit- publications-iti/docs/gerh-50002.pdf , code C # ici: http://code.google.com/p/aforge/source/browse/trunk/Sources/Imaging/Filters/Adaptive + Binarisation/BradleyLocalThresholding.cs? R = 136 . Il fonctionne sur l'image intégrale, qui peut être calculée en utilisant la fonction integral
d'OpenCV. Il est très fiable et rapide, mais lui-même n'est pas implémenté dans OpenCV, mais il est facile à porter.
Une autre option est la méthode adaptiveThreshold dans openCV, mais nous n'avons pas essayé: http://docs.opencv.org/modules/imgproc/doc/miscundry_transformations.html#adaptivethreshold . La version MEAN est la même que les bradleys, sauf qu'elle utilise une constante pour modifier la valeur moyenne au lieu d'un pourcentage, ce qui je pense est mieux.
En outre, un bon article est ici: https://dsp.stackexchange.com/a/2504
Approche alternative:
En supposant que votre intention est d'avoir les chiffres clairement binarisés ... déplacez votre focus sur les composants plutôt que sur l'image entière.
Voici une approche assez simple:
En considérant chaque Canny Edge comme un composant connecté (c'est-à-dire utiliser le cvFindContours () ou son homologue C++, selon le cas), on peut estimer les niveaux de gris de premier plan et d'arrière-plan et atteindre un seuil.
Pour le dernier morceau, jetez un oeil aux sections 2. et 3. de cet article . En ignorant la plupart des parties théoriques non essentielles, il ne devrait pas être trop difficile de l'implémenter dans OpenCV.
J'espère que cela vous a aidé!
Modifier 1:
Sur la base des seuils Canny Edge, voici une idée très approximative juste suffisante pour affiner les valeurs. Le high_threshold
contrôle la force d'un Edge avant qu'il ne soit détecté. Fondamentalement, un Edge doit avoir une amplitude de gradient supérieure à high_threshold
à détecter en premier lieu. Cela fait donc la détection initiale des bords.
Maintenant le low_threshold
traite de la connexion des bords voisins. Il contrôle la quantité de bords déconnectés à proximité qui seront combinés en un seul bord. Pour une meilleure idée, lisez "Étape 6" de cette page Web . Essayez de définir un très petit seuil bas et voyez comment les choses se passent. Vous pourriez jeter cette chose de 0,66 * [valeur moyenne] si cela ne fonctionne pas sur ces images - ce n'est qu'une règle de base de toute façon.
La forme d'une ellipse est complexe à calculer par rapport à une forme plate. Essayer de changer:
Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, new Size(19,19));
à:
Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(19,19));
peut accélérer votre solution avec un faible impact sur la précision.
Vous pouvez essayer de travailler sur une base par tuile si vous savez que vous avez un bon recadrage de la grille. Travailler sur 9 sous-images plutôt que sur la totalité de l'image entraînera très probablement une luminosité plus uniforme sur chaque sous-image. Si votre recadrage est parfait, vous pouvez même essayer d'aller pour chaque cellule de chiffres individuellement; mais tout dépend de la fiabilité de votre récolte.