Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get a single line representation for multiple close by lines clustered together in opencv

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.

enter image description here

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.

enter image description here

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

enter image description here

like image 428
Samitha Chathuranga Avatar asked Jun 10 '15 02:06

Samitha Chathuranga


1 Answers

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.

like image 148
akarsakov Avatar answered Sep 19 '22 07:09

akarsakov