OpenCV 사각형 검출 및 기울어진 각도 계산 :: Horizontal Grays S2


이번에 할 것은 영상내의 사각형을 찾아내고 그 사각형의 기울기를 계산하는 것


아래 OpenCV에서 친절히 샘플 소스를 줬는데... 뭔 소린지 잘 모르겠어서 참고하고 기존에 쓰던 알고리즘을 이용해서 하기로 했다.


1. 영상취득

2. Gray Scale로 변환 - cvCvtColor()

3. Smooth - cvSmooth()

4. 이진화 - cvThreshold()

5. Edge Detect - cvCanny()

6. 코너검출 - cvFindContours()

7. 다각형 추측 - cvApproxPoly()

8. cvApproxPoly의 결과값이 사각형이라면 각 코너포인트를 이용하여 사각형의 긴 부분과 짧은 부분 구분

9. 사각형의 짧은 두 부분의 중간점 찾아내고 그 중간점을 atan() 이용하여 두 중간점을 있는 선의 기울기를 구한다.



OpenCV Squares.c Sample Code

// 
// The full "Square Detector" program. 
// It loads several images subsequentally and tries to find squares in 
// each image 
// 
#ifdef _CH_ 
#pragma package <opencv> 
#endif 

#include "cv.h" 
#include "highgui.h" 
#include <stdio.h> 
#include <math.h> 
#include <string.h> 

int thresh = 50; 
IplImage* img = 0; 
IplImage* img0 = 0; 
CvMemStorage* storage = 0; 
const char* wndname = "Square Detection Demo"; 

// helper function: 
// finds a cosine of angle between vectors 
// from pt0->pt1 and from pt0->pt2 
double angle( CvPoint* pt1, CvPoint* pt2, CvPoint* 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); 


// returns sequence of squares detected on the image. 
// the sequence is stored in the specified memory storage 
CvSeq* findSquares4( IplImage* img, CvMemStorage* storage ) 

CvSeq* contours; 
int i, c, l, N = 11; 
CvSize sz = cvSize( img->width & -2, img->height & -2 ); 
IplImage* timg = cvCloneImage( img ); // make a copy of input image 
IplImage* gray = cvCreateImage( sz, 8, 1 ); 
IplImage* pyr = cvCreateImage( cvSize(sz.width/2, sz.height/2), 8, 3 ); 
IplImage* tgray; 
CvSeq* result; 
double s, t; 
// create empty sequence that will contain points - 
// 4 points per square (the square's vertices) 
CvSeq* squares = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvPoint), storage ); 

// select the maximum ROI in the image 
// with the width and height divisible by 2 
cvSetImageROI( timg, cvRect( 0, 0, sz.width, sz.height )); 

// down-scale and upscale the image to filter out the noise 
cvPyrDown( timg, pyr, 7 ); 
cvPyrUp( pyr, timg, 7 ); 
tgray = cvCreateImage( sz, 8, 1 ); 

// find squares in every color plane of the image 
for( c = 0; c < 3; c++ ) 

// extract the c-th color plane 
cvSetImageCOI( timg, c+1 ); 
cvCopy( timg, tgray, 0 ); 

// try several threshold levels 
for( l = 0; l < N; l++ ) 

// hack: use Canny instead of zero threshold level. 
// Canny helps to catch squares with gradient shading 
if( l == 0 ) 

// apply Canny. Take the upper threshold from slider 
// and set the lower to 0 (which forces edges merging) 
cvCanny( tgray, gray, 0, thresh, 5 ); 
// dilate canny output to remove potential 
// holes between edge segments 
cvDilate( gray, gray, 0, 1 ); 

else 

// apply threshold if l!=0: 
// tgray(x,y) = gray(x,y) < (l+1)*255/N ? 255 : 0 
cvThreshold( tgray, gray, (l+1)*255/N, 255, CV_THRESH_BINARY ); 


// find contours and store them all as a list 
cvFindContours( gray, storage, &contours, sizeof(CvContour), 
CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0) ); 

