2012-02-22 17 views
9

Quiero reconocer los dígitos de una tarjeta de crédito. Para empeorar las cosas, no se garantiza que la imagen de origen sea de alta calidad. El OCR se realizará a través de una red neuronal, pero ese no debería ser el tema aquí.Preparar imagen compleja para OCR

El problema actual es el preprocesamiento de la imagen. Como las tarjetas de crédito pueden tener fondos y otros gráficos complejos, el texto no es tan claro como con el escaneo de un documento. Hice experimentos con detección de bordes (Canny Edge, Sobel), pero no fue tan exitoso. También calcular la diferencia entre la imagen en escala de grises y una borrosa (como se indica en Remove background color in image processing for OCR) no condujo a un resultado de OCRable.

Creo que la mayoría de los enfoques fallan porque el contraste entre un dígito específico y su fondo no es lo suficientemente fuerte. Probablemente hay una necesidad de hacer una segmentación de la imagen en bloques y encontrar la mejor solución de preprocesamiento para cada bloque.

¿Tiene alguna sugerencia sobre cómo convertir la fuente en una imagen binaria legible? ¿La detección de bordes es el camino a seguir o debería seguir con el umbral de color básico?

Este es un ejemplo de un enfoque de escala de grises-umbral (donde obviamente no estoy feliz con los resultados):

imagen original:

Original image

imagen en escala de grises:

Greyscale image

Thresholded image :

Thresholded image

Gracias por cualquier consejo, Valentin

+0

Dado que hay muy poco contraste, probaría la detección de bordes, como usted mencionó. –

Respuesta

5

Si es posible, solicite que se utilice una mejor iluminación para capturar las imágenes. Una luz de ángulo bajo iluminaría los bordes de los caracteres elevados (o hundidos), lo que mejoraría en gran medida la calidad de la imagen. Si la imagen debe ser analizada por una máquina, entonces la iluminación debe optimizarse para la legibilidad de la máquina.

Dicho esto, un algoritmo que debe tener en cuenta es la Transformación del ancho de trazo, que se utiliza para extraer caracteres de imágenes naturales.

Stroke Width Transform (SWT) implementation (Java, C#...)

Un umbral mundial (por binarización o recorte de bordes fortalezas) probablemente no se corte para esta aplicación, y en su lugar usted debe buscar en los umbrales localizados. En sus imágenes de ejemplo, el "02" que sigue al "31" es particularmente débil, por lo que sería mejor buscar los bordes locales más fuertes en esa región que filtrar todos los bordes en la cadena de caracteres usando un único umbral.

Si puede identificar segmentos parciales de caracteres, puede usar algunas operaciones de morfología direccional para ayudar a unir segmentos. Por ejemplo, si tiene dos segmentos casi horizontales, como el siguiente, donde 0 es el fondo y el primer plano es 1 ...

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 

Posteriormente, se podría realizar una morfológico "cerrar" la operación a lo largo de la dirección horizontal sólo para únete a esos segmentos El núcleo podría ser algo como

x x x x x 
1 1 1 1 1 
x x x x x 

Existen métodos más sofisticados para llevar a cabo la terminación curva usando Bezier encaja o incluso Euler espirales (también conocido como clotoides), pero el procesamiento previo para identificar los segmentos a unir y post-procesamiento para eliminar la mala une puede conseguir Muy engañoso.

5

La manera cómo iba a ir sobre el problema está separado las tarjetas en diferentes secciones. No hay muchas tarjetas de crédito únicas para comenzar con (MasterCard, Visa, la lista depende de usted), por lo que puede hacer una lista desplegable para especificar qué tarjeta de crédito es. De esta manera, se puede eliminar y especificar el área de píxeles:

Ejemplo:

Sólo trabajar con el área de 20 píxeles desde la parte inferior, a 30 píxeles desde la dejaron a los 10 píxeles de derecha a 30 píxeles de fondo (la creación de un rectángulo ) - esto cubre todos los MasterCards

Cuando trabajé con programas de procesamiento de imágenes (divertido proyecto) subí el contraste de la imagen, convertida a escala de grises, tomó la avera ge de cada uno de los valores RGB individuales de 1 píxel, y lo comparó con los píxeles de todo:

Ejemplo:

PixAvg[i,j] = (Pix.R + Pix.G + Pix.B)/3 
if ((PixAvg[i,j] - PixAvg[i,j+1])>30) 
    boolEdge == true; 

30 sería distinta forma en que quiere que su imagen sea. Cuanto menor sea la diferencia, menor será la tolerancia.

En mi proyecto, para ver la detección de bordes, hice una matriz separada de booleanos, que contenía valores de boolEdge y una matriz de píxeles. La matriz de píxeles estaba llena solo de puntos blancos y negros. Obtuvo los valores de la matriz booleana, donde boolEdge = true es un punto blanco y boolEdge = falso es un punto negro. Entonces, al final, terminas con una matriz de píxeles (imagen completa) que solo contiene puntos blancos y negros.

Desde allí, es mucho más fácil detectar dónde comienza un número y dónde termina un número.

1

en mi aplicación Traté de usar el código de aquí: http://rnd.azoft.com/algorithm-identifying-barely-legible-embossed-text-image/ resultados son mejores, pero no es suficiente ... Me resulta difícil encontrar los parametros adecuados para las tarjetas de textura.

(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); 
    } 
} 
+0

Bien, proporcionaste el enlace, ¿puedes dar alguna explicación para el PO también? – Yahya