2014-05-17 12 views
5

Tôi hiện đang có phương pháp phát hiện thẻ trong hình ảnh và phần lớn nó hoạt động khi ánh sáng tương đối đồng nhất và nền rất tĩnh.Phát hiện thẻ mạnh/chỉnh sửa liên tục OpenCV

Đây là mã tôi đang sử dụng để phôi hoạt động này:

Mat img = inImg.clone(); 
    outImg = Mat(inImg.size(), CV_8UC1); 
    inImg.copyTo(outImg); 

    Mat img_fullRes = img.clone(); 

    pyrDown(img, img); 

    Mat imgGray; 
    cvtColor(img, imgGray, CV_RGB2GRAY); 
    outImg_gray = imgGray.clone(); 
    // Find Edges // 

    Mat detectedEdges = imgGray.clone(); 

    bilateralFilter(imgGray, detectedEdges, 0, 185, 3, 0); 
    Canny(detectedEdges, detectedEdges, 20, 65, 3); 

    dilate(detectedEdges, detectedEdges, Mat::ones(3,3,CV_8UC1)); 
    Mat cdst = img.clone(); 

    vector<Vec4i> lines; 
    HoughLinesP(detectedEdges, lines, 1, CV_PI/180, 60, 50, 3); 
    for(size_t i = 0; i < lines.size(); i++) 
    { 
     Vec4i l = lines[i]; 
     // For debug 
     //line(cdst, cv::Point(l[0], l[1]), cv::Point(l[2], l[3]), Scalar(0,0,255), 1); 
    } 
    //cdst.copyTo(inImg); 

// // Find points of intersection // 

    cv::Rect imgROI; 
    int ext = 10; 
    imgROI.x = ext; 
    imgROI.y = ext; 
    imgROI.width = img.size().width - ext; 
    imgROI.height = img.size().height - ext; 

    int N = lines.size(); 
    // Creating N amount of points // N == lines.size() 
    cv::Point** poi = new cv::Point*[N]; 
    for(int i = 0; i < N; i++) 
     poi[i] = new cv::Point[N]; 
    vector<cv::Point> poiList; 
    for(int i = 0; i < N; i++) 
    { 
     poi[i][i] = cv::Point(-1,-1); 
     Vec4i line1 = lines[i]; 
     for(int j = i + 1; j < N; j++) 
     { 
      Vec4i line2 = lines[j]; 
      cv::Point p = computeIntersect(line1, line2, imgROI); 

      if(p.x != -1) 
      { 
       //line(cdst, p-cv::Point(2,0), p+cv::Point(2,0), Scalar(0,255,0)); 
       //line(cdst, p-cv::Point(0,2), p+cv::Point(0,2), Scalar(0,255,0)); 

       poiList.push_back(p); 
      } 

      poi[i][j] = p; 
      poi[j][i] = p; 
     } 
    } 

    cdst.copyTo(inImg); 

    if(poiList.size()==0) 
    { 
     outImg = inImg.clone(); 
     //circle(outImg, cv::Point(100,100), 50, Scalar(255,0,0), -1); 
     return; 
    } 

    convexHull(poiList, poiList, false, true); 

    for(int i=0; i<poiList.size(); i++) 
    { 
     cv::Point p = poiList[i]; 
     //circle(cdst, p, 3, Scalar(255,0,0), 2); 
    } 
    //Evaluate all possible quadrilaterals 

    cv::Point cardCorners[4]; 
    float metric_max = 0; 
    int Npoi = poiList.size(); 
    for(int p1=0; p1<Npoi; p1++) 
    { 
     cv::Point pts[4]; 
     pts[0] = poiList[p1]; 

     for(int p2=p1+1; p2<Npoi; p2++) 
     { 
      pts[1] = poiList[p2]; 
      if(isCloseBy(pts[1],pts[0])) 
       continue; 

      for(int p3=p2+1; p3<Npoi; p3++) 
      { 
       pts[2] = poiList[p3]; 
       if(isCloseBy(pts[2],pts[1]) || isCloseBy(pts[2],pts[0])) 
        continue; 


       for(int p4=p3+1; p4<Npoi; p4++) 
       { 
        pts[3] = poiList[p4]; 
        if(isCloseBy(pts[3],pts[0]) || isCloseBy(pts[3],pts[1]) 
         || isCloseBy(pts[3],pts[2])) 
         continue; 


        // get the metrics 
        float area = getArea(pts); 

        cv::Point a = pts[0]-pts[1]; 
        cv::Point b = pts[1]-pts[2]; 
        cv::Point c = pts[2]-pts[3]; 
        cv::Point d = pts[3]-pts[0]; 
        float oppLenDiff = abs(a.dot(a)-c.dot(c)) + abs(b.dot(b)-d.dot(d)); 

        float metric = area - 0.35*oppLenDiff; 
        if(metric > metric_max) 
        { 
         metric_max = metric; 
         cardCorners[0] = pts[0]; 
         cardCorners[1] = pts[1]; 
         cardCorners[2] = pts[2]; 
         cardCorners[3] = pts[3]; 
        } 

       } 
      } 
     } 
    } 

    // find the corners corresponding to the 4 corners of the physical card 
    sortPointsClockwise(cardCorners); 

    // Calculate Homography // 

    vector<Point2f> srcPts(4); 
    srcPts[0] = cardCorners[0]*2; 
    srcPts[1] = cardCorners[1]*2; 
    srcPts[2] = cardCorners[2]*2; 
    srcPts[3] = cardCorners[3]*2; 


    vector<Point2f> dstPts(4); 
    cv::Size outImgSize(1400,800); 

    dstPts[0] = Point2f(0,0); 
    dstPts[1] = Point2f(outImgSize.width-1,0); 
    dstPts[2] = Point2f(outImgSize.width-1,outImgSize.height-1); 
    dstPts[3] = Point2f(0,outImgSize.height-1); 

    Mat Homography = findHomography(srcPts, dstPts); 

    // Apply Homography 
    warpPerspective(img_fullRes, outImg, Homography, outImgSize, INTER_CUBIC); 
    outImg.copyTo(inImg); 

