2014-10-17 17 views
7

Điều tôi đang cố gắng đo là độ dày của khung kính mắt. Tôi đã có ý tưởng để đo độ dày của các đường viền của khung (có thể là một cách tốt hơn?). Tôi cho đến nay vạch ra khung kính, nhưng có những khoảng trống mà các dòng không đáp ứng. Tôi đã nghĩ đến việc sử dụng HoughLinesP, nhưng tôi không chắc đây có phải là thứ tôi cần hay không.Phát hiện kính

Cho đến nay tôi đã tiến hành các bước sau: image

  • Chuyển đổi sang dạng grayscale
  • Tạo ROI xung quanh khu vực mắt/kính
  • Blur hình ảnh
  • giãn hình ảnh (đã làm điều này để loại bỏ bất kỳ kính có khung mỏng nào)
  • Tiến hành dò ​​tìm cận cảnh vú
  • Tìm thấy đường viền

Đây là kết quả:

Đây là mã của tôi cho đến nay:

//convert to grayscale 
cv::Mat grayscaleImg; 
cv::cvtColor(img, grayscaleImg, CV_BGR2GRAY); 

//create ROI 
cv::Mat eyeAreaROI(grayscaleImg, centreEyesRect); 
cv::imshow("roi", eyeAreaROI); 

//blur 
cv::Mat blurredROI; 
cv::blur(eyeAreaROI, blurredROI, Size(3,3)); 
cv::imshow("blurred", blurredROI); 

//dilate thin lines 
cv::Mat dilated_dst; 
int dilate_elem = 0; 
int dilate_size = 1; 
int dilate_type = MORPH_RECT; 

cv::Mat element = getStructuringElement(dilate_type, 
    cv::Size(2*dilate_size + 1, 2*dilate_size+1), 
    cv::Point(dilate_size, dilate_size)); 

cv::dilate(blurredROI, dilated_dst, element); 
cv::imshow("dilate", dilated_dst); 

//edge detection 
int lowThreshold = 100; 
int ratio = 3; 
int kernel_size = 3;  

cv::Canny(dilated_dst, dilated_dst, lowThreshold, lowThreshold*ratio, kernel_size); 

//create matrix of the same type and size as ROI 
Mat dst; 
dst.create(eyeAreaROI.size(), dilated_dst.type()); 
dst = Scalar::all(0); 

dilated_dst.copyTo(dst, dilated_dst); 
cv::imshow("edges", dst); 

//join the lines and fill in 
vector<Vec4i> hierarchy; 
vector<vector<Point>> contours; 

cv::findContours(dilated_dst, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE); 
cv::imshow("contours", dilated_dst); 

Tôi không hoàn toàn chắc chắn những gì các bước tiếp theo sẽ là , hoặc như tôi đã nói ở trên, nếu tôi nên sử dụng HoughLinesP và cách thực hiện nó. Bất kỳ giúp đỡ được rất nhiều đánh giá cao!

+1

Bạn đã cân nhắc phân đoạn chưa? Bằng bất kỳ phương tiện nào cần thiết, hãy tách các pixel của bạn thành hai nhóm: (1) kính thuộc các pixel (2) không thuộc về các điểm ảnh kính. Sử dụng khái niệm siêu pixel: mỗi pixel phải có các đặc điểm khác nhau: màu sắc, vị trí, nếu chúng thuộc về bất kỳ đường bao nào bạn đã tìm thấy, nếu chúng nằm trên các cạnh, v.v ... – William

+1

tôi nghĩ đường nét của bạn không tốt bởi vì có một số khoảng trống. Cố gắng làm giãn các kết quả của bạn trước khi cắt đường viền và xác minh các đường nét của bạn bằng cách vẽ chúng vào một hình ảnh mới. Nếu các đường bao được trích xuất một cách chính xác, bạn có thể tính toán biến đổi khoảng cách từ đường bao đầy ngược. độ dày khung có thể có thể xấp xỉ bằng khoảng cách tìm thấy tối đa * 2. – Micka

+1

Xin chào @William, cảm ơn bạn đã trả lời! Tôi đã nghĩ về việc thực hiện phát hiện và phân đoạn da từ đó. Cũng đã được tìm kiếm vào vị trí có thể xảy ra và muốn. Tôi không chắc chắn làm thế nào để đi về phát hiện những điểm ảnh thuộc về những gì, nhưng sẽ nhìn vào nó. – LKB

Trả lời

3

Tôi nghĩ rằng có 2 vấn đề chính.

  1. phân khúc kính khung

  2. tìm ra độ dày của khung phân đoạn

bây giờ tôi sẽ đăng một cách để phân khúc ly hình ảnh mẫu của bạn. Có lẽ phương pháp này cũng sẽ hoạt động cho các hình ảnh khác nhau, nhưng có thể bạn sẽ phải điều chỉnh các thông số hoặc bạn có thể sử dụng các ý tưởng chính.

Ý tưởng chính là: Đầu tiên, hãy tìm đường viền lớn nhất trong hình ảnh, nên là kính. Thứ hai, tìm hai đường nét lớn nhất trong đường viền lớn nhất được tìm thấy trước đó, mà nên là kính trong khung!

