2013-06-27 28 views
11

Witam, pracuję obecnie nad aplikacją do odczytu OCR, w której udało mi się przechwycić obraz karty za pomocą architektury AVFoundation.Wykrywanie krawędzi karty z zaokrąglonymi narożnikami

Aby przejść dalej, muszę poznać krawędzie karty, aby można było wykadrować obraz karty z głównego przechwyconego obrazu. & później mogę wysłać go do silnika OCR w celu przetworzenia.

Głównym problemem jest teraz znalezienie krawędzi karty & używam poniższego kodu (pobranego z innego projektu open source), który wykorzystuje w tym celu OpenCV. Działa dobrze, jeśli karta jest czystą prostokątną kartą lub papierem . Ale kiedy używam karty z zaokrąglonym rogiem (np. Prawo jazdy), nie udało się jej wykryć. Również nie mam wiele wiedzy w OpenCV, Czy ktoś może mi pomóc w rozwiązaniu tego problemu?

- (void)detectEdges 
{ 
    cv::Mat original = [MAOpenCV cvMatFromUIImage:_adjustedImage]; 
    CGSize targetSize = _sourceImageView.contentSize; 
    cv::resize(original, original, cvSize(targetSize.width, targetSize.height)); 

    cv::vector<cv::vector<cv::Point>>squares; 
    cv::vector<cv::Point> largest_square; 

    find_squares(original, squares); 
    find_largest_square(squares, largest_square); 

    if (largest_square.size() == 4) 
    { 

     // Manually sorting points, needs major improvement. Sorry. 

     NSMutableArray *points = [NSMutableArray array]; 
     NSMutableDictionary *sortedPoints = [NSMutableDictionary dictionary]; 

     for (int i = 0; i < 4; i++) 
     { 
      NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:[NSValue valueWithCGPoint:CGPointMake(largest_square[i].x, largest_square[i].y)], @"point" , [NSNumber numberWithInt:(largest_square[i].x + largest_square[i].y)], @"value", nil]; 
      [points addObject:dict]; 
     } 

     int min = [[points valueForKeyPath:@"@min.value"] intValue]; 
     int max = [[points valueForKeyPath:@"@max.value"] intValue]; 

     int minIndex; 
     int maxIndex; 

     int missingIndexOne; 
     int missingIndexTwo; 

     for (int i = 0; i < 4; i++) 
     { 
      NSDictionary *dict = [points objectAtIndex:i]; 

      if ([[dict objectForKey:@"value"] intValue] == min) 
      { 
       [sortedPoints setObject:[dict objectForKey:@"point"] forKey:@"0"]; 
       minIndex = i; 
       continue; 
      } 

      if ([[dict objectForKey:@"value"] intValue] == max) 
      { 
       [sortedPoints setObject:[dict objectForKey:@"point"] forKey:@"2"]; 
       maxIndex = i; 
       continue; 
      } 

      NSLog(@"MSSSING %i", i); 

      missingIndexOne = i; 
     } 

     for (int i = 0; i < 4; i++) 
     { 
      if (missingIndexOne != i && minIndex != i && maxIndex != i) 
      { 
       missingIndexTwo = i; 
      } 
     } 


     if (largest_square[missingIndexOne].x < largest_square[missingIndexTwo].x) 
     { 
      //2nd Point Found 
      [sortedPoints setObject:[[points objectAtIndex:missingIndexOne] objectForKey:@"point"] forKey:@"3"]; 
      [sortedPoints setObject:[[points objectAtIndex:missingIndexTwo] objectForKey:@"point"] forKey:@"1"]; 
     } 
     else 
     { 
      //4rd Point Found 
      [sortedPoints setObject:[[points objectAtIndex:missingIndexOne] objectForKey:@"point"] forKey:@"1"]; 
      [sortedPoints setObject:[[points objectAtIndex:missingIndexTwo] objectForKey:@"point"] forKey:@"3"]; 
     } 


     [_adjustRect topLeftCornerToCGPoint:[(NSValue *)[sortedPoints objectForKey:@"0"] CGPointValue]]; 
     [_adjustRect topRightCornerToCGPoint:[(NSValue *)[sortedPoints objectForKey:@"1"] CGPointValue]]; 
     [_adjustRect bottomRightCornerToCGPoint:[(NSValue *)[sortedPoints objectForKey:@"2"] CGPointValue]]; 
     [_adjustRect bottomLeftCornerToCGPoint:[(NSValue *)[sortedPoints objectForKey:@"3"] CGPointValue]]; 
    } 

    original.release(); 


} 
+0

Czy możesz podać link do obcinania karty? – faiziii

+0

@Aaz Mam podobny wymóg, czy możesz zaproponować mi projekt Open Source, z którego korzystałeś. To będzie wielka pomoc. –

+1

@John Czy zamierzasz udostępnić jeden lub dwa przykładowe obrazy? – karlphillip