đâu computeIntersect được định nghĩa là:

cv::Point computeIntersect(cv::Vec4i a, cv::Vec4i b, cv::Rect ROI) 
{ 
    int x1 = a[0], y1 = a[1], x2 = a[2], y2 = a[3]; 
    int x3 = b[0], y3 = b[1], x4 = b[2], y4 = b[3]; 

    cv::Point p1 = cv::Point (x1,y1); 
    cv::Point p2 = cv::Point (x2,y2); 
    cv::Point p3 = cv::Point (x3,y3); 
    cv::Point p4 = cv::Point (x4,y4); 
    // Check to make sure all points are within the image boundrys, if not reject them. 
    if(!ROI.contains(p1) || !ROI.contains(p2) 
     || !ROI.contains(p3) || !ROI.contains(p4)) 
     return cv::Point (-1,-1); 

    cv::Point vec1 = p1-p2; 
    cv::Point vec2 = p3-p4; 

    float vec1_norm2 = vec1.x*vec1.x + vec1.y*vec1.y; 
    float vec2_norm2 = vec2.x*vec2.x + vec2.y*vec2.y; 
    float cosTheta = (vec1.dot(vec2))/sqrt(vec1_norm2*vec2_norm2); 

    float den = ((float)(x1-x2) * (y3-y4)) - ((y1-y2) * (x3-x4)); 
    if(den != 0) 
    { 
     cv::Point2f pt; 
     pt.x = ((x1*y2 - y1*x2) * (x3-x4) - (x1-x2) * (x3*y4 - y3*x4))/den; 
     pt.y = ((x1*y2 - y1*x2) * (y3-y4) - (y1-y2) * (x3*y4 - y3*x4))/den; 

     if(!ROI.contains(pt)) 
      return cv::Point (-1,-1); 

     // no-confidence metric 
     float d1 = MIN(dist2(p1,pt), dist2(p2,pt))/vec1_norm2; 
     float d2 = MIN(dist2(p3,pt), dist2(p4,pt))/vec2_norm2; 

     float no_confidence_metric = MAX(sqrt(d1),sqrt(d2)); 

     // If end point ratios are greater than .5 reject 
     if(no_confidence_metric < 0.5 && cosTheta < 0.707) 
      return cv::Point (int(pt.x+0.5), int(pt.y+0.5)); 
    } 

    return cv::Point(-1, -1); 
} 

sortPointsClockWise được định nghĩa là:

void sortPointsClockwise(cv::Point a[]) 
{ 
    cv::Point b[4]; 

    cv::Point ctr = (a[0]+a[1]+a[2]+a[3]); 
    ctr.x /= 4; 
    ctr.y /= 4; 
    b[0] = a[0]-ctr; 
    b[1] = a[1]-ctr; 
    b[2] = a[2]-ctr; 
    b[3] = a[3]-ctr; 

    for(int i=0; i<4; i++) 
    { 
     if(b[i].x < 0) 
     { 
      if(b[i].y < 0) 
       a[0] = b[i]+ctr; 
      else 
       a[3] = b[i]+ctr; 
     } 
     else 
     { 
      if(b[i].y < 0) 
       a[1] = b[i]+ctr; 
      else 
       a[2] = b[i]+ctr; 
     } 
    } 

} 

getArea là được định nghĩa là:

float getArea(cv::Point arr[]) 
{ 
    cv::Point diag1 = arr[0]-arr[2]; 
    cv::Point diag2 = arr[1]-arr[3]; 

    return 0.5*(diag1.cross(diag2)); 
} 

isCloseBy được định nghĩa là:

bool isCloseBy(cv::Point p1, cv::Point p2) 
{ 
    int D = 10; 
    // Checking that X values are within 10, same for Y values. 
    return (abs(p1.x-p2.x)<=D && abs(p1.y-p2.y)<=D); 
} 

Và cuối cùng dist2:

float dist2(cv::Point p1, cv::Point p2) 
{ 
    return float((p1.x-p2.x)*(p1.x-p2.x) + (p1.y-p2.y)*(p1.y-p2.y)); 
} 

Dưới đây là một vài hình ảnh thử nghiệm và kết quả của họ:

Xin lỗi vì bài viết rất dài, tuy nhiên tôi hy vọng ai đó có thể đề xuất một cách tôi có thể làm phương pháp của tôi để trích xuất các thẻ từ hình ảnh mạnh mẽ hơn. Một trong đó có thể xử lý tốt hơn các nền gây rối cùng với ánh sáng không nhất quán.

Khi thẻ được đặt trên nền tương phản có ánh sáng tốt, phương pháp của tôi hoạt động gần 90% thời gian. Nhưng rõ ràng là tôi cần một cách tiếp cận mạnh mẽ hơn.

Có ai có bất kỳ đề xuất nào không?

Cảm ơn.

mưu toan của soloution dhanushka của

Mat gray, bw;  pyrDown(inImg, inImg); 
cvtColor(inImg, gray, CV_RGB2GRAY); 
int morph_size = 3; 
Mat element = getStructuringElement(MORPH_ELLIPSE, cv::Size(4*morph_size + 1, 2*morph_size+1), cv::Point(morph_size, morph_size)); 

morphologyEx(gray, gray, 2, element); 
threshold(gray, bw, 160, 255, CV_THRESH_BINARY); 

vector<vector<cv::Point> > contours; 
vector<Vec4i> hierarchy; 
findContours(bw, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, cv::Point(0, 0)); 

int largest_area=0; 
int largest_contour_index=0; 
cv::Rect bounding_rect; 

for(int i = 0; i< contours.size(); i++) 
{ 
    double a=contourArea(contours[i],false); // Find the area of contour 
    if(a>largest_area){ 
     largest_area=a; 
     largest_contour_index=i;    //Store the index of largest contour 
     bounding_rect=boundingRect(contours[i]); 
    } 
} 

//Scalar color(255,255,255); 
rectangle(inImg, bounding_rect, Scalar(0,255,0),1, 8,0); 
Mat biggestRect = inImg(bounding_rect); 
Mat card1 = biggestRect.clone(); 
+0

