2012-02-22 16 views
9

Desidero riconoscere le cifre da una carta di credito. Per peggiorare le cose, l'immagine sorgente non è garantita di alta qualità. L'OCR deve essere realizzato attraverso una rete neurale, ma non dovrebbe essere l'argomento qui.Preparare un'immagine complessa per OCR

Il problema attuale è la preelaborazione dell'immagine. Poiché le carte di credito possono avere sfondi e altri elementi grafici complessi, il testo non è chiaro come con la scansione di un documento. Ho fatto esperimenti con il rilevamento dei bordi (Canny Edge, Sobel), ma non è stato un successo. Anche il calcolo della differenza tra l'immagine in scala di grigi e uno sfocato (come indicato in Remove background color in image processing for OCR) non ha comportato un risultato OCRable.

Penso che la maggior parte degli approcci fallisce perché il contrasto tra una cifra specifica e il suo sfondo non è abbastanza forte. È probabilmente necessario eseguire una segmentazione dell'immagine in blocchi e trovare la migliore soluzione di pre-elaborazione per ogni blocco?

Avete qualche suggerimento su come convertire la fonte in un'immagine binaria leggibile? Il rilevamento dei bordi è la strada da percorrere o dovrei attenermi alle soglie di colore di base?

Ecco un esempio di un approccio in scala di grigi-thresholding (dove non sono ovviamente soddisfatto dei risultati):

immagine originale:

Original image

immagine in scala di grigi:

Greyscale image

Immagine Soglia :

Thresholded image

Grazie per qualsiasi consiglio, Valentin

+0

Dato che c'è così poco contrasto, vorrei provare il rilevamento dei bordi, come hai detto. –

risposta

5

Se è possibile, richiedere una migliore illuminazione per catturare le immagini. Una luce di basso angolo illuminerebbe i bordi dei caratteri sollevati (o incassati), migliorando così notevolmente la qualità dell'immagine. Se l'immagine deve essere analizzata da una macchina, l'illuminazione deve essere ottimizzata per la leggibilità della macchina.

Detto questo, un algoritmo da esaminare è la trasformazione della larghezza del tratto, che viene utilizzata per estrarre i caratteri dalle immagini naturali.

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

Una soglia globale (per binarizzazione o clipping punti di forza dei bordi) probabilmente non è tagliato per questa applicazione, e invece si dovrebbe guardare a soglie localizzate. Nelle tue immagini di esempio lo "02" che segue il "31" è particolarmente debole, quindi la ricerca dei bordi locali più forti in quella regione sarebbe meglio che filtrare tutti i bordi della stringa di caratteri usando una singola soglia.

Se è possibile identificare segmenti parziali di caratteri, è possibile utilizzare alcune operazioni di morfologia direzionale per unire i segmenti. Ad esempio, se si dispone di due segmenti quasi orizzontali come il seguente, dove 0 è lo sfondo e 1 è il piano ...

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 

allora si potrebbe eseguire una "chiusura" operazione morfologico lungo la direzione orizzontale solo unisciti a quei segmenti. Il kernel potrebbe essere qualcosa come

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

Ci sono metodi più sofisticati per eseguire il completamento curva utilizzando Bezier si adatta o anche Eulero a spirale (aka Clotoidi), ma pre-elaborazione per identificare i segmenti da unire e post-elaborazione per eliminare i poveri si unisce può ottenere molto difficile.

5

Il modo in cui vorrei andare circa il problema è separata le carte in sezione diversa. Non ci sono molte carte di credito uniche per iniziare con (MasterCard, Visa, l'elenco è a tua discrezione), quindi puoi fare un salto di qualità per specificare quale carta di credito è. In questo modo, si può eliminare e specificare l'area di pixel:

Esempio:

lavorare solo con l'area di 20 pixel dal basso, 30 pixel dal lasciati ai 10 pixel da destra a 30 pixel dal basso (la creazione di un rettangolo ) - Questo coprirebbe tutte MasterCard

Quando ho lavorato con i programmi di elaborazione delle immagini (divertente progetto) ho acceso il contrasto dell'immagine, convertita in scala di grigi, ha preso il Avera ge di ogni singolo valori RGB di 1 pixel, e lo ha confrontato ai pixel di tutto:

Esempio:

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

30 sarebbe come distinti che si desidera la vostra immagine di essere. Più bassa è la differenza, minore sarà la tolleranza.

Nel mio progetto, per visualizzare il rilevamento dei bordi, ho creato una matrice separata di booleani, che conteneva valori da boolEdge e un array di pixel. L'array di pixel era pieno solo di punti bianchi e neri. Ha ottenuto i valori dall'array booleano, dove boolEdge = true è un punto bianco e boolEdge = false è un punto nero. Alla fine, si finisce con un array di pixel (immagine intera) che contiene solo punti bianchi e neri.

Da lì, è molto più facile rilevare dove inizia un numero e dove finisce un numero.

1

nella mia implementazione Ho cercato di usare il codice da qui: http://rnd.azoft.com/algorithm-identifying-barely-legible-embossed-text-image/ risultati sono migliori ma non abbastanza ... Mi è difficile trovare i giusti params per le schede di texture.

(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

Bene, hai fornito il link, puoi fornire qualche spiegazione anche per l'OP. – Yahya