// test each contour 
while( contours ) 

// approximate contour with accuracy proportional 
// to the contour perimeter 
result = cvApproxPoly( contours, sizeof(CvContour), storage, 
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 && 
fabs(cvContourArea(result,CV_WHOLE_SEQ)) > 1000 && 
cvCheckContourConvexity(result) ) 

s = 0; 

for( i = 0; i < 5; i++ ) 

// find minimum angle between joint 
// edges (maximum of cosine) 
if( i >= 2 ) 

t = fabs(angle( 
(CvPoint*)cvGetSeqElem( result, i ), 
(CvPoint*)cvGetSeqElem( result, i-2 ), 
(CvPoint*)cvGetSeqElem( result, i-1 ))); 
s = s > t ? s : t; 



// 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 = 0; i < 4; i++ ) 
cvSeqPush( squares, 
(CvPoint*)cvGetSeqElem( result, i )); 


// take the next contour 
contours = contours->h_next; 




// release all the temporary images 
cvReleaseImage( &gray ); 
cvReleaseImage( &pyr ); 
cvReleaseImage( &tgray ); 
cvReleaseImage( &timg ); 

return squares; 



// the function draws all the squares in the image 
void drawSquares( IplImage* img, CvSeq* squares ) 

CvSeqReader reader; 
IplImage* cpy = cvCloneImage( img ); 
int i; 

// initialize reader of the sequence 
cvStartReadSeq( squares, &reader, 0 ); 

// read 4 sequence elements at a time (all vertices of a square) 
for( i = 0; i < squares->total; i += 4 ) 

CvPoint pt[4], *rect = pt; 
int count = 4; 

// read 4 vertices 
CV_READ_SEQ_ELEM( pt[0], reader ); 
CV_READ_SEQ_ELEM( pt[1], reader ); 
CV_READ_SEQ_ELEM( pt[2], reader ); 
CV_READ_SEQ_ELEM( pt[3], reader ); 

// draw the square as a closed polyline 
cvPolyLine( cpy, &rect, &count, 1, 1, CV_RGB(0,255,0), 3, CV_AA, 0 ); 


// show the resultant image 
cvShowImage( wndname, cpy ); 
cvReleaseImage( &cpy ); 



char* names[] = { "pic1.png", "pic2.png", "pic3.png", 
"pic4.png", "pic5.png", "pic6.png", 0 }; 

int main(int argc, char** argv) 

int i, c; 
// create memory storage that will contain all the dynamic data 
storage = cvCreateMemStorage(0); 

for( i = 0; names[i] != 0; i++ ) 

// load i-th image 
img0 = cvLoadImage( names[i], 1 ); 
if( !img0 ) 

printf("Couldn't load %s\n", names[i] ); 
continue; 

img = cvCloneImage( img0 ); 

// create window and a trackbar (slider) with parent "image" and set callback 
// (the slider regulates upper threshold, passed to Canny edge detector) 
cvNamedWindow( wndname, 1 ); 

// find and draw the squares 
drawSquares( img, findSquares4( img, storage ) ); 

// wait for key. 
// Also the function cvWaitKey takes care of event processing 
c = cvWaitKey(0); 
// release both images 
cvReleaseImage( &img ); 
cvReleaseImage( &img0 ); 
// clear memory storage - reset free space position 
cvClearMemStorage( storage ); 
if( (char)c == 27 ) 
break; 


cvDestroyWindow( wndname ); 

return 0; 
}




1~5 까지는 전에 설명했었으니 패스

6. cvFindCountours 는 영상에서 도형의 윤곽을 찾아내는 역할

   여기서 Mode를 잘 써야한다는데 난 그냥 예제대로 CV_RETR_LIST를 사용, 요놈은 도형의 외곽을 찾아 hierarchy구조 없이 List에 저장하는 모드.

   여튼 필요에 따라 이 모드를 잘 설정해야함


* cvFindContours


Finds contours in a binary image.


C++: void findContours(InputOutputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method, Point offset=Point())

