OpenCV HoughCircle를 이용해서 원 검출 프로그래밍 삽질기 :: Horizontal Grays S2

우선 제일 먼저 해본게 원을 검출하는 일이다. 

(이게 실제 내가 나중에 가장 중점적으로 해야할 일 중 하나이기도 해서 ^^;;)


기본적으로 다음과 같이 프로그래밍 하면 정석이지 않을까 싶어서 프로그래밍 시작.

 1. 영상 획득 

 2. 획득한 영상을 흑백영상으로 변환 ( cvCvtColor 이용)

 3. 흑백영상을 이진화 (cvThreshold 이용)

 4. Edge 검출 (cvCanny 이용)

 5. 원 검출 (cvHoughCircles 이용)

 6. 검출한 원을 중심점 및 반지름을 구해서 Window에 표시


이러면 될 것 같아서 짠 프로그램의 결과는 다음과 같았슴




 오오 잘된다!!! 그런데 60fps으로 입력받던 카메라 영상이 영상 처리하느라고 20프레임까지 떨어져 버림 ㅠㅜ

여튼 위에서 언급한 함수들을 적용하는데 파라미터 설정의 삽질이 좀 있었다. ㅠㅜ

그걸 좀 적어두자면


1. 흑백영상으로 변환하기 위해 GrayImg라고 Iplimage를 선언하고 cvCreateImage를 사용

C: IplImage* cvCreateImage(CvSize size, int depth, int channels)

Parameters:

size – Image width and height

depth – Bit depth of image elements. See IplImage for valid depths.

channels – Number of channels per pixel. See IplImage for details. This function only creates images with interleaved channels.


link:

http://docs.opencv.org/modules/core/doc/old_basic_structures.html?highlight=cvcreateimage#IplImage* cvCreateImage(CvSize size, int depth, int channels)


 여기서 첫번째 parameter size는 획득한 영상을 이용 cvGetSize()로 잘 얻어왔고, depth도 예제대로 IPL_DEPTH_8U(unsigned 8bit integer)로 문제 없었다.

문제는 channels parameter, 예제에 3으로 되어있어서 3으로 두었더니 


 what():  /home/ubuntu/release/opencv-2.4.8/modules/imgproc/src/color.cpp:4422: error: (-215) dst.data == dst0.data in function cvCvtColor


위와 같은 Run-time 에러가 발생한다. 대체 이게 뭔 문제가 싶어서 뒤적여본 결과 CreateImage를 할 때 3 channel로 RGB 컬러영상으로 만들고 나중에 cvCvtcolor로 영상변환을 하려고 하니

메모리가 다른거지 뭐 --;;;

그래서 Channel을 1 (흑백영상)로 바꾸고 해결


2. 영상을 흑백으로 변환

Converts an image from one color space to another.


C++: void cvtColor(InputArray src, OutputArray dst, int code, int dstCn=0 )

Python: cv2.cvtColor(src, code[, dst[, dstCn]]) → dst

C: void cvCvtColor(const CvArr* src, CvArr* dst, int code)

Python: cv.CvtColor(src, dst, code) → None

Parameters:

src – input image: 8-bit unsigned, 16-bit unsigned ( CV_16UC... ), or single-precision floating-point.

dst – output image of the same size and depth as src.

code – color space conversion code (see the description below).

dstCn – number of channels in the destination image; if the parameter is 0, the number of the channels is derived automatically from src and code ..


link:

http://docs.opencv.org/modules/imgproc/doc/miscellaneous_transformations.html?highlight=cvcvtcolor#void cvCvtColor(const CvArr* src, CvArr* dst, int code)


cvCvtColor함수를 이용하는데 이건 뭐 예제대로 별 무리 없이 되었슴. 
흑백으로 변환 할 때 code parameter에 CV_BGR2GRAY 로 하면 지가 알아서 흑백으로 변환, 마지막 파라미터는 안씀

3. 이진화 
Applies a fixed-level threshold to each array element.

C++: double threshold(InputArray src, OutputArray dst, double thresh, double maxval, int type)
Python: cv2.threshold(src, thresh, maxval, type[, dst]) → retval, dst
C: double cvThreshold(const CvArr* src, CvArr* dst, double threshold, double max_value, int threshold_type)
Python: cv.Threshold(src, dst, threshold, maxValue, thresholdType) → None
Parameters:
src – input array (single-channel, 8-bit or 32-bit floating point).
dst – output array of the same size and type as src.
thresh – threshold value.
maxval – maximum value to use with the THRESH_BINARY and THRESH_BINARY_INV thresholding types.
type – thresholding type (see the details below).

