2013-06-27 126 views
11

嗨,目前我正在研究OCR阅读应用程序,我已经成功地使用AVFoundation框架捕获卡片图像。检测圆角卡片的边缘

对于下一步,我需要找出卡的边缘,以便我可以从主捕获的图像&中裁剪卡图像,稍后我可以将它发送到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我有类似的要求,你可以建议我,你已经使用的开源项目。这将是很大的帮助。 – 2014-01-28 12:08:50

+1

@John你打算分享一个还是两个样本图片? – karlphillip

回答

12

这个天真的实现基于squares.cpp中演示的一些技术,可在OpenCV示例目录中找到。下面的帖子也讨论了类似的应用:

@约翰,下面的代码已经过测试,您提供的样本图像,另外一个我创建的:

处理流水线开始于findSquares(),简化了OpenCV的squares.cpp演示实现的相同功能。这个函数转换输入图像为灰度,并应用模糊以改善的边缘的检测(Canny算子):

边缘检测是好的,但一个形态运算(扩张)是需要加入附近的线:

之后,我们试图找到的轮廓(边缘)和组装广场了出来。如果我们试图借鉴输入图像的所有检测到的广场,这将是结果:

它看起来不错,但它不正是我们要找的,因为有太多的检测广场。然而,最大的广场实际上就是这张牌,所以从这里开始就很简单,我们只是找出哪个广场是最大的。这正是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-灰度和在图像Canny边缘检测

3-提取所有斑点(轮廓),并过滤出小的。你将使用findcontours和contourarea函数来达到这个目的。

4-使用moments,滤除非矩形的。首先你需要检查矩形类物体的时刻。你可以自己做或谷歌它。然后列出这些时刻并找出对象之间的相似性,创建您的过滤器。

例如:测试之后,说你找到了中心矩M30的是矩形状物体类似 - 有不准确的M30>过滤掉的对象。

1

我知道这篇文章可能已经太晚了,但是我发布这个以防万一它可以帮助别人。

iOS的核心图片框架已经具有良好的工具来检测功能,如矩形(因为iOS 5中),面QR码,甚至含在静止图像的文本区域。如果你看看CIDetector class,你会发现你需要的东西。我使用它的OCR应用程序也是如此,这是超级容易和你比起来,可以用OpenCV的做的非常可靠的(我也不好跟OpenCV的,但CIDetector给出了更好的结果与3-5行代码)。