8

在此处输入图像描述我有一张有很多凹槽的表面的照片。在大多数情况下,凹槽的边缘形成平行线,因此 Canny 和 Hough 变换可以很好地检测线并进行一些表征。然而,在几个地方,凹槽被损坏,边缘不再平行。

我正在寻找一种简单的方法来检查某个边缘是否是直线,或者是否与直线有任何间隙或偏差。我正在考虑线性插值中的 R 平方参数之类的东西,但在这里我需要一个更依赖于位置的参数。您还有其他想法如何表征边缘吗?

我附上了一张精巧边缘检测后的开槽图片。在这里,边缘是直线,开槽很好。不幸的是,我目前无法获得凹槽损坏的图片。但是,在凹槽损坏的图片中,线条会有很大的间隙(至少是图片大小的 10%)或不平行。

4

2 回答 2

7

我在下面分享的技术的核心是cv::HoughLinesP()用于在灰度图像中查找线段。

应用程序首先将输入图像加载为灰度。然后它执行基本的预处理操作以增强图像的某些特征,旨在改善通过以下方式执行的检测cv::HoughLinesP()

#include <cv.h>
#include <highgui.h>

#include <algorithm>

// Custom sort method adapted from: http://stackoverflow.com/a/328959/176769
// This is used later by std::sort()
struct sort_by_y_coord 
{
    bool operator ()(cv::Vec4i const& a, cv::Vec4i const& b) const 
    {
        if (a[1] < b[1]) return true;

        if (a[1] > b[1]) return false;

        return false;
    }
};


int main()
{
    /* Load input image as grayscale */

    cv::Mat src = cv::imread("13531682.jpg", 0);

    /* Pre-process the image to enhance the characteristics we are interested at */

    medianBlur(src, src, 5);

    int erosion_size = 2;
    cv::Mat element = cv::getStructuringElement(cv::MORPH_CROSS,
                                       cv::Size(2 * erosion_size + 1, 2 * erosion_size + 1),
                                       cv::Point(erosion_size, erosion_size) );
    cv::erode(src, src, element);
    cv::dilate(src, src, element);

    /* Identify all the lines in the image */

    cv::Size size = src.size();
    std::vector<cv::Vec4i> total_lines;
    cv::HoughLinesP(src, total_lines, 1, CV_PI/180, 100, size.width / 2.f, 20);

    int n_lines = total_lines.size();
    std::cout << "* Total lines: "<< n_lines << std::endl;

    cv::Mat disp_lines(size, CV_8UC1, cv::Scalar(0, 0, 0));

    // For debugging purposes, the block below writes all the lines into disp_lines
    // for (unsigned i = 0; i < n_lines; ++i)
    // {
    //     cv::line(disp_lines, 
    //              cv::Point(total_lines[i][0], total_lines[i][2]),
    //              cv::Point(total_lines[i][3], total_lines[i][4]), 
    //              cv::Scalar(255, 0 ,0));
    // }
    // cv::imwrite("total_lines.png", disp_lines);

此时,检测到的所有线段都可以写入文件以进行可视化:

在这一点上,我们需要对我们的线向量进行排序,因为cv::HoughLinesP()不这样做,并且我们需要排序的向量能够通过测量和比较线之间的距离来识别线组:

    /* Sort lines according to their Y coordinate. 
       The line closest to Y == 0 is at the first position of the vector.
    */

    sort(total_lines.begin(), total_lines.end(), sort_by_y_coord());

    /* Separate them according to their (visible) groups */ 

    // Figure out the number of groups by distance between lines
    std::vector<int> idx_of_groups;   // stores the index position where a new group starts
    idx_of_groups.push_back(0); // the first line indicates the start of the first group 

    // The loop jumps over the first line, since it was already added as a group
    int y_dist = 35; // the next groups are identified by a minimum of 35 pixels of distance  
    for (unsigned i = 1; i < n_lines; i++)  
    {
        if ((total_lines[i][5] - total_lines[i-1][6]) >= y_dist) 
        {
            // current index marks the position of a new group
            idx_of_groups.push_back(i); 
            std::cout << "* New group located at line #"<< i << std::endl;           
        }
    }

    int n_groups = idx_of_groups.size();
    std::cout << "* Total groups identified: "<< n_groups << std::endl;

上面代码的最后一部分只是将行向量的索引位置存储在一个新的行中,vector<int>这样我们就知道哪些行开始一个新的组。

例如,假设存储在新向量中的索引是:0 4 8 12。请记住:它们定义了每个组的开始。这意味着组的结束行是:0, 4-1, 4, 8-1, 8, 12-1, 12

知道了这一点,我们编写以下代码:

    /* Mark the beginning and end of each group */

    for (unsigned i = 0; i < n_groups; i++)  
    {
        // To do this, we discard the X coordinates of the 2 points from the line, 
        // so we can draw a line from X=0 to X=size.width

        // beginning
        cv::line(disp_lines, 
                 cv::Point(0, total_lines[ idx_of_groups[i] ][7]),
                 cv::Point(size.width, total_lines[ idx_of_groups[i] ][8]), 
                 cv::Scalar(255, 0 ,0));

        // end      
        if (i != n_groups-1)
        {
            cv::line(disp_lines, 
                     cv::Point(0, total_lines[ idx_of_groups[i+1]-1 ][9]),
                     cv::Point(size.width, total_lines[ idx_of_groups[i+1]-1 ][10]), 
                     cv::Scalar(255, 0 ,0));
        }
    }
    // mark the end position of the last group (not done by the loop above)    
    cv::line(disp_lines, 
             cv::Point(0, total_lines[n_lines-1][11]),
             cv::Point(size.width, total_lines[n_lines-1][12]), 
             cv::Scalar(255, 0 ,0));

    /* Save the output image and display it on the screen */

    cv::imwrite("groups.png", disp_lines);

    cv::imshow("groove", disp_lines);
    cv::waitKey(0);
    cv::destroyWindow("groove");

    return 0;
}

结果图像是:

这不是一个完美的匹配,但它很接近。在这里和那里进行一些调整,这种方法可以变得更好。我将首先为 编写一个更智能的逻辑sort_by_y_coord,它应该丢弃 X 坐标之间距离较小的线(即小线段),以及在 X 轴上未完全对齐的线(如第二组中的线)输出图像)。在您花时间评估应用程序生成的第一张图像之后,此建议更有意义。

祝你好运。

于 2012-11-28T03:15:16.657 回答
4

立即想到的是霍夫变换。这是行空间中的投票方案,它采用每条可能的行并给你一个分数。在我上面链接的代码中,您可以简单地设置一个阈值,该阈值大约为搞砸凹槽/线条的 10%。

于 2012-11-23T18:00:12.530 回答