Odpowiedz

12

Ta naiwna implementacja jest oparta na niektórych technikach zademonstrowanych w squares.cpp, dostępnych w przykładowym katalogu OpenCV. Poniższe posty również dyskutować podobnych wniosków:

@John kod poniżej zostało przetestowane z obrazem próbki podałeś i innym stworzyłem:

Rurociąg przetwarzający rozpoczyna się od findSquares(), co jest uproszczeniem tej samej funkcji wdrożonej przez OpenCV demo squares.cpp.Funkcja konwersji obrazu wejściowego do skali szarości i stosuje rozmycie poprawy wykrywania krawędzi (Canny'ego):

Wykrywanie krawędź jest dobra, ale morfologiczna operacja (rozszerzenie) jest potrzebny do przyłączyć pobliskie linie:

Po tym staramy się znaleźć kontury (krawędziach) i montaż placów z nich. Gdybyśmy próbowali wyciągnąć wszelkie wykryte kwadraty na zdjęciach wejściowych, będzie to wynikiem:

Wygląda dobrze, ale nie jest to dokładnie to, czego szukasz, ponieważ istnieje zbyt wiele wykrywane kwadraty. Jednak największy kwadrat to tak naprawdę karta, więc od tej chwili jest dość prosta i po prostu ustalamy, który z kwadratów jest największy. Dokładnie to robi findLargestSquare().

Gdy wiemy, największy plac, po prostu malować czerwone kropki w rogach kwadratu dla celów debugowania:

Jak widać, detekcja nie jest idealne, ale go Wydaje się wystarczająco dobre dla większości zastosowań. To nie jest solidne rozwiązanie i chciałem tylko podzielić się jednym podejściem, aby rozwiązać problem. Jestem pewien, że istnieją inne sposoby radzenia sobie z tym, co może być dla ciebie bardziej interesujące. Powodzenia!

#include <iostream> 
#include <cmath> 
#include <vector> 

#include <opencv2/highgui/highgui.hpp> 
#include <opencv2/imgproc/imgproc.hpp> 
#include <opencv2/imgproc/imgproc_c.h> 

/* angle: finds a cosine of angle between vectors, from pt0->pt1 and from pt0->pt2 
*/ 
double angle(cv::Point pt1, cv::Point pt2, cv::Point pt0) 
{ 
    double dx1 = pt1.x - pt0.x; 
    double dy1 = pt1.y - pt0.y; 
    double dx2 = pt2.x - pt0.x; 
    double dy2 = pt2.y - pt0.y; 
    return (dx1*dx2 + dy1*dy2)/sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10); 
} 

/* findSquares: returns sequence of squares detected on the image 
*/ 
void findSquares(const cv::Mat& src, std::vector<std::vector<cv::Point> >& squares) 
{ 
    cv::Mat src_gray; 
    cv::cvtColor(src, src_gray, cv::COLOR_BGR2GRAY); 

    // Blur helps to decrease the amount of detected edges 
    cv::Mat filtered; 
    cv::blur(src_gray, filtered, cv::Size(3, 3)); 
    cv::imwrite("out_blur.jpg", filtered); 

    // Detect edges 
    cv::Mat edges; 
    int thresh = 128; 
    cv::Canny(filtered, edges, thresh, thresh*2, 3); 
    cv::imwrite("out_edges.jpg", edges); 

    // Dilate helps to connect nearby line segments 
    cv::Mat dilated_edges; 
    cv::dilate(edges, dilated_edges, cv::Mat(), cv::Point(-1, -1), 2, 1, 1); // default 3x3 kernel 
    cv::imwrite("out_dilated.jpg", dilated_edges); 

    // Find contours and store them in a list 
    std::vector<std::vector<cv::Point> > contours; 
    cv::findContours(dilated_edges, contours, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE); 

    // Test contours and assemble squares out of them 
    std::vector<cv::Point> approx; 
    for (size_t i = 0; i < contours.size(); i++) 
    { 
     // approximate contour with accuracy proportional to the contour perimeter 
     cv::approxPolyDP(cv::Mat(contours[i]), approx, cv::arcLength(cv::Mat(contours[i]), true)*0.02, true); 

     // Note: absolute value of an area is used because 
     // area may be positive or negative - in accordance with the 
     // contour orientation 
     if (approx.size() == 4 && std::fabs(contourArea(cv::Mat(approx))) > 1000 && 
      cv::isContourConvex(cv::Mat(approx))) 
     { 
      double maxCosine = 0; 
      for (int j = 2; j < 5; j++) 
      { 
       double cosine = std::fabs(angle(approx[j%4], approx[j-2], approx[j-1])); 
       maxCosine = MAX(maxCosine, cosine); 
      } 

      if (maxCosine < 0.3) 
       squares.push_back(approx); 
     } 
    } 
} 

