我要尝试的是测量眼镜框的厚度。我的想法是测量框架轮廓的厚度(也许是更好的方法?)。到目前为止,我已经为眼镜的框架勾勒出轮廓,但是线条之间没有间隙。我曾考虑过使用HoughLinesP,但是我不确定这是否是我需要的。

到目前为止,我已经执行了以下步骤:

  • 将图像转换为灰度
  • 在眼睛/眼镜区域周围创建ROI
  • 模糊图像
  • 扩展图像(已执行此操作以去除任何薄框眼镜)
  • 进行Canny边缘检测
  • 找到轮廓

  • 结果如下:



    到目前为止,这是我的代码:
    //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);
    

    我不确定如果要使用HoughLinesP及其实现方式,下一步将是什么,或者如上所述。很感谢任何形式的帮助!

    最佳答案

    我认为主要有两个问题。

  • 段眼镜框架
  • 查找分段框架
  • 的厚度

    现在,我将发布一种 segmentation 示例图像眼镜的方法。也许这种方法也适用于不同的图像,但是您可能必须调整参数,或者您可以使用主要思想。

    主要思想是:
    首先,找到图像中最大的轮廓,应该是眼镜。其次,在先前找到的最大轮廓内找到两个最大轮廓,这应该是框架内的眼镜!

    我将此图像用作输入(应该是您的模糊但不膨胀的图像):
    // 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;
    }
    

    我得到此结果进行 segmentation :

    原始图像中的叠加层会给您质量的印象:

    和逆:

    代码中包含一些棘手的部分,尚未整理。我希望这是可以理解的。

    下一步将是计算分段框架的厚度。我的建议是计算反向蒙版的距离变换。由此,您将需要计算脊线检测或将蒙版骨架化以找到脊线。之后,使用山脊距离的中值。

    无论如何,我希望这篇文章可以对您有所帮助,尽管这还不是解决方案。

    08-28 19:11