2013-06-27 39 views
11

Hi hiện tại tôi đang làm việc trên ứng dụng đọc OCR, nơi tôi có thể chụp ảnh thẻ thành công bằng cách sử dụng khung công tác AVFoundation.Phát hiện các cạnh của thẻ có các góc bo tròn

Bước tiếp theo, tôi cần phải tìm ra các cạnh của thẻ, để tôi có thể cắt hình ảnh thẻ từ hình ảnh chính được chụp & sau đó tôi có thể gửi nó đến công cụ OCR để xử lý.

Vấn đề chính bây giờ là tìm các cạnh của thẻ & tôi đang sử dụng mã bên dưới (được lấy từ một dự án mã nguồn mở khác) sử dụng OpenCV cho mục đích này.Đó hoạt động tốt nếu thẻ là Thẻ hình chữ nhật thuần hoặc Giấy . Nhưng khi tôi sử dụng thẻ có góc tròn (ví dụ: Giấy phép lái xe), nó không thể phát hiện được. Ngoài ra tôi không có nhiều chuyên môn trong OpenCV, bất kỳ ai có thể giúp tôi trong việc giải quyết vấn đề này?

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

bạn có thể cung cấp cho tôi liên kết để cắt thẻ không? – faiziii

+0

@raaz i có yêu cầu tương tự, bạn có thể đề xuất cho tôi rằng dự án nguồn mở mà bạn đã sử dụng. Nó sẽ giúp ích rất nhiều. –

+1

@John Bạn sẽ chia sẻ một hoặc hai hình ảnh mẫu? – karlphillip

Trả lời

12

Triển khai ngây thơ này dựa trên một số kỹ thuật được minh họa trong squares.cpp, có sẵn trong thư mục mẫu OpenCV. Các bài viết sau đây cũng thảo luận về các ứng dụng tương tự:

@ John, mã dưới đây đã được thử nghiệm với hình ảnh mẫu mà bạn cung cấp và một số khác tôi đã tạo:

Đường ống xử lý bắt đầu bằng findSquares(), đơn giản hóa cùng một chức năng được thực hiện bởi OpenCV squares.cpp bản trình diễn.Chức năng này chuyển đổi các hình ảnh đầu vào thành trắng đen và áp dụng một mờ để cải thiện việc phát hiện các cạnh (Canny):

Các phát hiện cạnh là tốt, nhưng một hoạt động hình thái (sự giãn nở) là cần thiết để tham gia các đường lân cận:

Sau đó chúng tôi cố gắng tìm những đường nét (cạnh) và lắp ráp ô vuông ra khỏi chúng. Nếu chúng ta cố gắng để vẽ tất cả các ô vuông phát hiện trên hình ảnh đầu vào, đây sẽ là kết quả:

Nó có vẻ tốt, nhưng nó không phải là chính xác những gì chúng tôi đang tìm kiếm kể từ khi có quá nhiều phát hiện hình vuông. Tuy nhiên, hình vuông lớn nhất thực sự là thẻ, do đó, từ đây trên nó khá đơn giản và chúng tôi chỉ ra những hình vuông nào là lớn nhất. Đó chính là điều mà findLargestSquare() thực hiện.

Một khi chúng ta biết quảng trường lớn nhất, chúng tôi chỉ đơn giản là vẽ chấm đỏ ở các góc của hình vuông cho mục đích gỡ lỗi:

Như bạn có thể thấy, việc phát hiện không phải là hoàn hảo nhưng nó có vẻ đủ tốt cho hầu hết các công dụng. Đây không phải là một giải pháp mạnh mẽ và tôi chỉ muốn chia sẻ một cách tiếp cận để giải quyết vấn đề. Tôi chắc chắn rằng có nhiều cách khác để giải quyết vấn đề này có thể thú vị hơn đối với bạn. Chúc may mắn!

#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 - Cảm ơn bạn đã trả lời, Bằng cách sử dụng mã này, chúng tôi không thể phát hiện các cạnh lưu cho thẻ trên cùng một hình ảnh mỗi lần. nó hoạt động ngẫu nhiên. –

+0

Nếu bạn đang làm việc với một hình ảnh tĩnh, kết quả phải nhất quán: nó sẽ hoạt động hay không (mỗi lần). – karlphillip

+0

Cảm ơn bạn đã quay trở lại, Chúng tôi đang chọn hình ảnh từ thư viện ảnh thiết bị hoặc bằng cách chụp ảnh. Mỗi khi chúng ta chọn cùng một thư viện hình ảnh, phát hiện cạnh hoạt động khác nhau. Có lý do cụ thể nào không? –

0

Tôi không biết đó có phải là tùy chọn hay không, nhưng bạn có thể nhờ người dùng xác định các cạnh của nó thay vì cố gắng thực hiện theo chương trình.

+1

Điều này có thể được đặt tốt hơn làm nhận xét trong câu hỏi. – karlphillip

2

thay vì các hình chữ nhật "tinh khiết" hình chữ nhật, hãy cố gắng tìm các hình gần như hình chữ nhật.

1- gaussian blur

2- màu xám và khôn ngoan phát hiện cạnh

3 chiết xuất tất cả các đốm màu (đường viền) trong hình ảnh của bạn và lọc ra những cái nhỏ. bạn sẽ sử dụng hàm findcontours và contourarea cho mục đích đó.

4- sử dụng moments, lọc ra các hình chữ nhật không phải hình chữ nhật. Trước tiên, bạn cần phải kiểm tra các khoảnh khắc của các đối tượng giống như hình chữ nhật. Bạn có thể tự mình làm điều đó hoặc google. Sau đó liệt kê những khoảnh khắc đó và tìm thấy sự giống nhau giữa các đối tượng, tạo bộ lọc của bạn như vậy.

Ví dụ: Sau khi thử nghiệm, giả sử bạn phát hiện ra khoảnh khắc trung tâm m30 tương tự đối với các đối tượng giống hình chữ nhật -> lọc ra các đối tượng có m30 không chính xác.

1

Tôi biết có thể đã quá muộn cho bài đăng này, nhưng tôi đăng bài này trong trường hợp nó có thể giúp người khác.

Khuôn khổ hình ảnh lõi iOS đã có công cụ tốt để phát hiện các tính năng như hình chữ nhật (kể từ iOS 5), khuôn mặt, mã QR và thậm chí cả khu vực chứa văn bản trong hình ảnh tĩnh. Nếu bạn kiểm tra các CIDetector class bạn sẽ tìm thấy những gì bạn cần. Tôi đang sử dụng nó cho một ứng dụng OCR, nó siêu dễ dàng và rất đáng tin cậy so với những gì bạn có thể làm với OpenCV (Tôi không tốt với OpenCV, nhưng CIDetector cho kết quả tốt hơn nhiều với 3-5 dòng mã).

Các vấn đề liên quan