C++: void findContours(InputOutputArray image, OutputArrayOfArrays contours, int mode, int method, Point offset=Point())

Python: cv2.findContours(image, mode, method[, contours[, hierarchy[, offset]]]) → contours, hierarchy

C: int cvFindContours(CvArr* image, CvMemStorage* storage, CvSeq** first_contour, int header_size=sizeof(CvContour), int mode=CV_RETR_LIST, int method=CV_CHAIN_APPROX_SIMPLE, CvPoint offset=cvPoint(0,0) )

Python: cv.FindContours(image, storage, mode=CV_RETR_LIST, method=CV_CHAIN_APPROX_SIMPLE, offset=(0, 0)) → contours

Parameters:

image – Source, an 8-bit single-channel image. Non-zero pixels are treated as 1’s. Zero pixels remain 0’s, so the image is treated as binary . You can use compare() , inRange() , threshold() , adaptiveThreshold() , Canny() , and others to create a binary image out of a grayscale or color one. The function modifies the image while extracting the contours.

contours – Detected contours. Each contour is stored as a vector of points.

hierarchy – Optional output vector, containing information about the image topology. It has as many elements as the number of contours. For each i-th contour contours[i] , the elements hierarchy[i][0] , hiearchy[i][1] , hiearchy[i][2] , and hiearchy[i][3] are set to 0-based indices in contours of the next and previous contours at the same hierarchical level, the first child contour and the parent contour, respectively. If for the contour i there are no next, previous, parent, or nested contours, the corresponding elements of hierarchy[i] will be negative.

mode –

Contour retrieval mode (if you use Python see also a note below).


CV_RETR_EXTERNAL retrieves only the extreme outer contours. It sets hierarchy[i][2]=hierarchy[i][3]=-1 for all the contours.

CV_RETR_LIST retrieves all of the contours without establishing any hierarchical relationships.

CV_RETR_CCOMP retrieves all of the contours and organizes them into a two-level hierarchy. At the top level, there are external boundaries of the components. At the second level, there are boundaries of the holes. If there is another contour inside a hole of a connected component, it is still put at the top level.

CV_RETR_TREE retrieves all of the contours and reconstructs a full hierarchy of nested contours. This full hierarchy is built and shown in the OpenCV contours.c demo.

method –

Contour approximation method (if you use Python see also a note below).


CV_CHAIN_APPROX_NONE stores absolutely all the contour points. That is, any 2 subsequent points (x1,y1) and (x2,y2) of the contour will be either horizontal, vertical or diagonal neighbors, that is, max(abs(x1-x2),abs(y2-y1))==1.

CV_CHAIN_APPROX_SIMPLE compresses horizontal, vertical, and diagonal segments and leaves only their end points. For example, an up-right rectangular contour is encoded with 4 points.

CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS applies one of the flavors of the Teh-Chin chain approximation algorithm. See [TehChin89] for details.

offset – Optional offset by which every contour point is shifted. This is useful if the contours are extracted from the image ROI and then they should be analyzed in the whole image context.



7. cvApproxPoly는 위에  cvFindContours로 찾아낸 도형의 윤곽을 근사화 시키는데 결과값반환이 4이면 사각형 요놈을 이용해서 사각형을 판별한다.

여기서 4번째 파라미터는 오직 CV_POLY_APPROX_DP만 서포트 한다니 예제대로 CV_POLY_APPROX_DP 를 사용하고

5번째 파라미터 epsilon은 근사화 정밀도.. 역시 어떤값이 좋은지 잘 모르므로 예제대로 ^^;


cvApproxPoly

Approximates a polygonal curve(s) with the specified precision.


C++: void approxPolyDP(InputArray curve, OutputArray approxCurve, double epsilon, bool closed)

Python: cv2.approxPolyDP(curve, epsilon, closed[, approxCurve]) → approxCurve

C: CvSeq* cvApproxPoly(const void* src_seq, int header_size, CvMemStorage* storage, int method, double eps, int recursive=0 )

Parameters:

curve –

Input vector of a 2D point stored in:


std::vector or Mat (C++ interface)

Nx2 numpy array (Python interface)

CvSeq or `` CvMat (C interface)

approxCurve – Result of the approximation. The type should match the type of the input curve. In case of C interface the approximated curve is stored in the memory storage and pointer to it is returned.

epsilon – Parameter specifying the approximation accuracy. This is the maximum distance between the original curve and its approximation.

closed – If true, the approximated curve is closed (its first and last vertices are connected). Otherwise, it is not closed.

header_size – Header size of the approximated curve. Normally, sizeof(CvContour) is used.

storage – Memory storage where the approximated curve is stored.

method – Contour approximation algorithm. Only CV_POLY_APPROX_DP is supported.

recursive – Recursion flag. If it is non-zero and curve is CvSeq*, the function cvApproxPoly approximates all the contours accessible from curve by h_next and v_next links.

The functions approxPolyDP approximate a curve or a polygon with another curve/polygon with less vertices so that the distance between them is less or equal to the specified precision. It uses the Douglas-Peucker algorithm http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm


See https://github.com/Itseez/opencv/tree/master/samples/cpp/contours2.cpp for the function usage model.



http://docs.opencv.org/modules/imgproc/doc/structural_analysis_and_shape_descriptors.html?highlight=cvapproxpoly#CvSeq* cvApproxPoly(const void* src_seq, int header_size, CvMemStorage* storage, int method, double eps, int recursive)



8. 사실 OpenCV에 Moments 함수가 있어서 이를 이용하여 기울어진 이미지는 쉽게 판독이 가능하다는데 난 이미지 내의 도형의 기울기를 찾아내야 해서 안맞고

    뭔가 쉬운 방법이 있을 것 같은데  찾을수가 없었다. 그래서 그냥 무식한 방법으로 해결하기로 결정

   cvGetSeqElem을 이용해서 4개의 포인트를 찾고 이를 각각의 좌표로 변환

   x1 = pt1.x

   y1 = pt1.y

   이런식으로 4 포인트 각각의 x,y를 구하고


  4포인트간의 길이를 계산 pt1-pt2, pt2-pt3, pt3-pt4, pt4-pt1

  length = sqrt(pow(x1-x2,2) + pow(y1-y2,2)); <- 뭐 이거는 그냥 피타고라스 정리로 ^^;


  그 중 짧은 두 선분을 찾고 그에 해당하는 좌표를 기억


 

9. 사각형의 짧은 선분을 구성하는 두 포인트의 중간점을 계산

   그 두 중간점을 이용하여 각도를 계산

abase = px2 - px1;

aheight = py2 - py1;

angle = atan(aheight/abase);

angle_degree = angle*180.0/3.141592;


실행결과는 다음과 같이 잘 되었슴 ㅋ




왼쪽 위가 실제 촬영되고 있는 영상이고

빨간색 포인트는 검출한 사각형으 코너 포인트, 파란색 라인은 각 코너포인트간의 연결선으로 검출된 사각형을 그리고,

녹색선은 짧은 선의 중간점끼리 연결한 선

녹색선의 각도를 도형의 각도로 계산한다.


그 우측 Edge는 그 아래 이진화 한 영상의 Edge를 detect한 화면

속도도 초당 41fps 정도로 꽤 빠르다.


소스는 다음과 같다.

(좀 멍청하게 짜서 쓸데없이 중복되는거 많음 ㅋㅋㅋ 수정하기 귀찮아서 냅뒀슴)




//============================================================================

// Name        : 20140317_RECTANGLE2.cpp

// Author      : jslee

// Version     :

// Copyright   : Your copyright notice

// Description : Hello World in C++, Ansi-style

//============================================================================



#include <iostream>

#include <stdio.h>

#include <sys/time.h>

#include <math.h>

#include "opencv2/opencv.hpp"



struct timeval start_time;

struct timeval end_time;

double elapsed_time;

float fps;

double total_fps = 0.0,average_fps = 0.0;

