Tôi nghĩ rằng có 2 vấn đề chính.
phân khúc kính khung
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):
// 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:
lớp phủ trong hình ảnh gốc sẽ cho bạn ấn tượng về chất lượng:
và nghịch đảo:
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.
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
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
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