获取多行关闭的单行表示,在opencv中聚集在一起 [英] Get a single line representation for multiple close by lines clustered together in opencv

查看:292
本文介绍了获取多行关闭的单行表示,在opencv中聚集在一起的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在OpenCv C ++中使用HoughLinesP方法检测图像中的线条并将其绘制在单独的图像文件中。以下是生成的图像的一部分。实际上有数百条细线,形成一条大的单线。





但我想要几行代表所有这些行数。更紧密的线应合并在一起以形成单个线。例如,上面的一组行应该只用下面3个单独的行来表示。





预期输出如上。如何完成此任务。









到目前为止akarsakov的进度结果回答。






(不同类别的线条用不同的颜色绘制)。请注意,此结果是我正在处理的原始完整图片,但不是我在问题中使用的示例部分。



解决方案

知道图片中的行数,您可以使用 cv :: partition 函数来分割等价组上的行。



我建议您执行以下过程:


  1. 使用 cv :: partition 拆分线。你需要指定一个好的谓词函数。它实际上取决于你从图像中提取的行,但我认为应该检查以下条件:




    • 行之间的角度应该相当小例如小于3度)。使用点产品计算角度的余弦。

    • 距离细分中心的长度应小于两个细分受众群最大长度的一半。


例如,它可以如下实现:

  bool isEqual(const Vec4i& _l1,const Vec4i& _l2)
{
Vec4i l1(_l1),l2(_l2);

float length1 = sqrtf((l1 [2] -l1 [0])*(l1 [2] -l1 [0])+(l1 [3] -l1 [1] l1 [3] -11 [1]));
float length2 = sqrtf(l2 [2] -12 [0])*(l2 [2] -12 [0])+(l2 [3] -12 [1] - l2 [1]));

float product =(l1 [2] -l1 [0])*(l2 [2] -12 [0])+(l1 [3] -l1 [ 3] -12 [1]);

if(fabs(product /(length1 * length2))< cos(CV_PI / 30))
return false;

float mx1 =(l1 [0] + l1 [2])* 0.5f;
float mx2 =(l2 [0] + l2 [2])* 0.5f;

float my1 =(l1 [1] + l1 [3])* 0.5f;
float my2 =(l2 [1] + l2 [3])* 0.5f;
float dist = sqrtf((mx1 - mx2)*(mx1 - mx2)+(my1 - my2)*(my1 - my2));

if(dist> std :: max(length1,length2)* 0.5f)
return false;

return true;
}

猜测你的行在 vector< Vec4i>线; 。接下来,你应该调用 cv :: partition 如下:

  ; Vec4i>线; 
std :: vector< int>标签;
int numberOfLines = cv :: partition(lines,labels,isEqual);

您需要调用 cv :: partition 一次,它将聚集所有行。向量标签将为它所属的簇的每个行标签存储。有关 cv :: partition的信息,请参见文档


  1. 获得所有组合后,我建议计算组中所有线的平均角度并估计边界点。例如,如果角度为零(即,所有线几乎是水平的),则它将是最左和最右点。


  2. 我注意到,您的示例中的所有行都是水平或垂直的。在这种情况下,您可以计算所有分段的中心和边界点的平均点,然后绘制通过中心点的边界点限制的水平或垂直线。



    请注意, cv :: partition 需要O(N ^ 2)的时间,因此如果处理大量的行,可能需要很多时间。 / p>

    我希望它会有所帮助。我使用这种方法类似的任务。


    I detected lines in an image and drew them in a separate image file in OpenCv C++ using HoughLinesP method. Following is a part of that resulting image. There are actually hundreds of small and thin lines which form a big single line.

    But I want single few lines that represent all those number of lines. Closer lines should be merged together to form a single line. For example above set of lines should be represented by just 3 separate lines as below.

    The expected output is as above. How to accomplish this task.



    Up to now progress result from akarsakov's answer.


    (separate classes of lines resulted are drawn in different colors). Note that this result is the original complete image I am working on, but not the sample section I had used in the question

    解决方案

    If you don't know the number of lines in the image you can use the cv::partition function to split lines on equivalency group.

    I suggest you the following procedure:

    1. Split your lines using cv::partition. You need to specify a good predicate function. It really depends on lines which you extract from image, but I think it should check following conditions:

      • Angle between lines should be quite small (less 3 degrees, for example). Use dot product to calculate angle's cosine.
      • Distance between centers of segments should be less than half of maximum length of two segments.

    For example, it can be implemented as follows:

    bool isEqual(const Vec4i& _l1, const Vec4i& _l2)
    {
        Vec4i l1(_l1), l2(_l2);
    
        float length1 = sqrtf((l1[2] - l1[0])*(l1[2] - l1[0]) + (l1[3] - l1[1])*(l1[3] - l1[1]));
        float length2 = sqrtf((l2[2] - l2[0])*(l2[2] - l2[0]) + (l2[3] - l2[1])*(l2[3] - l2[1]));
    
        float product = (l1[2] - l1[0])*(l2[2] - l2[0]) + (l1[3] - l1[1])*(l2[3] - l2[1]);
    
        if (fabs(product / (length1 * length2)) < cos(CV_PI / 30))
            return false;
    
        float mx1 = (l1[0] + l1[2]) * 0.5f;
        float mx2 = (l2[0] + l2[2]) * 0.5f;
    
        float my1 = (l1[1] + l1[3]) * 0.5f;
        float my2 = (l2[1] + l2[3]) * 0.5f;
        float dist = sqrtf((mx1 - mx2)*(mx1 - mx2) + (my1 - my2)*(my1 - my2));
    
        if (dist > std::max(length1, length2) * 0.5f)
            return false;
    
        return true;
    }
    

    Guess you have your lines in vector<Vec4i> lines;. Next, you should call cv::partition as follows:

    vector<Vec4i> lines;
    std::vector<int> labels;
    int numberOfLines = cv::partition(lines, labels, isEqual);
    

    You need to call cv::partition once and it will clusterize all lines. Vector labels will store for each line label of cluster to which it belongs. See documentation for cv::partition

    1. After you get all groups of line you should merge them. I suggest calculating average angle of all lines in group and estimate "border" points. For example, if angle is zero (i.e. all lines are almost horizontal) it would be the left-most and right-most points. It remains only to draw a line between this points.

    I noticed that all lines in your examples are horizontal or vertical. In such case you can calculate point which is average of all segment's centers and "border" points, and then just draw horizontal or vertical line limited by "border" points through center point.

    Please note that cv::partition takes O(N^2) time, so if you process a huge number of lines it may take a lot of time.

    I hope it will help. I used such approach for similar task.

    这篇关于获取多行关闭的单行表示,在opencv中聚集在一起的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