long cnt=1;

int cx,cy,radius,ccnt;



using namespace std;



int main() {


IplImage* img;

IplImage* GrayImg=0;

IplImage* Edge=0;

IplImage* HSVImg;

IplImage* HImg = 0;

IplImage* SImg = 0;

IplImage* VImg = 0;


int key;

int i,k;

double dp = 1;

double min_dist = 300;

double t,s;

double angle12,angle23,angle34,angle41;

int px1,py1,px2,py2,sx1,sy1,sx2,sy2,sx3,sy3,sx4,sy4,spx1,spy1,spx2,spy2;

double len12,len23,len34,len41;

double longlen;

double abase, aheight;

char buffer[25];

CvFont font;

cvInitFont(&font,CV_FONT_HERSHEY_SIMPLEX,0.5,0.5);

CvSeq* result;

CvPoint* pt1;

CvPoint* pt2;

CvPoint* pt3;

CvPoint* pt4;



CvCapture* capture = cvCaptureFromCAM(0);

cvSetCaptureProperty( capture, CV_CAP_PROP_FRAME_WIDTH, 640);

cvSetCaptureProperty( capture, CV_CAP_PROP_FRAME_HEIGHT, 480);

cvSetCaptureProperty( capture, CV_CAP_PROP_FPS, 65);



while(1)

{

gettimeofday(&start_time,NULL);


cvGrabFrame(capture);

img = cvRetrieveFrame(capture);

GrayImg = cvCreateImage(cvGetSize(img),IPL_DEPTH_8U,1);

Edge = cvCreateImage(cvGetSize(img),IPL_DEPTH_8U,1);

cvCvtColor(img,GrayImg,CV_BGR2GRAY);


cvSmooth(GrayImg,GrayImg,CV_GAUSSIAN,9,9,2,2);

cvThreshold(GrayImg,GrayImg,200,255,CV_THRESH_BINARY);


cvCanny(GrayImg,Edge,25,200,3);


CvMemStorage* storage = cvCreateMemStorage(0);



CvSeq* Contours;

cvFindContours(Edge,storage,&Contours, sizeof(CvContour),CV_RETR_LIST,CV_CHAIN_APPROX_SIMPLE);

while(Contours)

{

result = cvApproxPoly(Contours,sizeof(CvContour),storage,CV_POLY_APPROX_DP,cvContourPerimeter(Contours)*0.02,0);


if(result->total == 4 && fabs(cvContourArea(result,CV_WHOLE_SEQ)) > 1000 && cvCheckContourConvexity(result))

{


pt1 = (CvPoint*)cvGetSeqElem(result,0);

pt2 = (CvPoint*)cvGetSeqElem(result,1);

pt3 = (CvPoint*)cvGetSeqElem(result,2);

pt4 = (CvPoint*)cvGetSeqElem(result,3);


int x1 = pt1->x;

int y1 = pt1->y;

int x2 = pt2->x;

int y2 = pt2->y;

int x3 = pt3->x;

int y3 = pt3->y;

int x4 = pt4->x;

int y4 = pt4->y;



cvCircle(img,cvPoint(x1,y1),3,CV_RGB(255,0,0),3,8,0);

cvCircle(img,cvPoint(x2,y2),3,CV_RGB(255,0,0),3,8,0);

cvCircle(img,cvPoint(x3,y3),3,CV_RGB(255,0,0),3,8,0);

cvCircle(img,cvPoint(x4,y4),3,CV_RGB(255,0,0),3,8,0);


cvLine(img,cvPoint(x1,y1),cvPoint(x2,y2),CV_RGB(0,0,255),2,8,0);

cvLine(img,cvPoint(x2,y2),cvPoint(x3,y3),CV_RGB(0,0,255),2,8,0);

cvLine(img,cvPoint(x3,y3),cvPoint(x4,y4),CV_RGB(0,0,255),2,8,0);

cvLine(img,cvPoint(x4,y4),cvPoint(x1,y1),CV_RGB(0,0,255),2,8,0);


//calculate angle

//calculate length of two point

len12 = sqrt(pow(x1-x2,2) + pow(y1-y2,2));

len23 = sqrt(pow(x2-x3,2) + pow(y2-y3,2));

len34 = sqrt(pow(x3-x4,2) + pow(y3-y4,2));

len41 = sqrt(pow(x4-x1,2) + pow(y4-y1,2));


//sort len

longlen = len12;

px1 = x1;

py1 = y1;

px2 = x2;

py2 = y2;


sx1 = x2;

sy1 = y2;

sx2 = x3;

sy2 = y3;

sx3 = x4;

sy3 = y4;

sx4 = x1;

sy4 = y1;



if(len23 > longlen)

{

longlen = len23;

px1 = x2;

py1 = y2;

px2 = x3;

py2 = y3;


sx1 = x3;

sy1 = y3;

sx2 = x4;

sy2 = y4;

sx3 = x1;

sy3 = y1;

sx4 = x2;

sy4 = y2;

}


if(len34 > longlen)

{

longlen = len34;

px1 = x3;

py1 = y3;

px2 = x4;

py2 = y4;


sx1 = x4;

sy1 = y4;

sx2 = x1;

sy2 = y1;

sx3 = x2;

sy3 = y2;

sx4 = x3;

sy4 = y3;

}


if(len41 > longlen)

{

longlen = len41;

px1 = x1;

py1 = y1;

px2 = x4;

py2 = y4;


sx1 = x1;

sy1 = y1;

sx2 = x2;

sy2 = y2;

sx3 = x3;

sy3 = y3;

sx4 = x4;

sy4 = y4;

}


spx1 = (sx1 + sx2) / 2;

spy1 = (sy1 + sy2) / 2;

spx2 = (sx3 + sx4) / 2;

spy2 = (sy3 + sy4) / 2;

cvLine(img,cvPoint(spx1,spy1),cvPoint(spx2,spy2),CV_RGB(0,255,0),2,8,0);

px1 = spx1;

py1 = spy1;

px2 = spx2;

py2 = spy2;

//find min x -> point2

//point 1 - point2

abase = px2 - px1;

aheight = py2 - py1;

angle23 = atan(aheight/abase);

angle12 = angle23*180.0/3.141592;

sprintf(buffer,"ang= %02.2f",angle12);

cvPutText(img,buffer,cvPoint(abs((px2+px1)/2),abs((py2+py1)/2)),&font,CV_RGB(255,0,0));





}

Contours = Contours->h_next;

}



cvNamedWindow( "PreviewImage", 1 );

cvShowImage( "PreviewImage", img );

cvMoveWindow("PreviewImage",200,100);


cvNamedWindow( "GrayImage", 1 );

cvShowImage( "GrayImage", GrayImg );

cvMoveWindow("GrayImage",850,600);


cvNamedWindow( "Edge", 1 );

cvShowImage( "Edge", Edge );

cvMoveWindow("Edge",850,100);


key = cvWaitKey(1);

if(key > 10) break;


gettimeofday(&end_time, NULL);



elapsed_time = (double)(end_time.tv_sec) + (double)(end_time.tv_usec)/1000000.0 - (double)(start_time.tv_sec) - (double)(start_time.tv_usec)/1000000.0;

fps = 1.0 / elapsed_time;

total_fps += fps;

average_fps = total_fps / (double)(cnt);

cnt++;

if(cnt == 100)

{

cnt = 1;

total_fps = 0.0;

}

elapsed_time *= 1000.0;


printf("Elapsed T = %2.3f ms, Frame = %2.3f (fps), Avrg Frame Rate = %2.3f count = %d\n",elapsed_time,fps,average_fps,result->total);

//Circles->total = 0;

cvReleaseImage(&GrayImg);

cvReleaseImage(&Edge);

cvReleaseMemStorage(&storage);




} //end of while(1)

cvReleaseImage(&img);

cvReleaseCapture( &capture );


return 0;

}





+ Recent posts