1793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler// The "Square Detector" program.
2793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler// It loads several images sequentially and tries to find squares in
3793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler// each image
4793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
5793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler#include "opencv2/core/core.hpp"
6793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler#include "opencv2/imgproc/imgproc.hpp"
7793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler#include "opencv2/imgcodecs.hpp"
8793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler#include "opencv2/highgui/highgui.hpp"
9793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
10793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler#include <iostream>
11793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler#include <math.h>
12793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler#include <string.h>
13793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
14793ee12c6df9cad3806238d32528c49a3ff9331dNoah Preslerusing namespace cv;
15793ee12c6df9cad3806238d32528c49a3ff9331dNoah Preslerusing namespace std;
16793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
17793ee12c6df9cad3806238d32528c49a3ff9331dNoah Preslerstatic void help()
18793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler{
19793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    cout <<
20793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    "\nA program using pyramid scaling, Canny, contours, contour simpification and\n"
21793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    "memory storage (it's got it all folks) to find\n"
22793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    "squares in a list of images pic1-6.png\n"
23793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    "Returns sequence of squares detected on the image.\n"
24793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    "the sequence is stored in the specified memory storage\n"
25793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    "Call:\n"
26793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    "./squares\n"
27793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    "Using OpenCV version %s\n" << CV_VERSION << "\n" << endl;
28793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler}
29793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
30793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
31793ee12c6df9cad3806238d32528c49a3ff9331dNoah Preslerint thresh = 50, N = 11;
32793ee12c6df9cad3806238d32528c49a3ff9331dNoah Preslerconst char* wndname = "Square Detection Demo";
33793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
34793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler// helper function:
35793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler// finds a cosine of angle between vectors
36793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler// from pt0->pt1 and from pt0->pt2
37793ee12c6df9cad3806238d32528c49a3ff9331dNoah Preslerstatic double angle( Point pt1, Point pt2, Point pt0 )
38793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler{
39793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    double dx1 = pt1.x - pt0.x;
40793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    double dy1 = pt1.y - pt0.y;
41793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    double dx2 = pt2.x - pt0.x;
42793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    double dy2 = pt2.y - pt0.y;
43793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    return (dx1*dx2 + dy1*dy2)/sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10);
44793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler}
45793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
46793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler// returns sequence of squares detected on the image.
47793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler// the sequence is stored in the specified memory storage
48793ee12c6df9cad3806238d32528c49a3ff9331dNoah Preslerstatic void findSquares( const Mat& image, vector<vector<Point> >& squares )
49793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler{
50793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    squares.clear();
51793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
52793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    Mat pyr, timg, gray0(image.size(), CV_8U), gray;
53793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
54793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    // down-scale and upscale the image to filter out the noise
55793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    pyrDown(image, pyr, Size(image.cols/2, image.rows/2));
56793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    pyrUp(pyr, timg, image.size());
57793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    vector<vector<Point> > contours;
58793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
59793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    // find squares in every color plane of the image
60793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    for( int c = 0; c < 3; c++ )
61793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    {
62793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        int ch[] = {c, 0};
63793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        mixChannels(&timg, 1, &gray0, 1, ch, 1);
64793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
65793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        // try several threshold levels
66793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        for( int l = 0; l < N; l++ )
67793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        {
68793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            // hack: use Canny instead of zero threshold level.
69793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            // Canny helps to catch squares with gradient shading
70793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            if( l == 0 )
71793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            {
72793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                // apply Canny. Take the upper threshold from slider
73793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                // and set the lower to 0 (which forces edges merging)
74793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                Canny(gray0, gray, 0, thresh, 5);
75793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                // dilate canny output to remove potential
76793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                // holes between edge segments
77793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                dilate(gray, gray, Mat(), Point(-1,-1));
78793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            }
79793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            else
80793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            {
81793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                // apply threshold if l!=0:
82793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                //     tgray(x,y) = gray(x,y) < (l+1)*255/N ? 255 : 0
83793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                gray = gray0 >= (l+1)*255/N;
84793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            }
85793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
86793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            // find contours and store them all as a list
87793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            findContours(gray, contours, RETR_LIST, CHAIN_APPROX_SIMPLE);
88793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
89793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            vector<Point> approx;
90793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
91793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            // test each contour
92793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            for( size_t i = 0; i < contours.size(); i++ )
93793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            {
94793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                // approximate contour with accuracy proportional
95793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                // to the contour perimeter
96793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                approxPolyDP(Mat(contours[i]), approx, arcLength(Mat(contours[i]), true)*0.02, true);
97793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
98793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                // square contours should have 4 vertices after approximation
99793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                // relatively large area (to filter out noisy contours)
100793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                // and be convex.
101793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                // Note: absolute value of an area is used because
102793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                // area may be positive or negative - in accordance with the
103793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                // contour orientation
104793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                if( approx.size() == 4 &&
105793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                    fabs(contourArea(Mat(approx))) > 1000 &&
106793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                    isContourConvex(Mat(approx)) )
107793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                {
108793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                    double maxCosine = 0;
109793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
110793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                    for( int j = 2; j < 5; j++ )
111793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                    {
112793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                        // find the maximum cosine of the angle between joint edges
113793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                        double cosine = fabs(angle(approx[j%4], approx[j-2], approx[j-1]));
114793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                        maxCosine = MAX(maxCosine, cosine);
115793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                    }
116793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
117793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                    // if cosines of all angles are small
118793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                    // (all angles are ~90 degree) then write quandrange
119793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                    // vertices to resultant sequence
120793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                    if( maxCosine < 0.3 )
121793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                        squares.push_back(approx);
122793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler                }
123793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            }
124793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        }
125793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    }
126793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler}
127793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
128793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
129793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler// the function draws all the squares in the image
130793ee12c6df9cad3806238d32528c49a3ff9331dNoah Preslerstatic void drawSquares( Mat& image, const vector<vector<Point> >& squares )
131793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler{
132793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    for( size_t i = 0; i < squares.size(); i++ )
133793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    {
134793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        const Point* p = &squares[i][0];
135793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        int n = (int)squares[i].size();
136793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        polylines(image, &p, &n, 1, true, Scalar(0,255,0), 3, LINE_AA);
137793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    }
138793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
139793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    imshow(wndname, image);
140793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler}
141793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
142793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
143793ee12c6df9cad3806238d32528c49a3ff9331dNoah Preslerint main(int /*argc*/, char** /*argv*/)
144793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler{
145793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    static const char* names[] = { "../data/pic1.png", "../data/pic2.png", "../data/pic3.png",
146793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        "../data/pic4.png", "../data/pic5.png", "../data/pic6.png", 0 };
147793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    help();
148793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    namedWindow( wndname, 1 );
149793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    vector<vector<Point> > squares;
150793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
151793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    for( int i = 0; names[i] != 0; i++ )
152793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    {
153793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        Mat image = imread(names[i], 1);
154793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        if( image.empty() )
155793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        {
156793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            cout << "Couldn't load " << names[i] << endl;
157793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            continue;
158793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        }
159793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
160793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        findSquares(image, squares);
161793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        drawSquares(image, squares);
162793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
163793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        int c = waitKey();
164793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler        if( (char)c == 27 )
165793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler            break;
166793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    }
167793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler
168793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler    return 0;
169793ee12c6df9cad3806238d32528c49a3ff9331dNoah Presler}
170