1

As an input I have a photo of a simple symbol, e.g.: https://www.dropbox.com/s/nrmsvfd0le0bkke/symbol.jpg

I would like to detect the straight lines in it, like points of start and ends of the lines. In this case, assuming the top left of the symbol is (0,0), the lines would be defined like this:

start end (coordinates of beginning and end of a line)
1. (0,0); (0,10) (vertical line)
2. (0,10); (15, 15)
3. (15,15); (0, 20)
4. (0,20); (0,30)

How can I do it (pereferably using OpenCV)? I though about Hough lines, but they seem to be good for perfect thin straight lines, which is not the case in a drawing. I'll probably work on binarized image, too.

burzan
  • 222
  • 1
  • 7

3 Answers3

1

Give a try on this,

  1. Apply thinning algorithm on threshold image.

  2. Find contours.

  3. approxPolyDP for the found contour.

See some reference:

Community
  • 1
  • 1
Haris
  • 13,645
  • 12
  • 90
  • 121
1

maybe you can work on this one.

  1. assume a perfect binarization: enter image description here
  2. run HoughLinesP enter image description here
  3. (not implemented) try to group those detected lines

I used this code:

    int main()
    {
        cv::Mat image = cv::imread("HoughLinesP_perfect.png");
        cv::Mat gray;
        cv::cvtColor(image,gray,CV_BGR2GRAY);

        cv::Mat output; image.copyTo(output);



        cv::Mat g_thres = gray == 0;


        std::vector<cv::Vec4i> lines;
        //cv::HoughLinesP( binary, lines, 1, 2*CV_PI/180, 100, 100, 50 );
        //  cv::HoughLinesP( h_thres, lines, 1, CV_PI/180, 100, image.cols/2, 10 );
        cv::HoughLinesP( g_thres, lines, 1, CV_PI/(4*180.0), 50, image.cols/20, 10 );

        for( size_t i = 0; i < lines.size(); i++ )
        {
            cv::line( output, cv::Point(lines[i][0], lines[i][3]),
                    cv::Point(lines[i][4], lines[i][3]), cv::Scalar(155,255,155), 1, 8 );
        }


        cv::imshow("g thres", g_thres);

        cv::imwrite("HoughLinesP_out.png", output);

        cv::resize(output, output, cv::Size(), 0.5,0.5);

        cv::namedWindow("output"); cv::imshow("output", output);

        cv::waitKey(-1);

        std::cout << "finished" << std::endl;

        return 0;

    }

EDIT:

