2012-02-22 8 views
9

Chcę rozpoznać cyfry z karty kredytowej. Co gorsza, obraz źródłowy nie może być wysokiej jakości. OCR ma być realizowany za pośrednictwem sieci neuronowej, ale nie powinno to być tematem tutaj.Przygotowanie złożonego obrazu do OCR

Aktualne wydanie to wstępne przetwarzanie obrazu. Ponieważ karty kredytowe mogą mieć tła i inną złożoną grafikę, tekst nie jest tak wyraźny, jak w przypadku skanowania dokumentu. Przeprowadziłem eksperymenty z wykrywaniem krawędzi (Canny Edge, Sobel), ale nie było to udane. Również obliczenie różnicy pomiędzy obrazem w skali szarości a rozmytym (zgodnie z Remove background color in image processing for OCR) nie doprowadziło do wyniku OCRable.

Myślę, że większość podejść kończy się niepowodzeniem, ponieważ kontrast między określoną cyfrą a jej tłem nie jest wystarczająco silny. Prawdopodobnie istnieje potrzeba dokonania segmentacji obrazu na bloki i znalezienia najlepszego rozwiązania do przetwarzania wstępnego dla każdego bloku?

Czy masz jakieś sugestie, jak przekonwertować źródło na czytelny obraz binarny? Czy wykrywanie krawędzi jest drogą, czy powinienem trzymać się podstawowych kolorów?

Oto przykład podejścia szarości-progowania (gdzie nie jestem oczywiście zadowolony z wyników):

Obraz oryginalny:

Original image

szarości:

Greyscale image

Próg obrazu :

Thresholded image

Dzięki za wszelkie rady, Valentin

+0

Ponieważ jest tak mało kontrastu, spróbowałbym wykrycia krawędzi, o czym wspomniałeś. –

Odpowiedz

5

Jeśli jest to w ogóle możliwe, poproś o lepsze oświetlenie w celu przechwytywania obrazów. Światło o niskim kącie oświetliłoby krawędzie uniesionych (lub zatopionych) znaków, co znacznie poprawiłoby jakość obrazu. Jeśli obraz ma być analizowany przez maszynę, oświetlenie powinno być zoptymalizowane pod kątem czytelności maszyny.

To powiedziawszy, jednym z algorytmów, który powinieneś sprawdzić, jest transformacja szerokości obrysu, która służy do wyodrębniania postaci z naturalnych obrazów.

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

Globalny próg (dla binaryzacji lub wycinek mocnych krawędzi) prawdopodobnie nie będzie go wyciąć dla tej aplikacji, a zamiast tego należy szukać w lokalnych progów. W przykładowych obrazach "02" po "31" jest szczególnie słaby, więc wyszukiwanie najsilniejszych lokalnych krawędzi w tym regionie byłoby lepsze niż filtrowanie wszystkich krawędzi ciągu znaków za pomocą pojedynczego progu.

Jeśli możesz zidentyfikować częściowe segmenty znaków, możesz użyć niektórych funkcji morfologii kierunkowej, aby ułatwić łączenie segmentów. Na przykład, jeśli masz dwie prawie poziome segmenty jak poniżej, gdzie 0 jest tło i 1 jest pierwszym planie ...

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 

następnie można wykonać morfologiczną „zamknij” operację wzdłuż kierunku poziomym tylko dołącz do tych segmentów. Jądro może być coś podobnego

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

Są bardziej wyrafinowane metody, aby wykonać zakończenie krzywej za pomocą Beziera pasuje lub nawet Euler spirale (aka clothoids), ale przerób zidentyfikować segmenty mają być połączone i postprocessing wyeliminować biednych dołącza można dostać bardzo trudne.

5

Sposób, w jaki sposób przejść o problemie jest oddzielne karty do innej sekcji. Nie ma wielu unikatowych kart kredytowych na początek: (MasterCard, Visa, lista należy do Ciebie), możesz więc wybrać menu rozwijane, aby określić kartę kredytową. W ten sposób można wyeliminować i określić obszar pikseli:

Przykład:

działa tylko z obszaru 20 pikseli od dołu, 30 pikseli od lewo do 10 pikseli od prawej do 30 pikseli od dołu (tworząc prostokąt) - To będzie obejmować wszystkie MasterCard

Kiedy pracowałem z programami do przetwarzania obrazu (projekt zabawa) Odwróciłem się kontrast obrazu, przekształcono go na skalę szarości, wziął avera ge każdej poszczególnej wartości RGB 1 piksel i porównaniu go z całym pikseli:

Przykład:

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

30 byłby sposób wyraźny obraz chcesz być. Im mniejsza różnica, tym niższa będzie tolerancja.

W moim projekcie, aby wyświetlić wykrywanie krawędzi, stworzyłem oddzielną tablicę wartości logicznych, które zawierały wartości z BoolEdge i tablicę pikseli. Tablica pikseli była wypełniona tylko czarnymi i białymi kropkami. Dostał wartości z tablicy boolowskiej, gdzie boolEdge = true to biała kropka, a boolEdge = false to czarna kropka. Ostatecznie otrzymujesz tablicę pikseli (pełny obraz), która zawiera tylko białe i czarne kropki.

Stamtąd znacznie łatwiej jest wykryć, gdzie zaczyna się numer i gdzie kończy się numer.

1

w moim realizacji próbowałem użyć kodu stąd: http://rnd.azoft.com/algorithm-identifying-barely-legible-embossed-text-image/ wyniki są lepsze, ale nie na tyle ... Trudno mi znaleźć odpowiednie params dla kart tekstur.

(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

Dobrze, podałeś link, proszę o wyjaśnienie OP. – Yahya