[giấy này] (http://www.cs.stevens.edu/~ghua/publication/ICIP06.pdf), bạn có thể tìm thấy hữu ích. – 01zhou

Trả lời

2

Một cách tiếp cận tổng quát hơn chắc chắn sẽ giống như Rutger Nijlunsing được đề xuất trong câu trả lời của ông. Tuy nhiên, trong trường hợp của bạn, ít nhất là đối với các hình ảnh mẫu được cung cấp, một cách tiếp cận rất đơn giản như mở hình thái tiếp theo là việc làm tròn, xử lý đường viền và lồi sẽ mang lại kết quả bạn muốn. Sử dụng một phiên bản thu nhỏ của các hình ảnh để xử lý để bạn không phải sử dụng một hạt nhân lớn cho các hoạt động hình thái học. Dưới đây là những hình ảnh được xử lý theo cách này.

pyrDown(large, rgb0); 
    pyrDown(rgb0, rgb0); 
    pyrDown(rgb0, rgb0); 

    Mat small; 
    cvtColor(rgb0, small, CV_BGR2GRAY); 

    Mat morph; 
    Mat kernel = getStructuringElement(MORPH_ELLIPSE, Size(11, 11)); 
    morphologyEx(small, morph, MORPH_OPEN, kernel); 

    Mat bw; 
    threshold(morph, bw, 0, 255.0, CV_THRESH_BINARY | CV_THRESH_OTSU); 

    Mat bdry; 
    kernel = getStructuringElement(MORPH_ELLIPSE, Size(3, 3)); 
    erode(bw, bdry, kernel); 
    subtract(bw, bdry, bdry); 

    // do contour processing on bdry 

Cách tiếp cận này sẽ không hoạt động nói chung, vì vậy tôi khuyên bạn nên đề xuất điều gì đó như Rutger đề xuất.

enter image description here

enter image description here

enter image description here

+0

Bạn có thể cung cấp một số mã mẫu không? Cảm ơn! – Clip

+0

Tôi nhận được nó làm việc với thẻ trắng trên gần như tất cả các nền, nhưng tôi không thể làm cho nó làm việc cho thẻ màu. Bạn có thể cung cấp mã bạn đã sử dụng không?Tôi đã cập nhật câu hỏi của mình với nỗ lực của mình. – Clip

+0

Tôi đã chỉnh sửa bài đăng của mình để cung cấp cho bạn một số gợi ý. Bạn cần thực hiện một số thay đổi đối với mã. – dhanushka

8

Nghệ thuật xử lý hình ảnh là (trong 10 năm kinh nghiệm của tôi) chỉ là: một nghệ thuật. Không có câu trả lời duy nhất, và luôn luôn có nhiều cách để làm điều đó. Và nó sẽ chắc chắn thất bại trong một số trường hợp.

Trong kinh nghiệm làm việc tự động phát hiện các tính năng trong hình ảnh y khoa, phải mất một thời gian dài để xây dựng thuật toán đáng tin cậy, nhưng trong kết quả tốt nhất thu được bằng thuật toán đơn giản tương đối. Tuy nhiên, phải mất rất nhiều thời gian để nhận được vào thuật toán đơn giản này.

Để có được điều này, cách tiếp cận chung luôn là như nhau:

  • bắt đầu là xây dựng một cơ sở dữ liệu lớn các thử nghiệm hình ảnh (ít nhất là 100). Điều này định nghĩa hình ảnh 'bình thường' sẽ hoạt động. Bằng cách thu thập các hình ảnh bạn đã bắt đầu suy nghĩ về vấn đề này.
  • chú thích các hình ảnh để xây dựng một loại 'chân lý cơ bản'. Trong trường hợp này, 'sự thật cơ bản' phải chứa 4 góc của thẻ vì đây là những điểm thú vị.
  • tạo một ứng dụng chạy trên các hình ảnh này một thuật toán và so sánh kết quả với sự thật mặt đất. Trong trường hợp này, 'so sánh với sự thật mặt đất' sẽ là lấy khoảng cách trung bình của điểm góc 4 được tìm thấy với các điểm góc chân lý.
  • Xuất tệp được phân tách bằng tab mà bạn gọi .xls và do đó có thể được mở (trên Windows) trong Excel bằng cách nhấp đúp. Tốt để có được một cái nhìn tổng quan nhanh chóng của các trường hợp. Nhìn vào những trường hợp xấu nhất trước. Sau đó, mở các trường hợp này theo cách thủ công để tìm hiểu lý do tại sao chúng không hoạt động.
  • Bây giờ bạn đã sẵn sàng để thay đổi thuật toán. Thay đổi một cái gì đó và chạy lại. So sánh trang tính Excel mới với trang tính Excel cũ. Bây giờ bạn bắt đầu nhận ra sự cân bằng mà bạn phải thực hiện.

Điều đó đã nói, tôi nghĩ rằng bạn cần phải trả lời những câu hỏi này trong thời gian điều chỉnh của thuật toán:

  • Bạn có cho phép một chút thẻ gấp? Vì vậy, không có đường thẳng hoàn toàn? Nếu vậy, hãy tập trung nhiều hơn vào các góc thay vì các đường thẳng/cạnh.
  • Bạn có cho phép sự khác biệt dần về ánh sáng không? Nếu vậy, bộ lọc có độ tương phản cục bộ có thể hữu ích.
  • Bạn có cho phép cùng một màu cho thẻ làm nền không? Nếu vậy, bạn phải tập trung vào nội dung của thẻ thay vì biên giới của thẻ.
  • Bạn có cho phép các ống kính không hoàn hảo không? Nếu vậy, để mở rộng?
  • Bạn có cho phép xoay thẻ không? Nếu vậy, để mở rộng?
  • Nền có đồng nhất về màu sắc và/hoặc kết cấu không?
  • Thẻ nhỏ nhất có thể phát hiện được có liên quan đến kích thước hình ảnh như thế nào? Nếu bạn cho rằng phải có ít nhất 80% chiều rộng hoặc chiều cao, bạn sẽ có được độ mạnh mẽ trở lại.
  • Nếu có nhiều thẻ hiển thị trong hình ảnh, thuật toán phải mạnh mẽ và chỉ chọn một hoặc bất kỳ đầu ra nào không?
  • Nếu không thấy thẻ nào, nó có phát hiện được trường hợp này không? Xây dựng trong việc phát hiện trường hợp này sẽ làm cho nó thân thiện với người dùng hơn ('không tìm thấy thẻ'), nhưng cũng kém mạnh mẽ hơn.

Điều này sẽ làm cho các yêu cầu và giả định về hình ảnh cần có. Giả định mà bạn có thể dựa vào rất mạnh: chúng làm cho thuật toán nhanh, mạnh mẽ và đơn giản nếu bạn chọn đúng thuật toán. Ngoài ra, hãy để các yêu cầu và giả định này là một phần của cơ sở dữ liệu thử nghiệm.

Vậy tôi sẽ chọn gì? Dựa trên ba hình ảnh bạn đã cung cấp, tôi sẽ bắt đầu bằng một cái gì đó như:

  • Giả sử các thẻ đang lấp đầy hình ảnh từ 50% đến 100%.
  • Giả sử các thẻ được xoay nhiều nhất là 10 độ.
  • Giả sử các góc có thể nhìn thấy rõ.
  • Giả sử tỉ lệ khung hình (chiều cao chia cho chiều rộng) của thẻ được giữa 1/3 và 3.
  • Giả sử không có đối tượng thẻ giống như trong nền

Thuật toán sau đó sẽ như thế nào:

  • Phát hiện trong mỗi góc phần tư của hình ảnh một góc cụ thể với bộ lọc góc. Vì vậy, trong góc phần tư phía trên bên trái của hình ảnh góc trên bên trái của thẻ. Tìm ví dụ tại http://www.ee.surrey.ac.uk/CVSSP/demos/corners/results3.html hoặc sử dụng chức năng OpenCV cho nó như cornerHarris.
  • Để mạnh mẽ hơn, hãy tính toán nhiều hơn một góc cho mỗi góc phần tư.
  • Cố gắng xây dựng hình bình hành với một góc cho mỗi góc phần tư bằng cách kết hợp các điểm từ mỗi góc phần tư. Tạo một chức năng tập thể dục mang đến cho điểm số cao hơn để:

    • có góc nội bộ gần 90 độ
    • được lớn
    • tùy chọn, so sánh các góc của thẻ dựa trên ánh sáng hoặc tính năng khác.

    Chức năng thể dục này cung cấp nhiều khả năng điều chỉnh sau này.

  • Trả lại hình bình hành có điểm số cao nhất.

Vậy tại sao lại sử dụng phát hiện góc thay vì phát hiện hough để phát hiện đường? Theo quan điểm của tôi, hough-transform (tiếp theo là chậm) khá nhạy cảm với các mẫu nền (đó là những gì bạn nhìn thấy trong hình đầu tiên của bạn - nó phát hiện một dòng mạnh hơn trong nền sau đó của thẻ), và nó không thể xử lý một chút đường cong tốt, trừ khi bạn sử dụng một kích thước thùng lớn hơn mà sẽ làm xấu đi phát hiện.

Chúc may mắn!

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