updated code with simple line clustering (`minimum_distance function taken from SO):

giving this result:

enter image description here

float minimum_distance(cv::Point2f v, cv::Point2f w, cv::Point2f p) {
      // Return minimum distance between line segment vw and point p
      const float l2 = cv::norm(w-v) * cv::norm(w-v);  // i.e. |w-v|^2 -  avoid a sqrt
      if (l2 == 0.0) return cv::norm(p-v);   // v == w case
      // Consider the line extending the segment, parameterized as v + t (w - v).
      // We find projection of point p onto the line.
      // It falls where t = [(p-v) . (w-v)] / |w-v|^2
      //const float t = dot(p - v, w - v) / l2;
      float t = ((p-v).x * (w-v).x + (p-v).y * (w-v).y)/l2;

      if (t < 0.0) return cv::norm(p-v);       // Beyond the 'v' end of the segment
      else if (t > 1.0) return cv::norm(p-w);  // Beyond the 'w' end of the segment
      const cv::Point2f projection = v + t * (w - v);  // Projection falls on the segment
      return cv::norm(p - projection);
    }

    int main()
    {
        cv::Mat image = cv::imread("HoughLinesP_perfect.png");
        cv::Mat gray;
        cv::cvtColor(image,gray,CV_BGR2GRAY);

        cv::Mat output; image.copyTo(output);



        cv::Mat g_thres = gray == 0;


        std::vector<cv::Vec4i> lines;
        cv::HoughLinesP( g_thres, lines, 1, CV_PI/(4*180.0), 50, image.cols/20, 10 );

        float minDist = 100;

        std::vector<cv::Vec4i> lines_filtered;
        for( size_t i = 0; i < lines.size(); i++ )
        {
            bool keep = true;
            int overwrite = -1;
            cv::Point2f a(lines[i][0], lines[i][6]);
            cv::Point2f b(lines[i][7], lines[i][3]);

            float lengthAB = cv::norm(a-b);


            for( size_t j = 0; j < lines_filtered.size(); j++ )
            {
                cv::Point2f c(lines_filtered[j][0], lines_filtered[j][8]);
                cv::Point2f d(lines_filtered[j][9], lines_filtered[j][3]);

                float distCDA =  minimum_distance(c,d,a);
                float distCDB =  minimum_distance(c,d,b);

                float lengthCD = cv::norm(c-d);


                if((distCDA < minDist) && (distCDB < minDist))
                {
                    if(lengthCD >= lengthAB)
                    {
                        keep = false;
                    }
                    else
                    {
                        overwrite = j;
                    }
                }

            }

            if(keep)
            {
                if(overwrite >= 0)
                {
                    lines_filtered[overwrite] = lines[i];

                }
                else
                {
                    lines_filtered.push_back(lines[i]);
                }
            }
        }


        for( size_t i = 0; i < lines_filtered.size(); i++ )
        {
            cv::line( output, cv::Point(lines_filtered[i][0], lines_filtered[i][10]),
                    cv::Point(lines_filtered[i][11], lines_filtered[i][3]), cv::Scalar(155,255,155), 2, 8 );
        }




        cv::imshow("g thres", g_thres);

        cv::imwrite("HoughLinesP_out.png", output);

        cv::resize(output, output, cv::Size(), 0.5,0.5);

        cv::namedWindow("output"); cv::imshow("output", output);

        cv::waitKey(-1);

        std::cout << "finished" << std::endl;

        return 0;

    }
Micka
  • 19,585
  • 4
  • 56
  • 74
  • Thank you. That is a very interesting approach and I think it actually could work. However, that kind of grouping would be probably too computationally expensive for my case. – burzan Jun 04 '14 at 09:18
  • then maybe try some thinning and try HoughLinesP instead of HoughLines, since it's better for non-perfect lines. But instead of grouping you might want to choose only the longest of "similar" lines, which isnt to computational expensive I guess. – Micka Jun 04 '14 at 09:46
  • Yeah but still you have to create groups of "similar" lines, so it gets more complicated. I'll try with method propsed by Haris but I'll keep yours in mind. – burzan Jun 04 '14 at 12:28
  • @user3704596 edited my answer with a simple similar lines clustering, just for completion – Micka Jun 04 '14 at 15:22
  • 1
    Thank you for you effort! I actually checked your solution and it does work very well. I decided to go with the other one though, because it gives me approximated lines at once, and this allows me to easily compute angles between adjacent lines. Thanks! – burzan Jun 06 '14 at 00:27
0

You should try the Hough Line Transform. And here is an example from this website

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>

using namespace cv;
using namespace std;

int main()
{
    Mat src = imread("building.jpg", 0);

    Mat dst, cdst;
    Canny(src, dst, 50, 200, 3);
    cvtColor(dst, cdst, CV_GRAY2BGR);

    vector<Vec2f> lines;
    // detect lines
    HoughLines(dst, lines, 1, CV_PI/180, 150, 0, 0 );

    // draw lines
    for( size_t i = 0; i < lines.size(); i++ )
    {
        float rho = lines[i][0], theta = lines[i][1];
        Point pt1, pt2;
        double a = cos(theta), b = sin(theta);
        double x0 = a*rho, y0 = b*rho;
        pt1.x = cvRound(x0 + 1000*(-b));
        pt1.y = cvRound(y0 + 1000*(a));
        pt2.x = cvRound(x0 - 1000*(-b));
        pt2.y = cvRound(y0 - 1000*(a));
        line( cdst, pt1, pt2, Scalar(0,0,255), 3, CV_AA);
    }

    imshow("source", src);
    imshow("detected lines", cdst);

    waitKey();
    return 0;
}

With this you should be able to tweak and get the proprieties you are looking for (vertices).

Mansueli
  • 6,223
  • 8
  • 33
  • 57
  • Thank you for your input. I decided to try your idea, but as I feared, it does not seem to be suitable. You can see my results here after tweaking some parameters: https://www.dropbox.com/s/kmlge9eyse0fc8f/symbols.png ; I binarized it with otsu threshold first, but it doesn't seem to affect the results. The main problem I guess is that i have to parallel lines beacause of the edges, but even if that was not the case, it rather detects the straightest of lines - tweaking parameters can help a bit, but it's not universal. Any other ideas? – burzan Jun 04 '14 at 00:26