1// The "Square Detector" program. 2// It loads several images sequentially and tries to find squares in 3// each image 4 5#include "opencv2/core/core.hpp" 6#include "opencv2/imgproc/imgproc.hpp" 7#include "opencv2/imgcodecs.hpp" 8#include "opencv2/highgui/highgui.hpp" 9 10#include <iostream> 11#include <math.h> 12#include <string.h> 13 14using namespace cv; 15using namespace std; 16 17static void help() 18{ 19 cout << 20 "\nA program using pyramid scaling, Canny, contours, contour simpification and\n" 21 "memory storage (it's got it all folks) to find\n" 22 "squares in a list of images pic1-6.png\n" 23 "Returns sequence of squares detected on the image.\n" 24 "the sequence is stored in the specified memory storage\n" 25 "Call:\n" 26 "./squares\n" 27 "Using OpenCV version %s\n" << CV_VERSION << "\n" << endl; 28} 29 30 31int thresh = 50, N = 11; 32const char* wndname = "Square Detection Demo"; 33 34// helper function: 35// finds a cosine of angle between vectors 36// from pt0->pt1 and from pt0->pt2 37static double angle( Point pt1, Point pt2, Point pt0 ) 38{ 39 double dx1 = pt1.x - pt0.x; 40 double dy1 = pt1.y - pt0.y; 41 double dx2 = pt2.x - pt0.x; 42 double dy2 = pt2.y - pt0.y; 43 return (dx1*dx2 + dy1*dy2)/sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10); 44} 45 46// returns sequence of squares detected on the image. 47// the sequence is stored in the specified memory storage 48static void findSquares( const Mat& image, vector<vector<Point> >& squares ) 49{ 50 squares.clear(); 51 52 Mat pyr, timg, gray0(image.size(), CV_8U), gray; 53 54 // down-scale and upscale the image to filter out the noise 55 pyrDown(image, pyr, Size(image.cols/2, image.rows/2)); 56 pyrUp(pyr, timg, image.size()); 57 vector<vector<Point> > contours; 58 59 // find squares in every color plane of the image 60 for( int c = 0; c < 3; c++ ) 61 { 62 int ch[] = {c, 0}; 63 mixChannels(&timg, 1, &gray0, 1, ch, 1); 64 65 // try several threshold levels 66 for( int l = 0; l < N; l++ ) 67 { 68 // hack: use Canny instead of zero threshold level. 69 // Canny helps to catch squares with gradient shading 70 if( l == 0 ) 71 { 72 // apply Canny. Take the upper threshold from slider 73 // and set the lower to 0 (which forces edges merging) 74 Canny(gray0, gray, 0, thresh, 5); 75 // dilate canny output to remove potential 76 // holes between edge segments 77 dilate(gray, gray, Mat(), Point(-1,-1)); 78 } 79 else 80 { 81 // apply threshold if l!=0: 82 // tgray(x,y) = gray(x,y) < (l+1)*255/N ? 255 : 0 83 gray = gray0 >= (l+1)*255/N; 84 } 85 86 // find contours and store them all as a list 87 findContours(gray, contours, RETR_LIST, CHAIN_APPROX_SIMPLE); 88 89 vector<Point> approx; 90 91 // test each contour 92 for( size_t i = 0; i < contours.size(); i++ ) 93 { 94 // approximate contour with accuracy proportional 95 // to the contour perimeter 96 approxPolyDP(Mat(contours[i]), approx, arcLength(Mat(contours[i]), true)*0.02, true); 97 98 // square contours should have 4 vertices after approximation 99 // relatively large area (to filter out noisy contours) 100 // and be convex. 101 // Note: absolute value of an area is used because 102 // area may be positive or negative - in accordance with the 103 // contour orientation 104 if( approx.size() == 4 && 105 fabs(contourArea(Mat(approx))) > 1000 && 106 isContourConvex(Mat(approx)) ) 107 { 108 double maxCosine = 0; 109 110 for( int j = 2; j < 5; j++ ) 111 { 112 // find the maximum cosine of the angle between joint edges 113 double cosine = fabs(angle(approx[j%4], approx[j-2], approx[j-1])); 114 maxCosine = MAX(maxCosine, cosine); 115 } 116 117 // if cosines of all angles are small 118 // (all angles are ~90 degree) then write quandrange 119 // vertices to resultant sequence 120 if( maxCosine < 0.3 ) 121 squares.push_back(approx); 122 } 123 } 124 } 125 } 126} 127 128 129// the function draws all the squares in the image 130static void drawSquares( Mat& image, const vector<vector<Point> >& squares ) 131{ 132 for( size_t i = 0; i < squares.size(); i++ ) 133 { 134 const Point* p = &squares[i][0]; 135 int n = (int)squares[i].size(); 136 polylines(image, &p, &n, 1, true, Scalar(0,255,0), 3, LINE_AA); 137 } 138 139 imshow(wndname, image); 140} 141 142 143int main(int /*argc*/, char** /*argv*/) 144{ 145 static const char* names[] = { "../data/pic1.png", "../data/pic2.png", "../data/pic3.png", 146 "../data/pic4.png", "../data/pic5.png", "../data/pic6.png", 0 }; 147 help(); 148 namedWindow( wndname, 1 ); 149 vector<vector<Point> > squares; 150 151 for( int i = 0; names[i] != 0; i++ ) 152 { 153 Mat image = imread(names[i], 1); 154 if( image.empty() ) 155 { 156 cout << "Couldn't load " << names[i] << endl; 157 continue; 158 } 159 160 findSquares(image, squares); 161 drawSquares(image, squares); 162 163 int c = waitKey(); 164 if( (char)c == 27 ) 165 break; 166 } 167 168 return 0; 169} 170