burzan
burzan

Reputation: 262

Approximate photo of a simple drawing using lines

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.

Upvotes: 1

Views: 981

Answers (3)

Micka
Micka

Reputation: 20160

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;

    }

Upvotes: 1

Haris
Haris

Reputation: 14053

Give a try on this,

  1. Apply thinning algorithm on threshold image.

  2. Find contours.

  3. approxPolyDP for the found contour.

See some reference:

Upvotes: 1

Mansueli
Mansueli

Reputation: 6984

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).

Upvotes: 0

Related Questions