2

I am trying to find the edges of the centered box in this image: black and white box

I have tried using a HoughLines using dRho=img_width/1000, dTheta=pi/180, and threshold=250 It works great on this image, scaled to 1/3 of the size, but on the full size image it just gets lines everywhere in every direction...

What can I do to tune this to be more accurate?

karlphillip
  • 92,053
  • 36
  • 243
  • 426
Steve Y
  • 343
  • 3
  • 11

3 Answers3

5

The code to achieve the result below is a slight modification of the one presented in this answer: how to detect a square:

enter image description here

The original program can be found inside OpenCV, it's called squares.cpp. The code below was modified to search squares only in the first color plane, but as it still detects many squares, at the end of the program I discard all of them except the first, and then call draw_squares() to show what was detected. You can change this easilly to draw all of them and see everything that was detected.

You can do all sorts of thing from now own, including setting a (ROI) region of interest to extract the area that's inside the square (ignore everything else around it).

You can see that the detected rectangle is not perfectly aligned with the lines in the image. You should perform some pre-processing (erode?) operations in the image to decrease the thickness of lines and improve the detection. But from here on it's all on you:

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

using namespace cv;

double angle( cv::Point pt1, cv::Point pt2, cv::Point pt0 ) {
    double dx1 = pt1.x - pt0.x;
    double dy1 = pt1.y - pt0.y;
    double dx2 = pt2.x - pt0.x;
    double dy2 = pt2.y - pt0.y;
    return (dx1*dx2 + dy1*dy2)/sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10);
}

void find_squares(Mat& image, vector<vector<Point> >& squares)
{
    // TODO: pre-processing

    // blur will enhance edge detection
    Mat blurred(image);
    medianBlur(image, blurred, 9);

    Mat gray0(blurred.size(), CV_8U), gray;
    vector<vector<Point> > contours;

    // find squares in the first color plane.
    for (int c = 0; c < 1; c++)
    {
        int ch[] = {c, 0};
        mixChannels(&blurred, 1, &gray0, 1, ch, 1);

        // try several threshold levels
        const int threshold_level = 2;
        for (int l = 0; l < threshold_level; l++)
        {
            // Use Canny instead of zero threshold level!
            // Canny helps to catch squares with gradient shading
            if (l == 0)
            {
                Canny(gray0, gray, 10, 20, 3); // 

                // Dilate helps to remove potential holes between edge segments
                dilate(gray, gray, Mat(), Point(-1,-1));
            }
            else
            {
                    gray = gray0 >= (l+1) * 255 / threshold_level;
            }

            // Find contours and store them in a list
            findContours(gray, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);

            // Test contours
            vector<Point> approx;
            for (size_t i = 0; i < contours.size(); i++)
            {
                    // approximate contour with accuracy proportional
                    // to the contour perimeter
                    approxPolyDP(Mat(contours[i]), approx, arcLength(Mat(contours[i]), true)*0.02, true);

                    // Note: absolute value of an area is used because
                    // area may be positive or negative - in accordance with the
                    // contour orientation
                    if (approx.size() == 4 &&
                            fabs(contourArea(Mat(approx))) > 1000 &&
                            isContourConvex(Mat(approx)))
                    {
                            double maxCosine = 0;

                            for (int j = 2; j < 5; j++)
                            {
                                    double cosine = fabs(angle(approx[j%4], approx[j-2], approx[j-1]));
                                    maxCosine = MAX(maxCosine, cosine);
                            }

                            if (maxCosine < 0.3)
                                    squares.push_back(approx);
                    }
            }
        }
    }
}

void draw_squares(Mat& img, vector<vector<Point> > squares)
{
    for (int i = 0; i < squares.size(); i++)
    {
        for (int j = 0; j < squares[i].size(); j++)
        {
            cv::line(img, squares[i][j], squares[i][(j+1) % 4], cv::Scalar(0, 255, 0), 1, CV_AA);
        }
    }
}


int main(int argc, char* argv[])
{
    Mat img = imread(argv[1]);

    vector<vector<Point> > squares;
    find_squares(img, squares);

    std::cout << "* " << squares.size() << " squares were found." << std::endl;

    // Ignore all the detected squares and draw just the first found
    vector<vector<Point> > tmp;
    if (squares.size() > 0)
    {
        tmp.push_back(squares[0]);
        draw_squares(img, tmp);
    }
    //imshow("squares", img);
    //cvWaitKey(0);

    imwrite("out.png", img);

    return 0;
}
Community
  • 1
  • 1
karlphillip
  • 92,053
  • 36
  • 243
  • 426
  • Can you please tell me how to access the lengths of the squares that we identified ? – SL_User Jun 26 '12 at 08:41
  • Ask yourself what is the length of the square and then notice that a square is defined as a set of 4 points (coordinates). The code above gives you the points, but the calc to reveal it's length is up to you to write. – karlphillip Jun 27 '12 at 13:05
3

Try using a preprocessing pass with the erosion filter. It will give you the same effect as the downscaling - the lines will become thinner and will not disappear at the same time.

The "Blur" filter is also a good idea, as chaiy says.

This way (with blur) it will become something like http://www.ic.uff.br/~laffernandes/projects/kht/index.html (Kernel Based Hough Transform)

Viktor Latypov
  • 14,289
  • 3
  • 40
  • 55
3

when resizing the image, the image is normally first blurred with a filter, e.g. Gaussian, in order to get rid of high frequencies. The fact that resized one works better is likely because your original image is somehow noisy.

Try blur the image first, e.g. with cv::GaussianBlur(src, target, Size(0,0), 1.5), then it should be equivalent to resizing. (It forgot the theory, if it does not work, try 3 and 6 as well)

guinny
  • 1,522
  • 10
  • 19