tôi sử dụng hình ảnh này như là đầu vào (mà phải được hình ảnh mờ nhưng không giãn của bạn):

enter image description here

// this functions finds the biggest X contours. Probably there are faster ways, but it should work... 
std::vector<std::vector<cv::Point>> findBiggestContours(std::vector<std::vector<cv::Point>> contours, int amount) 
{ 
    std::vector<std::vector<cv::Point>> sortedContours; 

    if(amount <= 0) amount = contours.size(); 
    if(amount > contours.size()) amount = contours.size(); 

    for(int chosen = 0; chosen < amount;) 
    { 
     double biggestContourArea = 0; 
     int biggestContourID = -1; 
     for(unsigned int i=0; i<contours.size() && contours.size(); ++i) 
     { 
      double tmpArea = cv::contourArea(contours[i]); 
      if(tmpArea > biggestContourArea) 
      { 
       biggestContourArea = tmpArea; 
       biggestContourID = i; 
      } 
     } 

     if(biggestContourID >= 0) 
     { 
      //std::cout << "found area: " << biggestContourArea << std::endl; 
      // found biggest contour 
      // add contour to sorted contours vector: 
      sortedContours.push_back(contours[biggestContourID]); 
      chosen++; 
      // remove biggest contour from original vector: 
      contours[biggestContourID] = contours.back(); 
      contours.pop_back(); 
     } 
     else 
     { 
      // should never happen except for broken contours with size 0?!? 
      return sortedContours; 
     } 

    } 

    return sortedContours; 
} 

int main() 
{ 
    cv::Mat input = cv::imread("../Data/glass2.png", CV_LOAD_IMAGE_GRAYSCALE); 
    cv::Mat inputColors = cv::imread("../Data/glass2.png"); // used for displaying later 
    cv::imshow("input", input); 

    //edge detection 
    int lowThreshold = 100; 
    int ratio = 3; 
    int kernel_size = 3;  

    cv::Mat canny; 
    cv::Canny(input, canny, lowThreshold, lowThreshold*ratio, kernel_size); 
    cv::imshow("canny", canny); 

    // close gaps with "close operator" 
    cv::Mat mask = canny.clone(); 
    cv::dilate(mask,mask,cv::Mat()); 
    cv::dilate(mask,mask,cv::Mat()); 
    cv::dilate(mask,mask,cv::Mat()); 
    cv::erode(mask,mask,cv::Mat()); 
    cv::erode(mask,mask,cv::Mat()); 
    cv::erode(mask,mask,cv::Mat()); 

    cv::imshow("closed mask",mask); 

    // extract outermost contour 
    std::vector<cv::Vec4i> hierarchy; 
    std::vector<std::vector<cv::Point>> contours; 
    //cv::findContours(mask, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE); 
    cv::findContours(mask, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE); 


    // find biggest contour which should be the outer contour of the frame 
    std::vector<std::vector<cv::Point>> biggestContour; 
    biggestContour = findBiggestContours(contours,1); // find the one biggest contour 
    if(biggestContour.size() < 1) 
    { 
     std::cout << "Error: no outer frame of glasses found" << std::endl; 
     return 1; 
    } 

    // draw contour on an empty image 
    cv::Mat outerFrame = cv::Mat::zeros(mask.rows, mask.cols, CV_8UC1); 
    cv::drawContours(outerFrame,biggestContour,0,cv::Scalar(255),-1); 
    cv::imshow("outer frame border", outerFrame); 

    // now find the glasses which should be the outer contours within the frame. therefore erode the outer border ;) 
    cv::Mat glassesMask = outerFrame.clone(); 
    cv::erode(glassesMask,glassesMask, cv::Mat()); 
    cv::imshow("eroded outer",glassesMask); 

    // after erosion if we dilate, it's an Open-Operator which can be used to clean the image. 
    cv::Mat cleanedOuter; 
    cv::dilate(glassesMask,cleanedOuter, cv::Mat()); 
    cv::imshow("cleaned outer",cleanedOuter); 


    // use the outer frame mask as a mask for copying canny edges. The result should be the inner edges inside the frame only 
    cv::Mat glassesInner; 
    canny.copyTo(glassesInner, glassesMask); 

    // there is small gap in the contour which unfortunately cant be closed with a closing operator... 
    cv::dilate(glassesInner, glassesInner, cv::Mat()); 
    //cv::erode(glassesInner, glassesInner, cv::Mat()); 
    // this part was cheated... in fact we would like to erode directly after dilation to not modify the thickness but just close small gaps. 
    cv::imshow("innerCanny", glassesInner); 


    // extract contours from within the frame 
    std::vector<cv::Vec4i> hierarchyInner; 
    std::vector<std::vector<cv::Point>> contoursInner; 
    //cv::findContours(glassesInner, contoursInner, hierarchyInner, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE); 
    cv::findContours(glassesInner, contoursInner, hierarchyInner, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE); 

    // find the two biggest contours which should be the glasses within the frame 
    std::vector<std::vector<cv::Point>> biggestInnerContours; 
    biggestInnerContours = findBiggestContours(contoursInner,2); // find the one biggest contour 
    if(biggestInnerContours.size() < 1) 
    { 
     std::cout << "Error: no inner frames of glasses found" << std::endl; 
     return 1; 
    } 

    // draw the 2 biggest contours which should be the inner glasses 
    cv::Mat innerGlasses = cv::Mat::zeros(mask.rows, mask.cols, CV_8UC1); 
    for(unsigned int i=0; i<biggestInnerContours.size(); ++i) 
     cv::drawContours(innerGlasses,biggestInnerContours,i,cv::Scalar(255),-1); 

    cv::imshow("inner frame border", innerGlasses); 

    // since we dilated earlier and didnt erode quite afterwards, we have to erode here... this is a bit of cheating :-(
    cv::erode(innerGlasses,innerGlasses,cv::Mat()); 

    // remove the inner glasses from the frame mask 
    cv::Mat fullGlassesMask = cleanedOuter - innerGlasses; 
    cv::imshow("complete glasses mask", fullGlassesMask); 

    // color code the result to get an impression of segmentation quality 
    cv::Mat outputColors1 = inputColors.clone(); 
    cv::Mat outputColors2 = inputColors.clone(); 
    for(int y=0; y<fullGlassesMask.rows; ++y) 
     for(int x=0; x<fullGlassesMask.cols; ++x) 
     { 
      if(!fullGlassesMask.at<unsigned char>(y,x)) 
       outputColors1.at<cv::Vec3b>(y,x)[1] = 255; 
      else 
       outputColors2.at<cv::Vec3b>(y,x)[1] = 255; 

     } 

    cv::imshow("output", outputColors1); 

    /* 
    cv::imwrite("../Data/Output/face_colored.png", outputColors1); 
    cv::imwrite("../Data/Output/glasses_colored.png", outputColors2); 
    cv::imwrite("../Data/Output/glasses_fullMask.png", fullGlassesMask); 
    */ 

    cv::waitKey(-1); 
    return 0; 
} 