link:
http://docs.opencv.org/modules/imgproc/doc/miscellaneous_transformations.html?highlight=cvthreshold#double cvThreshold(const CvArr* src, CvArr* dst, double threshold, double max_value, int threshold_type)
 
cvThreshold()를 이용  여기서는 Thresh parameter를 잘 선택해야함. 
type을 CV_THRESH_BINARY로 선택했기 때문에 임계값 초과는 255(white), 이하는 0(black)으로 만들기 때문이다.
적정한 값 대입해서 테스트 환경에 맞게 선택했슴

4. cvCanny를 이용하여 Edge 검출
Finds edges in an image using the [Canny86] algorithm.

C++: void Canny(InputArray image, OutputArray edges, double threshold1, double threshold2, int apertureSize=3, bool L2gradient=false )
Python: cv2.Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient]]]) → edges
C: void cvCanny(const CvArr* image, CvArr* edges, double threshold1, double threshold2, int aperture_size=3 )
Python: cv.Canny(image, edges, threshold1, threshold2, aperture_size=3) → None¶
Parameters:
image – single-channel 8-bit input image.
edges – output edge map; it has the same size and type as image .
threshold1 – first threshold for the hysteresis procedure.
threshold2 – second threshold for the hysteresis procedure.
apertureSize – aperture size for the Sobel() operator.
L2gradient – a flag, indicating whether a more accurate  L_2 norm  =\sqrt{(dI/dx)^2 + (dI/dy)^2} should be used to calculate the image gradient magnitude ( L2gradient=true ), or whether the default  L_1 norm =|dI/dx|+|dI/dy| is enough ( L2gradient=false ).

link:
http://docs.opencv.org/modules/imgproc/doc/feature_detection.html?highlight=cvcanny#void cvCanny(const CvArr* image, CvArr* edges, double threshold1, double threshold2, int aperture_size)

사실 cvCanny는 Edge Detector라는 것만 알고 Parameter의 사용법에 대해서 잘 몰랐슴..
역시 여기서도 중요한게 3,4번 parameter threshold1과 threshold2  5번 파라미터는 Edge의 두께 정도로 생각하면 될 듯.
여튼 threshold1과 threshold2의 설명을 읽어봐도 모르겠고 해서 뒤적였더니 각각은 다음과 같은 의미를 가지고 있는듯
threshold1 = low threshold
threshold2 = high threshold

 Edge를 검출함에 있어서 어두운 영역의 임계값과 밝은 영역의 임계값을 결정 짓는 요소인듯하다.
불확실한데 값들을 변경시켜가며 한 결과는 그런듯 ㅋ

5. cvHoughCircles를 이용하여 원 검출
Finds circles in a grayscale image using the Hough transform.

C++: void HoughCircles(InputArray image, OutputArray circles, int method, double dp, double minDist, double param1=100, double param2=100, int minRadius=0, int maxRadius=0 )
C: CvSeq* cvHoughCircles(CvArr* image, void* circle_storage, int method, double dp, double min_dist, double param1=100, double param2=100, int min_radius=0, int max_radius=0 )

Parameters:
image – 8-bit, single-channel, grayscale input image.
circles – Output vector of found circles. Each vector is encoded as a 3-element floating-point vector (x, y, radius) .
circle_storage – In C function this is a memory storage that will contain the output sequence of found circles.
method – Detection method to use. Currently, the only implemented method is CV_HOUGH_GRADIENT , which is basically 21HT , described in [Yuen90].
dp – Inverse ratio of the accumulator resolution to the image resolution. For example, if dp=1 , the accumulator has the same resolution as the input image. If dp=2 , the accumulator has half as big width and height.
minDist – Minimum distance between the centers of the detected circles. If the parameter is too small, multiple neighbor circles may be falsely detected in addition to a true one. If it is too large, some circles may be missed.
param1 – First method-specific parameter. In case of CV_HOUGH_GRADIENT , it is the higher threshold of the two passed to the Canny() edge detector (the lower one is twice smaller).
param2 – Second method-specific parameter. In case of CV_HOUGH_GRADIENT , it is the accumulator threshold for the circle centers at the detection stage. The smaller it is, the more false circles may be detected. Circles, corresponding to the larger accumulator values, will be returned first.
minRadius – Minimum circle radius.
maxRadius – Maximum circle radius.

