174 lines
5.3 KiB
Matlab
174 lines
5.3 KiB
Matlab
#! /usr/bin/env octave
|
|
##
|
|
## The full "Square Detector" program.
|
|
## It loads several images subsequentally and tries to find squares in
|
|
## each image
|
|
##
|
|
|
|
cv;
|
|
highgui;
|
|
|
|
global g;
|
|
|
|
g.thresh = 50;
|
|
g.img = [];
|
|
g.img0 = [];
|
|
g.storage = [];
|
|
g.wndname = "Square Detection Demo";
|
|
|
|
function ret = compute_angle( pt1, pt2, pt0 )
|
|
dx1 = pt1.x - pt0.x;
|
|
dy1 = pt1.y - pt0.y;
|
|
dx2 = pt2.x - pt0.x;
|
|
dy2 = pt2.y - pt0.y;
|
|
ret = (dx1*dx2 + dy1*dy2)/sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10);
|
|
endfunction
|
|
|
|
function squares = findSquares4( img, storage )
|
|
global g;
|
|
global cv;
|
|
|
|
N = 11;
|
|
sz = cvSize( img.width, img.height );
|
|
timg = cvCloneImage( img ); # make a copy of input image
|
|
gray = cvCreateImage( sz, 8, 1 );
|
|
pyr = cvCreateImage( cvSize(int32(sz.width/2), int32(sz.height/2)), 8, 3 );
|
|
## create empty sequence that will contain points -
|
|
## 4 points per square (the square's vertices)
|
|
squares = cvCreateSeq( 0, cv.sizeof_CvSeq, cv.sizeof_CvPoint, storage );
|
|
squares = cv.CvSeq_CvPoint.cast( squares );
|
|
|
|
## select the maximum ROI in the image
|
|
## with the width and height divisible by 2
|
|
subimage = cvGetSubRect( timg, cvRect( 0, 0, sz.width, sz.height ));
|
|
|
|
## down-scale and upscale the image to filter out the noise
|
|
cvPyrDown( subimage, pyr, 7 );
|
|
cvPyrUp( pyr, subimage, 7 );
|
|
tgray = cvCreateImage( sz, 8, 1 );
|
|
## find squares in every color plane of the image
|
|
for c=1:3,
|
|
## extract the c-th color plane
|
|
channels = {[], [], []};
|
|
channels{c} = tgray;
|
|
cvSplit( subimage, channels{1}, channels{2}, channels{3}, [] ) ;
|
|
for l=1:N,
|
|
## hack: use Canny instead of zero threshold level.
|
|
## Canny helps to catch squares with gradient shading
|
|
if( l == 1 )
|
|
## apply Canny. Take the upper threshold from slider
|
|
## and set the lower to 0 (which forces edges merging)
|
|
cvCanny( tgray, gray, 0, g.thresh, 5 );
|
|
## dilate canny output to remove potential
|
|
## holes between edge segments
|
|
cvDilate( gray, gray, [], 1 );
|
|
else
|
|
## apply threshold if l!=0
|
|
## tgray(x,y) = gray(x,y) < (l+1)*255/N ? 255 : 0
|
|
cvThreshold( tgray, gray, l*255/N, 255, cv.CV_THRESH_BINARY );
|
|
endif
|
|
|
|
## find contours and store them all as a list
|
|
[count, contours] = cvFindContours( gray, storage, cv.sizeof_CvContour, cv.CV_RETR_LIST, cv.CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0) );
|
|
|
|
if (!swig_this(contours))
|
|
continue;
|
|
endif
|
|
|
|
## test each contour
|
|
for contour = CvSeq_hrange(contours),
|
|
## approximate contour with accuracy proportional
|
|
## to the contour perimeter
|
|
result = cvApproxPoly( contour, cv.sizeof_CvContour, storage, cv.CV_POLY_APPROX_DP, cvContourPerimeter(contours)*0.02, 0 );
|
|
## square contours should have 4 vertices after approximation
|
|
## relatively large area (to filter out noisy contours)
|
|
## and be convex.
|
|
## Note: absolute value of an area is used because
|
|
## area may be positive or negative - in accordance with the
|
|
## contour orientation
|
|
if( result.total == 4 &&
|
|
abs(cvContourArea(result)) > 1000 &&
|
|
cvCheckContourConvexity(result) )
|
|
s = 0;
|
|
for i=1:5,
|
|
## find minimum angle between joint
|
|
## edges (maximum of cosine)
|
|
if( i > 2 )
|
|
t = abs(compute_angle( result{i}, result{i-2}, result{i-1}));
|
|
if (s<t)
|
|
s=t;
|
|
endif
|
|
endif
|
|
endfor
|
|
## if cosines of all angles are small
|
|
## (all angles are ~90 degree) then write quandrange
|
|
## vertices to resultant sequence
|
|
if( s < 0.3 )
|
|
for i=1:4,
|
|
squares.append( result{i} )
|
|
endfor
|
|
endif
|
|
endif
|
|
endfor
|
|
endfor
|
|
endfor
|
|
endfunction
|
|
|
|
## the function draws all the squares in the image
|
|
function drawSquares( img, squares )
|
|
global g;
|
|
global cv;
|
|
|
|
cpy = cvCloneImage( img );
|
|
## read 4 sequence elements at a time (all vertices of a square)
|
|
i=0;
|
|
while (i<squares.total)
|
|
pt = { squares{i}, squares{i+1}, squares{i+2}, squares{i+3} };
|
|
|
|
## draw the square as a closed polyline
|
|
cvPolyLine( cpy, {pt}, 1, CV_RGB(0,255,0), 3, cv.CV_AA, 0 );
|
|
i+=4;
|
|
endwhile
|
|
|
|
## show the resultant image
|
|
cvShowImage( g.wndname, cpy );
|
|
endfunction
|
|
|
|
function on_trackbar( a )
|
|
global g;
|
|
|
|
if( swig_this(g.img) )
|
|
drawSquares( g.img, findSquares4( g.img, g.storage ) );
|
|
endif
|
|
endfunction
|
|
|
|
g.names = {"../c/pic1.png", "../c/pic2.png", "../c/pic3.png", \
|
|
"../c/pic4.png", "../c/pic5.png", "../c/pic6.png" };
|
|
|
|
## create memory storage that will contain all the dynamic data
|
|
g.storage = cvCreateMemStorage(0);
|
|
for name = g.names,
|
|
g.img0 = cvLoadImage( name, 1 );
|
|
if (!swig_this(g.img0))
|
|
printf("Couldn't load %s\n",name);
|
|
continue;
|
|
endif
|
|
g.img = cvCloneImage( g.img0 );
|
|
## create window and a trackbar (slider) with parent "image" and set callback
|
|
## (the slider regulates upper threshold, passed to Canny edge detector)
|
|
cvNamedWindow( g.wndname, 1 );
|
|
cvCreateTrackbar( "canny thresh", g.wndname, g.thresh, 1000, @on_trackbar );
|
|
## force the image processing
|
|
on_trackbar(0);
|
|
## wait for key.
|
|
## Also the function cvWaitKey takes care of event processing
|
|
c = cvWaitKey(0);
|
|
## clear memory storage - reset free space position
|
|
cvClearMemStorage( g.storage );
|
|
if( c == '\x1b' )
|
|
break;
|
|
endif
|
|
endfor
|
|
cvDestroyWindow( g.wndname );
|
|
|