tôi nhận được kết quả này để phân đoạn:

enter image description here

lớp phủ trong hình ảnh gốc sẽ cho bạn ấn tượng về chất lượng:

enter image description here

và nghịch đảo:

enter image description here

Có một số bộ phận phức tạp trong các mã và nó không sắp xếp gọn gàng lên được nêu ra. Tôi hy vọng điều đó là dễ hiểu.

Bước tiếp theo là tính toán độ dày của khung được phân đoạn. Đề xuất của tôi là tính toán biến đổi khoảng cách của mặt nạ đảo ngược. Từ điều này, bạn sẽ muốn tính toán một phát hiện sườn núi hoặc skeletonize mặt nạ để tìm các sườn núi. Sau đó sử dụng giá trị trung bình của khoảng cách sườn núi.

Dù sao tôi hy vọng bài đăng này có thể giúp bạn một chút, mặc dù đây không phải là giải pháp.

+0

Xin chào Micka, cảm ơn một số tiền không thể tin được vì đã dành thời gian để giúp tôi.Tôi đã chạy mã của bạn và có đầu ra sau: http://i.imgur.com/aNnXOlq.png Nó hơi khác một chút với bạn (điều đó sẽ xảy ra như thế nào?), Tức là một trong những đường viền kính bên trong không đã đóng cửa. Bất kỳ ý tưởng làm thế nào tôi muốn đóng này lên? Tôi sẽ có một cái nhìn xung quanh web và chơi với các mã trong thời gian có nghĩa là, xem nếu tôi có thể sửa chữa nó lên. – LKB

+1

Rất tiếc, trước tiên hãy quên làm mờ hình ảnh. :) – LKB

+1

Hãy coi chừng rằng bạn có thể có vấn đề tương tự với các hình ảnh khác nhau! – Micka

1

Tùy thuộc vào ánh sáng, màu khung hình vv điều này có thể hoặc có thể không hoạt động nhưng cách phát hiện màu đơn giản để tách khung? Màu khung thường sẽ tối hơn nhiều so với da người. Bạn sẽ kết thúc với một hình ảnh nhị phân (chỉ màu đen và trắng) và bằng cách tính số (diện tích) của các pixel đen bạn sẽ có được diện tích của khung.

Một cách khác có thể là để phát hiện cạnh tốt hơn, bằng cách điều chỉnh/giãn/xói mòn/cả hai cho đến khi bạn nhận được đường nét tốt hơn. Bạn cũng sẽ cần phải phân biệt các đường viền từ các ống kính và sau đó áp dụng cvContourArea.

+0

Cảm ơn bạn đã trả lời, Sonny! Tôi không chắc chắn về việc phát hiện màu sắc, nhưng tôi có thể cho nó một đi! Tôi nghĩ đề xuất sau của bạn về việc dò tìm đường viền tinh chỉnh có thể hoạt động tốt hơn, vì vậy tôi cũng sẽ xem cách nó đi. – LKB