2014-10-17 12 views
7

Was ich versuche zu tun, ist die Dicke der Brillenfassungen zu messen. Ich hatte die Idee, die Dicke der Rahmenkonturen zu messen (ist vielleicht ein besserer Weg?). Ich habe bisher den Rahmen der Brille skizziert, aber es gibt Lücken, wo die Linien nicht zusammentreffen. Ich habe über die Verwendung von HoughLinesP nachgedacht, bin mir aber nicht sicher, ob ich das brauche.Brille Erkennung

Bisher habe ich durchgeführt habe die folgenden Schritte:

  • Convert Bild in Graustufen
  • ROI erstellen um das Auge/Gläser Bereich
  • Blur das Bild
  • das Bild Dilate (dies getan haben, um dünn gerahmte Gläser zu entfernen)
  • Verhalten Canny Kantenerkennung
  • Gefundene Konturen

Dies sind die Ergebnisse:

Dies ist mein Code so weit:

//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); 

Ich bin nicht ganz sicher, was die nächsten Schritte sein würde, oder wie ich oben sagte, ob ich HoughLinesP benutzen und wie ich es implementieren soll. Jede Hilfe wird sehr geschätzt!

+1

Haben Sie eine Segmentierung in Betracht gezogen? Unterscheiden Sie Ihre Pixel in zwei Gruppen: (1) Gläser gehören Pixel (2) nicht zu Brillen Pixel. Verwenden Sie Super-Pixel-Konzept: Jedes Pixel sollte verschiedene Eigenschaften haben: Farbe, Position, wenn sie zu irgendeiner Kontur gehören, die Sie bereits gefunden haben, wenn sie an Kanten etc. sind. – William

+1

Ich denke, Ihre Konturen sind nicht gut, weil es einige Lücken gibt. Versuchen Sie, Ihre Ergebnisse vor der Konturextraktion zu erweitern und überprüfen Sie Ihre Konturen, indem Sie sie auf ein neues Bild zeichnen. Wenn Konturen korrekt extrahiert werden, können Sie die Abstandstransformation von der invertierten gefüllten Kontur berechnen. Die Rahmenstärke könnte vielleicht durch die maximal gefundene Distanz * 2 angenähert werden. – Micka

+1

Hallo @William, danke für die Antwort! Ich habe darüber nachgedacht, von dort aus Hauterkennung und -segmentierung durchzuführen. Habe auch nach einer möglichen Positionierung und ähnlichem gesucht. Ich bin mir nicht sicher, wie ich herausfinden soll, welche Pixel zu welchen gehören, aber ich werde darüber nachdenken. – LKB

Antwort

3

Ich denke, es gibt 2 Hauptprobleme.

  1. Segment der Glasrahmen

  2. die Dicke des segmentierten Rahmen finden

Ich werde nun einen Weg zu segmentieren, die Gläser Ihrer Probe Bild hinterlassen. Vielleicht wird diese Methode auch für verschiedene Bilder funktionieren, aber Sie müssen wahrscheinlich Parameter anpassen, oder Sie können die wichtigsten Ideen verwenden.

Grundidee ist: Zuerst finden Sie die größte Kontur im Bild, die die Brille sein sollte. Zweitens, finden Sie die zwei größten Konturen innerhalb der zuvor gefunden größten Kontur, die die Brille innerhalb des Rahmens sein sollte!

Ich benutze dieses Bild als Eingabe (die Ihr verschwommen, aber nicht erweitert Bild sein sollte):

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; 
} 

ich dieses Ergebnis für die Segmentierung erhalten:

enter image description here

Die Überlagerung im Originalbild gibt Ihnen einen Eindruck von Qualität:

enter image description here

und invers:

enter image description here

Es gibt einige schwierige Teile in den Code und es ist noch nicht aufgeräumt. Ich hoffe es ist verständlich.

Der nächste Schritt wäre, die Dicke des segmentierten Rahmens zu berechnen. Mein Vorschlag ist, die Entfernungstransformation der invertierten Maske zu berechnen. Von diesem wollen Sie eine Gratdetektion berechnen oder die Maske skelettieren, um den Grat zu finden. Danach den Medianwert der Kammabstände verwenden.

Wie auch immer ich hoffe, dieser Beitrag kann Ihnen ein wenig helfen, obwohl es noch keine Lösung ist.

+0

Hallo Micka, ich danke dir unglaublich, dass du dir die Zeit genommen hast, mir zu helfen.Ich habe Ihren Code ausgeführt und habe folgende Ausgabe: http://i.imgur.com/aNnXOlq.png Es ist ein wenig anders als Ihres (wie würde das passieren?), Dh eine der inneren Glaskonturen hat nicht geschlossen. Irgendeine Idee, wie ich das schließen würde? Ich schaue mich im Internet um und spiele mit dem Code in der Zwischenzeit, um zu sehen, ob ich es beheben kann. – LKB

+1

Hoppla, vergessen, das Bild zuerst zu verwischen. :) – LKB

+1

Vorsicht, Sie könnten ähnliche Probleme für verschiedene Bilder haben! – Micka

1

Je nach Beleuchtung, Rahmenfarbe usw. kann dies funktionieren oder nicht, aber wie wäre es mit einer einfachen Farberkennung, um den Rahmen zu trennen? Die Rahmenfarbe ist normalerweise viel dunkler als die menschliche Haut. Sie erhalten ein Binärbild (nur Schwarz-Weiß) und durch Berechnen der Anzahl (Fläche) von schwarzen Pixeln erhalten Sie die Fläche des Rahmens.

Eine andere Möglichkeit besteht darin, eine bessere Kantenerkennung zu erhalten, indem Sie die Einstellungen anpassen/erweitern/erodieren/beides, bis Sie bessere Konturen erhalten. Sie müssen auch die Kontur von den Linsen unterscheiden und dann cvContourArea anwenden.

+0

Danke für deine Antwort, Sonny! Ich bin mir bei der Farberkennung nicht so sicher, aber ich kann es versuchen! Ich denke, Ihr letzter Vorschlag, die Konturenerkennung zu optimieren, könnte besser funktionieren, also werde ich sehen, wie das geht. – LKB