/* findLargestSquare: find the largest square within a set of squares 
*/ 
void findLargestSquare(const std::vector<std::vector<cv::Point> >& squares, 
         std::vector<cv::Point>& biggest_square) 
{ 
    if (!squares.size()) 
    { 
     std::cout << "findLargestSquare !!! No squares detect, nothing to do." << std::endl; 
     return; 
    } 

    int max_width = 0; 
    int max_height = 0; 
    int max_square_idx = 0; 
    for (size_t i = 0; i < squares.size(); i++) 
    { 
     // Convert a set of 4 unordered Points into a meaningful cv::Rect structure. 
     cv::Rect rectangle = cv::boundingRect(cv::Mat(squares[i])); 

     //std::cout << "find_largest_square: #" << i << " rectangle x:" << rectangle.x << " y:" << rectangle.y << " " << rectangle.width << "x" << rectangle.height << endl; 

     // Store the index position of the biggest square found 
     if ((rectangle.width >= max_width) && (rectangle.height >= max_height)) 
     { 
      max_width = rectangle.width; 
      max_height = rectangle.height; 
      max_square_idx = i; 
     } 
    } 

    biggest_square = squares[max_square_idx]; 
} 

int main() 
{ 
    cv::Mat src = cv::imread("cc.png"); 
    if (src.empty()) 
    { 
     std::cout << "!!! Failed to open image" << std::endl; 
     return -1; 
    } 

    std::vector<std::vector<cv::Point> > squares; 
    findSquares(src, squares); 

    // Draw all detected squares 
    cv::Mat src_squares = src.clone(); 
    for (size_t i = 0; i < squares.size(); i++) 
    { 
     const cv::Point* p = &squares[i][0]; 
     int n = (int)squares[i].size(); 
     cv::polylines(src_squares, &p, &n, 1, true, cv::Scalar(0, 255, 0), 2, CV_AA); 
    } 
    cv::imwrite("out_squares.jpg", src_squares); 
    cv::imshow("Squares", src_squares); 

    std::vector<cv::Point> largest_square; 
    findLargestSquare(squares, largest_square); 

    // Draw circles at the corners 
    for (size_t i = 0; i < largest_square.size(); i++) 
     cv::circle(src, largest_square[i], 4, cv::Scalar(0, 0, 255), cv::FILLED); 
    cv::imwrite("out_corners.jpg", src); 

    cv::imshow("Corners", src); 
    cv::waitKey(0); 

    return 0; 
} 
+0

karlphillip - Dziękujemy za odpowiedź, Za pomocą tego kodu nie jesteśmy w stanie wykryć krawędzi zapisywania dla karty na tym samym obrazie za każdym razem. działa losowo. –

+0

Jeśli pracujesz z obrazem statycznym, wynik powinien być spójny: powinien działać lub nie (za każdym razem). – karlphillip

+0

Dziękujemy za odesłanie, wybieramy obraz z biblioteki zdjęć urządzenia lub robiąc zdjęcie. Za każdym razem, gdy wybieramy tę samą bibliotekę obrazów, wykrywanie krawędzi działa inaczej. Czy istnieje jakiś szczególny powód? –

0

ja nie wiem, czy to jest opcja, ale można mieć użytkownikowi zdefiniować krawędzie nim zamiast próbować zrobić to programowo.

+1

To może być lepiej umieszczone jako komentarz w pytaniu. – karlphillip

2

zamiast "czystych" prostokątnych bąbelków, spróbuj przejść do prostokątnych.

1- Gaussa rozmycie

2 w odcieniach szarości i canny

3- wyodrębnienie plamy (konturu) w obrazie i odfiltrować małych. w tym celu użyjecie funkcji findcontours i contourarea.

4- używając moments, odfiltruj inne niż prostokątne. Najpierw musisz sprawdzić momenty obiektów podobnych do prostokątów. Możesz to zrobić samodzielnie lub google. Następnie wymień te momenty i znajdź podobieństwo między obiektami, utwórz swój filtr jako taki.

Np .: Po teście powiedzmy, że dowiedziałeś się, że moment centralny m30 jest podobny dla obiektów podobnych do prostokątów -> odfiltruj obiekty o niedokładnym m30.

1

Wiem, że może być za późno na ten post, ale zamieszczam go na wypadek, gdyby mógł pomóc komuś innemu.

Architektura iOS Core Image ma już dobre narzędzie do wykrywania takich funkcji, jak prostokąty (od iOS 5), twarze, kody QR, a nawet regiony zawierające tekst w obrazie nieruchomym. Jeśli sprawdzisz numer CIDetector class, znajdziesz to, czego potrzebujesz. Używam go również do aplikacji OCR, jest bardzo łatwy i bardzo niezawodny w porównaniu do tego, co można zrobić z OpenCV (nie jestem dobry z OpenCV, ale CIDetector daje znacznie lepsze wyniki z 3-5 liniami kodu).