2013-06-27 3 views
11

Привет, в настоящее время я работаю над приложением чтения OCR, где я успешно смогу захватить изображение карты с помощью AVFoundation framework.Обнаружение краев карты с закругленными углами

Для следующего шага мне нужно выяснить края карты, чтобы я мог обрезать изображение карты из основного захваченного изображения. & позже я могу отправить его в OCR-движок для обработки.

Основная проблема заключается в том, чтобы найти края карты & Я использую ниже код (взятый из другого проекта с открытым исходным кодом), который использует OpenCV для этой цели. Он отлично работает, если карта является чистой прямоугольной картой или бумагой , Но когда я использую карту с закругленным углом (например, водительскую лицензию), она не обнаруживается. Также у меня нет большого опыта в OpenCV, может ли кто-нибудь помочь мне в решении этой проблемы?

- (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

Можете ли вы предоставить мне ссылку для взлома карты? – faiziii

+0

@raaz У меня есть аналогичное требование, можете ли вы предложить мне этот проект с открытым исходным кодом, который вы использовали. Это будет большой помощью. –

+1

@John Собираетесь ли вы поделиться одним или двумя образцами? – karlphillip

ответ

12

Эта наивная реализация основана на некоторых методах, представленных в squares.cpp, доступных в каталоге образцов OpenCV. Следующие посты также обсуждать подобные приложения:

@John, приведенный ниже код был проверен с изображением образца вы предоставили и еще один я создал:

Технологический трубопровод начинается с findSquares(), что упрощает выполнение той же функции, что и OpenCV's squares.cpp demo.Эта функция преобразует входное изображение в оттенки серого и применяет размытие, чтобы улучшить обнаружение краев (Канне):

Обнаружение края хорошо, но морфологическая операция (дилатация) необходима, чтобы присоединиться соседние строки:

После этого мы пытаемся найти контуры (края) и собрать квадраты из них. Если бы мы попытались сделать все обнаруженные квадраты на входных изображений, это будет результат:

Он выглядит хорошо, но это не совсем то, что мы ищем, так как существует слишком много обнаружено квадраты. Тем не менее, самая большая площадь - это карта, поэтому отсюда это довольно просто, и мы просто выясняем, какая из квадратов самая большая. Это именно то, что делает findLargestSquare().

После того, как мы знаем, самую большую площадь, мы просто рисовать красные точки на углах квадрата, для целей отладки:

Как вы можете видеть, обнаружение не является совершенным, но его похоже достаточно хороший для большинства использует. Это не надежное решение, и я хотел бы поделиться одним из подходов к решению проблемы. Я уверен, что есть другие способы справиться с этим, что может быть более интересно для вас. Удачи!

#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 - Спасибо за ответ. Используя этот код, мы не можем каждый раз обнаруживать края сохранения карты на одном и том же изображении. он работает случайным образом. –

+0

Если вы работаете со статическим изображением, результат должен быть последовательным: он должен работать или нет (каждый раз). – karlphillip

+0

Благодарим вас за возвращение. Мы выбираем изображение из библиотеки фотографий устройства или фотографируем. Каждый раз, когда мы выбираем одну и ту же библиотеку форм изображения, обнаружение кромок работает по-разному. Есть ли какая-то конкретная причина? –

0

Я не знаю, если это вариант, но вы могли бы пользователь определить его ребра, а не пытаться сделать это программно.

+1

Это может быть лучше помечено как комментарий в вопросе. – karlphillip

2

вместо «чистых» прямоугольных капель, попробуйте пойти на почти прямоугольные.

1- гауссово размывание

2- полутоновый и оператор кэнни

3- Экстракт всех капли (контуры) в изображении и отфильтровать маленькие. для этой цели вы будете использовать функции findcontours и contourarea.

4- используя moments, отфильтруйте непрямоугольные. Сначала вам нужно проверить моменты прямоугольных объектов. Вы можете сделать это самостоятельно или google. Затем перечислите эти моменты и найдите сходство между объектами, создайте свой фильтр как таковой.

Ex: После теста скажите, что вы обнаружили, что центральный момент m30 подобен для прямоугольных объектов -> отфильтруйте объекты с неточным m30.

1

Я знаю, может быть, слишком поздно для этого поста, но я отправляю это на случай, если он может помочь кому-то другому.

Основы ядра Core iOS уже имеют хороший инструмент для обнаружения таких функций, как прямоугольники (начиная с iOS 5), лица, QR-коды и даже области, содержащие текст в неподвижном изображении. Если вы посмотрите CIDetector class, вы найдете то, что вам нужно. Я использую его для OCR-приложения тоже, он очень прост и очень надежный по сравнению с тем, что вы можете делать с OpenCV (я плохо разбираюсь в OpenCV, но CIDetector дает гораздо лучшие результаты с 3-5 строками кода).