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