link
http://docs.opencv.org/modules/imgproc/doc/feature_detection.html?highlight=cvhoughcircle#CvSeq* cvHoughCircles(CvArr* image, void* circle_storage, int method, double dp, double min_dist, double param1, double param2, int min_radius, int max_radius)

 여기서 dp 1이면 입력해상도와 동일한 해상도, 2면 절반이라는데 잘 모르겠으니 1만 썼다 ㅋ
minDist는 검출된 원의 중심사이의 거리를 설정하는 듯, 즉 이게 너무 좁으면 가까운 영역에서 많은 원들이 검출 될 수 있기에 적당히 조절할 필요가 있다.
minRadius 는 검출할 원의 최소 반지름, maxRadius는 검출할 원의 최대 반지름 <= 요넘들의 범위를 좁히면 cvHoughCircles()의 수행속도가 빨라진다. 그러므로 필요에 맞게 범위 조절할 것
여기서도 잘 모르겠는게 param1과 param2
param1은 Canny Edge Detector의 Threshold 값이라고 하고
param2는 accumulator 임계값이라는데 뭔소린지 모르겠슴.. ㅠㅜ (기본이 안되어 있다보니 이런게 어렵다 ㅠㅜ)
여튼 요놈들도 상황에 맞게 조절했슴 ㅋ


이렇게해서 프로그래밍한 소스는 다음과 같다.
//============================================================================
// Name        : OpenCVTest3.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 "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;



using namespace std;
int main() {

IplImage* img;
IplImage* GrayImg=0;
IplImage* Edge=0;

int key;
int k;
double dp = 1;
double min_dist = 100;



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, 60);


while(1)
{
gettimeofday(&start_time,NULL);

cvGrabFrame(capture);
img = cvRetrieveFrame(capture);

GrayImg = cvCreateImage(cvGetSize(img),IPL_DEPTH_8U,1);
cvCvtColor(img,GrayImg,CV_BGR2GRAY);
cvThreshold(GrayImg,GrayImg,200,255,CV_THRESH_BINARY);

Edge = cvCreateImage(cvGetSize(GrayImg),IPL_DEPTH_8U,CV_THRESH_BINARY);

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

CvMemStorage* storage = cvCreateMemStorage(0);

CvSeq* Circles = cvHoughCircles(Edge,storage,CV_HOUGH_GRADIENT,dp,min_dist,200,25,30,50);

for(k=0;k<Circles->total;k++)
{
float* circle;
int cx,cy,radius;

circle = (float*)cvGetSeqElem(Circles,k);
cvCircle(img,cvPoint(cvRound(circle[0]),cvRound(circle[1])),cvRound(circle[2]),CV_RGB(255,0,0),3,8,0);

}

cvNamedWindow( "PreviewImage", 1 );
cvShowImage( "PreviewImage", img );
cvNamedWindow( "EdgeImage", 1 );
cvShowImage( "EdgeImage", Edge );
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 = 60.0;
}
elapsed_time *= 1000.0;

printf("Elapsed Time = %2.3f ms, Frame Rate = %2.3f (fps), Average Frame Rate = %2.3f \n",elapsed_time,fps,average_fps);



} //end of while(1)

cvReleaseCapture( &capture );

return 0;
}



그런데 parameter 들을 찾고 적용하면서 cvHoughCircles()의 parameter를 보고 문득 든 생각이....
 cvHoughCircles의 canny threshold가 있는 것을 보니 cvHoughCircles 함수 내부적으로 Edge Detect를 하는 것은 아닌가 하는 생각이 들었다.
결정적인 이유가 cvCanny 함수의 임계값과 cvHoughCircles에서 canny threshold를 잘못 적용하면 원을 검출 못하는 결과가 발생해서였다.
그리고 인터넷에 있던 예제들 중에도 별다른 과정 없이 cvHoughCircles만 사용하는 것들이 있었다.
그렇다면 실행속도도 단축시킬겸 한번 해보기로!!



 실행결과 잘 된다!!! 그런데 실행속도의 향상은 없다고 봐야겠다. T.T
 그리고 흑백처리를 하지 않으면 Run-Time Error가 발생해서 그거 찾기보단 일단 흑백변환은 해서 적용했다.
기존에 사용했던 cvThreshold(), cvCanny() 삭제하고 cvHoughCircles의 param2만 값을 50으로 바꿔서 수행했다.

자.. 이제 실행속도는 어떻게 향상시킬수 있을 지 고민 좀 해봐야겠다.



















+ Recent posts