Préparer une image complexe pour la ROC
je veux reconnaître les chiffres d'une carte de crédit. Pour empirer les choses, l'image source n'est pas garantie d'être de haute qualité. La ROC doit être réalisée à travers un réseau neuronal, mais ce n'est pas le sujet ici.
le problème actuel est le prétraitement des images. Comme les cartes de crédit peuvent avoir des arrière-plans et d'autres graphiques complexes, le texte n'est pas aussi clair qu'avec la numérisation d'un document. J'ai fait des expériences avec la détection de bord (Canny Edge, Sobel), mais ce n'était pas que succès. Calculez également la différence entre l'image à l'échelle de gris et une image floue (comme indiqué à supprimer la couleur de fond dans le traitement d'image pour OCR) n'a pas conduit à un résultat OCR able.
je pense que la plupart des approches échouent parce que le contraste entre un chiffre spécifique et son arrière-plan n'est pas assez fort. Il est probablement nécessaire de faire une segmentation de l'image en blocs et de trouver la meilleure solution de prétraitement pour chaque bloc?
avez-vous comment convertir la source en une image binaire lisible? Est-ce que la détection de bord est la voie à suivre ou devrais-je m'en tenir au seuil de couleur de base?
voici un exemple d'approche à l'échelle de gris-seuil (où je ne suis évidemment pas satisfait des résultats):
image originale:
niveaux de Gris de l'image:
Battleholded image:
Merci pour tout conseil, Valentin
3 réponses
si c'est possible, demandez qu'un meilleur éclairage soit utilisé pour capturer les images. Une lumière à faible angle illuminerait les bords des caractères surélevés (ou enfoncés), améliorant ainsi grandement la qualité de l'image. Si l'image est destinée à être analysé par une machine, l'éclairage doit être optimisé pour la machine de lisibilité.
cela dit, un algorithme que vous devriez examiner est la transformation de largeur de trait, qui est utilisé pour extraire les caractères de la image.
la Largeur du trait de Transformation (SWT) mise en œuvre (Java, C#...)
un seuil global (pour les forces de binarisation ou de coupe de bord) ne le coupera probablement pas pour cette application, et vous devriez plutôt regarder les seuils localisés. Dans votre exemple d'images ,le " 02 "qui suit le" 31 " est particulièrement faible, donc rechercher les bords locaux les plus forts dans cette région serait mieux que filtrer tous les bords dans la chaîne de caractères en utilisant un seul seuil.
si vous pouvez identifier des segments partiels de caractères, alors vous pouvez utiliser certaines opérations de morphologie directionnelle pour aider à joindre les segments. Par exemple, si vous avez deux segments presque horizontaux comme le suivant, où 0 est le fond et 1 est le premier plan...
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 1 1 1 1 0 0 1 1 1 1 1 1 0 0 0
0 0 0 1 0 0 0 1 0 1 0 0 0 0 1 0 0 0
alors vous pourriez effectuer une opération morphologique "proche" le long de la direction horizontale seulement pour joindre ces segments. Le noyau pourrait être quelque chose comme
x x x x x
1 1 1 1 1
x x x x x
il existe des méthodes plus sophistiquées pour effectuer l'achèvement de courbe en utilisant des ajustements de Bezier ou même des spirales D'Euler (A. K. A. clothoids), mais le prétraitement pour identifier les segments à joindre et le posttraitement pour éliminer les jointures pauvres peut devenir très difficile.
la façon dont je m'occuperais du problème est de séparer les cartes en différentes sections. Il n'y a pas beaucoup de cartes de crédit pour commencer la liste est à vous), donc vous pouvez faire comme une goutte vers le bas pour spécifier quelle carte de crédit il s'agit. De cette façon, vous pouvez éliminer et spécifier la zone du pixel:
Exemple:
seulement travailler avec la zone 20 pixels à partir du bas, 30 pixels à partir du gauche aux 10 pixels de droite à 30 pixels à partir du bas (création d'un rectangle) - ceci couvrirait toutes les MasterCards
quand j'ai travaillé avec des programmes de traitement d'image (fun project) j'ai trouvé le contraste de l'image, l'ai convertie en échelle de gris, pris la moyenne de chaque valeur RVB individuelle de 1 pixel, et l'ai comparée à l'ensemble autour des pixels:
Exemple:
PixAvg[i,j] = (Pix.R + Pix.G + Pix.B)/3
if ((PixAvg[i,j] - PixAvg[i,j+1])>30)
boolEdge == true;
30 serait à quel point vous voulez que votre image soit distincte. Plus la différence, la baisse va être la tolérance.
dans mon projet, pour voir la détection de bord, j'ai fait un tableau séparé de booléens, qui contenait des valeurs de boolEdge, et un tableau de pixels. La gamme de pixels a été rempli avec seulement des points noirs et blancs. Il a obtenu les valeurs du tableau booléen, où boolEdge = true est un point blanc, et boolEdge = false est un point noir. Ainsi, à la fin, vous vous retrouvez avec un tableau de pixels (image complète) qui ne contient que des points blancs et noirs.
à Partir de là, il est beaucoup plus facile de détecter un nombre commence et où un certain nombre de finitions.
dans mon application j'ai essayé d'utiliser le code ici:http://rnd.azoft.com/algorithm-identifying-barely-legible-embossed-text-image/ les résultats sont meilleurs, mais pas assez... j'ai du mal à trouver les bons paramètres de texture cartes.
(void)processingByStrokesMethod:(cv::Mat)src dst:(cv::Mat*)dst {
cv::Mat tmp;
cv::GaussianBlur(src, tmp, cv::Size(3,3), 2.0); // gaussian blur
tmp = cv::abs(src - tmp); // matrix of differences between source image and blur iamge
//Binarization:
cv::threshold(tmp, tmp, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
//Using method of strokes:
int Wout = 12;
int Win = Wout/2;
int startXY = Win;
int endY = src.rows - Win;
int endX = src.cols - Win;
for (int j = startXY; j < endY; j++) {
for (int i = startXY; i < endX; i++) {
//Only edge pixels:
if (tmp.at<unsigned char="">(j,i) == 255)
{
//Calculating maxP and minP within Win-region:
unsigned char minP = src.at<unsigned char="">(j,i);
unsigned char maxP = src.at<unsigned char="">(j,i);
int offsetInWin = Win/2;
for (int m = - offsetInWin; m < offsetInWin; m++) {
for (int n = - offsetInWin; n < offsetInWin; n++) {
if (src.at<unsigned char="">(j+m,i+n) < minP) {
minP = src.at<unsigned char="">(j+m,i+n);
}else if (src.at<unsigned char="">(j+m,i+n) > maxP) {
maxP = src.at<unsigned char="">(j+m,i+n);
}
}
}
//Voiting:
unsigned char meanP = lroundf((minP+maxP)/2.0);
for (int l = -Win; l < Win; l++) {
for (int k = -Win; k < Win; k++) {
if (src.at<unsigned char="">(j+l,i+k) >= meanP) {
dst->at<unsigned char="">(j+l,i+k)++;
}
}
}
}
}
}
///// Normalization of imageOut:
unsigned char maxValue = dst->at<unsigned char="">(0,0);
for (int j = 0; j < dst->rows; j++) { //finding max value of imageOut
for (int i = 0; i < dst->cols; i++) {
if (dst->at<unsigned char="">(j,i) > maxValue)
maxValue = dst->at<unsigned char="">(j,i);
}
}
float knorm = 255.0 / maxValue;
for (int j = 0; j < dst->rows; j++) { //normalization of imageOut
for (int i = 0; i < dst->cols; i++) {
dst->at<unsigned char="">(j,i) = lroundf(dst->at<unsigned char="">(j,i)*knorm);
}
}