Ich denke, es gibt 2 Hauptprobleme.
Segment der Glasrahmen
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):

// 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:

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

und invers:

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