// (2) 폰트구조체를초기화한다 for (i = 0; i < 16; i += 2) { cvinitfont (&font[i], font_face[i / 2], 1.0, 1.0); cvinitfont (&font[i + 1], font_face[i / 2] CV_FONT_ITALIC, 1.0, 1.0); // (3) 폰트를지정하고, 텍스트를그리기한다 for (i = 0; i < 16; i++) { irandom = cvrandint (&rng); rcolor = CV_RGB (irandom & 255, (irandom >> 8) & 255, (irandom >> 16) & 255); cvputtext (img, "OpenCV sample code", cvpoint (15, (i + 1) * 30), &font[i], rcolor); // (4) 이미지의표시, 키가밀렸을때에종료 cvnamedwindow ("Text", CV_WINDOW_AUTOSIZE); cvshowimage ("Text", img); cvwaitkey (0); cvdestroywindow ("Text"); cvreleaseimage (&img); return 0; // (1) 이미지를확보해초기화한다폭 400, 높이 500 픽셀의이미지영역을확보해, 초기화 ( 제로클리어 ) 한다. // (2) 폰트구조체를초기화한다이번은,16 종류의폰트와 2 종류의자체 ( 노멀, 이탤릭 ) 를지정하고, 폰트구조체를초기화한다. 사이즈의비율은, 폭, 높이모두 1.0. // (3) 폰트를지정하고, 텍스트를그리기한다함수 cvputtext()( 을 ) 를이용하고, 텍스트의그리기를실시한다. 이때에, 초기화한폰트구조체와색을지정한다. 또,OpenCV-1.0.0 시점에서이용할수있는폰트세트에서는, 일본어의그리기는할수없다. // (4) 이미지의표시, 키가밀렸을때에종료이미지를실제로표시해, 무엇인가키가밀릴때까지기다린다. 201
실행결과예 IplImage 구조체정보의보존 cvwrite, cvwritecomment IplImage 구조체의정보를파일에보존한다 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> int main (int argc, char *argv[]) { IplImage *color_img, *gray_img; CvRect roi; CvFileStorage *fs; // (1) 이미지를읽어들인다 if (argc!= 2 (color_img = cvloadimage (argv[1], CV_LOAD_IMAGE_COLOR)) == 0) return -1; // (2)ROI 의설정과 2 치화처리 gray_img = cvcreateimage (cvgetsize (color_img), IPL_DEPTH_8U, 1); cvcvtcolor (color_img, gray_img, CV_BGR2GRAY); roi = cvrect (0, 0, color_img->width / 2, color_img->height / 2); cvsetimageroi (gray_img, roi); cvsetimageroi (color_img, roi); cvthreshold (gray_img, gray_img, 90, 255, CV_THRESH_BINARY); // (3)xml 파일에의써내 fs = cvopenfilestorage ("images.xml", 0, CV_STORAGE_WRITE); cvwritecomment (fs, "This is a comment line.", 0); cvwrite (fs, "color_img", color_img); cvstartnextstream (fs); cvwrite (fs, "gray_img", gray_img); cvreleasefilestorage (&fs); 202
cvreleaseimage (&color_img); cvreleaseimage (&gray_img); return 0; // (1) 이미지를읽어들인다함수 cvloadimage 에의해, 보존하기위한칼라이미지를읽어들인다. // (2)ROI 의설정과 2 치화처리함수 cvcvtcolor 에의해칼라이미지를그레이스케일이미지로변환해, 함수 cvsetimageroi 에의해 ROI( 을 ) 를설정한다. 또, 변환한이미지에대해서 2 치화처리를가한다. // (3)xml 파일에의써내함수 cvwrite 에의해, 입력된칼라이미지와변환 처리된그레이스케일이미지를파일에써낸다. 함수 cvsaveimage 과는달리, 오브젝트의정보를데이터로서보존한다. 또, 여기에서는, 둘의 IplImage 구조체의데이터를하나의파일에써내고있지만, 하나의데이터를하나의파일에써내는경우는, cvsave("image.xml", color_img); ( 와 ) 과같이해보존해도좋다. 함수 cvsave 의내부에서는, 함수 cvwrite 하지만불려간다. 함수 cvstartnextstream()( 은 ) 는, 파일기입중의스트림을새롭게하는함수이며, 이장소에서 <!-- next stream --> 그렇다고하는코멘트와개행이삽입되는것만으로있다 ( 이번경우, 표준타입의데이터를톱레벨에쓸뿐 ( 만큼 ) 이 므로 ). 물론, 복수의파일을쓸때에필수라고하는것은아니다. 실행결과예 images.xml.zip 하지만실제로써내진파일이다. 이하의 snapshot 로부터도알수있듯이,ROI 등의정보도보존되고있 다. 맵의순서를보존 cvstartwritestruct, cvendwritestruct 두개의엔트리를가지는맵의순서를파일에보존한다 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> 203
#include <time.h> int main (int argc, char *argv[]) { int size = 20; int i; CvFileStorage *fs; CvRNG rng = cvrng (time (NULL)); CvPoint *pt = (CvPoint *) cvalloc (sizeof (CvPoint) * size); // (1) 점렬의작성 for (i = 0; i < size; i++) { pt[i].x = cvrandint (&rng) % 100; pt[i].y = cvrandint (&rng) % 100; // (2) 맵의순서로서점렬을보존 fs = cvopenfilestorage ("sequence.yml", 0, CV_STORAGE_WRITE); cvstartwritestruct (fs, "points", CV_NODE_SEQ); for (i = 0; i < size; i++) { cvstartwritestruct (fs, NULL, CV_NODE_MAP CV_NODE_FLOW); cvwriteint (fs, "x", pt[i].x); cvwriteint (fs, "y", pt[i].y); cvendwritestruct (fs); cvendwritestruct (fs); cvreleasefilestorage (&fs); return 0; // (1) 점렬의작성랜덤인값을가지는,CvPoint 형태의배열 ( 요소수 20)( 을 ) 를작성한다. // (2) 맵의순서로서점렬을보존레퍼런스메뉴얼에있어서의함수 GetHashedKey() 의설명의항에있는것같은, 두개의엔트리를가지는맵의순서로서보존한다. 함수 cvstartwritestruct()( 을 ) 를이용하고,"x"( 와 ) 과 "y" 의엔트리를가지는맵의기입을실시한다. 한층더그러한맵을, 마지막인수에 CV_NODE_SEQ( 을 ) 를준함수 cvstartwritestruct() 그리고정리하는것으로, 순서로서쓴다. 이번은, 레퍼런스메뉴얼의예에배워,YAML 형식의파일로서보존한다. 204
실행결과예 IplImage 구조체정보의읽기 cvread, cvgetfilenodebyname IplImage 구조체의정보를파일로부터읽어들인다 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> int main (int argc, char *argv[]) { IplImage *color_img, *gray_img; CvRect roi_color, roi_gray; CvFileStorage *fs; CvFileNode *node; // (1) 파일을읽어들인다 if (argc!= 2 (fs = cvopenfilestorage (argv[1], 0, CV_STORAGE_READ)) == 0) return -1; node = cvgetfilenodebyname (fs, NULL, "color_img"); color_img = (IplImage *) cvread (fs, node); node = cvgetfilenodebyname (fs, NULL, "gray_img"); gray_img = (IplImage *) cvread (fs, node); cvreleasefilestorage (&fs); // (2)ROI 정보를취득해구형을그린후, 해방 roi_color = cvgetimageroi (color_img); roi_gray = cvgetimageroi (gray_img); cvresetimageroi (color_img); cvresetimageroi (gray_img); cvrectangle (color_img, cvpoint (roi_color.x, roi_color.y), cvpoint (roi_color.x + roi_color.width, roi_color.y + roi_color.height), CV_RGB (255, 0, 0)); cvrectangle (gray_img, cvpoint (roi_gray.x, roi_gray.y), cvpoint (roi_gray.x + roi_gray.width, roi_gray.y + roi_gray.height), cvscalar (0)); 205
// (3) 이미지를그리기 cvnamedwindow ("Color Image", CV_WINDOW_AUTOSIZE); cvshowimage ("Color Image", color_img); cvnamedwindow ("Grayscale Image", CV_WINDOW_AUTOSIZE); cvshowimage ("Grayscale Image", gray_img); cvwaitkey (0); cvdestroywindow ("Color Image"); cvdestroywindow ("Grayscale Image"); cvreleaseimage (&color_img); cvreleaseimage (&gray_img); return 0; // (1) 파일을읽어들인다커멘드인수로지정된파일을오픈해, 함수 cvopenfilestorage() 에의해파일스토리지포인터를얻는다. 함수 cvgetfilenodebyname() 에의해,"color_img" 라고 "gray_img"( 이 ) 라는이름으로부터노드를취득해, 함수 cvread() 에의해데이터를읽어들인다. // (2)ROI 정보를취득해구형을그린후, 해방함수 cvgetimageroi() 에의해, 읽어들였다 IplImage 구조체로부터 ROI 의정보를추출해, 그구형을그린다. 또, 그때문에 ( 위해 ) 이미지의 ROI( 을 ) 를해방해둔다. // (3) 이미지를그리기실제로, 파일로부터읽힌이미지를표시해, 무엇인가키가밀릴때까지기다린다. 실행결과예 여기서읽히고있는파일은,IplImage 구조체정보의보존그리고보존되었다 xml 파일이다. 맵의순서를읽어들인다 cvgethashedkey, cvgetfilenode 두개의엔트리를가지는맵의순서를파일로부터읽어들인다. 이것은, 참조설명서내의샘플과거의동일하다. 표시의변환 샘플코드 206
#include <cv.h> #include <stdio.h> int main (int argc, char **argv) { // (1) 파일스토리지의오픈, 핫슈드키의계산, 순서노드의취득 CvFileStorage *fs = cvopenfilestorage ("sequence.yml", 0, CV_STORAGE_READ); CvStringHashNode *x_key = cvgethashedkey (fs, "x", -1, 1); CvStringHashNode *y_key = cvgethashedkey (fs, "y", -1, 1); CvFileNode *points = cvgetfilenodebyname (fs, 0, "points"); // (2) 순서리더를초기화, 각노드를차례차례취득 if (CV_NODE_IS_SEQ (points->tag)) { CvSeq *seq = points->data.seq; int i, total = seq->total; CvSeqReader reader; cvstartreadseq (seq, &reader, 0); for (i = 0; i < total; i++) { CvFileNode *pt = (CvFileNode *) reader.ptr; #if 1 // (3) 고속버젼 CvFileNode *xnode = cvgetfilenode (fs, pt, x_key, 0); CvFileNode *ynode = cvgetfilenode (fs, pt, y_key, 0); assert (xnode && CV_NODE_IS_INT (xnode->tag) && ynode && CV_NODE_IS_INT (ynode- >tag)); int x = xnode->data.i; /* 혹은 x = cvreadint( xnode, 0 ); */ int y = ynode->data.i; /* 혹은 y = cvreadint( ynode, 0 ); */ #elif 1 // (4) 저속버젼.x_key( 와 ) 과 y_key( 을 ) 를사용하지않는다 CvFileNode *xnode = cvgetfilenodebyname (fs, pt, "x"); CvFileNode *ynode = cvgetfilenodebyname (fs, pt, "y"); assert (xnode && CV_NODE_IS_INT (xnode->tag) && ynode && CV_NODE_IS_INT (ynode- >tag)); int x = xnode->data.i; /* 혹은 x = cvreadint( xnode, 0 ); */ int y = ynode->data.i; /* 혹은 y = cvreadint( ynode, 0 ); */ #else // (5) 한층더저속이지만, 사용하기쉬운버젼 int x = cvreadintbyname (fs, pt, "x", 0); int y = cvreadintbyname (fs, pt, "y", 0); 207
#endif // (6) 데이터를표시해, 다음의순서노드를취득 CV_NEXT_SEQ_ELEM (seq->elem_size, reader); printf ("%d: (%d, %d)\n", i, x, y); cvreleasefilestorage (&fs); return 0; // (1) 파일스토리지의오픈, 핫슈드키의계산, 순서노드의취득최초로, 파일명을지정해파일스토리지를오픈한다. 또, 각순서의두개의엔트리의키이다 "x","y" 에대한독특한값을함수 cvgethashedkey() 에의해계산하고,CvStringHashNode 형태의포인터를취득한다. 여기서, 함수 cvgethashedkey() 의제 3 인수는, 키의이름의길이이므로, 여기에서는,"1"( 을 ) 를지정해도좋지만,0 이하의값을지정하는것으로자동적으로문자열의길이가계산된다. 오픈된파일노드로부터,"points"( 이 ) 라는이름을가지는순서노드의포인터를취득한다. // (2) 순서리더를초기화, 각노드를차례차례취득함수 cvstartreadseq()( 을 ) 를이용하고, 순서리더에의한연속읽기처리를초기화해, 차례차례노드를읽어들인다. 노드의취득은, 초기화된순서리더가가리키는노드 (ptr 멤버 ) 를캐스트하는것으로행해진다. 또, 이러한처리전에, 미리취득한파일노드의종류가순서인것을확인하기위해서, 매크로 CV_NODE_IS_SEQ()( 을 ) 를이용하고있다. 노드의멤버 tag( 을 ) 를이용하고, 그노드의종류를아는매크로는,CV_NODE_IS_* 그렇다고하는형태로,cxtypes.h 안에서정의되고있다. // (3) 고속버젼미리계산되었다 CvStringHashNode 형태의포인터를이용하고,"x","y" 의이름을가지는노드를취득한다. 이것은, 키의문자열의내용을비교하는것이아니라, 독특한포인터를비교하는것으로써노드를요구하기위해, 후술의함수 cvgetfielenodebyname()( 와 ) 과비교하면약간고속으로동작한다. 또, 취득한노드의값을직접참조하는것이, 함수 cvreadint()( 을 ) 를이용하고값을읽어내는것보다도약간고속으로있다. // (4) 저속버젼.x_key( 와 ) 과 y_key( 을 ) 를사용하지않는다함수 cvgetfielenodebyname() 에의해노드를취득하는일이외는, 전술의예와같이. // (5) 한층더저속이지만, 사용하기쉬운버젼함수 cvreadintbyname()( 은 ) 는, 그내부에서함수 cvgetfilenodebyname 라고함수 cvreadint()( 을 ) 를호출하고있다. 함수 cvreadint() 의호출분의오바헷드가걸리기위해, 상술의예보다한층더약간저속이된다. // (6) 데이터를표시해, 다음의순서노드를취득상술의몇개의방법으로취득된값을표시한다. 또, 매크로 CV_NEXT_SEQ_ELEM() 에의해, 순서리더가가리키는노드를요소의사이즈분만큼진행하고 ( 혹은블록을변환이라고 ), 다음의노드를취득한다. 208
실행결과예 여기서읽히고있는파일은, 맵의순서를보존그리고보존되었다 YAML 파일이다. K-means 법에따르는클러스터링 cvkmeans2 K-means 법으로, 샘플의클러스터링을실시한다. 이것은,OpenCV 부속의샘플코드 ( 와거의동일 ) 이다. 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> #include <time.h> #define MAX_CLUSTERS (5) int main (int argc, char **argv) { CvScalar color_tab[max_clusters] = { CV_RGB (255, 0, 0), CV_RGB (0, 255, 0), CV_RGB (100, 100, 255), CV_RGB (255, 0, 255), CV_RGB (255, 255, 0) ; IplImage *img = cvcreateimage (cvsize (500, 500), IPL_DEPTH_8U, 3); CvRNG rng = cvrng (time (NULL)); CvPoint ipt; while (1) { int c; int k, cluster_count = cvrandint (&rng) % MAX_CLUSTERS + 1; int i, sample_count = cvrandint (&rng) % 1000 + MAX_CLUSTERS; CvMat *points = cvcreatemat (sample_count, 1, CV_32FC2); CvMat *clusters = cvcreatemat (sample_count, 1, CV_32SC1); // (1) 복수의가우시안으로부터완성되는랜덤샘플을생성한다 for (k = 0; k < cluster_count; k++) { CvPoint center; CvMat point_chunk; center.x = cvrandint (&rng) % img->width; center.y = cvrandint (&rng) % img->height; cvgetrows (points, &point_chunk, k * sample_count / cluster_count, k == cluster_count - 1? sample_count : (k + 1) * sample_count / 209
cluster_count, 1); cvrandarr (&rng, &point_chunk, CV_RAND_NORMAL, cvscalar (center.x, center.y, 0, 0), cvscalar (img->width * 0.1, img- >height * 0.1, 0, 0)); // (2) 랜덤샘플을상훌한다 for (i = 0; i < sample_count / 2; i++) { CvPoint2D32f *pt1 = (CvPoint2D32f *) points->data.fl + cvrandint (&rng) % sample_count; CvPoint2D32f *pt2 = (CvPoint2D32f *) points->data.fl + cvrandint (&rng) % sample_count; CvPoint2D32f temp; CV_SWAP (*pt1, *pt2, temp); // (3)K-menas 법에따르는클러스터링 cvkmeans2 (points, cluster_count, clusters, cvtermcriteria (CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 10, 1.0)); // (4) 클러스터마다색을바꾸어샘플을그리기한다 cvzero (img); for (i = 0; i < sample_count; i++) { int cluster_idx = clusters->data.i[i]; ipt.x = (int) points->data.fl[i * 2]; ipt.y = (int) points->data.fl[i * 2 + 1]; cvcircle (img, ipt, 2, color_tab[cluster_idx], CV_FILLED, CV_AA, 0); cvreleasemat (&points); cvreleasemat (&clusters); // (5) 이미지를표시,"Esc" 키가밀렸을때에종료 cvnamedwindow ("Clusters", CV_WINDOW_AUTOSIZE); cvshowimage ("Clusters", img); c = cvwaitkey (0); if (c == '\x1b') break; cvreleasemat (&clusters); cvreleasemat (&points); cvdestroywindow ("Clusters"); cvreleaseimage (&img); 210
return 0; // (1) 복수의가우시안으로부터완성되는랜덤샘플을생성한다우선, 클러스터링대상데이터가되는데이터집합을작성한다. 각데이터는 2 차원좌표를가지는점이며, 그전데이터는,1 sample_count 의행렬 ( 각요소는,CV_32FC2)points 그리고나타내진다. 함수 cvgetrows 에의해의사적으로 ( 클러스터몇분에 ) 분할된부분행렬을, 정규분포하는데이터로초기화한다. 이때의평균은, 최초로랜덤에생성된값, 분산은,0.1* 이미지의사이즈, 이다. 이행렬의초기화에는, 함수 cvrandarr()( 을 ) 를이용한다. // (2) 랜덤샘플을상훌한다또, 생성된샘플은, 이시점에서는각클러스터마다올바르게정렬하고있으므로, 이것을, 매크로 CV_SWAP() 에의해상훌한다. 매크로 CV_SWAP( 은 ) 는,cxtypes.h 안에서, 다음과같이정의된다. #define CV_SWAP(a,b,t) ((t) = (a), (a) = (b), (b) = (t)) // (3)K-menas 법에따르는클러스터링함수 cvkmeans2() 에의해,K-menas 법에따르는클러스터링을실시한다. 인수에는, 각각, 샘플데이터, 클러스터수, 출력클러스터, 종료조건, 을준다. 출력클러스터에는, 각샘플이어느클러스터에속하는지를나타내는클러스터인덱스가들어간다. 또, 종료조건은, 반복계산의최대수와센터의각스텝에있어서의이동거리의최소치가주어져이어느쪽인지가만족되면계산이종료한다. // (4) 클러스터마다색을바꾸어샘플을그리기한다 clusters->data.i[i] 에,i 차례째의샘플이속하는클러스터의인덱스 (int 형태 ) 가보존되고있으므로, 거기에따라, 모든샘플을분류해엔으로그리기한다. // (5) 이미지를표시,"Esc" 키가밀렸을때에종료실제로이미지를표시해, 유저의입력을기다린다. "Esc" 키가밀렸을경우는종료해, 그이외의입력이맞았을경우는, 다시랜덤샘플을생성한다. 실행결과예 각클러스터를대표하는최초의센터는,OpenCV 내부에서랜덤에초기화되기위해, 반드시적절한클러스터링결과가 되는것은아니다. 클러스터링에의한감소색처리 cvkmeans2 k-means 법에따르는클러스터링을이용하고, 매우단순한감소색을실시한다 211
표시의변환 샘플코드 #include <cv.h> #include <highgui.h> #define MAX_CLUSTERS (32) /* 클러스터수 */ int main (int argc, char **argv) { int i, size; IplImage *src_img = 0, *dst_img = 0; CvMat *clusters; CvMat *points; CvMat *color = cvcreatemat (MAX_CLUSTERS, 1, CV_32FC3); CvMat *count = cvcreatemat (MAX_CLUSTERS, 1, CV_32SC1); // (1) 이미지를읽어들인다 if (argc!= 2 (src_img = cvloadimage (argv[1], CV_LOAD_IMAGE_COLOR)) == 0) return -1; size = src_img->width * src_img->height; dst_img = cvcloneimage (src_img); clusters = cvcreatemat (size, 1, CV_32SC1); points = cvcreatemat (size, 1, CV_32FC3); // (2) 픽셀의값을행렬에대입 for (i = 0; i < size; i++) { points->data.fl[i * 3 + 0] = (uchar) src_img->imagedata[i * 3 + 0]; points->data.fl[i * 3 + 1] = (uchar) src_img->imagedata[i * 3 + 1]; points->data.fl[i * 3 + 2] = (uchar) src_img->imagedata[i * 3 + 2]; // (3) 클러스터링 cvkmeans2 (points, MAX_CLUSTERS, clusters, cvtermcriteria (CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 10, 1.0)); // (4) 각클러스터의평균치를계산 cvsetzero (color); cvsetzero (count); for (i = 0; i < size; i++) { int idx = clusters->data.i[i]; int j = ++count->data.i[idx];; color->data.fl[idx * 3 + 0] = color->data.fl[idx * 3 + 0] * (j - 1) / j + points- 212
>data.fl[i * 3 + 0] / j; color->data.fl[idx * 3 + 1] = color->data.fl[idx * 3 + 1] * (j - 1) / j + points- >data.fl[i * 3 + 1] / j; color->data.fl[idx * 3 + 2] = color->data.fl[idx * 3 + 2] * (j - 1) / j + points- >data.fl[i * 3 + 2] / j; // (5) 클러스터마다색을그리기 for (i = 0; i < size; i++) { int idx = clusters->data.i[i]; dst_img->imagedata[i * 3 + 0] = (char) color->data.fl[idx * 3 + 0]; dst_img->imagedata[i * 3 + 1] = (char) color->data.fl[idx * 3 + 1]; dst_img->imagedata[i * 3 + 2] = (char) color->data.fl[idx * 3 + 2]; // (6) 이미지를표시, 키가밀렸을때에종료 cvnamedwindow ("src", CV_WINDOW_AUTOSIZE); cvshowimage ("src", src_img); cvnamedwindow ("low-color", CV_WINDOW_AUTOSIZE); cvshowimage ("low-color", dst_img); cvwaitkey (0); cvdestroywindow ("src"); cvdestroywindow ("low-color"); cvreleaseimage (&src_img); cvreleaseimage (&dst_img); cvreleasemat (&clusters); cvreleasemat (&points); cvreleasemat (&color); cvreleasemat (&count); return 0; // (1) 이미지를읽어들인다함수 vcloadimage() 에의해, 입력상을칼라이미지로서읽어들인다. // (2) 픽셀의값을행렬에대입각픽셀을클러스터링하기위해서, 각픽셀의 RGB 채널의값을 CvMat 형태의변수에대입한다. // (3) 클러스터링전술의샘플과같게, 함수 cvkmeans2() 에의해,k-means 법을이용한클러스터링을실시한다. 클러스터수는미리정의되고있어이것이, 감소색후의색가지수가된다. // (4) 각클러스터의평균치를계산클러스터링된각픽셀군을, 그클러스터의센터에서대표할수있으면처리가간단하지만, OpenCV 그럼, 함수 cvkmeans2() 에의해클러스터링된각클러스터의센터에액세스할수 213
없다. 거기서, 이샘플에서는클러스터링후의각클러스터의 (K 개의대표치는아니다 ) 완전한평균치를요구해그것을각클러스터의대표치로한다. // (5) 클러스터마다색을그리기각클러스터의픽셀을, 구할수있던평균치로묻는다. // (6) 이미지를표시, 키가밀렸을때에종료실제로, 입력이미지와클러스터링 ( 감소색 ) 된이미지를표시해, 무엇인가키가밀릴때까지기다린다. 이와같이 K-means 법을이용해감소색을실시하는일은, 색에의한이미지의영역분할과동일하다. 그러한처리에대해서는, 이미지분할, 영역결합, 윤곽검출 ( 을 ) 를참조의일. 실행결과예 입력이미지 4 색 8 색 16 색 32 색 엣지의검출 cvsobel, cvlaplace, cvcanny Sobel,Laplacian,Canny 에의한엣지검출 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> int main (int argc, char **argv) { IplImage *src_img, *dst_img1, *dst_img2, *dst_img3; IplImage *tmp_img; // (1) 이미지의읽기 214
if (argc!= 2 (src_img = cvloadimage (argv[1], CV_LOAD_IMAGE_GRAYSCALE)) == 0) return -1; tmp_img = cvcreateimage (cvgetsize (src_img), IPL_DEPTH_16S, 1); dst_img1 = cvcreateimage (cvgetsize (src_img), IPL_DEPTH_8U, 1); dst_img2 = cvcreateimage (cvgetsize (src_img), IPL_DEPTH_8U, 1); dst_img3 = cvcreateimage (cvgetsize (src_img), IPL_DEPTH_8U, 1); // (2)Sobel 필터에의한미분이미지의작성 cvsobel (src_img, tmp_img, 1, 0); cvconvertscaleabs (tmp_img, dst_img1); // (3) 이미지의 Laplacian 의작성 cvlaplace (src_img, tmp_img); cvconvertscaleabs (tmp_img, dst_img2); // (4)Canny 에의한엣지이미지의작성 cvcanny (src_img, dst_img3, 50.0, 200.0); // (5) 이미지의표시 cvnamedwindow ("Original", CV_WINDOW_AUTOSIZE); cvshowimage ("Original", src_img); cvnamedwindow ("Sobel", CV_WINDOW_AUTOSIZE); cvshowimage ("Sobel", dst_img1); cvnamedwindow ("Laplace", CV_WINDOW_AUTOSIZE); cvshowimage ("Laplace", dst_img2); cvnamedwindow ("Canny", CV_WINDOW_AUTOSIZE); cvshowimage ("Canny", dst_img3); cvwaitkey (0); cvdestroywindow ("Original"); cvdestroywindow ("Sobel"); cvdestroywindow ("Laplace"); cvdestroywindow ("Canny"); cvreleaseimage (&src_img); cvreleaseimage (&dst_img1); cvreleaseimage (&dst_img2); cvreleaseimage (&dst_img3); cvreleaseimage (&tmp_img); return 0; // (1) 이미지의읽기커멘드인수로지정된파일명의이미지 ( 입력이미지 ) 을오픈해, 함수 cvloadimage() 그리고 215
읽어들인다.2 번째의인수에 CV_LOAD_IMAGE_GRAYSCALE ( 을 ) 를지정하는것으로, 원이미지를그레이스케일이미지로서읽어들인다. // (2)Sobel 필터에의한미분이미지의작성 x 방향으로 1 다음미분하기위한,3 3 사이즈의 Sobel 필터를이용해미분이미지를작성한다. 여기에서는,aperture_size( 을 ) 를지정하고있지않지만, 그경우는디폴트의사이즈 3 하지만이용된다. 또, 여기서 CV_SCHARR(=-1)( 을 ) 를지정하면,Sharr 필터를이용할수있다. 이함수에서는슬캘링은행해지지않기때문에, 출력이미지의각픽셀치는, 대부분의경우입력이미지의그것보다큰값이된다. 오버플로우를피차기위해서, 예를들면입력이미지가 8 비트의경우, 출력이미지로서 16 비트이미지를지정할필요가있다. // (3) 이미지의 Laplacian 의작성이하와같이,x-y 양방향의 2 차미분의화를계산해, 이미지의 Laplacian( 을 ) 를작성한다. dst(x,y) = d 2 src/dx 2 + d 2 src/dy 2 이함수에서도,cvSobel 의경우와같게출력이미지로서 16 비트이미지를지정한다. // (4)Canny 에의한엣지이미지의작성함수 cvcanny() 하지만출력하는결과는, 전술의두개의함수와는달라, 구배이미지는아니다. 여기서출력되는이미지는, 엣지인가, 그렇지않은가의 2 치이미지가된다. cvcanny()( 은 ) 는, 2 종류의반응을일으키는최소의물리량을인수 (3 번째,4 번째 ) 에있어, 작은편이엣지끼리를접속하기위해서이용되어큰편이강한엣지의초기검출에이용된다. 엣지의접속에서는, 주목픽셀의 8 근방 (4 방향 ) 에대한구배가요구되어가장강한구배를가질방향으로 ( 반응을일으키는최소의물리량을밑돌지않는한 ) 엣지가접속된다. // (5) 이미지의표시입력이미지와처리이미지를실제로표시해, 무엇인가키가밀릴때까지기다린다. 실행결과예 입력이미지결과 (Sobel) 결과 (Laplacian) 결과 (Canny) 216
코너 샘플 코너의검출 cvgoodfeaturestotrack, cvfindcornersubpix 이미지중의코너 ( 특징점 ) 검출 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> int main (int argc, char **argv) { int i, corner_count = 150; IplImage *dst_img1, *dst_img2, *src_img_gray; IplImage *eig_img, *temp_img; CvPoint2D32f *corners; if (argc!= 2 (dst_img1 = cvloadimage (argv[1], CV_LOAD_IMAGE_ANYCOLOR CV_LOAD_IMAGE_ANYDEPTH)) == 0) return -1; dst_img2 = cvcloneimage (dst_img1); src_img_gray = cvloadimage (argv[1], CV_LOAD_IMAGE_GRAYSCALE); eig_img = cvcreateimage (cvgetsize (src_img_gray), IPL_DEPTH_32F, 1); temp_img = cvcreateimage (cvgetsize (src_img_gray), IPL_DEPTH_32F, 1); corners = (CvPoint2D32f *) cvalloc (corner_count * sizeof (CvPoint2D32f)); // (1)cvCornerMinEigenVal( 을 ) 를이용한코너검출 cvgoodfeaturestotrack (src_img_gray, eig_img, temp_img, corners, &corner_count, 0.1, 15); cvfindcornersubpix (src_img_gray, corners, corner_count, cvsize (3, 3), cvsize (-1, -1), cvtermcriteria (CV_TERMCRIT_ITER CV_TERMCRIT_EPS, 20, 0.03)); // (2) 코너의그리기 for (i = 0; i < corner_count; i++) cvcircle (dst_img1, cvpointfrom32f (corners[i]), 3, CV_RGB (255, 0, 0), 2); // (3)cvCornerHarris( 을 ) 를이용한코너검출 corner_count = 150; cvgoodfeaturestotrack (src_img_gray, eig_img, temp_img, corners, &corner_count, 0.1, 15, NULL, 3, 1, 0.01); 217
cvfindcornersubpix (src_img_gray, corners, corner_count, cvsize (3, 3), cvsize (-1, -1), cvtermcriteria (CV_TERMCRIT_ITER CV_TERMCRIT_EPS, 20, 0.03)); // (4) 코너의그리기 for (i = 0; i < corner_count; i++) cvcircle (dst_img2, cvpointfrom32f (corners[i]), 3, CV_RGB (0, 0, 255), 2); // (5) 이미지의표시 cvnamedwindow ("EigenVal", CV_WINDOW_AUTOSIZE); cvshowimage ("EigenVal", dst_img1); cvnamedwindow ("Harris", CV_WINDOW_AUTOSIZE); cvshowimage ("Harris", dst_img2); cvwaitkey (0); cvdestroywindow ("EigenVal"); cvdestroywindow ("Harris"); cvreleaseimage (&dst_img1); cvreleaseimage (&dst_img2); cvreleaseimage (&eig_img); cvreleaseimage (&temp_img); cvreleaseimage (&src_img_gray); return 0; // (1)cvCornerMinEigenVal( 을 ) 를이용한코너검출함수 cvgoodfeaturestotrack() ( 은 ) 는, 이미지중으로부터큰고유치를가지는코너를검출한다. 이함수는최초로, 모든입력이미지의픽셀에대해서, cvcornermineigenval ( 을 ) 를이용해최소의고유치를계산해, 결과를 (2 번째의인수로지정되었다 ) eig_image 에보존한다. 다음에,"non-maxima suppression"( 을 ) 를실행한다 (3 3 의인접영역내의극대만이남는다 ). 다음의스텝에서는, (6 번째의인수로지정되었다 )quality_levelth ( 와 ) 과 max(eig_image(x,y)) 의적보다작은최소고유치를가지는코너를삭제한다. 마지막으로, 이함수는코너점에주목해 ( 가장강한코너가제일최초로대상이된다 ), 새롭게주목한특징점으로그이전에대상으로한특징점군과의거리가 (7 번째의인수로지정되었다 ) min_distance 보다큰것을체크하는것으로, 모든검출된코너각각의거리가충분히떨어져있는것을보증한다. 그때문에, 이함수는선명한특징점과의거리가가까운특징점을삭제한다. 게다가함수 cvfindcornersubpix()( 을 ) 를이용하고,4 번째의인수로지정한사이즈의배의탐색윈도우로, 코너 ( 특징점 ) 의것보다정확한좌표를탐색한다. // (2) 코너의그리기검출된코너를적색의엔으로그리기한다. // (3)cvCornerHarris( 을 ) 를이용한코너검출 10 번째의인수가 0( 이 ) 가아닌경우는, 디폴트의 cvcornermineigenval 대신에, Harris 오퍼레이터 (cvcornerharris)( 을 ) 를이용한다. 218
// (4) 코너의그리기검출된코너를청색의엔으로그리기한다. // (5) 이미지의표시실제로검출된코너의이미지를표시해, 무엇인가키가밀릴때까지기다린다. 실행결과예 최소고유치 Harris 병진이동을위한픽셀산프링 cvgetrectsubpix 지정한좌표가이미지중심이되도록 ( 듯이 ) 원이미지를병진시킨다. 비정수치좌표의픽셀치는, 바이리니아보간에의해얻을수있다, 또이미지경계의외측에존재하는픽셀의값을얻기위해서, 복제경계모드 ( 이미지의가장외측의픽셀치가외측무한원까지성장하고있으면가정 ) 하지만사용된다. 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> int main (int argc, char **argv) { IplImage *src_img = 0, *dst_img = 0; CvPoint2D32f center; // (1) 이미지의읽어들여, 출력용이미지영역의확보를행한다 if (argc >= 2) src_img = cvloadimage (argv[1], CV_LOAD_IMAGE_ANYDEPTH CV_LOAD_IMAGE_ANYCOLOR); if (src_img == 0) return -1; dst_img = cvcloneimage (src_img); // (2)dst_img 의이미지중심이된다 src_img 안의위치 center( 을 ) 를지정한다 center.x = src_img->width - 1; center.y = src_img->height - 1; // (3)center 하지만이미지중심이되도록 ( 듯이 ),GetRectSubPix( 을 ) 를이용해이미지전체를시프트시킨다 219
cvgetrectsubpix (src_img, dst_img, center); // (4) 결과를표시한다 cvnamedwindow ("src", CV_WINDOW_AUTOSIZE); cvnamedwindow ("dst", CV_WINDOW_AUTOSIZE); cvshowimage ("src", src_img); cvshowimage ("dst", dst_img); cvwaitkey (0); cvdestroywindow ("src"); cvdestroywindow ("dst"); cvreleaseimage (&src_img); cvreleaseimage (&dst_img); return 1; // (1) 이미지의읽어들여, 출력용이미지영역의확보를행한다커멘드인수로지정된파일명의이미지 ( 입력이미지 ) 을오픈해, 함수 cvloadimage() 그리고읽어들인다. 또, 출력용이미지의영역을, 입력이미지를카피하는것으로확보한다. // (2)dst_img 의이미지중심이된다 src_img 안의위치 center( 을 ) 를지정한다이샘플에서는, 입력이미지의우하구석을출력이미지의중심으로하고있다. // (3)center 하지만이미지중심이되도록 ( 듯이 ),GetRectSubPix( 을 ) 를이용해이미지전체를시프트시킨다이미지를시프트시키는것은, 출력이미지 (dst_img) 안의각픽셀치를이하의식을이용해입력이미지중의픽셀로부터샘플링하는일로결정물은것이라도있다. dst(x, y) = src(x + center.x - (width(dst)-1)*0.5, y + center.y - (height(dst)-1)*0.5) // (4) 결과를표시한다윈도우를생성해, 입력이미지, 결과이미지를표시해, 무엇인가키가밀릴때까지기다린다. 실행결과예입력이미지와출력이미지 [ 왼쪽에서 ] 입력이미지, 출력이미지출력이미지의중심이입력이미지의우하가되도록 ( 듯이 ) 화면이좌상방향으로시프트되고있다. 나머지의부분은복제경계모드가적용되고, 원이미지의경계부의픽셀치가옆 세로그리고우하영역에뻗어있다. 회전이동을위한픽셀산프링 cvgetquadranglesubpix 220
지정한회전행렬을이용해원이미지를회전시킨다. 비정수치좌표의픽셀치는, 바이리니아보간에의해얻을수있다, 또이미지경계의외측에존재하는픽셀의값을얻기위해서, 복제경계모드 ( 이미지의가장외측의픽셀치가외측무한원까지성장하고있으면가정 ) 가사용된다. 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> #include <math.h> int main (int argc, char **argv) { int angle = 45; float m[6]; IplImage *src_img = 0, *dst_img = 0; CvMat M; // (1) 이미지의읽어들여, 출력용이미지영역의확보를행한다 if (argc >= 2) src_img = cvloadimage (argv[1], CV_LOAD_IMAGE_ANYDEPTH CV_LOAD_IMAGE_ANYCOLOR); if (src_img == 0) return -1; dst_img = cvcloneimage (src_img); // (2) 회전을위한행렬 ( 아핀행렬 ) 요소를설정해,CvMat 행렬 M( 을 ) 를초기화한다 m[0] = (float) (cos (angle * CV_PI / 180.)); m[1] = (float) (-sin (angle * CV_PI / 180.)); m[2] = src_img->width * 0.5; m[3] = -m[1]; m[4] = m[0]; m[5] = src_img->height * 0.5; cvinitmatheader (&M, 2, 3, CV_32FC1, m, CV_AUTOSTEP); // (3) 지정된회전행렬에의해,GetQuadrangleSubPix( 을 ) 를이용해이미지전체를회전시킨다 cvgetquadranglesubpix (src_img, dst_img, &M); // (4) 결과를표시한다 cvnamedwindow ("src", CV_WINDOW_AUTOSIZE); cvnamedwindow ("dst", CV_WINDOW_AUTOSIZE); cvshowimage ("src", src_img); cvshowimage ("dst", dst_img); cvwaitkey (0); 221
cvdestroywindow ("src"); cvdestroywindow ("dst"); cvreleaseimage (&src_img); cvreleaseimage (&dst_img); return 1; // (1) 이미지의읽어들여, 출력용이미지영역의확보를행한다커멘드인수로지정된파일명의이미지 ( 입력이미지 ) 을오픈해, 함수 cvloadimage() 그리고읽어들인다. 또, 출력용이미지의영역을, 입력이미지를카피하는것으로확보한다. // (2) 회전을위한행렬 ( 아핀행렬 ) 요소를설정해,CvMat 행렬 M( 을 ) 를초기화한다여기서지정하는아핀행렬은다음과같이설정한다. A 11 A 12 b 1 map_matrix = A 21 A 22 b 2 m[0] = A 11 = cos(angle*π/180) m[1] = A 12 = -sin(angle*π/180) m[2] = b 1 = 회전중심의 x 좌표 m[3] = A 21 = sin(angle*π/180) m[4] = A 22 = cos(angle*π/180) m[5] = b 2 = 회전중심의 y 좌표 angle 의단위는 degree. // (3) 지정된회전행렬에의해,GetQuadrangleSubPix( 을 ) 를이용해이미지전체를회전시킨다이미지를회전시키는것은, 출력이미지 (dst_img) 안의각픽셀치를입력이미지중의픽셀로부터이하의식에따라서샘플링하는일로, 결정하는것이기도하다. dst(x, y) = src( A 11 x' + A 12 y' + b 1, A 21 x' + A 22 y' + b 2 ) x' = x - ( dst_img->width - 1 ) * 0.5 y' = y - ( dst_img->height - 1 ) * 0.5 // (4) 결과를표시한다윈도우를생성해, 입력이미지, 결과이미지를표시해, 무엇인가키가밀릴때까지기다린다. 222
실행결과예입력이미지와출력이미지 [ 왼쪽에서 ] 입력이미지, 출력이미지입력이미지의중심좌표를회전중심으로서 45[deg] 회전한결과가출력이되어있다. 출력이미지의픽셀로대응점이없는경우 ( 입력이미지의영역외 ) 는, 복제경계모드가적용되어원이미지의경계부의픽셀치가각우방향으로뻗어있는것이확인할수있다. 보간 여러가지보간방법을이용해이미지의사이즈변경을실시한다. 샘플 이미지의사이즈변경 cvresize 지정한출력이미지사이즈에맞도록 ( 듯이 ), 입력이미지의사이즈를변경해출력한다. 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> int main (int argc, char **argv) { IplImage *src_img = 0, *dst_img1 = 0, *dst_img2 = 0; // (1) 이미지를읽어들인다 if (argc >= 2) src_img = cvloadimage (argv[1], CV_LOAD_IMAGE_ANYDEPTH CV_LOAD_IMAGE_ANYCOLOR); if (src_img == 0) return -1; // (2) 출력용이미지영역의확보를행한다 dst_img1 = cvcreateimage (cvsize (src_img->width / 2, src_img->height / 2), src_img- >depth, src_img->nchannels); dst_img2 = cvcreateimage (cvsize (src_img->width * 2, src_img->height * 2), src_img- >depth, src_img->nchannels); 223
// (3) 이미지의사이즈변경을실시한다 cvresize (src_img, dst_img1, CV_INTER_NN); cvresize (src_img, dst_img2, CV_INTER_NN); // (4) 결과를표시한다 cvnamedwindow ("src", CV_WINDOW_AUTOSIZE); cvnamedwindow ("dst1", CV_WINDOW_AUTOSIZE); cvnamedwindow ("dst2", CV_WINDOW_AUTOSIZE); cvshowimage ("src", src_img); cvshowimage ("dst1", dst_img1); cvshowimage ("dst2", dst_img2); cvwaitkey (0); cvdestroywindow ("src"); cvdestroywindow ("dst1"); cvdestroywindow ("dst2"); cvreleaseimage (&src_img); cvreleaseimage (&dst_img1); cvreleaseimage (&dst_img2); return 1; // (1) 이미지를읽어들인다커멘드인수로지정된파일명의이미지 ( 입력이미지 ) 을오픈해, 함수 cvloadimage() 그리고읽어들인다. // (2) 출력용이미지영역의확보를행한다입력이미지사이즈의각각 1/2 배,2 배의사이즈를지정한출력이미지영역을함수 cvcreateimage() 그리고확보한다. // (3) 이미지의사이즈변경을실시한다보간방법을지정하고, 사이즈변경을실시한다. // (4) 결과를표시한다윈도우를생성해, 입력이미지, 결과이미지를표시해, 무엇인가키가밀릴때까지기다린다. 실행결과예 최근인접보간과바이큐빅크보간에서는이미지축소때에무아레가발생하고있는것을확인할수있다. 보간방법사이즈 1/2 배입력이미지사이즈 2 배 224
최근인접 바이리니아 225
에리어 바이큐빅크 기하변환 아핀변환 투시투영변환등의이미지의기하학적변환을실장하고있다 샘플 이미지의아핀변환 (1) cvgetaffinetransform + cvwarpaffine 이미지상의 3 점대응보다아핀변환행렬을계산해, 그행렬을이용해이미지전체의아핀변환을실시한다. 226
표시의변환 샘플코드 #include <cv.h> #include <highgui.h> int main (int argc, char **argv) { IplImage *src_img = 0, *dst_img = 0; CvMat *map_matrix; CvPoint2D32f src_pnt[3], dst_pnt[3]; // (1) 이미지의읽어들여, 출력용이미지영역의확보를행한다 if (argc >= 2) src_img = cvloadimage (argv[1], CV_LOAD_IMAGE_ANYDEPTH CV_LOAD_IMAGE_ANYCOLOR); if (src_img == 0) return -1; dst_img = cvcloneimage (src_img); // (2) 삼각형의회전전과회전후의대응하는정점을각각세트해 // cvgetaffinetransform( 을 ) 를이용해아핀행렬을요구한다 src_pnt[0] = cvpoint2d32f (200.0, 200.0); src_pnt[1] = cvpoint2d32f (250.0, 200.0); src_pnt[2] = cvpoint2d32f (200.0, 100.0); dst_pnt[0] = cvpoint2d32f (300.0, 100.0); dst_pnt[1] = cvpoint2d32f (300.0, 50.0); dst_pnt[2] = cvpoint2d32f (200.0, 100.0); map_matrix = cvcreatemat (2, 3, CV_32FC1); cvgetaffinetransform (src_pnt, dst_pnt, map_matrix); // (3) 지정된아핀행렬에의해,cvWarpAffine( 을 ) 를이용해이미지를회전시킨다 cvwarpaffine (src_img, dst_img, map_matrix, CV_INTER_LINEAR + CV_WARP_FILL_OUTLIERS, cvscalarall (0)); // (4) 결과를표시한다 cvnamedwindow ("src", CV_WINDOW_AUTOSIZE); cvnamedwindow ("dst", CV_WINDOW_AUTOSIZE); cvshowimage ("src", src_img); cvshowimage ("dst", dst_img); cvwaitkey (0); 227
cvdestroywindow ("src"); cvdestroywindow ("dst"); cvreleaseimage (&src_img); cvreleaseimage (&dst_img); cvreleasemat (&map_matrix); return 1; // (1) 이미지의읽기와출력용이미지의파티션커멘드인수로지정된파일명의이미지 ( 입력이미지 ) 을오픈해, 함수 cvloadimage() 그리고읽어들인다. 또, 출력용이미지의영역을, 입력이미지를카피하는것으로확보한다. // (2) 삼각형의회전전과회전후의대응하는정점을각각세트해 cvgetaffinetransform()( 을 ) 를이용해아핀행렬을요구한다 윗이미지의물색삼각형이핑크의삼각형이되도록 ( 듯이 ) 회전했다고한다. 이샘플프로그램에서는, 삼각형의각각의대응점이기존으로서코드를기술하고있지만, 실제의프로그램에서는삼각형닮아대응하는특징점의트랙킹등에의해이러한점대응을요구할수가있다.src_pnt[] 에회전전의정점좌표를,dst_pnt[] 에회전후의대응점을세트한다.cvGetAffineTransform() 에의해,2 3 의 map_matrix 에요구된아핀행렬이격납된다. // (3) 지정된아핀행렬에의해,cvWarpAffine( 을 ) 를이용해이미지를회전시킨다변환후대응을취할수없는화소에대해서는 fillval( 으 ) 로서 cvscalarall(0) ( 을 ) 를지정해, 쿠로에서묻는다. 아핀변환을이용하면, 이미지의확대 축소, 병진 회전을행할수가있다. // (4) 결과를표시한다윈도우를생성해, 입력이미지, 결과이미지를표시해, 무엇인가키가밀릴때까지기다린다. 실행결과예입력이미지와출력이미지 [ 왼쪽에서 ] 입력이미지, 출력이미지 228
이미지의아핀변환 (2) cv2drotationmatrix + cvwarpaffine 회전각도, 스케일계수, 회전중심을주어변환행렬을계산해, 그행렬을이용해이미지의 ROI 부분의아핀변환을실시한다.cvWarpAffine() 하위에서나타내보였다 cvgetquadranglesubpix() 에유사한기능을가지고있지만, 입출력의이미지형식이같을이라고하는제약을가져오버헤드가크다. 그러나,ROI( 을 ) 를이용해이미지의일부를변환할수가있다. 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> int main (int argc, char **argv) { double angle = -45.0, scale = 1.0; IplImage *src_img = 0, *dst_img = 0; CvMat *map_matrix; CvPoint2D32f center; CvPoint pt1, pt2; CvRect rect; // (1) 이미지의읽어들여, 출력용이미지영역의확보를행한다 if (argc >= 2) src_img = cvloadimage (argv[1], CV_LOAD_IMAGE_ANYDEPTH CV_LOAD_IMAGE_ANYCOLOR); if (src_img == 0) return -1; rect.x = (int) (src_img->width * 0.25); rect.y = (int) (src_img->height * 0.25); rect.width = (int) (src_img->width * 0.5); rect.height = (int) (src_img->height * 0.5); cvsetimageroi (src_img, rect); dst_img = cvcloneimage (src_img); // (2) 각도, 스케일계수, 회전중심을지정해 // cv2drotationmatrix( 을 ) 를이용해아핀행렬을요구한다 map_matrix = cvcreatemat (2, 3, CV_32FC1); center = cvpoint2d32f (src_img->width * 0.25, src_img->height * 0.25); cv2drotationmatrix (center, angle, scale, map_matrix); // (3) 지정된아핀행렬에의해,cvWarpaffine( 을 ) 를이용해이미지의 ROI 부분을회전시킨다 229
cvwarpaffine (src_img, dst_img, map_matrix, CV_INTER_LINEAR + CV_WARP_FILL_OUTLIERS, cvscalarall (255)); // (4) 결과를표시한다 cvresetimageroi (src_img); cvresetimageroi (dst_img); pt1 = cvpoint (rect.x, rect.y); pt2 = cvpoint (rect.x + rect.width, rect.y + rect.height); cvrectangle (src_img, pt1, pt2, CV_RGB (255, 0, 255), 2, 8, 0); cvnamedwindow ("src", CV_WINDOW_AUTOSIZE); cvnamedwindow ("dst", CV_WINDOW_AUTOSIZE); cvshowimage ("src", src_img); cvshowimage ("dst", dst_img); cvwaitkey (0); cvdestroywindow ("src"); cvdestroywindow ("dst"); cvreleaseimage (&src_img); cvreleaseimage (&dst_img); cvreleasemat (&map_matrix); return 1; // (1) 이미지의읽어들여, 출력용이미지영역의확보를행한다커멘드인수로지정된파일명의이미지 ( 입력이미지 ) 을오픈해, 함수 cvloadimage() 그리고읽어들여, 이미지의중앙에서, 폭 높이모두원이미지의반의영역을지정해 ROI( 을 ) 를세트한다. 그후, 출력용이미지의영역을입력이미지를카피하는것으로확보한다. // (2) 각도, 스케일계수, 회전중심을지정해 cv2drotationmatrix( 을 ) 를이용해아핀행렬을요구한다샘플프로그램에서는, 시계회전에 45 번, 배율 1.0 배를지정해,cv2DRotationMatrix() 그리고아핀변환행렬을요구한다. // (3) 지정된아핀행렬에의해,cvWarpaffine( 을 ) 를이용해이미지의 ROI 부분을회전시킨다변환후대응을취할수없는화소에대해서는 fillval( 으 ) 로서 cvscalarall(255) ( 을 ) 를지정해, 흰색으로묻는다.ROI 하지만지정되어있는경우, 지정영역만을변환하고있다. // (4) 결과를표시한다결과표시전에 ROI( 을 ) 를해제하는것으로, 이미지전체를표시할수있도록한다. 그후, 윈도우를생성해, 입력이미지, 결과이미지를표시해, 무엇인가키가밀릴때까지기다린다. 230
실행결과예입력이미지와출력이미지 [ 왼쪽에서 ] 입력이미지, 출력이미지 ROI 그리고지정한영역만, 변환되어그외의부분으로변경이없는것을확인할수있다. 이미지의투시투영변환 cvgetperspectivetransform + cvwarpperspective 이미지상의 4 점대응보다투시투영변환행렬을계산해, 그행렬을이용해이미지전체의투시투영변환을실시한다. 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> int main (int argc, char **argv) { IplImage *src_img = 0, *dst_img = 0; CvMat *map_matrix; CvPoint2D32f src_pnt[4], dst_pnt[4]; // (1) 이미지의읽어들여, 출력용이미지영역의확보를행한다 if (argc >= 2) src_img = cvloadimage (argv[1], CV_LOAD_IMAGE_ANYDEPTH CV_LOAD_IMAGE_ANYCOLOR); if (src_img == 0) return -1; dst_img = cvcloneimage (src_img); // (2) 사각형의변환전과변환후의대응하는정점을각각세트해 // cvwarpperspective( 을 ) 를이용해투시투영변환행렬을요구한다 src_pnt[0] = cvpoint2d32f (150.0, 150.0); src_pnt[1] = cvpoint2d32f (150.0, 300.0); 231
src_pnt[2] = cvpoint2d32f (350.0, 300.0); src_pnt[3] = cvpoint2d32f (350.0, 150.0); dst_pnt[0] = cvpoint2d32f (200.0, 200.0); dst_pnt[1] = cvpoint2d32f (150.0, 300.0); dst_pnt[2] = cvpoint2d32f (350.0, 300.0); dst_pnt[3] = cvpoint2d32f (300.0, 200.0); map_matrix = cvcreatemat (3, 3, CV_32FC1); cvgetperspectivetransform (src_pnt, dst_pnt, map_matrix); // (3) 지정된투시투영변환행렬에의해,cvWarpPerspective( 을 ) 를이용해이미지를변환시킨다 cvwarpperspective (src_img, dst_img, map_matrix, CV_INTER_LINEAR + CV_WARP_FILL_OUTLIERS, cvscalarall (100)); // (4) 결과를표시한다 cvnamedwindow ("src", CV_WINDOW_AUTOSIZE); cvnamedwindow ("dst", CV_WINDOW_AUTOSIZE); cvshowimage ("src", src_img); cvshowimage ("dst", dst_img); cvwaitkey (0); cvdestroywindow ("src"); cvdestroywindow ("dst"); cvreleaseimage (&src_img); cvreleaseimage (&dst_img); cvreleasemat (&map_matrix); return 1; // (1) 이미지의읽어들여, 출력용이미지영역의확보를행한다커멘드인수로지정된파일명의이미지 ( 입력이미지 ) 을오픈해, 함수 cvloadimage() 그리고읽어들인다. 또, 출력용이미지의영역을, 입력이미지를카피하는것으로확보한다. // (2) 사각형의변환전과변환후의대응하는정점을각각세트해 cvgetperspectivetransform()( 을 ) 를이용해투시투영변환행렬을요구한다 232
윗이미지의물색사각형이핑크의사각형이되도록 ( 듯이 )( 샘플코드에서는, 각각의사각형의저변은같다라고하고있다 ) 이미지를변환시킨다. 이샘플프로그램에서는, 사각형의각각의대응점이기존으로서코드를기술하고있지만, 실제의프로그램에서는특징점추출등에의해대응점을요구해행렬을작성하거나카메라 calibration matrices 등에서직접투시투영변환행렬을요구한다. src_pnt[] 에변환전의좌표를,dst_pnt[] 에대응하는반환후의좌표를세트해,cvGetPerspectiveTransform() ( 을 ) 를부르는일에의해서,3 3 의 map_matrix 에투시투영변환행렬이격납되어돌려주어진다. // (3) 지정된투시투영변환행렬에의해,cvWarpPerspective( 을 ) 를이용해이미지를변환시킨다변환후대응을취할수없는화소에대해서는 fillval( 으 ) 로서 cvscalarall(100) ( 을 ) 를지정해, 그레이로묻고있다. // (4) 결과를표시한다윈도우를생성해, 입력이미지, 결과이미지를표시해, 무엇인가키가밀릴때까지기다린다. 실행결과예입력이미지와출력이미지 [ 왼쪽에서 ] 입력이미지, 출력이미지 전방위이미지의투시투영변환 cvgetperspectivetransform + cvwarpperspective 전방위이미지의일부에대해서투시투영변환을실시한다. 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> #include <stdio.h> #include <ctype.h> int main (int argc, char **argv) { IplImage *src = NULL, *dst = NULL; CvPoint2D32f src_pnt[4], dst_pnt[4]; CvMat *map_matrix; int i; 233
double t; double a = 29.0, b = 40.0, c = sqrt (a * a + b * b); /* 밀러파라미터 */ double f = 100; int xc = 343, yc = 258; /* 이미지중심 */ int x[4] = { -30, 30, 30, -30 ; int y[4] = { -50, -50, -50, -50 ; int z[4] = { 180, 180, 100, 100 ; int xx[4], yy[4]; // (1) 파일로부터이미지를읽어들인다 src = cvloadimage ("src.jpg", CV_LOAD_IMAGE_ANYDEPTH CV_LOAD_IMAGE_ANYCOLOR); if (src == NULL) exit (-1); dst = cvcloneimage (src); // (2) 윈도우를작성한다 cvnamedwindow ("src", CV_WINDOW_AUTOSIZE); cvnamedwindow ("dst", CV_WINDOW_AUTOSIZE); // (3) 대응점을계산한다 for (i = 0; i < 4; i++) { t = -a * a * f / ((b * b + c * c) * z[i] - 2 * b * c * sqrt (x[i] * x[i] + y[i] * y[i] + z[i] * z[i])); xx[i] = (int) (x[i] * t + xc + 0.5); yy[i] = (int) (y[i] * t + yc + 0.5); src_pnt[i] = cvpoint2d32f (xx[i], yy[i]); // (4) 출력이미지의대응점을지정 dst_pnt[0] = cvpoint2d32f (0.0, 0.0); dst_pnt[1] = cvpoint2d32f (dst->width, 0.0); dst_pnt[2] = cvpoint2d32f (dst->width, dst->height); dst_pnt[3] = cvpoint2d32f (0.0, dst->height); // (5) 투시투영변환을실행한다 map_matrix = cvcreatemat (3, 3, CV_32FC1); cvgetperspectivetransform (src_pnt, dst_pnt, map_matrix); cvwarpperspective (src, dst, map_matrix, CV_INTER_LINEAR + CV_WARP_FILL_OUTLIERS, cvscalarall (0)); // (6) 전개영역을그리기한다 for (i = 0; i < 4; i++) { cvline (src, cvpoint (xx[i], yy[i]), cvpoint (xx[(i + 1) % 4], yy[(i + 1) % 4]), CV_RGB (255, 255, 0), 1, 0, 0); 234
// (7) 입출력이미지를표시한다 cvshowimage ("src", src); cvshowimage ("dst", dst); cvwaitkey (0); cvdestroywindow ("src"); cvdestroywindow ("dst"); cvreleaseimage (&src); return 0; // (1) 파일로부터이미지를읽어들인다입력이미지 ( 전방위이미지 ) 을함수 cvloadimage() 그리고읽어들인다. 또출력이미지의영역을함수 cvcloneimage () 그리고확보한다. // (2) 윈도우를작성한다입력이미지와출력이미지를표시하기위한윈도우를작성한다. // (3) 대응점을계산한다삼차원좌표의 4 정점으로부터이미지좌표의 4 정점을이하의계산식에의해요구해배열 src_pnt 에격납한다. 여기서 ( 은 ) 는삼차원좌표의점, ( 은 ) 는이미지좌표계의점, ( 은 ) 는이미지의중심좌표, ( 은 ) 는쌍곡면밀러의파라미터로, ( 을 ) 를채운다. ( 은 ) 는촛점거리이지만여기에서는적당하게지정해있다. // (4) 출력이미지의대응점을지정 src_pnt 에대응하는출력이미지의좌표를배열 dst_pnt 에격납한다. // (5) 투시투영변환을실행한다투시투영변환을실행한다. // (6) 전개영역을그리기한다입력이미지에전개영역을그리기한다. 235
실행결과예입력이미지와출력이미지 모르포로지변환 구조요소를지정하고, 여러가지모르포로지연산을행한다. 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> /* 메인프로그램 */ int main (int argc, char **argv) { IplImage *src_img = 0; IplImage *dst_img_dilate, *dst_img_erode; IplImage *dst_img_opening, *dst_img_closing; IplImage *dst_img_gradient, *dst_img_tophat, *dst_img_blackhat; IplImage *tmp_img; IplConvKernel *element; //(1) 이미지의읽어들여, 연산결과이미지영역의확보를행한다 if (argc >= 2) src_img = cvloadimage (argv[1], CV_LOAD_IMAGE_ANYDEPTH CV_LOAD_IMAGE_ANYCOLOR); if (src_img == 0) exit (-1); dst_img_dilate = cvcloneimage (src_img); dst_img_erode = cvcloneimage (src_img); dst_img_opening = cvcloneimage (src_img); dst_img_closing = cvcloneimage (src_img); dst_img_gradient = cvcloneimage (src_img); dst_img_tophat = cvcloneimage (src_img); dst_img_blackhat = cvcloneimage (src_img); tmp_img = cvcloneimage (src_img); //(2) 구조요소를생성한다 236
element = cvcreatestructuringelementex (9, 9, 4, 4, CV_SHAPE_RECT, NULL); //(3) 각종의모르포로지연산을실행한다 cvdilate (src_img, dst_img_dilate, element, 1); cverode (src_img, dst_img_erode, element, 1); cvmorphologyex (src_img, dst_img_opening, tmp_img, element, CV_MOP_OPEN, 1); cvmorphologyex (src_img, dst_img_closing, tmp_img, element, CV_MOP_CLOSE, 1); cvmorphologyex (src_img, dst_img_gradient, tmp_img, element, CV_MOP_GRADIENT, 1); cvmorphologyex (src_img, dst_img_tophat, tmp_img, element, CV_MOP_TOPHAT, 1); cvmorphologyex (src_img, dst_img_blackhat, tmp_img, element, CV_MOP_BLACKHAT, 1); //(4) 모르포로지연산결과를표시한다 cvnamedwindow ("src", CV_WINDOW_AUTOSIZE); cvnamedwindow ("dilate", CV_WINDOW_AUTOSIZE); cvnamedwindow ("erode", CV_WINDOW_AUTOSIZE); cvnamedwindow ("opening", CV_WINDOW_AUTOSIZE); cvnamedwindow ("closing", CV_WINDOW_AUTOSIZE); cvnamedwindow ("gradient", CV_WINDOW_AUTOSIZE); cvnamedwindow ("tophat", CV_WINDOW_AUTOSIZE); cvnamedwindow ("blackhat", CV_WINDOW_AUTOSIZE); cvshowimage ("src", src_img); cvshowimage ("dilate", dst_img_dilate); cvshowimage ("erode", dst_img_erode); cvshowimage ("opening", dst_img_opening); cvshowimage ("closing", dst_img_closing); cvshowimage ("gradient", dst_img_gradient); cvshowimage ("tophat", dst_img_tophat); cvshowimage ("blackhat", dst_img_blackhat); cvwaitkey (0); cvdestroywindow ("src"); cvdestroywindow ("dilate"); cvdestroywindow ("erode"); cvdestroywindow ("opening"); cvdestroywindow ("closing"); cvdestroywindow ("gradient"); cvdestroywindow ("tophat"); cvdestroywindow ("blackhat"); cvreleaseimage (&src_img); cvreleaseimage (&dst_img_dilate); cvreleaseimage (&dst_img_erode); cvreleaseimage (&dst_img_opening); cvreleaseimage (&dst_img_closing); cvreleaseimage (&dst_img_gradient); cvreleaseimage (&dst_img_tophat); 237
cvreleaseimage (&dst_img_blackhat); cvreleaseimage (&tmp_img); return 1; // (1) 이미지의읽어들여, 연산결과이미지영역의확보를행한다커멘드인수로지정된파일명의이미지 ( 입력이미지 ) 을오픈해, 함수 cvloadimage() 그리고읽어들인다. 또, 출력용이미지의영역을, 입력이미지를카피하는것으로확보한다. 고도의모르포로지연산 cvmorphologyex()( 을 ) 를행하는경우는텐포라리의영역이필요하다 ( 모르포로지구배때와인프레이스모드로의톱하트변환 ( 와 ) 과블랙하트변환의경우 ) 의로,tmp_img( 으 ) 로서준비해있다. // (2) 구조요소를생성한다모르포로지연산에사용하는구조요소를생성한다. 샘플프로그램에서는,9 9 의구형으로, 정확히그구형의중심으로엥커포인트를가지는구조요소를지정해있다. // (3) 각종의모르포로지연산을실행한다 OpenCV 에준비떠날수있어모든모르포로지연산의실행을행해, 결과를각각의출력영역에보존한다. // (4) 모르포로지연산결과를표시한다윈도우를생성해, 각종모르포로지검출결과를표시해, 무엇인가키가밀릴때까지기다린다. 실행결과예입력이미지와연산결과 [ 상단왼쪽에서 ] 입력이미지 (2 값이미지 ),Dilation,Erosion,Opening [ 하단왼쪽에서 ]Closing, 경사, 톱하트변환, 블랙하트변환 238
[ 상단왼쪽에서 ] 입력이미지 ( 칼라이미지 ),Dilation,Erosion,Opening [ 하단왼쪽에서 ]Closing, 경사, 톱하트변환, 블랙하트변환 평활화 cvsmooth 브라 -, 가우시안, 미디언, 쌍방, 의각필터에의한평활화 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> int main (int argc, char **argv) { int i; IplImage *src_img = 0, *dst_img[4]; // (1) 이미지를읽어들인다 if (argc >= 2) src_img = cvloadimage (argv[1], CV_LOAD_IMAGE_ANYDEPTH CV_LOAD_IMAGE_ANYCOLOR); if (src_img == 0) exit (-1); for (i = 0; i < 4; i++) dst_img[i] = cvcloneimage (src_img); // (2) 수법을지정해이미지를평활화 cvsmooth (src_img, dst_img[0], CV_BLUR, 5, 0, 0, 0); cvsmooth (src_img, dst_img[1], CV_GAUSSIAN, 11, 0, 0, 0); cvsmooth (src_img, dst_img[2], CV_MEDIAN, 5, 0, 0, 0); cvsmooth (src_img, dst_img[3], CV_BILATERAL, 80, 80, 0, 0); 239
// (3) 처리된이미지를실제로표시 cvnamedwindow ("Blur", CV_WINDOW_AUTOSIZE); cvshowimage ("Blur", dst_img[0]); cvnamedwindow ("Gaussian", CV_WINDOW_AUTOSIZE); cvshowimage ("Gaussian", dst_img[1]); cvnamedwindow ("Median", CV_WINDOW_AUTOSIZE); cvshowimage ("Median", dst_img[2]); cvnamedwindow ("Bilateral", CV_WINDOW_AUTOSIZE); cvshowimage ("Bilateral", dst_img[3]); cvwaitkey (0); cvdestroywindow ("Blur"); cvdestroywindow ("Gaussian"); cvdestroywindow ("Median"); cvdestroywindow ("Bilateral"); cvreleaseimage (&src_img); for (i = 0; i < 4; i++) { cvreleaseimage (&dst_img[i]); return 0; // (1) 이미지를읽어들인다커멘드인수로지정된파일명의이미지 ( 입력이미지 ) 을오픈해, 함수 cvloadimage() 그리고읽어들인다.2 번째의인수에 CV_LOAD_IMAGE_ANYDEPTH CV_LOAD_IMAGE_ANYCOLOR( 을 ) 를지정하는것으로, 원이미지의데프스, 채널을변갱하지않고읽어들인다. // (2) 수법을지정해이미지를평활화함수 cvsmooth() 의 3 번째의인수로, CV_BLUR( 브라피르타 ),CV_GAUSSIAN( 가우시안피르타 ),CV_MEDIAN( 미디언필터 ),CV_BILATERAL( 쌍방필터 ) 의각수법을지정하고평활화를실시한다. 4 번째이후의인수의의미는, 수법마다다르다. 자세한것은, 레퍼런스메뉴얼을참조하는것. // (3) 처리된이미지를실제로표시각수법으로평활화된이미지를실제로표시해, 무엇인가키가밀릴때까지기다린다. 실행결과예하단의이미지는, 처리이미지의일부를확대한것이다. 다만, 수법마다파라미터를바꾸어있으므로, 단순하게비교할수있는이미지는아니다. 입력이미지 Blur Gaussian Median Bilateral 240
유저정의필터 cvfilter2d 유저가정의한커넬에의한필터링 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> #include <stdio.h> int main (int argc, char **argv) { IplImage *src_img = 0, *dst_img; float data[] = { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ; CvMat kernel = cvmat (1, 21, CV_32F, data); // (1) 이미지의읽기 if (argc >= 2) src_img = cvloadimage (argv[1], CV_LOAD_IMAGE_ANYDEPTH CV_LOAD_IMAGE_ANYCOLOR); if (src_img == 0) exit (-1); dst_img = cvcreateimage (cvgetsize (src_img), src_img->depth, src_img->nchannels); // (2) 커넬의정규화와필터처리 cvnormalize (&kernel, &kernel, 1.0, 0, CV_L1); cvfilter2d (src_img, dst_img, &kernel, cvpoint (0, 0)); // (3) 처리이미지의표시 cvnamedwindow ("Filter2D", CV_WINDOW_AUTOSIZE); cvshowimage ("Filter2D", dst_img); cvwaitkey (0); cvdestroywindow ("Filter2D"); 241
cvreleaseimage (&src_img); cvreleaseimage (&dst_img); return 0; // (1) 이미지의읽기커멘드인수로지정된파일명의이미지 ( 입력이미지 ) 을오픈해, 함수 cvloadimage() 그리고읽어들인다.2 번째의인수에 CV_LOAD_IMAGE_ANYDEPTH CV_LOAD_IMAGE_ANYCOLOR( 을 ) 를지정하는것으로, 원이미지의데프스, 채널을변갱하지않고읽어들인다. // (2) 커넬의정규화와필터처리부동소수점형배열 data 의값을각요소로하는,1 21; 의행렬을커넬로하는필터처리를실시한다. 함수 cvnormalize() 에의해서, 커넬을정규화한다. 즉, 여기에서는, 절대치의법칙이 "1.0"( 이 ) 가되도록 ( 듯이 ), 배열의값을정규화하고있다. 그리고, 그커넬을이용 ( 함수 cvfilter2d() 의 3 번째의인수로지정 ) 하고, 필터처리를실시한다. 함수 cvfilter2d() 의 4 번째의인수는, 필터대상픽셀의커넬내에서의상대위치를나타내고있어디폴트는 cvpoint(-1,-1)( 이어 ) 여커넬중심을가리키지만, 이번은,cvPoint(0,0)( 을 ) 를지정하는것으로, 커넬의좌단을가리키고있다. // (3) 처리이미지의표시처리된이미지를실제로표시해, 무엇인가키가밀릴때까지기다린다. 실행결과예 경계선이미지의경계를어떻게취급할까는, 많은이미지처리에대해자주문제가된다. 예를들면, 필터처리에대하고, 필터가부분적으로이미지의밖에는보기시작하고해기다렸을경우, 어떠한화소치를가정해이용할까에의해서, 처리결과가다르다. OpenCV그럼, 이미지를카피해, 그주위에경계선을붙이는처리를실시하는함수가용뜻되고있어IPL_BORDER_CONSTANT, 및IPL_BORDER_REPLICAT( 을 ) 를서포트하고있다. OpenCV그리고이용되는함수는,IP L_BORDER_REPLICAT, 즉복제경계모드를이용하는것이많이있지만, 유저가그러한처리를바라지않은경우에는, 미리경계를정수치로묻고나서처리를실시해, 처리후의이미지를클리핑하는일로경계의취급을컨트롤할수있다. 샘플 경계선의작성 cvcopymakeborder 이미지의카피와경계의작성 242
표시의변환 샘플코드 #include <cxcore.h> #include <cv.h> #include <highgui.h> int main (int argc, char **argv) { int i, offset = 30; IplImage *src_img = 0, *dst_img[2]; // (1) 이미지의읽기 if (argc >= 2) src_img = cvloadimage (argv[1], CV_LOAD_IMAGE_ANYDEPTH CV_LOAD_IMAGE_ANYCOLOR); if (src_img == 0) exit (-1); for (i = 0; i < 2; i++) dst_img[i] = cvcreateimage (cvsize (src_img->width + offset * 2, src_img->height + offset * 2), src_img->depth, src_img->nchannels); // (2) 경계선의작성 cvcopymakeborder (src_img, dst_img[0], cvpoint (offset, offset), IPL_BORDER_REPLICATE); cvcopymakeborder (src_img, dst_img[1], cvpoint (offset, offset), IPL_BORDER_CONSTANT, CV_RGB (255, 0, 0)); // (3) 이미지의표시 cvnamedwindow ("Border_replicate", CV_WINDOW_AUTOSIZE); cvshowimage ("Border_replicate", dst_img[0]); cvnamedwindow ("Border_constant", CV_WINDOW_AUTOSIZE); cvshowimage ("Border_constant", dst_img[1]); cvwaitkey (0); cvdestroywindow ("Border_replicate"); cvdestroywindow ("Border_constant"); cvreleaseimage (&src_img); for (i = 0; i < 2; i++) { cvreleaseimage (&dst_img[i]); 243
return 0; // (1) 이미지의읽기커멘드인수로지정된파일명의이미지 ( 입력이미지 ) 을오픈해, 함수 cvloadimage() 그리고읽어들인다.2 번째의인수에 CV_LOAD_IMAGE_ANYDEPTH CV_LOAD_IMAGE_ANYCOLOR( 을 ) 를지정하는것으로, 원이미지의데프스, 채널을변갱하지않고읽어들인다. 또, 경계를작성하기위해서, 입력이미지보다큰출력영역을확보한다. // (2) 경계선의작성함수 cvcopymakeborder() 에의해서, 입력이미지를출력이미지에카피해, 한층더경계를작성한다. 3 번째의인수는, 출력이미지상에카피되는입력이미지의좌상좌표를나타내고있어이미지가카피되지않는부분 ( 이번은, 상하좌우의구석으로부터 offset 픽셀분 ) 이경계영역이된다. 4 번째의인수는, 경계의종류를나타내고있어 IPL_BORDER_REPLICATE 하지만지정되면, 이미지위 / 아래의구석과왼쪽 / 오른쪽의구석 ( 이미지영역의제일외측의값 ) 을이용해경계선을생성한다. 또,4 번째의인수에,IPL_BORDER_CONSTANT 하지만지정되면, 경계는이함수의최후 (5 번째 ) 의파라미터로서건네받은정수 ( 이번은적색 ) 로묻힌다. // (3) 이미지의표시처리된이미지를실제로표시해, 무엇인가키가밀릴때까지기다린다. 실행결과예 반응을일으키는최소의물리량처리이미지에있는반응을일으키는최소의물리량을마련하고, 화소치가그값보다큰가작은것처럼따르고처리를바꾸고싶다고하는일은자주있다. 예를들면, 단순한배경차분에서는, 현이미지와배경이미지와의화소치의차이의절대치가있는반응을일으키는최소의물리량이하가되는지아닌지로, 배경인가전경인지를판단한다. OpenCV 그럼, 이러한반응을일으키는최소의물리량처리를행하기위한함수,cvThreshold, 및 cvadaptivethreshold ( 이 ) 가준비되어있다. 샘플 이미지의 2 치화 cvthreshold, cvadaptivethreshold cvthreshold, cvadaptivethreshold( 을 ) 를이용하고, 이미지의 2 치화를실시한다 표시의변환 샘플코드 #include <cv.h> 244
#include <highgui.h> int main (int argc, char **argv) { IplImage *src_img = 0, *dst_img; IplImage *src_img_gray = 0; IplImage *tmp_img1, *tmp_img2, *tmp_img3; // (1) 이미지를읽어들인다 if (argc >= 2) src_img = cvloadimage (argv[1], CV_LOAD_IMAGE_COLOR); if (src_img == 0) return -1; tmp_img1 = cvcreateimage (cvgetsize (src_img), IPL_DEPTH_8U, 1); tmp_img2 = cvcreateimage (cvgetsize (src_img), IPL_DEPTH_8U, 1); tmp_img3 = cvcreateimage (cvgetsize (src_img), IPL_DEPTH_8U, 1); src_img_gray = cvcreateimage (cvgetsize (src_img), IPL_DEPTH_8U, 1); cvcvtcolor (src_img, src_img_gray, CV_BGR2GRAY); dst_img = cvcloneimage (src_img); // (2) 가우시안피르타로평활화를실시한다 cvsmooth (src_img_gray, src_img_gray, CV_GAUSSIAN, 5); // (3)2 치화 :cvthreshold cvthreshold (src_img_gray, tmp_img1, 90, 255, CV_THRESH_BINARY); // (4)2 치화 :cvadaptivethreshold cvadaptivethreshold (src_img_gray, tmp_img2, 255, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY, 11, 10); // (5) 두개의 2 치화이미지의논리적 cvand (tmp_img1, tmp_img2, tmp_img3); cvcvtcolor (tmp_img3, dst_img, CV_GRAY2BGR); // (6) 원이미지와 2 치이미지의논리적 cvsmooth (src_img, src_img, CV_GAUSSIAN, 11); cvand (dst_img, src_img, dst_img); // (7) 이미지를표시한다 cvnamedwindow ("Threshold", CV_WINDOW_AUTOSIZE); cvshowimage ("Threshold", tmp_img1); cvnamedwindow ("AdaptiveThreshold", CV_WINDOW_AUTOSIZE); cvshowimage ("AdaptiveThreshold", tmp_img2); cvnamedwindow ("Image", CV_WINDOW_AUTOSIZE); 245
cvshowimage ("Image", dst_img); cvwaitkey (0); cvdestroywindow ("Threshold"); cvdestroywindow ("AdaptiveThreshold"); cvdestroywindow ("Image"); cvreleaseimage (&src_img); cvreleaseimage (&dst_img); cvreleaseimage (&src_img_gray); cvreleaseimage (&tmp_img1); cvreleaseimage (&tmp_img2); cvreleaseimage (&tmp_img3); return 0; // (1) 이미지를읽어들인다커멘드인수로지정된파일명의이미지 ( 입력이미지 ) 을오픈해, 함수 cvloadimage() 그리고읽어들인다. 또, 2 치화를실시하기위해서입력이미지를그레이스케일로변환한다. // (2) 가우시안피르타로평활화를실시한다 2 치화를실시하기전에, 가우시안피르타로평활화를실시한다. 평활화를실시하는것에의해서, 이미지의노이즈를감소시켜, 안정된 ( 노이즈가적다 ) 2 치화이미지를얻을수있다. // (3)2 치화 :cvthreshold 함수 cvthreshold() 에의해서, 이미지의 2 치화처리를실시한다. 이함수의마지막인수가, 반응을일으키는최소의물리량처리의종류를나타내고있다. CV_THREASH_BINARY 하지만지정되어있는경우는, src(x,y)>threshold 의경우는,dst(x,y) = max_value 그이외는, 0 되도록 ( 듯이 ) 처리를실시한다. 여기서,threshold( 반응을일으키는최소의물리량 )( 은 ) 는 3 번째의인수로, max_value( 최대 치 )( 은 ) 는 4 번째의인수로, 각각지정되어있다. 그외의수법을지정했을경우의처리에대해서는, 레퍼런스메뉴얼을 참조. // (4)2 치화 :cvadaptivethreshold 함수 cvadaptivethreshold() 에의해서, 이미지의 2 치화처리를실시한다. 함수 cvthreshold() 그럼, 모든픽셀에대해같은반응을일으키는최소의물리량을가지고처리를실시했지만, 함수 cvadaptivethreshold() 의경우는, 픽셀마다사용하는반응을일으키는최소의물리량이다르다. 이함수의 5 번째의인수가, 함수 cvthreshold( 와 ) 과같게반응을일으키는최소의물리량처리의종류를나타내고있다. CV_THRESH_BINARY 하지만지정되어있는경우는, src(x,y)>t(x,y) 의경우는,dst(x,y) = max_value 그이외는, 0 되도록 ( 듯이 ) 처리를실시한다. 여기서,max_value( 최대치 )( 은 ) 는 3 번째의인수로지정되어있다. 또,T(x,y)( 은 ) 는, 각픽셀 마다계산된반응을일으키는최소의물리량이며,4 번째의인수로, 의반응을일으키는최소의물리량의산출방법이 지정된다. CV_ADAPTIVE_THRESH_MEAN_C 하지만지정되었을경우는, 주목픽셀의 block_size block_size 인접영역 246
의평균으로부터,param1 ( 을 ) 를당긴값이되어, block_size 하 6 번째의인수로,param1 하 7 번째의인수로, 각각지정된 다. // (5) 두개의 2 치화이미지의논리적두개의함수에의해서 2 치화된이미지의논리적을계산한다. 즉, 2 종류의 2 치화이미지를합성해,3 채널이미지로변환한다. // (6) 원이미지와 2 치이미지의논리적 ( 평활화한 ) 입력이미지와 2 치화이미지의요소마다의논리적을계산한다. 이것에의해, 입력이미지상에 2 치화이미지가합성된이미지를얻는다. // (7) 이미지를표시한다각각의 2 치화이미지, 및입력이미지와거듭해맞춘이미지를실제로표시해, 무엇인가키가밀릴때까지기다린다. 실행결과예 입력이미지 Threshold AdaptiveThreshold 합성이미지 이미지의 2 치화 ( 오츠의수법 ) cvthreshold 오츠의수법을이용하고반응을일으키는최소의물리량을결정해, 이미지의 2 치화를실시한다 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> int main (int argc, char **argv) { IplImage *src_img = 0, *dst_img; if (argc >= 2) src_img = cvloadimage (argv[1], CV_LOAD_IMAGE_GRAYSCALE); if (src_img == 0) return -1; dst_img = cvcreateimage (cvgetsize (src_img), IPL_DEPTH_8U, 1); 247
cvsmooth (src_img, src_img, CV_GAUSSIAN, 5); // (1)2 치화 ( 오츠의수법을이용 ) cvthreshold (src_img, dst_img, 0, 255, CV_THRESH_BINARY CV_THRESH_OTSU); cvnamedwindow ("Threshold", CV_WINDOW_AUTOSIZE); cvshowimage ("Threshold", dst_img); cvwaitkey (0); cvdestroywindow ("Threshold"); cvreleaseimage (&src_img); cvreleaseimage (&dst_img); return 0; // (1)2 치화 ( 오츠의수법을이용 ) 전술의 2 치화와다른것은, 마지막인수 (threshold type) 에,CV_THRESHOLD_OTSU 하지만더해지고있는점이다. OpencvCV-1.0.0 시점에서의레퍼런스메뉴얼에는기술이없다 (CVS 판에는있다 ) 가, 함수 cvthreshold()( 은 ) 는, 오츠의수법으로불리는반응을일으키는최소의물리량결정수법을실장하고있다. 여기서이용되는오츠의수법이란, 어느값의집합을두개클래스로분류하는경우의, 적절한반응을일으키는최소의물리량을결정하는수법이다. 두개의클래스내의분산과클래스간의분산을생각해이러한비가최소에 ( 즉, 클래스비밀산은가능한한작고, 클래스간분산은가능한한크고 ) 되는반응을일으키는최소의물리량을요구한다. 따라서,3 번째의인수 (threshold) 에게주는값은이용되지않기때문에, 적당한값을지정해좋다. 자세한것은, 이하의문헌을참고로하는것. 오츠, " 판별및최소 2 승기준에근거하는자동해귀의치선정법 ", 전자통신학회논문잡지, Vol.J63-D, No.4, pp.349-356, 1980. N. Otsu, "A threshold selection method from gray level histograms",ieee Trans. Systems, Man and Cybernetics, 1979, Vol.9, pp.62-66 실행결과예 이미지피라밋드의작성 cvpyrdown, cvpyrup 입력이미지로부터, 피라밋드의상하의층이미지를작성한다 248
표시의변환 샘플코드 #include <cv.h> #include <highgui.h> int main (int argc, char **argv) { IplImage *src_img, *dst_img1, *dst_img2; if (argc!= 2 (src_img = cvloadimage (argv[1], CV_LOAD_IMAGE_ANYDEPTH CV_LOAD_IMAGE_ANYCOLOR)) == 0) return -1; dst_img1 = cvcreateimage (cvsize (src_img->width / 2, src_img->height / 2), src_img- >depth, src_img->nchannels); dst_img2 = cvcreateimage (cvsize (src_img->width * 2, src_img->height * 2), src_img- >depth, src_img->nchannels); // (1) 입력이미지에대한이미지피라밋드를구성 cvpyrdown (src_img, dst_img1, CV_GAUSSIAN_5x5); cvpyrup (src_img, dst_img2, CV_GAUSSIAN_5x5); cvnamedwindow ("Original", CV_WINDOW_AUTOSIZE); cvshowimage ("Original", src_img); cvnamedwindow ("PyrDown", CV_WINDOW_AUTOSIZE); cvshowimage ("PyrDown", dst_img1); cvnamedwindow ("PyrUp", CV_WINDOW_AUTOSIZE); cvshowimage ("PyrUp", dst_img2); cvwaitkey (0); cvdestroywindow ("Original"); cvdestroywindow ("PyrDown"); cvdestroywindow ("PyrUp"); cvreleaseimage (&src_img); cvreleaseimage (&dst_img1); cvreleaseimage (&dst_img2); return 0; // (1) 입력이미지에대한이미지피라밋드를구성함수 cvpyrdown() 에의해서, 입력이미지의저해상 (1/4) 번이미지를, 함수 cvpryup() 에의해서, 고해상도이미지 (2)( 을 ) 를구성한다. 마지막인수,CV_GAUSSIAN_5x5( 현재서포트되고있는값은, 이것만 )( 을 ) 를지정하는것에의해서, 가우시안피르타에근거한리산프링을한다. 249
실행결과예 이미지피라밋드를이용한이미지의영역분할 cvpyrsegmentation 레벨을지정해이미지피라밋드를작성해, 그정보를이용해이미지의부분화를행한다. 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> int main (int argc, char **argv) { int level = 4; double threshold1, threshold2; IplImage *src_img = 0, *dst_img; CvMemStorage *storage = 0; CvSeq *comp = 0; CvRect roi; // (1) 이미지의읽기 if (argc >= 2) src_img = cvloadimage (argv[1], CV_LOAD_IMAGE_ANYDEPTH CV_LOAD_IMAGE_ANYCOLOR); if (src_img == 0) exit (-1); // (2) 영역분할을위해서 ROI( 을 ) 를세트한다 roi.x = roi.y = 0; roi.width = src_img->width & -(1 << level); roi.height = src_img->height & -(1 << level); cvsetimageroi (src_img, roi); 250
// (3) 분할결과이미지출력용의이미지영역을확보해, 영역분할을실행 dst_img = cvcloneimage (src_img); storage = cvcreatememstorage (0); threshold1 = 255.0; threshold2 = 50.0; cvpyrsegmentation (src_img, dst_img, storage, &comp, level, threshold1, threshold2); // (4) 입력이미지와분할결과이미지의표시 cvnamedwindow ("Source", CV_WINDOW_AUTOSIZE); cvnamedwindow ("Segmentation", CV_WINDOW_AUTOSIZE); cvshowimage ("Source", src_img); cvshowimage ("Segmentation", dst_img); cvwaitkey (0); cvdestroywindow ("Source"); cvdestroywindow ("Segmentation"); cvreleaseimage (&src_img); cvreleaseimage (&dst_img); cvreleasememstorage (&storage); return 0; // (1) 이미지의읽기커멘드인수로지정된파일명의이미지 ( 입력이미지 ) 을오픈해, 함수 cvloadimage() 그리고읽어들인다. // (2) 영역분할을위해서 ROI( 을 ) 를세트한다 cvpyrsegmentation( 은 ) 는,2 ( 피라밋드의레벨 ) 그리고나뉘어떨어지는이미지사이즈밖에처리할수없다 ( 에러가발생한다 ). 그때문에, 지정한레벨에대응한다 ROI 사이즈를입력이미지에세트할필요가있다. 샘플프로그램에서는, 지정한레벨수만큼 1 을왼쪽으로시프트해 (2 ( 지정한레벨 ) ( 을 ) 를계산 ), 그후 2 의보수를취하는일로비트마스크를작성해, 원이미지의 width, height( 와 ) 과의 AND( 을 ) 를취해, 나뉘어떨어지는이미지사이즈 ( 을 ) 를계산한다. // (3) 분할결과이미지출력용의이미지영역을확보해, 영역분할을실행입력이미지와같은사이즈, 채널수, 데프스의출력용이미지 dst_img( 을 ) 를,cvCloneImage() 그리고확보해, 2 개의반응을일으키는최소의물리량을세트하고, 영역분할을실행한다. // (4) 입력이미지와분할결과이미지의표시윈도우를생성해, 입력이미지, 분할결과이미지를표시해, 무엇인가키가밀릴때까지기다린다. 251
실행결과예입력이미지와영역분할결과 [ 왼쪽에서 ] 입력이미지, 영역분할결과 (threshold2=25), 영역분할결과 (threshold2=50), 영역분할결과 (threshold2=75), 영역분할결과 (threshold2=100) 평균치시프트법에따르는이미지의부분화 cvpyrmeanshiftfiltering 평균치시프트법에따르는이미지의부분화를실시한다. 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> int main (int argc, char **argv) { int level = 2; IplImage *src_img = 0, *dst_img; CvRect roi; // (1) 이미지의읽기 if (argc >= 2) src_img = cvloadimage (argv[1], CV_LOAD_IMAGE_ANYDEPTH CV_LOAD_IMAGE_ANYCOLOR); if (src_img == 0) exit (-1); if (src_img->nchannels!= 3) exit (-1); if (src_img->depth!= IPL_DEPTH_8U) exit (-1); // (2) 영역분할을위해서 ROI( 을 ) 를세트한다 roi.x = roi.y = 0; roi.width = src_img->width & -(1 << level); 252
roi.height = src_img->height & -(1 << level); cvsetimageroi (src_img, roi); // (3) 분할결과이미지출력용의이미지영역을확보해, 영역분할을실행 dst_img = cvcloneimage (src_img); cvpyrmeanshiftfiltering (src_img, dst_img, 30.0, 30.0, level, cvtermcriteria (CV_TERMCRIT_ITER + CV_TERMCRIT_EPS, 5, 1)); // (4) 입력이미지와분할결과이미지의표시 cvnamedwindow ("Source", CV_WINDOW_AUTOSIZE); cvnamedwindow ("MeanShift", CV_WINDOW_AUTOSIZE); cvshowimage ("Source", src_img); cvshowimage ("MeanShift", dst_img); cvwaitkey (0); cvdestroywindow ("Source"); cvdestroywindow ("MeanShift"); cvreleaseimage (&src_img); cvreleaseimage (&dst_img); return 0; // (1) 이미지의읽기커멘드인수로지정된파일명의이미지 ( 입력이미지 ) 을오픈해, 함수 cvloadimage() 그리고읽어들인다. // (2) 영역분할을위해서 ROI( 을 ) 를세트한다 cvpyrmeanshiftfiltering 도위에서나타내보였다 cvpyrsegmentation( 와 ) 과같이, 2 ( 피라밋드의레벨 ) 그리고나뉘어떨어지는이미지사이즈밖에처리할수없다 ( 에러가발생한다 ). 그때문에, 지정한레벨에대응한다 ROI 사이즈를입력이미지에세트할필요가있다. 샘플프로그램에서는, 지정한레벨수만큼 1 을왼쪽으로시프트해 (2 ( 지정한레벨 ) ( 을 ) 를계산 ), 그후 2 의보수를취하는일로비트마스크를작성해, 원이미지의 width, height( 와 ) 과의 AND( 을 ) 를취해, 나뉘어떨어지는이미지사이즈를계산한다. // (3) 분할결과이미지출력용의이미지영역을확보해, 영역분할을실행입력이미지와같은사이즈, 채널수, 데프스의출력용이미지 dst_img( 을 ) 를,cvCloneImage() 그리고확보해, 2 개의반응을일으키는최소의물리량을세트하고, 영역분할을실행한다. // (4) 입력이미지와분할결과이미지의표시윈도우를생성해, 입력이미지, 분할결과이미지를표시해, 무엇인가키가밀릴때까지기다린다. 253
실행결과예입력이미지와부분화결과 [ 왼쪽에서 ] 입력이미지, 부분화결과 Watershed 알고리즘에의한이미지의영역분할 cvwatershed 마우스로원형의마커 ( 배정영역 ) 의중심을지정해, 복수의마커를설정한다. 이마커를이미지의 gradient 에따라서넓혀가,gradient 의높은부분에할수있는경계를바탕으로영역을분할한다. 영역은, 최초로지정한마커의수로분할된다. 이샘플코드는, 마우스이벤트를처리하기위한함수 (on_mouse)( 을 ) 를포함하고있다. 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> IplImage *markers = 0, *dsp_img = 0; /* 마우스이벤트용콜백함수 */ void on_mouse (int event, int x, int y, int flags, void *param) { int seed_rad = 20; static int seed_num = 0; CvPoint pt; // (1) 클릭에의해중심을지정해, 원형의배정영역을설정한다 if (event == CV_EVENT_LBUTTONDOWN) { seed_num++; pt = cvpoint (x, y); cvcircle (markers, pt, seed_rad, cvscalarall (seed_num), CV_FILLED, 8, 0); cvcircle (dsp_img, pt, seed_rad, cvscalarall (255), 3, 8, 0); cvshowimage ("image", dsp_img); /* 메인프로그램 */ 254
int main (int argc, char **argv) { int *idx, i, j; IplImage *src_img = 0, *dst_img = 0; // (2) 이미지의읽어들여, 마커이미지의초기화, 결과표시용이미지영역의확보를행한다 if (argc >= 2) src_img = cvloadimage (argv[1], CV_LOAD_IMAGE_ANYDEPTH CV_LOAD_IMAGE_ANYCOLOR); if (src_img == 0) exit (-1); dsp_img = cvcloneimage (src_img); dst_img = cvcloneimage (src_img); markers = cvcreateimage (cvgetsize (src_img), IPL_DEPTH_32S, 1); cvzero (markers); // (3) 입력이미지를표시해배정컴퍼넌트지정을위한마우스이벤트를등록한다 cvnamedwindow ("image", CV_WINDOW_AUTOSIZE); cvshowimage ("image", src_img); cvsetmousecallback ("image", on_mouse, 0); cvwaitkey (0); // (4)watershed 분할을실행한다 cvwatershed (src_img, markers); // (5) 실행결과의이미지중의 watershed 경계 ( 픽셀치 =-1)( 을 ) 를결과표시용이미지상에표시한다 for (i = 0; i < markers->height; i++) { for (j = 0; j < markers->width; j++) { idx = (int *) cvptr2d (markers, i, j, NULL); if (*idx == -1) cvset2d (dst_img, i, j, cvscalarall (255)); cvnamedwindow ("watershed transform", CV_WINDOW_AUTOSIZE); cvshowimage ("watershed transform", dst_img); cvwaitkey (0); cvdestroywindow ("watershed transform"); cvreleaseimage (&markers); cvreleaseimage (&dsp_img); cvreleaseimage (&src_img); cvreleaseimage (&dst_img); 255
return 1; // (1) 클릭에의해중심을지정해, 원형의배정영역을설정한다입력이미지를표시한윈도우상에서, 마우스클릭의이벤트가발생하면, 배정의수 (seed_num)( 을 ) 를하나늘려, 마커이미지 (markers) 위에, 기정반경에서색을 seed_num 인전부칠해엔을그리기한다. 이,marker 하지만 watershed 분할의배정이된다. 무엇인가키입력을행할때까지, 배정을추가할수가있다. // (2) 이미지의읽어들여, 마커이미지의초기화, 결과표시용이미지영역의확보를행한다커멘드인수로지정된파일명의이미지 ( 입력이미지 ) 을오픈해, 함수 cvloadimage() 그리고읽어들인다. 동시에표시용이미지로서입력이미지를카피한다. 마커이미지로서 32 비트싱글채널의이미지영역 (markers)( 을 ) 를확보한다. 이마커이미지에서는,watershed 경계가 -1, 관계가미지의영역은 0, 그이외에서는속한다영역번호가들어가있다. // (3) 입력이미지를표시해배정컴퍼넌트지정을위한마우스이벤트를등록한다입력이미지를표시해, 이윈도우상에서의마우스이벤트에대한콜백함수 (on_mouse)( 을 ) 를지정한다. // (4)watershed 분할을실행한다마커이미지설정완료의키입력을기다려,cvWatershed()( 을 ) 를실행한다. // (5) 실행결과의이미지중의 watershed 경계 ( 픽셀치 =-1)( 을 ) 를결과표시용이미지상에표시한다분할결과를보관유지하고있는마커이미지를스캔 (cvptr2d 그리고지정한배열요소에의포인터가되돌아온다 ) 해, 값이 -1 의픽셀에대응하는결과표시용이미지 (dst_img) 의픽셀치를 255( 흰색 ) 에 cvset2d()( 을 ) 를이용해설정해, 결과를표시한다. 실행결과예입력이미지와영역분할결과 [ 왼쪽에서 ] 입력이미지, 입력이미지상에표시한마커영역, 영역분할결과 이미지의윤곽검출 OpenCV그럼, 윤곽은다양한데이터구조에의해관리된다. 즉, 모든윤곽이단순한리스트로표현되기도하면, 부모와자식관계를보관유지한트리구조로표현되기도한다. 윤곽을이용한처리 ( 포함구형, 근사, 비교 ) 의해설붙어다른장에양보해, 여기에서는, 윤곽의검출과주사에대해간단하게설명한다. 샘플 256
윤곽의검출과그리기 cvfindcontours 이미지중으로부터윤곽을검출해,-1~+1 까지의레벨에있는윤곽을그리기한다 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> int main (int argc, char *argv[]) { int i; IplImage *src_img = 0, **dst_img; IplImage *src_img_gray = 0; IplImage *tmp_img; CvMemStorage *storage = cvcreatememstorage (0); CvSeq *contours = 0; int levels = 0; if (argc >= 2) src_img = cvloadimage (argv[1], CV_LOAD_IMAGE_COLOR); if (src_img == 0) return -1; src_img_gray = cvcreateimage (cvgetsize (src_img), IPL_DEPTH_8U, 1); cvcvtcolor (src_img, src_img_gray, CV_BGR2GRAY); tmp_img = cvcreateimage (cvgetsize (src_img), IPL_DEPTH_8U, 1); dst_img = (IplImage **) cvalloc (sizeof (IplImage *) * 3); for (i = 0; i < 3; i++) { dst_img[i] = cvcloneimage (src_img); // (1) 이미지의 2 치화 cvthreshold (src_img_gray, tmp_img, 120, 255, CV_THRESH_BINARY); // (2) 윤곽의검출 cvfindcontours (tmp_img, storage, &contours, sizeof (CvContour), CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE); // (3) 윤곽의그리기 cvdrawcontours (dst_img[0], contours, CV_RGB (255, 0, 0), CV_RGB (0, 255, 0), levels - 1, 2, CV_AA, cvpoint (0, 0)); 257
cvdrawcontours (dst_img[1], contours, CV_RGB (255, 0, 0), CV_RGB (0, 255, 0), levels, 2, CV_AA, cvpoint (0, 0)); cvdrawcontours (dst_img[2], contours, CV_RGB (255, 0, 0), CV_RGB (0, 255, 0), levels + 1, 2, CV_AA, cvpoint (0, 0)); // (4) 이미지의표시 cvnamedwindow ("Level:-1", CV_WINDOW_AUTOSIZE); cvshowimage ("Level:-1", dst_img[0]); cvnamedwindow ("Level:0", CV_WINDOW_AUTOSIZE); cvshowimage ("Level:0", dst_img[1]); cvnamedwindow ("Level:1", CV_WINDOW_AUTOSIZE); cvshowimage ("Level:1", dst_img[2]); cvwaitkey (0); cvdestroywindow ("Level:-1"); cvdestroywindow ("Level:0"); cvdestroywindow ("Level:1"); cvreleaseimage (&src_img); cvreleaseimage (&src_img_gray); cvreleaseimage (&tmp_img); for (i = 0; i < 3; i++) { cvreleaseimage (&dst_img[i]); cvfree (dst_img); cvreleasememstorage (&storage); return 0; // (1) 이미지의 2 치화읽어들인이미지를 2 치화한다. 함수 cvfindcontours()( 은 ) 는, 처리대상의이미지를 2 값이미지로서취급한다 ( 값이 0 이외의픽셀은 "1",0 의픽셀은 "0"( 으 ) 로한다 ) 유익이다. // (2) 윤곽의검출 2 치화된이미지로부터, 윤곽을검출한다.5 번째의인수에서는, 윤곽의추출모드를지정한다. 여기에서는,CV_RETR_TREE ( 을 ) 를지정해, 모든윤곽을추출해, 분기한윤곽을완전하게표현하는계층구조를구성한다. 또,6 번째의인수는, 윤곽의근사수법을나타내고있어 CV_CHAIN_APPROX_SIMPLE 하지만지정되었을경우는, 수평 수직 기울기의선분이압축되어각각의단점만이점렬로서남는다. 그외의모드, 근사수법에대해서는, 레퍼런스메뉴얼을참조하는것. // (3) 윤곽의그리기함수 cvdrawcontours()( 을 ) 를이용하고, 윤곽을그리기한다. 이번은, 이하와같이 -1,0,1 의세개의레벨을지정해윤곽을그리기하고있다. 258
-1:2 번째의인수로지정한윤곽과그아이의레벨 0 까지의윤곽 ( 즉, 하나하의계층의윤곽 ) 이그리기된다 0:2 번째의인수로지정한윤곽만이그리기된다 +1:2 번째의인수로지정한윤곽과동레벨의윤곽 ( 즉, 같은계층에있는윤곽 ) 이그리기된다 // (4) 이미지의표시윈도우를생성해,3 종류의윤곽추출이미지를표시하고, 무엇인가키가밀릴때까지기다린다. 실행결과예 Level:-1 Level:0 Level:+1 이미지와형상의모멘트 이미지나형상의특징을정량적으로표현하는방법의하나에모멘트가있다. 이미지의 (x_order+y_order) 다음의공간 모멘트는다음과같이정의된다. M x_order,y_order =sum x,y (I(x,y) x x_order y y_order ) 여기서,I(x,y)( 은 ) 는좌표 (x, y) 의휘도치이며,x_order, y_order( 은 ) 는각각x방향,y방향의차수를나타내고있다. 2값이미지의경우,0다음모멘트M 0,0 하지만면적을나타내,( M 1,0 /M 0,0, M 0,1 /M 0,0 ) 하지만중심좌표를나타내는일이된다. 샘플 이미지의모멘트 이미지의각종모멘트치를계산한다 표시의변환 샘플코드 #include <stdio.h> #include <cv.h> #include <highgui.h> int main (int argc, char **argv) { char text[10][30]; int i; double spatial_moment, central_moment, norm_c_moment; 259
IplImage *src_img = 0; CvFont font; CvSize text_size; CvMoments moments; CvHuMoments hu_moments; // (1) 이미지를읽어들인다.3 채널이미지의경우는 COI 하지만세트되어있지않으면안된다 if (argc >= 2) src_img = cvloadimage (argv[1], CV_LOAD_IMAGE_ANYDEPTH CV_LOAD_IMAGE_ANYCOLOR); if (src_img == 0) return -1; if (src_img->nchannels == 3 && cvgetimagecoi (src_img) == 0) cvsetimagecoi (src_img, 1); // (2) 입력이미지의 3 차까지의이미지모멘트를계산한다 cvmoments (src_img, &moments, 0); cvsetimagecoi (src_img, 0); // (3) 모멘트나 Hu 모멘트불변량을, 얻을수있었다 CvMoments 구조체의값을사용해계산한다. spatial_moment = cvgetspatialmoment (&moments, 0, 0); central_moment = cvgetcentralmoment (&moments, 0, 0); norm_c_moment = cvgetnormalizedcentralmoment (&moments, 0, 0); cvgethumoments (&moments, &hu_moments); // (4) 얻을수있던모멘트나 Hu 모멘트불변량을문자로서이미지에그리기 cvinitfont (&font, CV_FONT_HERSHEY_SIMPLEX, 1.0, 1.0, 0, 2, 8); snprintf (text[0], 30, "spatial=%.3f", spatial_moment); snprintf (text[1], 30, "central=%.3f", central_moment); snprintf (text[2], 30, "norm=%.3f", spatial_moment); snprintf (text[3], 30, "hu1=%f", hu_moments.hu1); snprintf (text[4], 30, "hu2=%f", hu_moments.hu2); snprintf (text[5], 30, "hu3=%f", hu_moments.hu3); snprintf (text[6], 30, "hu4=%f", hu_moments.hu4); snprintf (text[7], 30, "hu5=%f", hu_moments.hu5); snprintf (text[8], 30, "hu6=%f", hu_moments.hu6); snprintf (text[9], 30, "hu7=%f", hu_moments.hu7); cvgettextsize (text[0], &font, &text_size, 0); for (i = 0; i < 10; i++) cvputtext (src_img, text[i], cvpoint (10, (text_size.height + 3) * (i + 1)), &font, cvscalarall (0)); // (5) 입력이미지와모멘트계산결과를표시, 키가밀렸을때에종료 cvnamedwindow ("Image", CV_WINDOW_AUTOSIZE); cvshowimage ("Image", src_img); 260
cvwaitkey (0); cvdestroywindow ("Image"); cvreleaseimage (&src_img); return 0; // (1) 이미지를읽어들인다.3 채널이미지의경우는 COI 하지만세트되어있지않으면안된다커멘드인수로지정된파일명의이미지 ( 입력이미지 ) 을오픈해, 함수 cvloadimage() 그리고읽어들인다. 만약, 지정된이미지가 3 채널이미지이면,COI 하지만세트되어있지않으면, 모멘트의계산을할수없기때문에체크한후, 제일최초의채널을지정해둔다. // (2) 입력이미지의 3 차까지의이미지모멘트를계산한다이미지의 3 다음까지의공간모멘트와중심모멘트를함수 cvmoments() 그리고계산한다. CvMoments 구조체는, 이하와같이정의되고있어 typedef struct CvMoments { double m00, m10, m01, m20, m11, m02, m30, m21, m12, m03; /* spatial moments */ double mu20, mu11, mu02, mu30, mu21, mu12, mu03; /* central moments */ double inv_sqrt_m00; /* m00!= 0? 1/sqrt(m00) : 0 */ CvMoments; 샘플예의경우이면, 예를들면직접,moments.m00 ( 으 ) 로서 0 다음모멘트의계산결과를취득하는일도할수있다. 결과표시때문에,COI 의설정을해제해둔다. // (3) 모멘트나 Hu 모멘트불변량을얻을수있었다 CvMoments 구조체의값을사용해계산한다. 위와같게직접구조체의내용에액세스해도계산결과는취득할수있지만,OpenCV 에는함수 cvgetspatialmoment(), cvgetcentralmoment(), cvgetnormalizedcentralmoment() 등에서, 필요한공간모멘트나중심모멘트, 정규화된중심모멘트등을취득할수가있다. 샘플예에서는모든 0 차모멘트치의취득을지정했다. 또 Hu 모멘트불변량도계산한다.7 의 Hu 모멘트불변량의각각의의미는, 레퍼런스를참조의일. // (4) 얻을수있던각종모멘트치나 Hu 모멘트불변량을문자로서이미지에그리기윈도우를생성해, 요구한모멘트치를표시하기위해서, 함수 cvputtext()( 을 ) 를이용하고, 값을텍스트로서이미지에덧쓰기해, 무엇인가키가밀릴때까지기다린다. 실행결과예 261
하후변환 OpenCV그럼, 표준적하후변환, 확률적하후변환, 멀티스케일형의고전적하후변환의 3 종류의수법을실장하고있다. 표준적하후변환과고전적하후변환에서는, 검출된선은점 (0, 0)( 으 ) 로부터선까지의거리ρ라고선의법선이 x축과이루는모퉁이θ의2개의값으로나타내진다. 한편확률적하후변환에서는, 단점을가지는선분으로서선을검출한다. 그때문에, 검출된선분은, 시점과종점에서나타내진다. 또, 엔은중심과반경을나타내는 3개의파라미터로표현할수있기위해, 직선검출로투표에이용하는하후공간을, 삼차원에확장하는것으로엔을검출하는것이가능하게된다. 샘플 하후변환에의한직선검출 cvhoughlines2 표준적하후변환과확률적하후변환을지정해선 ( 선분 ) 의검출을행한다. 샘플코드내의각파라미터치는처리예의이미지에대해서튜닝되고있다. 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> #include <math.h> int main (int argc, char **argv) { int i; float *line, rho, theta; double a, b, x0, y0; IplImage *src_img_std = 0, *src_img_prob = 0, *src_img_gray = 0; CvMemStorage *storage; CvSeq *lines = 0; CvPoint *point, pt1, pt2; // (1) 이미지의읽기 if (argc >= 2) src_img_gray = cvloadimage (argv[1], CV_LOAD_IMAGE_GRAYSCALE); if (src_img_gray == 0) return -1; src_img_std = cvloadimage (argv[1], CV_LOAD_IMAGE_COLOR); src_img_prob = cvcloneimage (src_img_std); // (2) 하후변환을위한사전처리 cvcanny (src_img_gray, src_img_gray, 50, 200, 3); storage = cvcreatememstorage (0); 262
// (3) 표준적하후변환에의한선의검출과검출한선의그리기 lines = cvhoughlines2 (src_img_gray, storage, CV_HOUGH_STANDARD, 1, CV_PI / 180, 50, 0, 0); for (i = 0; i < MIN (lines->total, 100); i++) { line = (float *) cvgetseqelem (lines, i); rho = line[0]; theta = line[1]; a = cos (theta); b = sin (theta); x0 = a * rho; y0 = b * rho; pt1.x = cvround (x0 + 1000 * (-b)); pt1.y = cvround (y0 + 1000 * (a)); pt2.x = cvround (x0-1000 * (-b)); pt2.y = cvround (y0-1000 * (a)); cvline (src_img_std, pt1, pt2, CV_RGB (255, 0, 0), 3, 8, 0); // (4) 확률적하후변환에의한선분의검출과검출한선분의그리기 lines = 0; lines = cvhoughlines2 (src_img_gray, storage, CV_HOUGH_PROBABILISTIC, 1, CV_PI / 180, 50, 50, 10); for (i = 0; i < lines->total; i++) { point = (CvPoint *) cvgetseqelem (lines, i); cvline (src_img_prob, point[0], point[1], CV_RGB (255, 0, 0), 3, 8, 0); // (5) 검출결과표시용의윈도우를확보해표시한다 cvnamedwindow ("Hough_line_standard", CV_WINDOW_AUTOSIZE); cvnamedwindow ("Hough_line_probalistic", CV_WINDOW_AUTOSIZE); cvshowimage ("Hough_line_standard", src_img_std); cvshowimage ("Hough_line_probalistic", src_img_prob); cvwaitkey (0); cvdestroywindow ("Hough_line_standard"); cvdestroywindow ("Hough_line_standard"); cvreleaseimage (&src_img_std); cvreleaseimage (&src_img_prob); cvreleaseimage (&src_img_gray); cvreleasememstorage (&storage); return 0; // (1) 이미지의읽기커멘드인수로지정된파일명의이미지 ( 입력이미지 ) 을오픈해, 함수 cvloadimage() 그리고 263
읽어들인다.2 번째의인수에 CV_LOAD_IMAGE_GRAYSCALE ( 을 ) 를지정하는것으로, 처리용으로서 1 채널, 굿의자케일로변환한다. 또, 표시용 ( 검출한선을빨강으로나타내보인다 ) 을위해서,2 번째의인수에 CV_LOAD_IMAGE_COLOR ( 을 ) 를지정하고, 칼라이미지이라고해도읽어들여둔다. // (2) 하후변환을위한사전처리 cvhoughlines2()( 은 ) 는, 입력으로서 8 비트, 싱글채널, 2 치화이미지를취한다. 그때문에사전처리로서 cvcanny()( 을 ) 를이용하고, 입력처리용의그레이스케일이미지를엣지이미지로변환한다.cvCanny()( 은 ) 는, 샘플과같이인프레이스처리가가능하다. 또,cvHoughLines2() 그리고사용하는검출결과의선을보존하는메모리스트레이지를생성해둔다. // (3) 표준적하후변환에의한선의검출과검출한선의그리기표준적하후변환 CV_HOUGH_STANDARD( 을 ) 를지정하고, 엣지이미지로부터선을검출한다. 검출결과는순서 lines 의선두포인터로서돌려주어진다. 하나하나의선은, 순서 lines( 으 ) 로부터,cvGetSeqElem()( 을 ) 를사용하고, 인덱스의순서에꺼낸다. 순서 lines 의하나의요소 line( 은 ) 는,32 비트부동소수점형 2 채널배열 ( 배열요소가 2 개 ) CV_32FC2 ( 주 1) 이기위해,line[0] 에 ρ 하지만,line[1] 에 θ 하지만들어간다. 최초로,ρ( 와 ) 과 θ( 을 ) 를이용해선상의 1 점 (x0, y0)( 을 ) 를,x0=ρ*cos(θ), y0=ρ*sin(θ)( 으 ) 로서계산한다. 다음에 (x0, y0)( 을 ) 를중심으로서각각 1000pixel 멀어진선상의 2 점 pt1,pt2( 을 ) 를계산한다. 여기서 θ 하지만검출된선의법선방향을나타내고있기때문에, 검출된선과 x 축이이루는모퉁이는 θ+90[deg] 된다.sin(θ+90) = cos(θ),cos(θ+90) = -sin(θ) 이기위해,pt1.x = x0 + 1000 * (-sin(θ)), pt1.y = y0 + 1000 * cos(θ) 그리고얻을수있다. 선상의 2 점을이용하고, 결과표시용의이미지데이터 src_img_std 위에적색, 선폭 3 그리고직선을표시한다. 이미지영역외의선은클리핑된다. 검출직선수가많아져결과가보기나뻐지므로, 표시를 100 line 까지누르고있다. // (4) 확률적하후변환에의한선분의검출과검출한선분의그리기확률적하후변환 CV_HOUGH_PROBABILISTIC( 을 ) 를지정하고, 엣지이미지로부터선분을검출한다. 검출결과는순서 lines 의선두포인터로서돌려주어진다. 하나하나의선분은, 순서 lines( 으 ) 로부터,cvGetSeqElem()( 을 ) 를사용하고, 인덱스의순서에꺼낸다. 순서 lines 의하나의요소 point( 은 ) 는,32 비트정수형 4 채널배열 ( 배열요소가 4 개 ) CV_32SC4 ( 주 1) 이기위해,point[0] 에시점좌표가,point[1] 에종점좌표가들어간다. 시점, 종점을이용하고, 결과표시용의이미지데이터 src_img_prob 위에적색, 선폭 3 그리고선분을표시한다. // (5) 검출결과표시용의윈도우를확보해표시한다윈도우를생성해, 직선검출결과를표시해, 무엇인가키가밀릴때까지기다린다. ( 주 1) CV_< 비트수 ( 데프스 )>(S U F)C< 채널수 > 264
실행결과예입력이미지와직선검출결과 [ 좌 ] 입력이미지 [ 중 ] 표준적하후변환에의해, 검출된직선 (100[line] 마셔표시 ) [ 우 ] 확률적하후변환에의해, 검출된선분 하후변환에의한엔검출 cvhoughcircles 그레이스케일이미지중의엔을하후변환을이용해검출한다. 샘플코드내의각파라미터치는처리예의이미지에대해서튜닝되고있다. 표시의변환 샘플코드 #include <stdio.h> #include <cv.h> #include <highgui.h> int main (int argc, char **argv) { int i; float *p; IplImage *src_img = 0, *src_img_gray = 0; CvMemStorage *storage; CvSeq *circles = 0; // (1) 이미지의읽기 if (argc >= 2) src_img_gray = cvloadimage (argv[1], CV_LOAD_IMAGE_GRAYSCALE); if (src_img_gray == 0) exit (-1); src_img = cvloadimage (argv[1], CV_LOAD_IMAGE_COLOR); // (2) 하후변환을위한사전처리 ( 이미지의평활화를행하지않으면오류검출이발생하기쉽다 ) cvsmooth (src_img_gray, src_img_gray, CV_GAUSSIAN, 11, 11, 0, 0); storage = cvcreatememstorage (0); 265
// (3) 하후변환에의한엔의검출과검출한엔의그리기 circles = cvhoughcircles (src_img_gray, storage, CV_HOUGH_GRADIENT, 1, 100, 20, 50, 10, MAX (src_img_gray->width, src_img_gray- >height)); for (i = 0; i < circles->total; i++) { p = (float *) cvgetseqelem (circles, i); cvcircle (src_img, cvpoint (cvround (p[0]), cvround (p[1])), 3, CV_RGB (0, 255, 0), - 1, 8, 0); cvcircle (src_img, cvpoint (cvround (p[0]), cvround (p[1])), cvround (p[2]), CV_RGB (255, 0, 0), 3, 8, 0); // (4) 검출결과표시용의윈도우를확보해표시한다 cvnamedwindow ("circles", 1); cvshowimage ("circles", src_img); cvwaitkey (0); cvdestroywindow ("circles"); cvreleaseimage (&src_img); cvreleaseimage (&src_img_gray); cvreleasememstorage (&storage); return 0; // (1) 이미지의읽기커멘드인수로지정된파일명의이미지 ( 입력이미지 ) 을오픈해, 함수 cvloadimage() 그리고 src_img_gray 에읽어들인다.2 번째의인수에 CV_LOAD_IMAGE_GRAYSCALE ( 을 ) 를지정하는것으로, 처리용으로서 1 채널, 그레이스케일로변환한다. 또, 표시용 ( 검출한선을빨강으로나타내보인다 ) 을위해서,2 번째의인수에 CV_LOAD_IMAGE_COLOR ( 을 ) 를지정하고, 칼라이미지 src_img( 이 ) 라고해도읽어들여둔다. // (2) 하후변환을위한사전처리 cvhoughcircles()( 은 ) 는, 입력으로서 8 비트, 싱글채널, 그레이스케일이미지를이용한다. 오류검출을줄이기위해서, 사전처리로서 cvsmooth()( 을 ) 를이용한이미지의평활화를베푼다. cvsmooth()( 은 ) 는, 샘플과같이인프레이스처리가가능하다. 또,cvHoughCircles() 그리고사용하는검출결과의엔을보존하는메모리스트레이지를생성해둔다. // (3) 하후변환에의한엔의검출과검출한엔의그리기기본적인 2 단계하후변환 CV_HOUGH_GRADIENT( 을 ) 를지정하고 ( 현재는이것밖에실장되어있지않다 ), 그레이스케일이미지로부터엔을검출한다. 검출결과는순서 circles 의선두포인터로서돌려주어진다. 하나하나의엔은, 순서 circles( 으 ) 로부터,cvGetSeqElem()( 을 ) 를사용하고, 인덱스의순서에꺼낸다. 순서 circles 의하나의요소 p( 은 ) 는,32 비트부동소수점형 3 채널배열 ( 배열요소가 3 개 ) 266
CV_32FC3 ( 주 1) 이기위해,p[0] 에엔중심의 x 좌표치가,p[1] 에엔중심의 y 좌표가, p[2] 에엔의반경이각각들어간다. 검출된엔을결과표시용의이미지데이터 src_img 위에적색, 선폭 3 그리고표시해, 엔의중심을초록으로표시하는, // (4) 검출결과표시용의윈도우를확보해표시한다윈도우를생성해, 엔검출결과를표시해, 무엇인가키가밀릴때까지기다린다. ( 주 1) CV_< 비트수 ( 데프스 )>(S U F)C< 채널수 > 실행결과예입력이미지와엔검출결과 [ 좌 ] 입력이미지 [ 우 ] 하후변환에의해검출된엔 거리변환 2값이미지의각화소에대해서, 거기로부터값이0인화소에의최단거리를주는변환을거리변환이라고한다.Open CV그럼, 마스크를지정하지않고근사없음의정확한거리계산을행하는일도, 처리의고속화를위해서, 지정한마스크의시프트를이용한근사계산을행하는일도가능하다. 샘플 거리변환과그가시화 cvdisttransform 입력이미지에대해서거리변환을행해, 결과를 0-255 에정규화해가시화한다 표시의변환 샘플코드 267
#include <stdio.h> #include <cv.h> #include <highgui.h> int main (int argc, char **argv) { IplImage *src_img = 0, *dst_img, *dst_img_norm; // (1) 이미지를읽어들여 if (argc >= 2) src_img = cvloadimage (argv[1], CV_LOAD_IMAGE_ANYDEPTH CV_LOAD_IMAGE_ANYCOLOR); if (src_img == 0) exit (-1); if (src_img->nchannels!= 1) exit (-1); if (src_img->depth!= IPL_DEPTH_8U) exit (-1); // (2) 처리결과의거리이미지출력용의이미지영역과표시윈도우를확보 dst_img = cvcreateimage (cvsize (src_img->width, src_img->height), IPL_DEPTH_32F, 1); dst_img_norm = cvcreateimage (cvsize (src_img->width, src_img->height), IPL_DEPTH_8U, 1); // (3) 거리이미지를계산해, 표시용으로결과를 0-255 에정규화한다 cvdisttransform (src_img, dst_img, CV_DIST_L2, 3, NULL, NULL); cvnormalize (dst_img, dst_img_norm, 0.0, 255.0, CV_MINMAX, NULL); // (4) 거리이미지를표시, 키가밀렸을때에종료 cvnamedwindow ("Source", CV_WINDOW_AUTOSIZE); cvnamedwindow ("Distance Image", CV_WINDOW_AUTOSIZE); cvshowimage ("Source", src_img); cvshowimage ("Distance Image", dst_img_norm); cvwaitkey (0); cvdestroywindow ("Source"); cvdestroywindow ("Distance Image"); cvreleaseimage (&src_img); cvreleaseimage (&dst_img); cvreleaseimage (&dst_img_norm); return 0; 268
// (1) 이미지의읽기커멘드인수로지정된파일명의이미지 ( 입력이미지 ) 을오픈해, 함수 cvloadimage() 그리고읽어들인다.2 번째의인수에 CV_LOAD_IMAGE_ANYDEPTH CV_LOAD_IMAGE_ANYCOLOR( 을 ) 를지정하는것으로, 원이미지의데프스, 채널을변갱하지않고읽어들인다. cvdisttransform()( 은 ) 는, 입력으로서 8 비트싱글채널의 2 치이미지밖에잡히지않는다의로, 여기서입력이미지의채널수와데프스의체크를행한다. // (2) 처리결과의거리이미지출력용의이미지영역과표시윈도우를확보 cvdisttransform() 의출력은,32 비트부동소수점형싱글채널이기위해, 채널, 데프스를지정해출력용이미지 dst_img( 을 ) 를준비한다. 또, 결과를보기쉽게표시하기위한결과표시용의 8 비트부호없음싱글채널의이미지영역 dst_img_norm( 을 ) 를준비해, 이이미지를표시하기위한윈도우를준비한다. // (3) 거리이미지를계산해, 표시용으로결과를 0-255 에정규화한다각픽셀로부터값 0 까지의최근거리거리를계산한다.cvDistTransform() 의제 1, 제 2 인수의형태는 CvArr* 이지만, 샘플로나타내보이도록 ( 듯이 ), 직접 IplImage* 형태의변수를지정할수있다. 또이샘플에서는, 고속으로근사거리를요구하기위해서 CV_DIST_L2(Euclid 거리 ), 3 3 마스크를지정해있다. 계산후의이미지의화소치의범위를,cvNormalized()( 을 ) 를이용해 0-255 에정규화한다 (8 비트부호없음싱글채널이미지로한다 ). 정규화의타입으로서 CV_MINMAX( 배열의값이지정의범위에들어가도록 ( 듯이 ) 슬캘링과시프트를실시한다 )( 을 ) 를지정해, 제 3, 제 4 인수로그범위를지정해두면간단하게정규화를할수있다. // (4) 거리이미지를표시, 키가밀렸을때에종료입력이미지를표시하기위한윈도우를 cvnamedwindow() 그리고확보해,cvShowImage() 에입력이미지의포인터를건네주는것으로, 실제의표시를행한다. 거리이미지도마찬가지. 정규화된거리이미지 dst_img_norm( 을 ) 를윈도우를실제로표시해, 무엇인가키가밀릴때까지기다린다. 마지막으로, 종료할경우에는확보한구조체의메모리를해방한다. 실행결과예입력이미지 (8 비트,1 채널,2 값이미지 ) 과 0-255 에정규화한거리이미지 [ 좌 ] 256 256 의이미지의주위에값 0 의폭 20 의보더를가지는입력이미지. [ 우 ] 입력이미지에대해서거리변환한결과, 이미지의중심이가장거리가멀고, 휘도가높아지고있다. 이미지수복 OpenCV1.0그리고새롭게추가된기능으로, 지정한영역경계근방의화소를이용해지정한영역내의화소치를재구성한다. PhotoShop(Ver.CS2 이후 ) 의스포트수복브러쉬기능과같이, 이미지노이즈를제거하거나이미지로부터불필요한오브젝트를제거하기위해서이용하는것이가능하다. 269
샘플 불요오브젝트의제거 cvinpaint 이미지의불필요한문자열부분에대한마스크이미지를지정해문자열을제거한다 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> int main (int argc, char **argv) { IplImage *src_img = 0, *mask_img = 0, *dst_img; // (1) 이미지의읽기 if (argc >= 2) src_img = cvloadimage (argv[1], CV_LOAD_IMAGE_ANYDEPTH CV_LOAD_IMAGE_ANYCOLOR); if (src_img == 0) exit (-1); if (src_img->depth!= IPL_DEPTH_8U) exit (-1); // (2) 마스크이미지의읽기 if (argc >= 3) mask_img = cvloadimage (argv[2], CV_LOAD_IMAGE_ANYDEPTH CV_LOAD_IMAGE_ANYCOLOR); if (mask_img == 0) exit (-1); if (mask_img->nchannels!= 1) exit (-1); if (mask_img->depth!= IPL_DEPTH_8U) exit (-1); // (3) 수복이미지출력용의이미지영역을확보해, 이미지수복을실행 dst_img = cvcloneimage (src_img); cvinpaint (src_img, mask_img, dst_img, CV_INPAINT_NS, 10); // (4) 입력, 마스크, 수복결과이미지의표시 cvnamedwindow ("Source", CV_WINDOW_AUTOSIZE); cvnamedwindow ("Mask", CV_WINDOW_AUTOSIZE); cvnamedwindow ("Inpaint", CV_WINDOW_AUTOSIZE); cvshowimage ("Source", src_img); cvshowimage ("Mask", mask_img); 270
cvshowimage ("Inpaint", dst_img); cvwaitkey (0); cvdestroywindow ("Source"); cvdestroywindow ("Mask"); cvdestroywindow ("Inpaint"); cvreleaseimage (&src_img); cvreleaseimage (&mask_img); cvreleaseimage (&dst_img); return 1; // (1) 이미지의읽기커멘드인수로지정된파일명의이미지 ( 입력이미지 ) 을오픈해, 함수 cvloadimage() 그리고읽어들인다.2 번째의인수에 CV_LOAD_IMAGE_ANYDEPTH CV_LOAD_IMAGE_ANYCOLOR( 을 ) 를지정하는것으로, 원이미지의데프스, 채널을변갱하지않고읽어들인다. cvinpaint()( 은 ) 는, 입력으로서 8 비트의이미지밖에잡히지않기때문에, 여기서입력이미지의데프스의체크를행한다. // (2) 마스크이미지의읽기커멘드인수로지정된파일명의이미지 ( 마스크이미지 ) 을읽어들인다. 마스크로서는 8 비트, 싱글채널의이미지밖에잡히지않는다의로, 여기서마스크이미지의채널수와데프스의체크를행한다. 마스크이미지의비 0 부분이, 수복대상영역이된다. // (3) 수복이미지출력용의이미지영역을확보해, 이미지수복을실행입력이미지와같은사이즈, 채널수, 데프스의출력용이미지 dst_img( 을 ) 를,cvCloneImage() 그리고확보해, 나비에 stokes 베이스의수법 CV_INPAINT_NS ( 을 ) 를지정하고, 이미지수복을실행한다. // (4) 입력, 마스크, 수복결과이미지의표시윈도우를생성해, 입력, 마스크, 결과를표시해, 무엇인가키가밀릴때까지기다린다. 실행결과예 [ 좌 ] Inpaint Sample 라고하는불필요한문자가삽입된이미지 [ 중 ] 문자영역을화소치 255 의흰색픽셀로나타내보인마스크이미지 [ 우 ] 이미지수복결과, 예쁘게수복하려면마스크형상을세세하게지정하는편이좋다. 히스토그램의그리기 cvcalchist 271
입력이미지의히스토그램을계산해, 결과의그래프를그리기한다 샘플코드 표시의변환 Python 판으로변경 #include <cv.h> #include <highgui.h> int main (int argc, char **argv) { int i, j, bin_w; int hist_size = 256; int sch = 0, ch_width = 260; float max_value = 0; float range_0[] = { 0, 256 ; float *ranges[] = { range_0 ; IplImage *src_img = 0, *dst_img[4] = { 0, 0, 0, 0, *hist_img; CvHistogram *hist; // (1) 이미지를읽어들인다 if (argc < 2 //(src_img = cvloadimage (argv[1], CV_LOAD_IMAGE_ANYDEPTH CV_LOAD_IMAGE_ANYCOLOR))==0) (src_img = cvloadimage (argv[1], CV_LOAD_IMAGE_ANYCOLOR)) == 0) return -1; // (2) 입력이미지의채널몇분의이미지영역을확보 sch = src_img->nchannels; for (i = 0; i < sch; i++) { dst_img[i] = cvcreateimage (cvsize (src_img->width, src_img->height), src_img->depth, 1); // (3) 히스토그램구조체, 히스토그램이미지영역을확보 hist = cvcreatehist (1, &hist_size, CV_HIST_ARRAY, ranges, 1); hist_img = cvcreateimage (cvsize (ch_width * sch, 200), 8, 1); // (4) 입력이미지가멀티채널의경우, 이미지를채널마다분할 if (sch == 1) cvcopy (src_img, dst_img[0], NULL); else cvsplit (src_img, dst_img[0], dst_img[1], dst_img[2], dst_img[3]); // (5) 히스토그램이미지를클리어 272
cvset (hist_img, cvscalarall (255), 0); for (i = 0; i < sch; i++) { // (6) 히스토그램을계산하고, 슬캘링 cvcalchist (&dst_img[i], hist, 0, NULL); cvgetminmaxhistvalue (hist, 0, &max_value, 0, 0); cvscale (hist->bins, hist->bins, ((double) hist_img->height) / max_value, 0); // (7) 히스토그램의그리기 bin_w = cvround ((double) ch_width / hist_size); for (j = 0; j < hist_size; j++) cvrectangle (hist_img, cvpoint (j * bin_w + (i * ch_width), hist_img->height), cvpoint ((j + 1) * bin_w + (i * ch_width), hist_img->height - cvround (cvgetreal1d (hist->bins, j))), cvscalarall (0), -1, 8, 0); // (8) 히스토그램이미지를표시, 키가밀렸을때에종료 cvnamedwindow ("Image", CV_WINDOW_AUTOSIZE); cvshowimage ("Image", src_img); cvnamedwindow ("Histogram", CV_WINDOW_AUTOSIZE); cvshowimage ("Histogram", hist_img); cvwaitkey (0); cvdestroywindow ("Histogram"); cvreleaseimage (&src_img); cvreleaseimage (&hist_img); for (i = 0; i < sch; i++) { cvreleaseimage (&dst_img[i]); cvreleasehist (&hist); return 0; // (1) 이미지를읽어들인다커멘드인수로지정된파일명의이미지 ( 입력이미지 ) 을오픈해, 함수 cvloadimage() 그리고읽어들인다. 2 번째의인수에 CV_LOAD_IMAGE_ANYDEPTH CV_LOAD_IMAGE_ANYCOLOR( 을 ) 를지정하는것으로, 원이미지의데프스, 채널을변경하지않고읽어들인다. // (2) 입력이미지의채널몇분의이미지영역을확보입력이미지의채널수와같은수의이미지영역을확보해, 그포인터를배열 dst_img[] 에납입한다. 또, 이배열은 0 그리고초기화되고있으므로, 장네루수이상의인덱스를가지는요소의값은 0( 이 ) 가된다. 273
// (3) 히스토그램구조체, 히스토그램이미지영역을확보히스토그램구조체 CvHistogram, 히스토그램이미지를그리기한다 IplImage 의영역을확보한다. 이번계산되는것은, 휘도치만의히스토그램이므로, 히스토그램구조체하 1 차원, 빈의수는 256(=hist_size), 빈의반응을일으키는최소의물리량은 0~255(=ranges) 이므로, 입력치중,0~255 의범위에있는값 ( 하나의채널에대해 8bit) 만이, 256 개의빈에누적되어가게된다. 또, 마지막인수를 1( 비 0 의값 ) 으로하는것으로, 등간격인빈을가지는히스토그램이된다. 또,ranges[i][0] v<ranges[i][1] 의범위의값이입력으로서놓치는것에주의한다. // (4) 입력이미지가멀티채널의경우, 이미지를채널마다분할입력이미지가멀티채널의경우에는, 이미지를채널마다분할하고, 의히스토그램을요구한다. 여기서, 분할후의이미지배열 dst_img[]( 을 ) 를최초로 0 그리고초기화하고있으므로, 그대로함수 cvsplit() 에건네주어도좋은것에주의한다. // (5) 히스토그램이미지를클리어다음에, 우선, 히스토그램을그리기한다 IplImage( 을 ) 를, 휘도치 255( 백색 )( 으 ) 로쿠리어한다. 즉, 히스토그램은, 백색배경의이미지가된다. // (6) 히스토그램을계산하고, 슬캘링각채널마다히스토그램을계산한다. 계산후의히스토그램의최대치를꺼내, 그값을이용해히스토그램의높이가이미지내에들어가도록 ( 듯이 ) 슬캘링을실시한다. 여기에서는, 히스토그램의피크 ( 최대치 ) 가, 이미지 hist_img 의높이로동일하고되도록 ( 듯이 ) 조정하고있다. // (7) 히스토그램의그리기결과적으로얻을수있던히스토그램을, 차례차례, 이미지에그리기한다. // (8) 히스토그램이미지를표시, 키가밀렸을때에종료전채널의히스토그램이그리기된이미지포함한윈도우를실제로표시해, 무엇인가키가밀릴때까지기다린다. 실행결과예입력이미지 (24 비트,3 채널 ) 과각채널에대한히스토그램 입력이미지 (24 비트,1 채널 ) 과그에대한히스토그램 [ 좌 ] 높은명도를가지도록 ( 듯이 ) 변경한이미지와그히스토그램. 그래프가높은휘도치측에모이고있다. [ 우 ] 낮은명도를가지도록 ( 듯이 ) 변경한이미지와그히스토그램. 그래프가낮은휘도치측에모이고있다. 274
[ 좌 ] 높은콘트라스트를가지도록 ( 듯이 ) 변경한이미지와그히스토그램. 최저휘도가 0, 최고휘도가 255 되는넓은분포를가지지만, 콘트라스트를너무강조해서, 특정의범위 ( 빈 ) 의정보가빠져있다. [ 우 ] 낮은콘트라스트를가지도록 ( 듯이 ) 변경한이미지와그히스토그램. 히스토그램이중앙부분에치우쳐, 최저휘도와최고명도와의차이가작아지고있다. 히스토그램간의거리 cvcomparehist 두개의입력이미지의히스토그램의거리를계산해, 그값을표시한다 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> #include <math.h> #include <stdio.h> int main (int argc, char **argv) { char text[16]; int i, hist_size = 256, sch = 0; float range_0[] = { 0, 256 ; float *ranges[] = { range_0 ; double tmp, dist = 0; IplImage *src_img1 = 0, *src_img2 = 0, *dst_img1[4] = { 0, 0, 0, 0, *dst_img2[4] = { 0, 0, 0, 0; CvHistogram *hist1, *hist2; CvFont font; CvSize text_size; // (1)2 매의이미지를읽어들인다. 채널수가동일하지않은경우는, 종료 if (argc >= 3) { 275
src_img1 = cvloadimage (argv[1], CV_LOAD_IMAGE_ANYDEPTH CV_LOAD_IMAGE_ANYCOLOR); src_img2 = cvloadimage (argv[2], CV_LOAD_IMAGE_ANYDEPTH CV_LOAD_IMAGE_ANYCOLOR); if (src_img1 == 0 src_img2 == 0) return -1; if (src_img1->nchannels!= src_img2->nchannels) return -1; // (2) 입력이미지의채널몇분의이미지영역을확보 sch = src_img1->nchannels; for (i = 0; i < sch; i++) { dst_img1[i] = cvcreateimage (cvsize (src_img1->width, src_img1->height), src_img1- >depth, 1); dst_img2[i] = cvcreateimage (cvsize (src_img2->width, src_img2->height), src_img2- >depth, 1); // (3) 히스토그램구조체를확보 hist1 = cvcreatehist (1, &hist_size, CV_HIST_ARRAY, ranges, 1); hist2 = cvcreatehist (1, &hist_size, CV_HIST_ARRAY, ranges, 1); // (4) 입력이미지가멀티채널의경우, 이미지를채널마다분할 if (sch == 1) { cvcopy (src_img1, dst_img1[0], NULL); cvcopy (src_img2, dst_img2[0], NULL); else { cvsplit (src_img1, dst_img1[0], dst_img1[1], dst_img1[2], dst_img1[3]); cvsplit (src_img2, dst_img2[0], dst_img2[1], dst_img2[2], dst_img2[3]); // (5) 히스토그램을계산, 정규화하고, 거리를요구한다 for (i = 0; i < sch; i++) { cvcalchist (&dst_img1[i], hist1, 0, NULL); cvcalchist (&dst_img2[i], hist2, 0, NULL); cvnormalizehist (hist1, 10000); cvnormalizehist (hist2, 10000); tmp = cvcomparehist (hist1, hist2, CV_COMP_BHATTACHARYYA); dist += tmp * tmp; dist = sqrt (dist); // (6) 요구한거리를문자로서이미지에그리기 snprintf (text, 16, "Distance=%.3f", dist); cvinitfont (&font, CV_FONT_HERSHEY_SIMPLEX, 2.0, 2.0, 0, 4, 8); cvputtext (src_img2, text, cvpoint (10, src_img2->height - 10), &font, cvscalarall 276
(0)); cvgettextsize ("Input1", &font, &text_size, 0); cvputtext (src_img1, "Input1", cvpoint (10, text_size.height), &font, cvscalarall (0)); cvputtext (src_img2, "Input2", cvpoint (10, text_size.height), &font, cvscalarall (0)); // (7) 두개의이미지를표시, 키가밀렸을때에종료 cvnamedwindow ("Image1", CV_WINDOW_AUTOSIZE); cvnamedwindow ("Image2", CV_WINDOW_AUTOSIZE); cvshowimage ("Image1", src_img1); cvshowimage ("Image2", src_img2); cvwaitkey (0); cvdestroywindow ("Image1"); cvdestroywindow ("Image2"); for (i = 0; i < src_img1->nchannels; i++) { cvreleaseimage (&dst_img1[i]); cvreleaseimage (&dst_img2[i]); cvreleaseimage (&src_img1); cvreleaseimage (&src_img2); cvreleasehist (&hist1); cvreleasehist (&hist2); return 0; // (1)2 매의이미지를읽어들인다 히스토그램이미지의그리기 의경우와같게, 커멘드인수로지정된이미지 ( 을 ) 를, 함수 cvloadimage() 에의해서읽어들인다. 이번은, 히스토그램간의거리 ( 을 ) 를계산하므로, 2 매의이미지를읽어들인다. // (2) 입력이미지의채널몇분의이미지영역을확보채널마다의히스토그램간거리를계산하기위해서, 히스토그램이미지의그리기 의경우와같게, 채널몇분의이미지구조체를확보한다. // (3) 히스토그램구조체를확보히스토그램구조체를확보. // (4) 입력이미지가멀티채널의경우, 이미지를채널마다분할이것도, 히스토그램이미지의그리기 의경우와같게, 각채널을분할한다. // (5) 히스토그램을계산, 정규화하고, 거리를요구한다함수 cvcalchist() 에의해서, 각이미지의히스토그램을계산한후에, 거리를비교하기위해서정규화를실시한다. 어느쪽의히스토그램도, 전빈의값의합계하지만일정 ( 여기에서는,10000)( 이 ) 가되도록 ( 듯이 ) 조정한다. 실제로거리를계산하는함수 cvcomparehist()( 은 ) 는, 이와같이정규화된히파업그램에대해서만올바르게실행되는 277
것에주의. 이번은, 히스토그램비교의수법으로서 CV_COMP_BHATTACHARYYA( 을 ) 를지정했다하지만, 그밖에도 CV_COMP_CORREL,CV_COMP_CHISQR,CV_COMP_INTERSECT 등이손가락정가능하다. 자세한것은레퍼런스를참조하는것. 또, 이프로그램에서는, 멀티채널이미지의경우의최종적인거리와하고, 채널간의거리의자승을의화의평방근을요구하도록 ( 듯이 ) 하고있다. 다만, 이것이유일한거리의정의라고하는것으로않는다. // (6) 요구한거리를문자로서이미지에그리기요구한거리를표시하기위해서, 함수 cvputtext()( 을 ) 를이용하고, 값을테키스트로서 2 매목의이미지에덧쓰기한다. 또,1 매목,2 매목의이미지에,"Input1","Input2" 의문자를그린다. // (7) 두개의이미지를표시, 키가밀렸을때에종료 2 매의이미지를실제로표시해, 무엇인가키가밀릴때까지기다린다. 종료시에는, 메모리를해방한다. 실행결과예입력이미지 (Input1, Input2) 의히스토그램간의거리 Input1 0.382 0.779 0.823 1.239 이차원의히스토그램 cvcreatehist 입력이미지의색공간 (color space) 를 HSV( 으 ) 로변환해,H-S 의히스토그램을그리기한다 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> int main (int argc, char **argv) { IplImage *src, *hsv; IplImage *h_plane, *s_plane, *v_plane; IplImage *planes[2]; int h_bins = 30, s_bins = 32; int hist_size[] = { h_bins, s_bins ; float h_ranges[] = { 0, 181 ; /* 색상은 0(0 도, 빨강 ) 으로부터 180(360 도, 빨강 ) 까지변화한다 */ 278
float s_ranges[] = { 0, 256 ; /* 채도는 0( 흑-그레이-흰색 ) 으로부터 255( 순수한스펙트럼칼라 ) 까지변화한다 */ float *ranges[] = { h_ranges, s_ranges ; int scale = 10; IplImage *hist_img = cvcreateimage (cvsize (h_bins * scale, s_bins * scale), 8, 3); CvHistogram *hist; float max_value = 0; int h, s; if (argc!= 2 (src = cvloadimage (argv[1], CV_LOAD_IMAGE_COLOR)) == 0) return -1; hsv = cvcreateimage (cvgetsize (src), 8, 3); planes[0] = h_plane = cvcreateimage (cvgetsize (src), 8, 1); planes[1] = s_plane = cvcreateimage (cvgetsize (src), 8, 1); v_plane = cvcreateimage (cvgetsize (src), 8, 1); // (1) 입력이미지의색공간 (color space) 를 RGB( 으 ) 로부터 HSV( 으 ) 로변환한다 cvcvtcolor (src, hsv, CV_BGR2HSV); cvcvtpixtoplane (hsv, h_plane, s_plane, v_plane, 0); // (2) 히스토그램을계산 hist = cvcreatehist (2, hist_size, CV_HIST_ARRAY, ranges, 1); cvcalchist (planes, hist, 0, 0); // (3) 각빈에있어서의값을요구해그것을바탕으로휘도치를산출해그리기 cvgetminmaxhistvalue (hist, 0, &max_value, 0, 0); cvzero (hist_img); for (h = 0; h < h_bins; h++) { for (s = 0; s < s_bins; s++) { float bin_val = cvqueryhistvalue_2d (hist, h, s); int intensity = cvround (bin_val * 255 / max_value); cvrectangle (hist_img, cvpoint (h * scale, s * scale), cvpoint ((h + 1) * scale - 1, (s + 1) * scale - 1), CV_RGB (intensity, intensity, intensity), CV_FILLED); // (4) 입력이미지와이차원 (H-S) 히스토그램을표시하고, 무엇인가키가밀릴때까지기다린다 cvnamedwindow ("Source", 1); cvshowimage ("Source", src); cvnamedwindow ("H-S Histogram", 1); cvshowimage ("H-S Histogram", hist_img); cvwaitkey (0); cvdestroywindow ("Source"); cvdestroywindow ("H-S Histogram"); 279
cvreleaseimage (&src); cvreleaseimage (&hsv); cvreleaseimage (&h_plane); cvreleaseimage (&s_plane); cvreleaseimage (&v_plane); cvreleaseimage (&hist_img); cvreleasehist (&hist); return 0; // (1) 입력이미지의색공간 (color space) 를 RGB( 으 ) 로부터 HSV( 으 ) 로변환한다함수 cvcvtcolor()( 을 ) 를이용하고, 입력된칼라이미지의색공간 (color space) 를 RGB 공간으로부터 HSV 공간으로변환한다. 실제로는,cvLoadImage() 그리고읽힌이미지는,BGR 의차례로줄지어있으므로, CV_BGR2HSV( 을 ) 를지정해변환한다. 다음에,cvCvtPixToPlane()( 을 ) 를이용하고,H,S,V 의각채널 ( 색평면 ) 로분해한다. 이,cvCvtPixToPlane()( 은 ) 는,cvcompat.h 그리고 #define cvcvtpixtoplane cvsplit ( 이 ) 라고정의되고있어 cvsplit()( 와 ) 과등가이다. // (2) 히스토그램을계산이번은,2 차원의히스토그램구조체를작성해, 함수 cvcalchist() 그럼, 둘의색평면 (H,S) 에있어서의히스토그램을계산한다. // (3) 각빈에있어서의값을요구해그것을바탕으로휘도치를산출해그리기함수 cvqueryhistvalue_2d() 에의해서, 지정했다 H-S 에있어서의빈의값을요구해그값을이용하고, 최대치가 255, 최소치가 0 되어에휘도치를산출한다. 그리고, 그휘도치로전부칠해진구형을순서대로, 이미지 hist_img 에쓴다. // (4) 입력이미지와이차원 (H-S) 히스토그램을표시하고, 무엇인가키가밀릴때까지기다린다마지막으로, 입력이미지와그리기되었다 2 차원 (H-S) 히스토그램이미지를표시한다. 횡축이 H( 색상 ), 세로축이 S( 채도 )( 을 ) 를나타낸다. 실행결과예 280
백프로젝션패치 cvcalcbackprojectpatch 히스토그램간거리에의한, 이미지내의템플릿탐색 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> int main (int argc, char **argv) { int i, hist_size = 90; float h_ranges[] = { 0, 181 ; float *ranges[] = { h_ranges ; double min_val, max_val; CvSize dst_size; CvPoint min_loc, max_loc; IplImage *src_img, *tmp_img, *dst_img; IplImage *src_hsv, *tmp_hsv; IplImage **src_planes, *tmp_planes[3]; CvHistogram *hist = 0; if (argc!= 3 (src_img = cvloadimage (argv[1], CV_LOAD_IMAGE_COLOR)) == 0 (tmp_img = cvloadimage (argv[2], CV_LOAD_IMAGE_COLOR)) == 0) return -1; src_planes = (IplImage **) cvalloc (sizeof (IplImage *) * 3); for (i = 0; i < 3; i++) { src_planes[i] = cvcreateimage (cvgetsize (src_img), IPL_DEPTH_8U, 1); tmp_planes[i] = cvcreateimage (cvgetsize (tmp_img), IPL_DEPTH_8U, 1); // (1)2 개입력이미지 ( 탐색이미지, 템플릿이미지 ) 의색공간 (color space) 를,RGB( 으 ) 로부터 HSV 에변환 281
src_hsv = cvcreateimage (cvgetsize (src_img), IPL_DEPTH_8U, 3); tmp_hsv = cvcreateimage (cvgetsize (tmp_img), IPL_DEPTH_8U, 3); cvcvtcolor (src_img, src_hsv, CV_BGR2HSV); cvcvtcolor (tmp_img, tmp_hsv, CV_BGR2HSV); cvcvtpixtoplane (src_hsv, src_planes[0], src_planes[1], src_planes[2], 0); cvcvtpixtoplane (tmp_hsv, tmp_planes[0], tmp_planes[1], tmp_planes[2], 0); // (2) 템플릿이미지의히스토그램을계산 hist = cvcreatehist (1, &hist_size, CV_HIST_ARRAY, ranges, 1); cvcalchist (&tmp_planes[0], hist, 0, 0); // (3) 탐색이미지전체에대해서, 템플릿의히스토그램과의거리 ( 수법으로의존 ) 를계산 dst_size = cvsize (src_img->width - tmp_img->width + 1, src_img->height - tmp_img- >height + 1); dst_img = cvcreateimage (dst_size, IPL_DEPTH_32F, 1); cvcalcbackprojectpatch (src_planes, dst_img, cvgetsize (tmp_img), hist, CV_COMP_CORREL, 1.0); cvminmaxloc (dst_img, &min_val, &max_val, &min_loc, &max_loc, NULL); // (4) 템플릿에대응하는위치에구형을그리기 cvrectangle (src_img, max_loc, cvpoint (max_loc.x + tmp_img->width, max_loc.y + tmp_img->height), CV_RGB (255, 0, 0), 3); cvnamedwindow ("Image", 1); cvshowimage ("Image", src_img); cvwaitkey (0); cvdestroywindow ("Image"); cvreleaseimage (&src_img); cvreleaseimage (&dst_img); cvreleaseimage (&tmp_img); cvreleaseimage (&src_hsv); cvreleaseimage (&tmp_hsv); for (i = 0; i < 3; i++) { cvreleaseimage (&src_planes[i]); cvreleaseimage (&tmp_planes[i]); cvfree (&src_planes); return 0; // (1)2 개입력이미지 ( 탐색이미지, 템플릿이미지 ) 의색공간 (color space) 를,RGB( 으 ) 로부터 HSV 에변환이번은,HSV 공간에있어서의 H( 색상 ) 의히스토그램을비교하므로, 입력이미지의색공간 (color space) 를 RGB( 으 ) 로부터 HSV( 으 ) 로변환한다. 비교하는히스토그램은, 하나의채널 ( 색평면 ) 에한정하지않고와도좋다. 282
// (2) 템플릿이미지의히스토그램을계산최초로, 템플릿이상의히스토그램을미리계산해둔다. 함수 cvcalchist() 에대해서는, 전술의샘플코드를참조하는것. // (3) 탐색이미지전체에대해서, 템플릿의히스토그램과의거리 ( 수법으로의존 ) 를계산함수 cvcalcbackprojectpatch() 에의해서출력되는이미지는,32bit 부동소수점, 1 채널의이미지며, 그사이즈는, 탐색이미지 ( 의폭, 높이 )- 템플릿이미지 ( 의폭높이 )+ 1 된다. 이이미지영역 dst_img( 을 ) 를확보하고, 함수 cvcalcbackprojectpatch()( 을 ) 를실행한다. 이번은, 히스토그램비교의수법으로서 CV_COMP_CORREL( 상관 )( 을 ) 를지정해있다. 결과이미지중에있고최대치를가지는위치, 즉템플릿의히스토그램과가장가까운히스토그램을가지는위치, 를템플릿에대응하는위치로한다. 이미지중의값의최대, 최소치, 및그위치는, 함수 cvminmaxloc() 에의해서꺼낸다. // (4) 템플릿에대응하는위치에구형을그리기전술의처리에의해서꺼내진위치 ( 좌표 ) 는, 템플릿의좌상의좌표 ( 을 ) 를나타내므로, 거기에텐프레이트사이즈의구형을그리기한다. 마지막으로, 그이미지를표시하고, 무엇인가키가밀릴때까지기다린다. 실행결과예이하의예에서는, 탐색이미지의일부를자른것을템플릿이미지로서급 ( 이 ) 라고있다 ( 탐색이미지에직접 ROI( 을 ) 를설정해도같은일이가능하다 ). 따라서, 매칭결과의피크치는 1.0( 이 ) 가되지만, 실제로는이러한완전하게템플릿과영역이입력이미지내에존재하는것은드물다. 매칭위치의피크는 1.0( 을 ) 를밑돌아, 또, 피크자체가복수존재하는일도생각할수있다. 입력이미지템플릿이미지결과이미지 히스토그램의균일화 cvequalizehist 그레이스케일이미지의히스토그램을균일화한다 표시의변환 샘플코드 283
#include <cv.h> #include <highgui.h> int main (int argc, char **argv) { int i, j, bin_w, ch_width = 260; int hist_size = 256; float max_value = 0; float range_0[] = { 0, 256 ; float *ranges[] = { range_0 ; IplImage *src_img[2], *hist_img[2]; CvHistogram *hist; if (argc!= 2 (src_img[0] = cvloadimage (argv[1], CV_LOAD_IMAGE_GRAYSCALE)) == 0) return -1; hist = cvcreatehist (1, &hist_size, CV_HIST_ARRAY, ranges, 1); hist_img[0] = cvcreateimage (cvsize (ch_width, 200), 8, 1); hist_img[1] = cvcreateimage (cvsize (ch_width, 200), 8, 1); src_img[1] = cvcreateimage (cvgetsize (src_img[0]), 8, 1); // (1) 히스토그램을균일화한다 cvequalizehist (src_img[0], src_img[1]); for (i = 0; i < 2; i++) { cvset (hist_img[i], cvscalarall (255), 0); // (2) 히스토그램을계산해, 슬캘링 cvcalchist (&src_img[i], hist, 0, NULL); cvgetminmaxhistvalue (hist, 0, &max_value, 0, 0); cvscale (hist->bins, hist->bins, ((double) hist_img[i]->height) / max_value, 0); bin_w = cvround ((double) ch_width / hist_size); // (3) 히스토그램을그리기 for (j = 0; j < hist_size; j++) cvrectangle (hist_img[i], cvpoint (j * bin_w, hist_img[i]->height), cvpoint ((j + 1) * bin_w, hist_img[i]->height - cvround (cvgetreal1d (hist->bins, j))), cvscalarall (0), -1, 8, 0); // (4) 균일화전후의이미지와그에대한히스토그램을표시 cvnamedwindow ("Image1", CV_WINDOW_AUTOSIZE); cvshowimage ("Image1", src_img[0]); cvnamedwindow ("Histogram1", CV_WINDOW_AUTOSIZE); 284
cvshowimage ("Histogram1", hist_img[0]); cvnamedwindow ("Image2", CV_WINDOW_AUTOSIZE); cvshowimage ("Image2", src_img[1]); cvnamedwindow ("Histogram2", CV_WINDOW_AUTOSIZE); cvshowimage ("Histogram2", hist_img[1]); cvwaitkey (0); cvdestroywindow ("Image1"); cvdestroywindow ("Image2"); cvdestroywindow ("Histogram1"); cvdestroywindow ("Histogram2"); cvreleaseimage (&src_img[0]); cvreleaseimage (&src_img[1]); cvreleaseimage (&hist_img[0]); cvreleaseimage (&hist_img[1]); cvreleasehist (&hist); return 0; // (1) 히스토그램을균일화한다함수 cvequalizehist() 에의해, 입력이미지를, 그히스토그램이균일하게분포하는이미지로변환한다. 이변환에의해, 이미지의콘트라스트가오른다. 그러나, 변환후의이미지의휘도치는, 변환전의이미지의히스토그램을정규화한값의부분적인총화이므로, 변환후는히스토그램에누락이생긴다. // (2) 히스토그램을계산해, 슬캘링재차, 히스토그램균일화전후의이미지에대해히스토그램을계산해, 스케이링을베푼다. // (3) 히스토그램을그리기계산한히스토그램을그리기한다. // (4) 균일화전후의이미지와그에대한히스토그램을표시실제로, 균일화전후의이미지와그에대한히스토그램을표시해, 무엇인가키가밀릴때까지기다린다. 실행결과예 템플릿매칭 cvmatchtemplate 탐색이미지로부터템플릿의위치를찾는다 285
표시의변환 샘플코드 #include <cv.h> #include <highgui.h> int main (int argc, char **argv) { double min_val, max_val; CvPoint min_loc, max_loc; CvSize dst_size; IplImage *src_img, *tmp_img, *dst_img; if (argc!= 3 (src_img = cvloadimage (argv[1], CV_LOAD_IMAGE_COLOR)) == 0 (tmp_img = cvloadimage (argv[2], CV_LOAD_IMAGE_COLOR)) == 0) return -1; // (1) 탐색이미지전체에대해서, 템플릿의매칭치 ( 지정한수법으로의존 ) 를계산 dst_size = cvsize (src_img->width - tmp_img->width + 1, src_img->height - tmp_img- >height + 1); dst_img = cvcreateimage (dst_size, IPL_DEPTH_32F, 1); cvmatchtemplate (src_img, tmp_img, dst_img, CV_TM_CCOEFF_NORMED); cvminmaxloc (dst_img, &min_val, &max_val, &min_loc, &max_loc, NULL); // (2) 템플릿에대응하는위치에구형을그리기 cvrectangle (src_img, max_loc, cvpoint (max_loc.x + tmp_img->width, max_loc.y + tmp_img->height), CV_RGB (255, 0, 0), 3); cvnamedwindow ("Image", 1); cvshowimage ("Image", src_img); cvwaitkey (0); cvdestroywindow ("Image"); cvreleaseimage (&src_img); cvreleaseimage (&tmp_img); cvreleaseimage (&dst_img); return 0; // (1) 탐색이미지전체에대해서, 템플릿의매칭치 ( 지정한수법으로의존 ) 를계산읽힌탐색이미지와템플릿이미지를이용하고, 템플릿매칭을실시한다. 매칭에이용하는상관함수에는,CV_TM_SQDIFF,CV_TM_SQDIFF_NORMED, CV_TM_CCORR,CV_TM_CCORR_NORMED,CV_TM_CCOEFF,CV_TM_CCOEFF_NORMED, 하지만 286
지정가능하다. 이번은,CV_TM_CCOEFF_NORMED( 을 ) 를지정해있다. 이것은, 각비교영역의휘도치의평균이동일하면가정했을경우의상관연산식의근사이다. 자세한것은, 레퍼런스를참조하는것. 매칭은, 함수 cvcalcbackprojectionpatch()( 와 ) 과닮아있지만, 미리히스토그램을계산하는사전처리는필요하지않다. 이미지중의값의최대, 최소치, 및그위치는, 함수 cvminmaxloc() 에의해서꺼낸다. CV_TM_CCOEFF_NORMED( 을 ) 를지정했을경우에는, 최대치의좌표가매칭위치가된다. 또,CV_TM_SQDIFF(SSD( 이 ) 라고도불린다 ),CV_TM_SQDIFF_NORMED( 을 ) 를지정한장소합에는, 최소치의좌표가매칭위치가된다. // (2) 템플릿에대응하는위치에구형을그리기탐색이미지중이구할수있던좌표위치에구형을그리기한다. 그이미지를표시해, 무엇인가키가밀릴때까지기다린다. 실행결과예이하의예에서는, 탐색이미지의일부를자른것을템플릿이미지로서급 ( 이 ) 라고있다 ( 탐색이미지에직접 ROI( 을 ) 를설정해도같은일이가능하다 ) 의로, 완전하게올바른결과가얻어지고있다. 따라서, 탐색영역과템플릿이미지로이미지가변화하는경우는, 항상올바른위치에매칭한다고는할수없다. 입력이미지템플릿이미지결과이미지 형상매칭두개의형상 ( 윤곽데이터, 혹은그레이스케일이미지 ) 을입력으로한다. OpenCV의함수cvMatchShapes() 그럼, 형상의비교에Hu모멘트를이용하기위해, 회전, 슬캘링, 반전에대해서불변이다. 샘플 형상의매칭 cvmatchshapes 두개의입력이미지에대해서,3 종류의수법으로형상비교를실시한다 표시의변환 샘플코드 287
#include <cv.h> #include <highgui.h> #include <stdio.h> int main (int argc, char **argv) { char text[16]; int i; double result[3]; CvFont font; IplImage *src_img1, *src_img2; IplImage *dst_img[3]; if (argc!= 3 (src_img1 = cvloadimage (argv[1], CV_LOAD_IMAGE_GRAYSCALE)) == 0 (src_img2 = cvloadimage (argv[2], CV_LOAD_IMAGE_GRAYSCALE)) == 0) return -1; for (i = 0; i < 3; i++) { dst_img[i] = (IplImage *) cvclone (src_img2); // (1)3 종류의수법으로형상을비교 result[0] = cvmatchshapes (src_img1, src_img2, CV_CONTOURS_MATCH_I1, 0); result[1] = cvmatchshapes (src_img1, src_img2, CV_CONTOURS_MATCH_I2, 0); result[2] = cvmatchshapes (src_img1, src_img2, CV_CONTOURS_MATCH_I3, 0); // (2) 형상매칭의결과를이미지에그리기 for (i = 0; i < 3; i++) { snprintf (text, 16, "%.5f", result[i]); cvinitfont (&font, CV_FONT_HERSHEY_SIMPLEX, 1.0, 1.0, 0, 4, 8); cvputtext (dst_img[i], text, cvpoint (10, dst_img[i]->height - 10), &font, cvscalarall (0)); // (3) 입력이미지 1( 와 ) 과 3 종류의값이써진입력이미지 2( 을 ) 를표시해, 무엇인가키가밀릴때까지기다린다 cvnamedwindow ("Image1", CV_WINDOW_AUTOSIZE); cvnamedwindow ("Image2a", CV_WINDOW_AUTOSIZE); cvnamedwindow ("Image2b", CV_WINDOW_AUTOSIZE); cvnamedwindow ("Image2c", CV_WINDOW_AUTOSIZE); cvshowimage ("Image1", src_img1); cvshowimage ("Image2a", dst_img[0]); cvshowimage ("Image2b", dst_img[1]); 288
cvshowimage ("Image2c", dst_img[2]); cvwaitkey (0); cvdestroywindow ("Image1"); cvdestroywindow ("Image2a"); cvdestroywindow ("Image2b"); cvdestroywindow ("Image2c"); cvreleaseimage (&src_img1); cvreleaseimage (&src_img2); cvreleaseimage (&dst_img[0]); cvreleaseimage (&dst_img[1]); cvreleaseimage (&dst_img[2]); return 0; // (1)3 종류의수법으로형상을비교함수 cvmatchshapes() 에의해서, 두개의입력이미지에대해서형상매칭을실시한다. 여기서,3 번째의인수가비교수법을나타낸다. 또, 4 번째의인수는, 현재로서는이용되어있지않다. // (2) 형상매칭의결과를이미지에그리기비교결과를, 이미지에문자열로서그리기한다. 작은값인만큼두개의이미지의형상이가깝다 (0 의경우는같은형상 ) 이라고하는것이된다. // (3) 입력이미지 1( 와 ) 과 3 종류의값이써진입력이미지 2( 을 ) 를표시결과의이미지를표시해, 무엇인가키가밀릴때까지기다린다. 실행결과예 입력이미지비교이미지 1 비교이미지 2 비교이미지 3 비교이미지 4 CV_CONTOURS_MATCH_I1 CV_CONTOURS_MATCH_I2 CV_CONTOURS_MATCH_I3 289
입력이미지비교이미지 1 비교이미지 2 비교이미지 3 비교이미지 4 CV_CONTOURS_MATCH_I1 CV_CONTOURS_MATCH_I2 CV_CONTOURS_MATCH_I3 점렬을포함하는구형 cvboundingrect 점렬을포함하는구형을요구한다 샘플코드 표시의변환 Python 판으로변경 #include <cv.h> #include <highgui.h> #include <time.h> int main (int argc, char **argv) { int i; IplImage *img = 0; CvMemStorage *storage = cvcreatememstorage (0); CvSeq *points; CvRNG rng = cvrng (time (NULL)); CvPoint pt; CvRect rect; // (1) 이미지를확보해초기화한다 img = cvcreateimage (cvsize (640, 480), IPL_DEPTH_8U, 3); cvzero (img); // (2) 점렬을생성한다 points = cvcreateseq (CV_SEQ_ELTYPE_POINT, sizeof (CvSeq), sizeof (CvPoint), storage); for (i = 0; i < 20; i++) { 290
pt.x = cvrandint (&rng) % (img->width / 2) + img->width / 4; pt.y = cvrandint (&rng) % (img->height / 2) + img->height / 4; cvseqpush (points, &pt); cvcircle (img, pt, 3, CV_RGB (0, 255, 0), CV_FILLED); // (3) 점렬을포함하는구형을요구해그리기한다 rect = cvboundingrect (points, 0); cvrectangle (img, cvpoint (rect.x, rect.y), cvpoint (rect.x + rect.width, rect.y + rect.height), CV_RGB (255, 0, 0), 2); // (4) 이미지의표시, 키가밀렸을때에종료 cvnamedwindow ("BoundingRect", CV_WINDOW_AUTOSIZE); cvshowimage ("BoundingRect", img); cvwaitkey (0); cvdestroywindow ("BoundingRect"); cvreleaseimage (&img); cvreleasememstorage (&storage); return 0; // (1) 이미지를확보해초기화한다이샘플에서는, 외부이미지를읽어들이지않고, 프로그램내부에서이미지를준비한다. 또, 작성된칼라이미지를, 흑 (0)( 으 ) 로전부칠해초기화한다. // (2) 점렬을생성한다함수 cvrandint()( 을 ) 를이용하고, 이미지내가있는범위에들어가는랜덤인점렬을생성한다. 또, 개개의점을중심으로하는작은엔을이미지에그리기해, 점의위치를나타낸다. 이샘플에서는, 랜덤인점렬을이용하고있지만, 물론사전에검출했다윤곽 ( 를구성하는점렬 ) 에대해서도, 처리를실시할수있다. // (3) 점렬을포함하는구형을요구해그리기한다함수 cvboundingrect()( 을 ) 를이용하고, 점렬을포함하는최소의구형 ( 다만기울지않았다 ) 을요구한다. 함수 cvminarearect2()( 와 ) 과의차이는, 이기울기를고려하는지아닌지이다. cvboundingrect() 하 cvrect( 을 ) 를돌려주지만,cvMinAreaRect2() 하 cvbox2d( 을 ) 를돌려준다. 구할수있었다 cvrect( 을 ) 를이미지에그리기한다. // (4) 이미지의표시, 키가밀렸을때에종료결과의이미지를표시해, 무엇인가키가밀릴때까지기다린다. 291
실행결과예 윤곽영역의면적과윤곽의길이 cvcontourarea, cvarclength 윤곽에의해서단락지어진영역의면적과윤곽의길이를요구한다 샘플코드 표시의변환 Python 판으로변경 #include <cv.h> #include <highgui.h> #include <time.h> #include <math.h> #include <stdio.h> int main (int argc, char **argv) { int i, size = 500; char text[2][64]; double scale, area, length; IplImage *img = 0; CvMemStorage *storage = cvcreatememstorage (0); CvSeq *points; CvRNG rng = cvrng (time (NULL)); CvPoint pt0, pt1; CvRect rect; CvFont font; // (1) 이미지를확보해초기화한다 img = cvcreateimage (cvsize (size, size), IPL_DEPTH_8U, 3); cvzero (img); // (2) 점렬을생성한다 points = cvcreateseq (CV_SEQ_POLYLINE, sizeof (CvSeq), sizeof (CvPoint), storage); scale = cvrandreal (&rng) + 0.5; pt0.x = int (cos (0) * size / 4 * scale + size / 2); pt0.y = int (sin (0) * size / 4 * scale + size / 2); cvcircle (img, pt0, 2, CV_RGB (0, 255, 0)); cvseqpush (points, &pt0); 292
for (i = 1; i < 20; i++) { scale = cvrandreal (&rng) + 0.5; pt1.x = int (cos (i * 2 * CV_PI / 20) * size / 4 * scale + size / 2); pt1.y = int (sin (i * 2 * CV_PI / 20) * size / 4 * scale + size / 2); cvline (img, pt0, pt1, CV_RGB (0, 255, 0), 2); pt0.x = pt1.x; pt0.y = pt1.y; cvcircle (img, pt0, 3, CV_RGB (0, 255, 0), CV_FILLED); cvseqpush (points, &pt0); cvline (img, pt0, *CV_GET_SEQ_ELEM (CvPoint, points, 0), CV_RGB (0, 255, 0), 2); // (3) 포함구형, 면적, 길이를요구한다 rect = cvboundingrect (points, 0); area = cvcontourarea (points); length = cvarclength (points, CV_WHOLE_SEQ, 1); // (4) 결과를이미지에쓴다 cvrectangle (img, cvpoint (rect.x, rect.y), cvpoint (rect.x + rect.width, rect.y + rect.height), CV_RGB (255, 0, 0), 2); snprintf (text[0], 64, "Area: wrect=%d, contour=%d", rect.width * rect.height, int (area)); snprintf (text[1], 64, "Length: rect=%d, contour=%d", 2 * (rect.width + rect.height), int (length)); cvinitfont (&font, CV_FONT_HERSHEY_SIMPLEX, 0.7, 0.7, 0, 1, CV_AA); cvputtext (img, text[0], cvpoint (10, img->height - 30), &font, cvscalarall (255)); cvputtext (img, text[1], cvpoint (10, img->height - 10), &font, cvscalarall (255)); // (5) 이미지를표시, 키가밀렸을때에종료 cvnamedwindow ("BoundingRect", CV_WINDOW_AUTOSIZE); cvshowimage ("BoundingRect", img); cvwaitkey (0); cvdestroywindow ("BoundingRect"); cvreleaseimage (&img); cvreleasememstorage (&storage); return 0; // (1) 이미지를확보해초기화한다이샘플에서는, 외부이미지를읽어들이지않고, 프로그램내부에서이미지를준비한다. 또, 작성된칼라이미지를, 흑 (0)( 으 ) 로전부칠해초기화한다. 293
// (2) 점렬을생성한다함수 cvrandint()( 을 ) 를이용하고, 이미지내가있는범위에들어가는랜덤인점렬을생성한다. 다만, 면적을요구할때에알기쉽게자기교차하지않게한다. 또, 개개의점을중심으로하는작은엔을이미지에그리기해, 점의위치를나타낸다. 게다가닫은영역이되듯이더욱점렬끼리를묶는선분을그리기한다. 이샘플에서는, 랜덤인점렬을이용하고있지만, 물론사전에검출했다윤곽 ( 를구성하는점렬 ) 등에대해도, 처리를실시할수있다. // (3) 포함구형, 면적, 길이를요구한다전술의샘플과같게, 함수 cvboundingrect()( 을 ) 를이용해점렬을포함하는구형영역을요구한다. 게다가함수 cvcontourarea() 에의해서점렬이구성하는윤곽의면적을요구해함수 cvarclength() 에는그길이 ( 주위장 ) 를요구한다. cvcontourarea() 의 2 번째의인수는, 윤곽의어느부분의면적을계산하는지를나타내고있어이인수가지정되지않는경우에는디폴트인수이다 CV_WHOLE_SEQ 하지만이용된다. 이경우는, 윤곽이나타내는영역모든면적이요구된다. cvarclength() 의 2 번째의인수의의미도마찬가지여,3 번째의인수에 0 보다큰값을지정하는것으로, 윤곽을폐곡선으로서취급한다. 그외의값에대해서는, 레퍼런스를참조하는것. // (4) 결과를이미지에쓴다포함구형, 포함구형의면적과길이, 윤곽의면적과길이, 를이미지에쓴다. // (5) 이미지를표시, 키가밀렸을때에종료결과의이미지를표시해, 무엇인가키가밀릴때까지기다린다. 실행결과예 배경과주목물체의분리 OpenCV에는, 배경차분을계산할때에편리한배경통계량의누적에관한함수가실장되고있다. 이미지중으로부터, 변화가없는배경영역과그이외의영역을분리하는것은, 컴퓨터비전시스템에대해많이사용되는기술이며, 여러가지수법이제안되고있다. OpenCV냄새나도,cvaux그리고2종류의동적배경차분수법이실장되고있다. 여기에서는, 참고문헌 [1] 그리고이용되고있는배경이미지의시간적인변화를고려한동적배경갱신에의한로바스트인주목물체의검출수법을,OpenCV의함수를이용해실장했다. 이수법에서는, 배경영역화소의휘도 ( 을 ) 를이하와같이모델화하고있다. sin ( 은 ) 는휘도치의시간평균, ( 은 ) 는휘도의진폭, ( 은 ) 는휘도의주파수, ( 은 ) 는시간, ( 은 ) 는계수, ( 은 ) 는카메라에만의존하는노이즈의최대치를각각나타낸다. 이모델에대하고, 하지만, 의경우에, 그화 294
소는배경영역에존재하는화소이다고판단한다. 배경으로판정된영역에서는휘도평균치 ( 와 ) 과진폭 ( 을 ) 를이하의식을이용해갱신한다. 여기서, ( 은 ) 는갱신속도파라미터이다. 한편, 물체영역으로판정된영역에서는, 휘도평균치는원래의값을보관유지해, 진폭 해갱신한다. 만을이하의식을이용 여기서, ( 은 ) 는물체영역갱신속도파라미터이다. [1] 모리타신지, 산택일성, 테라사와마사히코, 요코야곧화 : " 전방위이미지센서를이용한네트워크대응형원격감시시스템 ", 전자정보통신학회논문잡지 (D-II), Vol. J88-D-II, No. 5, pp. 864-875, (2005.5). 샘플 동적배경갱신에의한물체검출 cvacc, cvrunningavg 배경이미지의시간적인명도변화를고려한물체검출. 프로그램개시보다, 윈도우가표시될때까지는배경통계량을초기화하고있기때문에카메라를작동시키지않게주의한다. 샘플내의초기화프레임수나갱신파라미터는테스트로사용카메라에관해서튜닝되고있다. 또,OpenCV 함수의사용예제시를우선했기때문에, 처리효율은약간낮다. 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> #include <ctype.h> #include <stdio.h> int main (int argc, char **argv) { 295
int i, c, counter; int INIT_TIME = 100; int w = 0, h = 0; double B_PARAM = 1.0 / 50.0; double T_PARAM = 1.0 / 200.0; double Zeta = 10.0; CvCapture *capture = 0; IplImage *frame = 0; IplImage *av_img, *sgm_img; IplImage *lower_img, *upper_img, *tmp_img; IplImage *dst_img, *msk_img; CvFont font; char str[64]; // (1) 커멘드인수에의해서지정된번호의카메라에대한캡쳐구조체를작성한다 if (argc == 1 (argc == 2 && strlen (argv[1]) == 1 && isdigit (argv[1][0]))) capture = cvcreatecameracapture (argc == 2? argv[1][0] - '0' : 0); // (2)1 프레임캡쳐해, 캐프체사이즈를취득한다. frame = cvqueryframe (capture); w = frame->width; h = frame->height; // (3) 작업용의영역을생성한다 av_img = cvcreateimage (cvsize (w, h), IPL_DEPTH_32F, 3); sgm_img = cvcreateimage (cvsize (w, h), IPL_DEPTH_32F, 3); tmp_img = cvcreateimage (cvsize (w, h), IPL_DEPTH_32F, 3); lower_img = cvcreateimage (cvsize (w, h), IPL_DEPTH_32F, 3); upper_img = cvcreateimage (cvsize (w, h), IPL_DEPTH_32F, 3); dst_img = cvcreateimage (cvsize (w, h), IPL_DEPTH_8U, 3); msk_img = cvcreateimage (cvsize (w, h), IPL_DEPTH_8U, 1); // (4) 배경의휘도평균의초기치를계산한다 printf ("Background statistics initialization start\n"); cvsetzero (av_img); for (i = 0; i < INIT_TIME; i++) { frame = cvqueryframe (capture); cvacc (frame, av_img); cvconvertscale (av_img, av_img, 1.0 / INIT_TIME); // (5) 배경의휘도진폭의초기치를계산한다 cvsetzero (sgm_img); 296
for (i = 0; i < INIT_TIME; i++) { frame = cvqueryframe (capture); cvconvert (frame, tmp_img); cvsub (tmp_img, av_img, tmp_img); cvpow (tmp_img, tmp_img, 2.0); cvconvertscale (tmp_img, tmp_img, 2.0); cvpow (tmp_img, tmp_img, 0.5); cvacc (tmp_img, sgm_img); cvconvertscale (sgm_img, sgm_img, 1.0 / INIT_TIME); printf ("Background statistics initialization finish\n"); // (6) 표시용윈도우를생성한다 cvinitfont (&font, CV_FONT_HERSHEY_COMPLEX, 0.7, 0.7); cvnamedwindow ("Input", CV_WINDOW_AUTOSIZE); cvnamedwindow ("Substraction", CV_WINDOW_AUTOSIZE); // (7) 취득이미지로부터배경을분리하는루프 counter = 0; while (1) { frame = cvqueryframe (capture); cvconvert (frame, tmp_img); // (8) 배경이될수있는화소의휘도치의범위를체크한다 cvsub (av_img, sgm_img, lower_img); cvsubs (lower_img, cvscalarall (Zeta), lower_img); cvadd (av_img, sgm_img, upper_img); cvadds (upper_img, cvscalarall (Zeta), upper_img); cvinrange (tmp_img, lower_img, upper_img, msk_img); // (9) 휘도진폭을재계산한다 cvsub (tmp_img, av_img, tmp_img); cvpow (tmp_img, tmp_img, 2.0); cvconvertscale (tmp_img, tmp_img, 2.0); cvpow (tmp_img, tmp_img, 0.5); // (10) 배경이라고판단된영역의배경의휘도평균과휘도진폭을갱신한다 cvrunningavg (frame, av_img, B_PARAM, msk_img); cvrunningavg (tmp_img, sgm_img, B_PARAM, msk_img); // (11) 물체영역이라고판단된영역에서는휘도진폭만을 ( 배경영역보다늦은속도로 ) 갱신한다 cvnot (msk_img, msk_img); cvrunningavg (tmp_img, sgm_img, T_PARAM, msk_img); 297
// (12) 물체영역만을출력이미지에카피한다 ( 배경영역은흑 ) cvsetzero (dst_img); cvcopy (frame, dst_img, msk_img); // (13) 처리결과를표시한다 snprintf (str, 64, "%03d[frame]", counter); cvputtext (dst_img, str, cvpoint (10, 20), &font, CV_RGB (0, 255, 100)); cvshowimage ("Input", frame); cvshowimage ("Substraction", dst_img); counter++; c = cvwaitkey (10); if (c == '\x1b') break; cvdestroywindow ("Input"); cvdestroywindow ("Substraction"); cvreleaseimage (&frame); cvreleaseimage (&dst_img); cvreleaseimage (&av_img); cvreleaseimage (&sgm_img); cvreleaseimage (&lower_img); cvreleaseimage (&upper_img); cvreleaseimage (&tmp_img); cvreleaseimage (&msk_img); return 0; // (1) 커멘드인수에의해서지정된번호의카메라에대한캡쳐구조체를작성한다함수 cvcreatecameracapture()( 을 ) 를이용하고, 커멘드인수로서주어진번호의카메라에대한캡쳐구조체 CvCapture( 을 ) 를작성한다. 인수가지정되지않는경우는, 디폴트의값이다 "0" 하지만이용된다. // (2)1 프레임캡쳐해, 캐프체사이즈를취득한다. 함수 cvqueryframe() ( 을 ) 를호출해, 실제로 1 프레임캡쳐를실시해, 취득한이미지로부터, 이미지의폭 w( 와 ) 과높이 h( 을 ) 를취득한다. // (3) 작업용의영역을생성한다휘도평균이나휘도진폭계산을위한작업영역을확보한다. 누적계산이나누승계산을행하기위해, 데프스는 32 비트를지정해둔다. 마스크이미지는 8 비트, 싱글채널을지정한다. // (4) 배경의휘도평균의초기치를계산한다배경의모델화를위해, 지정된프레임수 (INIT_TIME) 사이의각화소치의평균을요구한다. cvacc() 함수를이용하고, 이미지의누적치를계산한다. 298
// (5) 배경의휘도진폭의초기치를계산한다 (4) 그리고계산된배경이미지의평균치를이용하고, 휘도진폭의초기치를계산한다. 휘도진폭 하프레임수 (INIT_TIME) 사이의평균을요구한다. ( 으 ) 로서계산한다. 휘도진폭에대해서도지정된 // (6) 표시용윈도우를생성한다입력이미지, 출력이미지를표시하는윈도우를생성한다. // (7) 취득이미지로부터배경을분리하는루프배경이미지모델의초기치를계산한후, 취득이미지로부터배경분리를행하는루프에들어간다. // (8) 배경이될수있는화소의휘도치의범위를체크한다 이미지의각화소에대해 ( 을 ) 를계산해,cvInRange() 그리고값이범위내인지어떤지를비교해,msk_img 에결과를보존해돌려준다. 범위내의화소에관해서는 msk_img 의대응하는화소치가 0xFF( 으 ) 로서세트된다. // (9) 휘도진폭을재계산한다 (5)( 와 ) 과같게해각화소의휘도진폭 ( 을 ) 를재계산한다. // (10) 배경이라고판단된영역의배경의휘도평균과휘도진폭을갱신한다 (8) 그리고돌려주어지는마스크이미지를이용하고, 배경영역만 cvrunningavg() 함수를이용해휘도평균과휘도진폭을갱신한다. // (11) 물체영역이라고판단된영역에서는휘도진폭만을 ( 배경영역보다늦은속도로 ) 갱신한다물체영역의휘도진폭을갱신하기위해서, 마스크이미지를반전시킨다. 그후 (10)( 와 ) 과같게해휘도진폭을갱신한다. 여기서사용하는갱신속도파라미터는배경영역갱신에사용하는파라미터보다크지않으면안된다. // (12) 물체영역만을출력이미지에카피한다 ( 배경영역은흑 ) 처리결과를나타내기위해서, 이미지 dst_img 에대해서,frame( 으 ) 로부터마스크에따라서화소치를카피한다. // (13) 처리결과를표시한다입력이미지와처리결과를표시한다. 캡쳐안에 "Esc" 키가밀렸을경우는, 종료한다. 실행결과예 CLOSE( 왼쪽 ) 입력이미지, ( 오른쪽 ) 물체검출결과 299
동적윤곽추적동적윤곽추적이란, 변형하는윤곽 ( 물체형상그자체의윤곽이변형하지않는경우에서도, 이미지에투영된윤곽이변형하는경우가있다 ) ( 을 ) 를순서대로계산하는 ( 많은경우는반복계산 ) 수법이다. 추적대상이되는물체는, 그러한수법에의해, 삼차원형상물체, 변형물체, 관절구조체등여러가지이다. Opencv그리고실장되고있다snake( 은 ) 는, 동적윤곽추적수법의가장기본적인방법의하나이다. snake그럼, 내부에너지와외부에너지의화가최소가되는윤곽을요구한다. 적절한윤곽을얻기위해는각에너지의중량감이되는파라미터가중요한역할을완수하지만, 이러한파라미터를결정하기위해서는, 추적물체형상이나배경이미지의특징이어느정도기존일필요가있다. 또, 기본적인snake그럼, 탐색공간이이차원이미지그자체로나타내지는공간이므로, 윤곽의자유도가높은분, 이미지자등의외란의영향을받기쉽다. 샘플 snake 에의한윤곽추적 ( 정지화면 ) cvsnakeimage snake 에의해동적윤곽추적을실시한다 ( 이번은대상이정지화면이므로, 어느쪽인가하면윤곽검출 ) 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> #include <math.h> #include <stdio.h> typedef struct parameter Parameter; struct parameter { float alpha; float beta; float gamma; ; int main (int argc, char **argv) { int i, j = 0, c; IplImage *src_img, *dst_img; CvPoint *contour; CvPoint center; int length = 60; /* 동적윤곽을구성하는점수 */ Parameter param = { 0.45, 0.35, 0.2 ; /* cvsnakeimage 의파라미터 */ CvFont font; char iter[8]; // (1) 이미지를읽어들인다 300
1); if (argc < 2 (src_img = cvloadimage (argv[1], CV_LOAD_IMAGE_GRAYSCALE)) == 0) return -1; dst_img = cvcreateimage (cvgetsize (src_img), IPL_DEPTH_8U, 3); cvinitfont (&font, CV_FONT_HERSHEY_DUPLEX, 0.7, 0.7); center.x = src_img->width / 2; center.y = src_img->height / 2; // (2) 동적윤곽의초기화 contour = (CvPoint *) cvalloc (sizeof (CvPoint) * length); for (i = 0; i < length; i++) { contour[i].x = (int) (center.x * cos (2 * CV_PI * i / length) + center.x); contour[i].y = (int) (center.y * sin (2 * CV_PI * i / length) + center.y); // (3) 초기윤곽의그리기 cvcvtcolor (src_img, dst_img, CV_GRAY2RGB); for (i = 0; i < length - 1; i++) { cvline (dst_img, contour[i], contour[i + 1], CV_RGB (255, 0, 0), 2, 8, 0); cvline (dst_img, contour[length - 1], contour[0], CV_RGB (255, 0, 0), 2, 8, 0); cvnamedwindow ("Snakes", CV_WINDOW_AUTOSIZE); cvshowimage ("Snakes", dst_img); cvwaitkey (0); /* 동적윤곽의수습계산 ( 과정을표시한다 ) */ while (1) { // (4) 동적윤곽의윤곽계산 cvsnakeimage (src_img, contour, length, m.alpha, m.beta, m.gamma, CV_VALUE, cvsize (15, 15), cvtermcriteria (CV_TERMCRIT_ITER, 1, 0.0), // (5) 계산된동적윤곽의그리기 cvcvtcolor (src_img, dst_img, CV_GRAY2RGB); for (i = 0; i < length - 1; i++) { cvline (dst_img, contour[i], contour[i + 1], CV_RGB (255, 0, 0), 2); cvline (dst_img, contour[length - 1], contour[0], CV_RGB (255, 0, 0), 2); snprintf (iter, 8, "%03d", ++j); cvputtext (dst_img, iter, cvpoint (15, 30), &font, CV_RGB (0, 0, 255)); // (6) 결과의표시 cvshowimage ("Snakes", dst_img); c = cvwaitkey (0); if (c == '\x1b') break; 301
cvdestroywindow ("Snakes"); cvreleaseimage (&src_img); cvreleaseimage (&dst_img); return 0; // (1) 이미지를읽어들인다커멘드인수로지정된파일명의이미지 ( 입력이미지 ) 을오픈해, 함수 cvloadimage() 그리고읽어들인다.2 번째의인수에 CV_LOAD_IMAGE_GRAYSCALE ( 을 ) 를지정하는것으로, 원이미지를그레이스케일이미지로서읽어들인다. 또, 결과를표시하기위해서, 입력이미지와동일사이즈의칼라이미지를작성한다. // (2) 동적윤곽의초기화초기윤곽의위치를결정한다. 본래는, 추적되는물체의형상에응한적절한초기윤곽이설정되는것이당연하지만, 이번은단지, 주어진이미지구형에내접하는타원을초기윤곽으로서이용하고있다. // (3) 초기윤곽의그리기초기화된윤곽을그리기하고, 실제로표시한다. 무엇인가키가압하될때까지기다린다. // (4) 동적윤곽의윤곽계산동적윤곽의윤곽을계산한다. 이샘플에서는, 입력이미지에대한사전처리등 ( 은 ) 는아무것도가서않지만, 실제로는, 예를들면노이즈의제거나콘트라스트의강조, 혹은엣지의검출등이사전처리로서행해지는경우가있다. 여기에서는, 윤곽이수습하는과정을표시하기위해서, 함수 cvsnakeimage() 하지만 1 회의반복계산밖에실시하지않게파라미터를설정해있다 (CvTermCriteria). 예를들면, 이하와같이하면, 100 회반복계산을하든가, 혹은각반복계산에대해이동하는점의수하지만 0 이하 ( 모든점이이동하지않게된다 ) 까지, 함수 cvsnakeimage() 안에서반복계산을하고나서돌아간다. 즉, 통상은, 반복계산을위한루프는필요없다. cvsnakeimage( src_img, contour, length, m.alpha, m.beta, m.gamma, CV_VALUE, cvsize(15, 15), cvtermcriteria( CV_TERMCRIT_ITER, 100, 0.0), 1); 또, 구배플래그 ( 함수 cvsnakeimage() 의마지막인수 ) 가 1 그래서, 이미지구배가에너지장으로서이용된다. 그외, 각 인수의상세한것에대하여는, 참조설명서를참조. // (5) 계산된동적윤곽의그리기계산되어갱신된윤곽 ( 여기에서는,1 스텝분 ), 및, 현재의반복수를이미지상에그리기한다. // (6) 결과의표시처리결과를표시한다. "Esc" 키가밀렸을경우는종료해, 그이외가밀렸을경우는, 다음의반복계산에들어간다. 실행결과예 초기위치 iter=10 iter=30 iter=50 302
옵티컬플로우옵티컬플로우란, 시간연속인이미지열을이용하고, 이미지의속도장 ( 물체의속도+카메라의속도 ) 를요구해그것을벡터집합으로표현한것이다. 크게나누고, 구배법, 블록매칭법이존재한다. 구배법에서는, 옵티컬플로우구속방정식 이라고불리는, 휘도의시간 / 공간적미분 ( 휘도구배 ) 의구속방정식을이용하고, 이것에제약조건 ( 을 ) 를부가하는것으로플로우를요구한다. 비교적고속으로전화소에대한속도장을계산할수있지만, 전제조건에맞지않는개곳 ( 급격한휘도변화, 노이즈 ) 에서는, 현저한오차가발생하는것이있다. 블록매칭법에서는, 이미지중이있는블록을템플릿으로서다음시간의이미지중으로부터매치하는곳을탐색하는것으로플로우를요구한다. 적절한특징을가지는이미지에대해서는, 로바스트에플로우를계산할수있지만, 비교적계산시간이걸린다. 또, 회전, 슬캘링에대한로바스트성은, 템플릿이되는블록의사이즈나이미지의특징에의존한다. 샘플 옵티컬플로우 1 cvcalcopticalflowhs, cvcalcopticalflowlk 구배법에따르는옵티컬플로우의계산 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> int main (int argc, char **argv) { int i, j, dx, dy, rows, cols; IplImage *src_img1, *src_img2, *dst_img1, *dst_img2; CvMat *velx, *vely; CvTermCriteria criteria; if (argc!= 3 (src_img1 = cvloadimage (argv[1], CV_LOAD_IMAGE_GRAYSCALE)) == 0 (src_img2 = cvloadimage (argv[2], CV_LOAD_IMAGE_GRAYSCALE)) == 0) return -1; dst_img1 = cvloadimage (argv[2], CV_LOAD_IMAGE_COLOR); dst_img2 = (IplImage *) cvclone (dst_img1); 303
// (1) 속도벡터를격납하는구조체의확보, 등 cols = src_img1->width; rows = src_img1->height; velx = cvcreatemat (rows, cols, CV_32FC1); vely = cvcreatemat (rows, cols, CV_32FC1); cvsetzero (velx); cvsetzero (vely); criteria = cvtermcriteria (CV_TERMCRIT_ITER CV_TERMCRIT_EPS, 64, 0.01); // (2) 옵티컬플로우를계산 (HS) cvcalcopticalflowhs (src_img1, src_img2, 0, velx, vely, 100.0, criteria); // (3) 옵티컬플로우를그리기 (HS) for (i = 0; i < cols; i += 5) { for (j = 0; j < rows; j += 5) { dx = (int) cvgetreal2d (velx, j, i); dy = (int) cvgetreal2d (vely, j, i); cvline (dst_img1, cvpoint (i, j), cvpoint (i + dx, j + dy), CV_RGB (255, 0, 0), 1, CV_AA, 0); // (4) 옵티컬플로우를계산 (LK) cvcalcopticalflowlk (src_img1, src_img2, cvsize (15, 15), velx, vely); // (5) 계산된플로우를그리기 (LK) for (i = 0; i < cols; i += 5) { for (j = 0; j < rows; j += 5) { dx = (int) cvgetreal2d (velx, j, i); dy = (int) cvgetreal2d (vely, j, i); cvline (dst_img2, cvpoint (i, j), cvpoint (i + dx, j + dy), CV_RGB (255, 0, 0), 1, CV_AA, 0); // (6) 옵티컬플로우의표시 cvnamedwindow ("ImageHS", 1); cvshowimage ("ImageHS", dst_img1); cvnamedwindow ("ImageLK", 1); cvshowimage ("ImageLK", dst_img2); cvwaitkey (0); cvdestroywindow ("ImageHS"); cvdestroywindow ("ImageLK"); cvreleaseimage (&src_img1); cvreleaseimage (&src_img2); cvreleaseimage (&dst_img1); 304
cvreleaseimage (&dst_img2); cvreleasemat (&velx); cvreleasemat (&vely); return 0; // (1) 속도벡터를격납하는구조체의확보, 등입력이미지와같은사이즈의 CvArr( 을 ) 를확보한다. 이번은,cvMat( 을 ) 를이용하고있지만, IplImage 등에서도상관없다. 또, 함수 cvcalcopticalflowhs() 그리고이용되는종료조건구조체도확보한다. // (2) 옵티컬플로우를계산 (HS) Horn & Schunck 알고리즘을이용해옵티컬플로우를계산한다. // (3) 옵티컬플로우를그리기 (HS) X 방향,y 방향모두 5[pixel] 마다, 함수 cvgetreal2d()( 을 ) 를이용하고, 인덱스 ( 좌표 ) 를지정하고속도벡터를꺼내, 그것을입력이미지상에그리기한다. // (4) 옵티컬플로우를계산 (LK) Lucas & Kanade 알고리즘을이용해옵티컬플로우를계산한다. // (5) 계산된플로우를그리기 (LK) X 방향,y 방향모두 5[pixel] 마다, 함수 cvgetreal2d()( 을 ) 를이용하고, 인덱스 ( 좌표 ) 를지정하고속도벡터를꺼내, 그것을입력이미지상에그리기한다. // (6) 옵티컬플로우의표시상기의두개의수법에의해계산된옵티컬플로우를, 입력이미지상에그리기한것을실제로표시한다. 무엇인가키가밀릴때까지기다린다. 실행결과예 입력이미지 1 입력이미지 2 결과이미지 (HS) 결과이미지 (LK) 옵티컬플로우 2 cvcalcopticalflowbm 블록매칭에의한옵티컬플로우의계산 표시의변환 샘플코드 305
#include <cv.h> #include <highgui.h> int main (int argc, char **argv) { int i, j, dx, dy, rows, cols; int block_size = 10; int shift_size = 1; CvMat *velx, *vely; CvSize block = cvsize (block_size, block_size); CvSize shift = cvsize (shift_size, shift_size); CvSize max_range = cvsize (50, 50); IplImage *src_img1, *src_img2, *dst_img; if (argc!= 3 (src_img1 = cvloadimage (argv[1], CV_LOAD_IMAGE_GRAYSCALE)) == 0 (src_img2 = cvloadimage (argv[2], CV_LOAD_IMAGE_GRAYSCALE)) == 0) return -1; dst_img = cvloadimage (argv[2], CV_LOAD_IMAGE_COLOR); // (1) 속도벡터를격납하는구조체의확보 rows = int (ceil (double (src_img1->height) / block_size)); cols = int (ceil (double (src_img1->width) / block_size)); velx = cvcreatemat (rows, cols, CV_32FC1); vely = cvcreatemat (rows, cols, CV_32FC1); cvsetzero (velx); cvsetzero (vely); // (2) 옵티컬플로우의계산 cvcalcopticalflowbm (src_img1, src_img2, block, shift, max_range, 0, velx, vely); // (3) 계산된플로우를그리기 for (i = 0; i < velx->width; i++) { for (j = 0; j < vely->height; j++) { dx = (int) cvgetreal2d (velx, j, i); dy = (int) cvgetreal2d (vely, j, i); cvline (dst_img, cvpoint (i * block_size, j * block_size), cvpoint (i * block_size + dx, j * block_size + dy), CV_RGB (255, 0, 0), 1, CV_AA, 0); cvnamedwindow ("Image", 1); cvshowimage ("Image", dst_img); 306
cvwaitkey (0); cvdestroywindow ("Image"); cvreleaseimage (&src_img1); cvreleaseimage (&src_img2); cvreleaseimage (&dst_img); cvreleasemat (&velx); cvreleasemat (&vely); return 0; // (1) 속도벡터를격납하는구조체의확보사이즈 ceil(src.width/block_size) ceil(src.height/block_size), 32 비트, 싱글채널의, 속도벡터를격납하는구조체를확보한다. // (2) 옵티컬플로우의계산블록매칭에의해, 옵티컬플로우를계산한다. 3 번째의인수는, 블록 ( 템플릿 ) 사이즈를나타낸다. 4 번째의인수는,5 번째의인수로나타내지는하나의블록에대한탐색범위내를, 얼마나늦추어져서면서매칭을실시하는지를나타낸다. // (3) 계산된플로우를그리기함수 cvgetreal2d()( 을 ) 를이용하고, 인덱스 ( 좌표 ) 를지정하고속도벡터를꺼내, 그것을입력이미지상에그리기한다. 실행결과예 입력이미지 1 입력이미지 2 결과이미지 307
옵티컬플로우 3 cvcalcopticalflowpyrlk 드문드문한특징에대한옵티컬플로우의계산 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> int main (int argc, char **argv) { char *status; int i, corner_count = 150; CvPoint2D32f *corners1, *corners2; CvTermCriteria criteria; IplImage *src_img1, *src_img2, *dst_img; IplImage *eig_img, *temp_img; IplImage *prev_pyramid, *curr_pyramid; if (argc!= 3 (src_img1 = cvloadimage (argv[1], CV_LOAD_IMAGE_GRAYSCALE)) == 0 (src_img2 = cvloadimage (argv[2], CV_LOAD_IMAGE_GRAYSCALE)) == 0) return -1; dst_img = cvloadimage (argv[2], CV_LOAD_IMAGE_COLOR); // (1) 필요한구조체의확보 eig_img = cvcreateimage (cvgetsize (src_img1), IPL_DEPTH_32F, 1); temp_img = cvcreateimage (cvgetsize (src_img1), IPL_DEPTH_32F, 1); corners1 = (CvPoint2D32f *) cvalloc (corner_count * sizeof (CvPoint2D32f)); corners2 = (CvPoint2D32f *) cvalloc (corner_count * sizeof (CvPoint2D32f)); prev_pyramid = cvcreateimage (cvsize (src_img1->width + 8, src_img1->height / 3), IPL_DEPTH_8U, 1); curr_pyramid = cvcreateimage (cvsize (src_img1->width + 8, src_img1->height / 3), IPL_DEPTH_8U, 1); 308
status = (char *) cvalloc (corner_count); criteria = cvtermcriteria (CV_TERMCRIT_ITER CV_TERMCRIT_EPS, 64, 0.01); // (2) 드문드문한특징점을검출 cvgoodfeaturestotrack (src_img1, eig_img, temp_img, corners1, &corner_count, 0.001, 5, NULL); // (3) 옵티컬플로우를계산 cvcalcopticalflowpyrlk (src_img1, src_img2, prev_pyramid, curr_pyramid, corners1, corners2, corner_count, cvsize (10, 10), 4, status, NULL, criteria, 0); // (4) 계산된플로우를그리기 for (i = 0; i < corner_count; i++) { if (status[i]) cvline (dst_img, cvpointfrom32f (corners1[i]), cvpointfrom32f (corners2[i]), CV_RGB (255, 0, 0), 1, CV_AA, 0); cvnamedwindow ("Image", 1); cvshowimage ("Image", dst_img); cvwaitkey (0); cvdestroywindow ("Image"); cvreleaseimage (&src_img1); cvreleaseimage (&src_img2); cvreleaseimage (&dst_img); cvreleaseimage (&eig_img); cvreleaseimage (&temp_img); cvreleaseimage (&prev_pyramid); cvreleaseimage (&curr_pyramid); return 0; // (1) 필요한구조체의확보함수 cvgoodfeaturestotrack() 및, 함수 cvcalcopticalflowpyrlk() 에필요구조체를확보한다. // (2) 드문드문한특징점을검출함수 cvgoodfeaturestotrack()( 을 ) 를이용하고, 이미지중에서큰고유치를가지는코너 ( 분명히한특징점 )( 을 ) 를찾아낸다. 검출된코너는,4 번째의인수로나타나는배열에격납되어그개수는, 5 번째의인수로나타나는변수에격납된다. 6 번째, 및 7 번째의인수는각각, 특징점의질과특징점끼리의최저거리를나타내고있어이러한값이작아지면검출되는특징점이증가한다. 상세한것에대하여는, 레퍼런스를참조하는것. 309
// (3) 옵티컬플로우를계산앞의함수에의해검출된드문드문한특징점에관해서, 옵티컬플로우를계산한다. // (4) 계산된플로우를그리기플로우가발견된점에대해서만, 그플로우를그리기한다. 실행결과예 입력이미지 1 입력이미지 2 결과이미지 Condensation OpenCV에는, 추정기의하나로서Condensation( 파티클필터 )( 이 ) 가실장되고있다. 레퍼런스메뉴얼에도있도록 ( 듯이 ), 알고리즘의자세한것은, http://www.dai.ed.ac.uk/cvonline/local_copies/isard1/condensation.html ( 을 ) 를참조되고싶다. 여기에서는, 특히OpenCV에실장되고있다Condensation알고리즘의함수의사용방법에대해말한다. 샘플 Condensation Condensation 알고리즘을이용해이미지중의붉은영역을트랙킹한다 표시의변환 샘플코드 #include <cv.h> 310
#include <highgui.h> #include <ctype.h> #include <math.h> // (1) 우도의계산을행하는함수 float calc_likelihood (IplImage * img, int x, int y) { float b, g, r; float dist = 0.0, sigma = 50.0; b = img->imagedata[img->widthstep * y + x * 3]; //B g = img->imagedata[img->widthstep * y + x * 3 + 1]; //G r = img->imagedata[img->widthstep * y + x * 3 + 2]; //R dist = sqrt (b * b + g * g + (255.0 - r) * (255.0 - r)); return 1.0 / (sqrt (2.0 * CV_PI) * sigma) * expf (-dist * dist / (2.0 * sigma * sigma)); int main (int argc, char **argv) { int i, c; double w = 0.0, h = 0.0; CvCapture *capture = 0; IplImage *frame = 0; int n_stat = 4; int n_particle = 4000; CvConDensation *cond = 0; CvMat *lowerbound = 0; CvMat *upperbound = 0; int xx, yy; // (2) 커멘드인수에의해서지정된번호의카메라에대한캡쳐구조체를작성한다 if (argc == 1 (argc == 2 && strlen (argv[1]) == 1 && isdigit (argv[1][0]))) capture = cvcreatecameracapture (argc == 2? argv[1][0] - '0' : 0); // (3)1 프레임캡쳐해, 캐프체사이즈를취득한다. frame = cvqueryframe (capture); w = frame->width; h = frame->height; 311
cvnamedwindow ("Condensation", CV_WINDOW_AUTOSIZE); // (4)Condensation 구조체를작성한다. cond = cvcreatecondensation (n_stat, 0, n_particle); // (5) 상태벡터각차원이취할수있는최소치 최대치를지정한다. lowerbound = cvcreatemat (4, 1, CV_32FC1); upperbound = cvcreatemat (4, 1, CV_32FC1); cvmset (lowerbound, 0, 0, 0.0); cvmset (lowerbound, 1, 0, 0.0); cvmset (lowerbound, 2, 0, -10.0); cvmset (lowerbound, 3, 0, -10.0); cvmset (upperbound, 0, 0, w); cvmset (upperbound, 1, 0, h); cvmset (upperbound, 2, 0, 10.0); cvmset (upperbound, 3, 0, 10.0); // (6)Condensation 구조체를초기화한다 cvcondensinitsampleset (cond, lowerbound, upperbound); // (7)ConDensation 알고리즘에있어서의상태벡터의다이내믹스를지정한다 cond->dynammatr[0] = 1.0; cond->dynammatr[1] = 0.0; cond->dynammatr[2] = 1.0; cond->dynammatr[3] = 0.0; cond->dynammatr[4] = 0.0; cond->dynammatr[5] = 1.0; cond->dynammatr[6] = 0.0; cond->dynammatr[7] = 1.0; cond->dynammatr[8] = 0.0; cond->dynammatr[9] = 0.0; cond->dynammatr[10] = 1.0; cond->dynammatr[11] = 0.0; cond->dynammatr[12] = 0.0; cond->dynammatr[13] = 0.0; cond->dynammatr[14] = 0.0; cond->dynammatr[15] = 1.0; // (8) 노이즈파라미터를재설정한다. cvrandinit (&(cond->rands[0]), -25, 25, 0); cvrandinit (&(cond->rands[1]), -25, 25, 1); cvrandinit (&(cond->rands[2]), -5, 5, 2); cvrandinit (&(cond->rands[3]), -5, 5, 3); while (1) { 312
frame = cvqueryframe (capture); // (9) 각파티클에대해우도를계산한다. for (i = 0; i < n_particle; i++) { xx = (int) (cond->flsamples[i][0]); yy = (int) (cond->flsamples[i][1]); if (xx < 0 xx >= w yy < 0 yy >= h) { cond->flconfidence[i] = 0.0; else { cond->flconfidence[i] = calc_likelihood (frame, xx, yy); cvcircle (frame, cvpoint (xx, yy), 2, CV_RGB (0, 0, 255), -1); cvshowimage ("Condensation", frame); c = cvwaitkey (10); if (c == '\x1b') break; // (10) 다음의모델상태를추정한다 cvcondensupdatebytime (cond); cvdestroywindow ("Condensation"); cvreleaseimage (&frame); cvreleasecapture (&capture); cvreleasecondensation (&cond); cvreleasemat (&lowerbound); cvreleasemat (&upperbound); return 0; // (1) 우도의계산을행하는함수 Condensation 알고리즘에서는, 각파티클의우도를줄필요가있다. 샘플코드에서는, 각파티클의위치의정보로부터, 그픽셀치의빨강인것같음을우도로서이용하는일로했다. 직감적인알기쉬움을위해서 RGB 겉 ( 표 ) 색계인채계산을행한다. 구체적으로는이하와 같이,R(255,0,0)( 으 ) 로부터의 Euclid 거리 에대해서,0( 을 ) 를평균, sigma( 을 ) 를분산 ( 으 ) 로서가지는정규분포를우도함수 ( 으 ) 로서정의하고있다. exp 313
샘플에서는분산 sigma=50.0( 으 ) 로하고있지만, 이파라미터를튜닝하는일에따라서트랙킹성능이바뀌므로, 각자 확인되고싶다. 적색인것같음평가에는, 샘플과같이 RGB 겉 ( 표 ) 색계는아니고 HSV 겉 ( 표 ) 색계로행하는것이밝기의변화에대해서로 바스트인결과를얻을수있다. 그러나, 샘플에서는위에서설명한바와같이알고리즘의직감적인이해를위해서 RGB 겉 ( 표 ) 색계로처리를행하고있다. // (2) 커멘드인수에의해서지정된번호의카메라에대한다캡쳐구조체를작성한다함수 cvcreatecameracapture()( 을 ) 를이용하고, 커멘드인수로서주어진번호의카메라에대한캡쳐구조체 CvCapture( 을 ) 를작성한다. 인수가지정되지않는경우는, 디폴트의값이다 "0" 하지만이용된다. // (3)1 프레임캡쳐해, 캐프체사이즈 ( 을 ) 를취득한다. 함수 cvqueryframe() ( 을 ) 를호출해, 실제로 1 프레이무캐프체를실시해, 취득한이미지로부터, 이미지의폭 w( 와 ) 과높이 h( 을 ) 를취득한다. // (4)Condensation 구조체를작성한다. 함수 cvcreatecondensation() ( 을 ) 를, 상태벡터의차원수 n_stat( 와 ) 과파티클의수 n_particle( 을 ) 를지정해호출해,Condensatopn 구조체를작성한다. 제 2 인수는, 관측벡터수를지정하는일이되어있지만,OpenCV 의현버젼에서는관측벡터는사용하고있지않기때문에, 값에는 0( 을 ) 를지정했으므로상관없다. 샘플코드에서는, 다이내믹스로서이하와같이등속직선운동을가정하고있으므로, 상태벡터는그위치 (x,y)( 와 ) 과각축방향의속도 (u,v) 의 4 차원이된다. // (5) 상태벡터각차원이취할수있는최소치 최대치를지정한다. 벡터 lowerbound, upperbound ( 을 ) 를각각함수 cvcreatemat() 그리고파티션해, 상태벡터의각차원의변수가취할수있는값의하한치 ( 최소치 ) 와상한치 ( 최대치 ) 를설정한다. 이값은, 초기치설정시에는파티클을일모양분포시키기위해서사용된다. 샘플코드에서는, 위치에대해서최소치 0, 최대치는이미지사이즈 (w,h)( 을 ) 를지정, 또속도에대해 -10~10[pixel]( 을 ) 를지정해있다. // (6)Condensation 구조체를초기화한다함수 cvcondensinitsampleset() 그리고, 구조체의초기화, 초기파티클상태의설정을행한다. // (7)ConDensation 알고리즘에있어서의상태벡터의다이내믹스를지정한다 CvConDensation 구조체의멤버 DynamMatr[] 에다이내믹스를표현하는행렬요소를순서로지정한다. 샘플코드에서는,(4) 그리고나타내보인등속직선운동을이하와같이행렬표현했을때의 4X4 의배열을주고있다. 314
모델의다이내믹스로서랜덤워크를가정하는경우는, 여기서단위행렬을지정한다. // (8) 노이즈파라미터를재설정한다. 초기화시에, 상태벡터의각차원의변수에대한노이즈파라미터는자동적으로설정된다 ( 상한치, 하한치각각의범위의 1/5 의값이된다 ) 의로, 그것이무례한경우는여기서재설정한다. 샘플코드에서는, 위치에관해서는 -25~25[pixel], 속도에관해서는 -5~5[pixel] 의범위의노이즈 ( 을 ) 를지정해있다. // (9) 각파티클에대해우도를계산한다. 루프블록중에서함수 cvqueryframe() ( 을 ) 를호출해, 이미지를취득한다. 그후, 각파티클의위치 ( 샘플코드에서는,CvConDensation 구조체의 flsamples[i][0]( 와 ) 과 [i][1]) ( 을 ) 를이용해그우도를 (1) 그리고정의한우도함수를이용해계산한다. 파티클의위치가이미지범위내이면, 푸른환을캡쳐한이미지상에추가해, 이미지를그리기한다. "Esc" 키가밀렸을경우는, 트랙킹을종료한다. // (10) 다음의모델상태를추정한다함수 cvcondensupdatebytime()( 을 ) 를이용하고, 다음의시각의모델상태를추정한다. condensation 알고리즘에있어서의, 리산풀 이동 확산의국면을행하고있다 ( 이 ) 라고생각해도좋다. 실행결과예 CLOSE 물체검출 OpenCV그리고준비되어있는물체검출은, (Haar-like특징을이용한 ) 부스트된약분류기의캐스케이드를이용하고있다. 세세한알고리즘에관해서는, 레퍼런스및그외의참고문헌에양보하지만, 분류기는대략적으로이하와같이구성되어있다. Haar-like 특징을입력으로취하는결정목을, 기본분류기로한다. 부스팅기법을이용하고, 몇개의기본분류기를복합시켜스테이지분류기가구성된다. 스테이지분류기가죽늘어서묶은것에연결되어최종적인캐스케이드가구성된다. 인식시에입력되는이미지는, 캐스케이드의각스테이지에있어평가되어도중에기각되면그부분이미지에는물체가없는, 모든스테이지를패스하면, 그부분이미지는물체를포함하고있다고여겨진다. Haar-like특징은, ( 형상이나국소적인특징은아니고 ) 전체적인아피아랑스를이용하고있으므로, 이특징을이용한만큼류기에도, 검출의특기, 서툼이존재한다. 예를들면, 다소흔들린이미지나희미해진이미지에서도, ( 대략의 texture) 의대국적인특징은변화하지않기때문에, 이것들을검출할수있다. 그러나, 같은종류에대해도전체의 texture의개체차이가크고, 그것이넓게분포하는오브젝트는, 그형상이나국소적인특징이닮고있었다고해도검출하는것은곤란하다. 315
샘플 얼굴의검출 cvhaardetectobjects 미리학습된만큼류기를이용해입력이미지중의얼굴을검출한다 샘플코드 표시의변환 Python 판으로변경 #include <cv.h> #include <highgui.h> int main (int argc, char **argv) { int i; IplImage *src_img = 0, *src_gray = 0; const char *cascade_name = "haarcascade_frontalface_default.xml"; CvHaarClassifierCascade *cascade = 0; CvMemStorage *storage = 0; CvSeq *faces; static CvScalar colors[] = { {{0, 0, 255, {{0, 128, 255, {{0, 255, 255, {{0, 255, 0, {{255, 128, 0, {{255, 255, 0, {{255, 0, 0, {{255, 0, 255 ; // (1) 이미지를읽어들인다 if (argc < 2 (src_img = cvloadimage (argv[1], CV_LOAD_IMAGE_COLOR)) == 0) return -1; src_gray = cvcreateimage (cvgetsize (src_img), IPL_DEPTH_8U, 1); // (2) 부스트된만큼류기의캐스케이드를읽어들인다 cascade = (CvHaarClassifierCascade *) cvload (cascade_name, 0, 0, 0); // (3) 메모리를확보해, 읽어들인이미지의그레이스케일화, 히스토그램의균일화를실시한다 storage = cvcreatememstorage (0); cvclearmemstorage (storage); cvcvtcolor (src_img, src_gray, CV_BGR2GRAY); cvequalizehist (src_gray, src_gray); // (4) 물체 ( 얼굴 ) 검출 faces = cvhaardetectobjects (src_gray, cascade, storage, 1.11, 4, 0, cvsize (40, 40)); 316
// (5) 검출된모든얼굴위치에, 엔을그리기한다 for (i = 0; i < (faces? faces->total : 0); i++) { CvRect *r = (CvRect *) cvgetseqelem (faces, i); CvPoint center; int radius; center.x = cvround (r->x + r->width * 0.5); center.y = cvround (r->y + r->height * 0.5); radius = cvround ((r->width + r->height) * 0.25); cvcircle (src_img, center, radius, colors[i % 8], 3, 8, 0); // (6) 이미지를표시, 키가밀렸을때에종료 cvnamedwindow ("Face Detection", CV_WINDOW_AUTOSIZE); cvshowimage ("Face Detection", src_img); cvwaitkey (0); cvdestroywindow ("Face Detection"); cvreleaseimage (&src_img); cvreleaseimage (&src_gray); cvreleasememstorage (&storage); return 0; // (1) 이미지를읽어들인다함수 cvloadimage()( 을 ) 를이용하고, 얼굴을검출하는대상이되는 ( 커멘드인수로지정된 ) 이미지를읽어들인다. 2 번째의인수에 CV_LOAD_IMAGE_COLOR( 을 ) 를지정하는것으로, 칼라이미지로서읽어들인다. 검출처리자체는그레이스케일이미지를대상에행해지지만, 검출결과를그리기하기위해서, 칼라이미지로서읽어들여둔다. 또, 그처리를위해서, 입력이상과동사이즈의그레이스케일이미지 (IplImage)( 을 ) 를작성해둔다. // (2) 부스트된만큼류기의캐스케이드를읽어들인다학습에의해서미리획득된, 분류기의캐스케이드가기술되었다 xml 파일을읽어들인다. 여기에서는,OpenCV 의샘플에부속되는정면얼굴이미지학습결과의 1 개인,"haarcascade_frontalface_default.xml"( 을 ) 를이용한다. // (3) 메모리를확보해, 읽어들인이미지의그레이스케일화, 히스토그램의균일화를실시한다실제의얼굴검출시에이용하는메모리를확보, 초기화한다. 또, 읽힌칼라이미지를그레이스케일이미지로변경해, 게다가함수 cvequalizehist() 에의해, 그히스토그램을균일화한다. // (4) 물체 ( 얼굴 ) 검출함수 cvhaardetectobjects() 에의해, 이미지로부터얼굴을검출한다. 4 번째의인수 (1.11)( 은 ) 는, 탐색윈도우의스케일변화를나타내고있어이번경우는, 스케일마다의탐색에대하고, 윈드우사이즈가 11[%] 변화하는일을나타내고있다. 또,5 번째의인수는, 오브젝트를구성하는근방구형의최소수를나타내고있어이것보다적은구형으로부터구성되는오브젝트는, 노이즈로서무시된다. 즉, 실제의오브젝트가존재하는부근에서는, 다소어긋난장소에있어도 317
오브젝트를포함한영역으로서인식될것이므로, 1 개의오브젝트는, 복수의구형영역의집합으로서나타내진다. 그렇지않은개소 ( 예를들어, 그주변에단일의구형밖에존재하지않는오브젝트후보 ) 는, 우연히캐스케이드를패스한영역으로서무시된다. // (5) 검출된모든얼굴위치에, 엔을그리기한다검출된모든얼굴에대해서, 사이즈와중심을요구해그자리소에, 엔을그리기한다. 엔의그리기에는, 미리정했다 8 색을차례차례이용한다. // (6) 이미지를표시, 키가밀렸을때에종료검출결과적으로엔이그리기된이미지를표시해, 무엇인가키가밀릴때까지기다린다. 실행결과예 카메라 calibration 카메라 calibration란, ( 어떤시점에있어 ) 카메라고유의내부파라미터와월드좌표계에있어서의위치자세를의미하는외부파라미터를요구하는처리이다. 카메라의 calibration가되면, 있다3차원좌표를가진점이카메라이미지의어디에투영되는지, 혹은복수의카메라에투영된점이3차원공간안의어디에있는지, 등을계산할수있다. 또, 카메라특유의 ( 원주방향및반경방향 ) 일그러짐의보정을실시할수도성과 ( 가, 이것은 calibration 수법으로의존해, 일그러짐을가지지않는모델을이용하는단순한수법도존재한다 ). 적응할수있는 calibration 수법은, 카메라의대수나준비할수있는치구에의해서변화한다. OpenCV의카메라 calibration는,z.zhang의수법을기본으로실장되고있다 (Zhang의수법에대해서는,"A flexible new technique for camera calibration". IEEE Transactions on Patte rn Analysis and Machine Intelligence, 22(11):1330-1334, 2000.( 을 ) 를참조되고싶다 ). Ver.2( 으 ) 로부터 (cvcalibratecam era2등 ) 은,Matlab의 calibration 엔진 ( 이것도Zhang의수법 ) 을이식한것이되었다. 샘플 카메라 calibration cvcalibratecamera2, cvfindextrinsiccameraparams2 Zhang 의수법을이용해카메라의 calibration 를실시해, 결과를파일에보존한다 표시의변환 샘플코드 #include <stdio.h> #include <cv.h> #include <highgui.h> #define IMAGE_NUM (25) /* 이미지수 */ #define PAT_ROW (7) /* 패턴의행수 */ #define PAT_COL (10) /* 패턴의렬수 */ 318
#define PAT_SIZE (PAT_ROW*PAT_COL) #define ALL_POINTS (IMAGE_NUM*PAT_SIZE) #define CHESS_SIZE (24.0) /* 패턴 1 매스의 1 옆사이즈 [mm] */ int main (int argc, char *argv[]) { int i, j, k; int corner_count, found; int p_count[image_num]; IplImage *src_img[image_num]; CvSize pattern_size = cvsize (PAT_COL, PAT_ROW); CvPoint3D32f objects[all_points]; CvPoint2D32f *corners = (CvPoint2D32f *) cvalloc (sizeof (CvPoint2D32f) * ALL_POINTS); CvMat object_points; CvMat image_points; CvMat point_counts; CvMat *intrinsic = cvcreatemat (3, 3, CV_32FC1); CvMat *rotation = cvcreatemat (1, 3, CV_32FC1); CvMat *translation = cvcreatemat (1, 3, CV_32FC1); CvMat *distortion = cvcreatemat (1, 4, CV_32FC1); // (1)calibration 이미지의읽기 for (i = 0; i < IMAGE_NUM; i++) { char buf[32]; sprintf (buf, "calib_img/%02d.png", i); src_img[i] = cvloadimage (buf, CV_LOAD_IMAGE_COLOR); // (2)3 차원공간좌표의설정 for (i = 0; i < IMAGE_NUM; i++) { for (j = 0; j < PAT_ROW; j++) { for (k = 0; k < PAT_COL; k++) { objects[i * PAT_SIZE + j * PAT_COL + k].x = j * CHESS_SIZE; objects[i * PAT_SIZE + j * PAT_COL + k].y = k * CHESS_SIZE; objects[i * PAT_SIZE + j * PAT_COL + k].z = 0.0; cvinitmatheader (&object_points, ALL_POINTS, 3, CV_32FC1, objects); // (3) 체스보드 (calibration 패턴 ) 의코너검출 int found_num = 0; cvnamedwindow ("Calibration", CV_WINDOW_AUTOSIZE); for (i = 0; i < IMAGE_NUM; i++) { found = cvfindchessboardcorners (src_img[i], pattern_size, &corners[i * PAT_SIZE], 319
&corner_count); fprintf (stderr, "%02d...", i); if (found) { fprintf (stderr, "ok\n"); found_num++; else { fprintf (stderr, "fail\n"); // (4) 코너위치를사브픽셀정도에수정, 그리기 IplImage *src_gray = cvcreateimage (cvgetsize (src_img[i]), IPL_DEPTH_8U, 1); cvcvtcolor (src_img[i], src_gray, CV_BGR2GRAY); cvfindcornersubpix (src_gray, &corners[i * PAT_SIZE], corner_count, cvsize (3, 3), cvsize (-1, -1), cvtermcriteria (CV_TERMCRIT_ITER CV_TERMCRIT_EPS, 20, 0.03)); cvdrawchessboardcorners (src_img[i], pattern_size, &corners[i * PAT_SIZE], corner_count, found); p_count[i] = corner_count; cvshowimage ("Calibration", src_img[i]); cvwaitkey (0); cvdestroywindow ("Calibration"); if (found_num!= IMAGE_NUM) return -1; cvinitmatheader (&image_points, ALL_POINTS, 1, CV_32FC2, corners); cvinitmatheader (&point_counts, IMAGE_NUM, 1, CV_32SC1, p_count); // (5) 내부파라미터, 일그러짐계수의추정 cvcalibratecamera2 (&object_points, &image_points, &point_counts, cvsize (640, 480), intrinsic, distortion); // (6) 외부파라미터의추정 CvMat sub_image_points, sub_object_points; int base = 0; cvgetrows (&image_points, &sub_image_points, base * PAT_SIZE, (base + 1) * PAT_SIZE); cvgetrows (&object_points, &sub_object_points, base * PAT_SIZE, (base + 1) * PAT_SIZE); cvfindextrinsiccameraparams2 (&sub_object_points, &sub_image_points, intrinsic, distortion, rotation, translation); // (7)XML 파일에의써내 CvFileStorage *fs; fs = cvopenfilestorage ("camera.xml", 0, CV_STORAGE_WRITE); cvwrite (fs, "intrinsic", intrinsic); cvwrite (fs, "rotation", rotation); cvwrite (fs, "translation", translation); 320
cvwrite (fs, "distortion", distortion); cvreleasefilestorage (&fs); for (i = 0; i < IMAGE_NUM; i++) { cvreleaseimage (&src_img[i]); return 0; // (1)calibration 이미지의읽기 calibration 에필요한이미지열을읽어들인다. 여기에서는,calib_img 이하에미리준비되어있는,"00.png"~"24.png"( 이 ) 라는이름의이미지파일을이용했다. zhang 의 calibration 에필요한이미지매수 ( 뷰의수 ) 는정해져있지않지만, 일반적으로는, 치우침등이없는적절한이미지를수십매정도준비하면충분한정도를얻을수있다. // (2)3 차원공간좌표의설정이번은,1 매스의길이가 24.0[mm], 안쪽의코너수가 7 10 의체스보드패턴을이용했다 ( 실제의체스보드패턴파일 ). 후술하는이유에의해, 코너수는, 홀수 짝수, 혹은짝수 홀수의편성을이용하는것이바람직하다. x 및 y 의값은, 패턴의매스의사이즈에맞추어값을대입한다. 여기서설정되는축방향은, 카메라의외부파라미터에영향을준다. 또,z( 은 ) 는모두 0 또는 1( 으 ) 로설정할필요가있다. // (3) 체스보드 (calibration 패턴 ) 의코너검출함수 cvfindchessboardcorners() 에의해, 체스보드의코너를검출한다. 코너는좌하로부터우상을향해서검출되어제 2 인수로설정된사이즈의코너가모두검출되었을경우에는,0 이외의값을돌려준다. 덧붙여서, 본프로그램과는너무관계없지만,2 매이상의 calibration 패턴을인식시키고싶은경우는, ( 종횡의구형수가 ) 다른타입의 calibration 패턴을준비하는, 혹은, 하나의패턴을검출할때마다거기를전부칠해재탐색하는, 등의수법을생각할수있다. OpenCV 의함수 cvfindchessboardcorners() 하지만검출하는코너의차례는, 패턴의행과열의정의에의존한다. 이하의이미지체스패턴을, 샘플프로그램과같이 7 10(7 행 10 열 ) 이라고정의하면, 검출코너의최초, 즉 calibration 결과의월드좌표원점은, 이미지의 (A) 의장소가된다. 또, 같은이미지를 10 7(10 행 7 열 ) 이라고정의하면, 원점은 (B) 된다. 함수 cvfindchessboardcorners()( 은 ) 는, 체스패턴의코너를검출하기위해서구형의검출을실시하지만, 구형 ( 흑색 ) 에비스듬하게인접하는구형의수가 1 개의것이있으면 ( 이미지중의좌상과우상 ), 그구형이포함한코너의최초의점으로한다. 그것이존재하지않는경우는, 기울기인접구형수가 2 개의것을이용한다. 코너수가, 홀수 짝수, 혹은짝수 홀수의편성의체스패턴이라면, 그최초의코너가일의로결정되지만, 그렇지않은경우는, 코너가일의로결정되지않고, 체스패턴의이미지에따라서는검출되는코너의차례가달라버릴가능성이있다. 321
// (4) 코너위치를사브픽셀정도에수정, 그리기함수 cvfindchessboardcorners() 냄새나도,0.5[pixel] 단위로코너가검출되지만, 한층더검출된코너를사브픽셀정도로수정한다. 또, 그러한코너를이미지상에그리기해, 실제로표시한다. 모든코너가올바르게검출되었을경우는내부에서정의되었다 7 색으로그리기되어그렇지않은경우는모든코너가적색으로그리기된다. 이샘플코드에서는, 모든이미지 (25 매 ) 로코너가올바르게검출되지않는경우는, 프로그램이종료한다. // (5) 내부파라미터, 일그러짐계수의추정함수 cvcalibratecamera2() 에의해, 카메라의내부파라미터, 및일그러짐계수를추정한다. 내부파라미터는, 이하의식의 A 에해당한다. // (6) 외부파라미터의추정카메라의외부파라미터를추정한다. 이것은, 상술의식의 [R t] 에해당한다. 즉, 삼차원이있는월드좌표계원점으로부터, 카메라좌표계에의변환을나타내는파라미터이다. 월드좌표계원점으로부터의회전및병진을나타내는외부파라미터를결정하기위해서는, 기준이되는월드좌표계원점이필요하다. 여기에서는,base=0( 으 ) 로서 0 번째의이미지 ("00.png")( 을 ) 를지정하고있어, 이이미지에비치는패턴의 3 차원좌표계 ((2) 그리고주어진 ) 에있어서의외부파라미터가추정된다. 이때의월드좌표계의원점은,(3) 의항으로설명한것처럼결정된다. 이샘플프로그램에서는, 실행결과예에있는이미지의적색의행의최초의코너가원점이된다. // (7)XML 파일에의써내구할수있던, 내부파라미터, 외부파라미터, 일그러짐계수를파일에써낸다. 여기에서는, 확장자 (extension) 에의해 XML 형식을지정해있지만,YAML 형식에서출력하는것도가능하다. 실행결과예 카메라 calibration 에이용한이미지의일부 ( 무순서 ) 의인식결과. 실제의 calibration 결과파일 (XML). 이 calibration 결과는,DFW-VL500(sony) 에광각렌즈 VCL-00637S(sony)( 을 ) 를단것으로갔다. 일그러짐보정 cvundistort2 calibration 데이터를이용하고, 일그러짐을보정한다 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> 322
int main (int argc, char *argv[]) { IplImage *src_img, *dst_img; CvMat *intrinsic, *distortion; CvFileStorage *fs; CvFileNode *param; // (1) 보정대상이되는이미지의읽기 if (argc < 2 (src_img = cvloadimage (argv[1], CV_LOAD_IMAGE_COLOR)) == 0) return -1; dst_img = cvcloneimage (src_img); // (2) 파라미터파일의읽기 fs = cvopenfilestorage ("camera.xml", 0, CV_STORAGE_READ); param = cvgetfilenodebyname (fs, NULL, "intrinsic"); intrinsic = (CvMat *) cvread (fs, param); param = cvgetfilenodebyname (fs, NULL, "distortion"); distortion = (CvMat *) cvread (fs, param); cvreleasefilestorage (&fs); // (3) 일그러짐보정 cvundistort2 (src_img, dst_img, intrinsic, distortion); // (4) 이미지를표시, 키가밀렸을때에종료 cvnamedwindow ("Distortion", CV_WINDOW_AUTOSIZE); cvshowimage ("Distortion", src_img); cvnamedwindow ("UnDistortion", CV_WINDOW_AUTOSIZE); cvshowimage ("UnDistortion", dst_img); cvwaitkey (0); cvdestroywindow ("Distortion"); cvdestroywindow ("UnDistortion"); cvreleaseimage (&src_img); cvreleaseimage (&dst_img); cvreleasemat (&intrinsic); cvreleasemat (&distortion); return 0; // (1) 보정대상이되는이미지의읽기보정을실시하는이미지를읽어들인다. 물론, 보정대상이되는이미지를촬영한카메라는, calibration 가끝난상태이다고한다. 323
// (2) 파라미터파일의읽기 calibration 결과를격납한파라미터파일을읽어들인다. 일그러짐보정에필요한파라미터는, 내부파라미터, 및일그러짐계수뿐이므로, 이것을파일로부터읽어들인다. // (3) 일그러짐보정함수 cvundisotort2() 에의해, 입력이미지의일그러짐보정을행한다. // (4) 이미지를표시, 키가밀렸을때에종료일그러짐보정전이미지와비뚤어져보정후이미지를표시해, 무엇인가키가밀릴때까지기다린다. 실행결과예 [ 좌 ] 일그러짐보정전. [ 우 ] 일그러짐보정후. 맵을이용한일그러짐보정 cvinitundistortmap calibration 데이터를이용해맵을작성해, 일그러짐을보정한다 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> int main (int argc, char *argv[]) { IplImage *src_img, *dst_img; CvMat *intrinsic, *distortion; CvFileStorage *fs; CvFileNode *param; IplImage *mapx = cvcreateimage (cvsize (640, 480), IPL_DEPTH_32F, 1); IplImage *mapy = cvcreateimage (cvsize (640, 480), IPL_DEPTH_32F, 1); if (argc < 2 (src_img = cvloadimage (argv[1], CV_LOAD_IMAGE_COLOR)) == 0) return -1; dst_img = cvcloneimage (src_img); fs = cvopenfilestorage ("camera.xml", 0, CV_STORAGE_READ); param = cvgetfilenodebyname (fs, NULL, "intrinsic"); 324
intrinsic = (CvMat *) cvread (fs, param); param = cvgetfilenodebyname (fs, NULL, "distortion"); distortion = (CvMat *) cvread (fs, param); cvreleasefilestorage (&fs); // (1) 일그러짐보정을위한맵초기화 cvinitundistortmap (intrinsic, distortion, mapx, mapy); // (2) 일그러짐보정 cvremap (src_img, dst_img, mapx, mapy); cvnamedwindow ("Distortion", CV_WINDOW_AUTOSIZE); cvshowimage ("Distortion", src_img); cvnamedwindow ("UnDistortion", CV_WINDOW_AUTOSIZE); cvshowimage ("UnDistortion", dst_img); cvwaitkey (0); cvdestroywindow ("Distortion"); cvdestroywindow ("UnDistortion"); cvreleaseimage (&src_img); cvreleaseimage (&dst_img); cvreleaseimage (&mapx); cvreleaseimage (&mapy); cvreleasemat (&intrinsic); cvreleasemat (&distortion); return 0; // (1) 일그러짐보정을위한맵초기화함수 cvinitundistortmap()( 을 ) 를이용하고, 일그러짐을보정하기위한맵 mapx,mapy 의초기화를실시한다. // (2) 일그러짐보정작성된맵을이용하고, 이미지의기하변환 (cvremap)( 을 ) 를실시한다. 함수 cvundistort2()( 을 ) 를이용하는경우와비교해서, 계산속도가향상한다. 그러나, 일반적인기하변환을실시하는함수를이용하고있기때문에인가, 이전의버젼으로실장되고있던일그러짐보정함수에비해퍼포먼스가뒤떨어진다. 물론, 이전의함수 cvundistortinit,cvundistort 도 cvcompat.h 안에서선언되고있지만, 장래적으로폐지될예정이므로이용은추천되지않는다 ( 또코멘트에의하면, 이러한함수는,quite hackerish implementations 이다 ). 단순하게일그러짐보정의속도만을요구한다면, Yahoo!Groups 의투고 (Speeding up undistort) 에도있도록 ( 듯이 ), 스스로 LUT( 을 ) 를작성하면좋다. 그경우는, 이미지의보완을별도실시할필요가있다. 실행결과예 [ 좌 ] 일그러짐보정전. [ 우 ] 일그러짐보정후. 325
서포트벡터머신 OpenCV-1.0.0그리고제공된다SVM에관한함수는,libsvm라이브러리(version 2.6) 의기능을실장한것이다. 또, 이버젼에서는, 학습후의파라미터를보존, 읽는함수 (save, load) 에버그가존재해,Yahoo!Groups그리고보고되고있는패치 (http://tech.groups.yahoo.com/group/opencv/message/48635 ) 등을수신자명차면, 해당기능을이용할수없기때문에주의하는것. 샘플 서포트벡터머신 CvSVM SVM( 을 ) 를이용해 2 차원벡터의 3 클래스분류문제를푼다 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> #include <ml.h> #include <time.h> int main (int argc, char **argv) { const int s = 1000; int size = 400; int i, j, sv_num; IplImage *img; CvSVM svm = CvSVM (); CvSVMParams param; CvTermCriteria criteria; CvRNG rng = cvrng (time (NULL)); CvPoint pts[s]; float data[s * 2]; int res[s]; CvMat data_mat, res_mat; CvScalar rcolor; const float *support; // (1) 이미지영역의확보와초기화 img = cvcreateimage (cvsize (size, size), IPL_DEPTH_8U, 3); 326
cvzero (img); // (2) 학습데이터의생성 for (i = 0; i < s; i++) { pts[i].x = cvrandint (&rng) % size; pts[i].y = cvrandint (&rng) % size; if (pts[i].y > 50 * cos (pts[i].x * CV_PI / 100) + 200) { cvline (img, cvpoint (pts[i].x - 2, pts[i].y - 2), cvpoint (pts[i].x + 2, pts[i].y + 2), CV_RGB (255, 0, 0)); cvline (img, cvpoint (pts[i].x + 2, pts[i].y - 2), cvpoint (pts[i].x - 2, pts[i].y + 2), CV_RGB (255, 0, 0)); res[i] = 1; else { if (pts[i].x > 200) { cvline (img, cvpoint (pts[i].x - 2, pts[i].y - 2), cvpoint (pts[i].x + 2, pts[i].y + 2), CV_RGB (0, 255, 0)); cvline (img, cvpoint (pts[i].x + 2, pts[i].y - 2), cvpoint (pts[i].x - 2, pts[i].y + 2), CV_RGB (0, 255, 0)); res[i] = 2; else { cvline (img, cvpoint (pts[i].x - 2, pts[i].y - 2), cvpoint (pts[i].x + 2, pts[i].y + 2), CV_RGB (0, 0, 255)); cvline (img, cvpoint (pts[i].x + 2, pts[i].y - 2), cvpoint (pts[i].x - 2, pts[i].y + 2), CV_RGB (0, 0, 255)); res[i] = 3; // (3) 학습데이터의표시 cvnamedwindow ("SVM", CV_WINDOW_AUTOSIZE); cvshowimage ("SVM", img); cvwaitkey (0); // (4) 학습파라미터의생성 for (i = 0; i < s; i++) { data[i * 2] = float (pts[i].x) / size; data[i * 2 + 1] = float (pts[i].y) / size; cvinitmatheader (&data_mat, s, 2, CV_32FC1, data); cvinitmatheader (&res_mat, s, 1, CV_32SC1, res); criteria = cvtermcriteria (CV_TERMCRIT_EPS, 1000, FLT_EPSILON); param = CvSVMParams (CvSVM::C_SVC, CvSVM::RBF, 10.0, 8.0, 1.0, 10.0, 0.5, 0.1, NULL, criteria); 327
// (5)SVM 의학습 svm.train (&data_mat, &res_mat, NULL, NULL, param); // (6) 학습결과의그리기 for (i = 0; i < size; i++) { for (j = 0; j < size; j++) { CvMat m; float ret = 0.0; float a[] = { float (j) / size, float (i) / size ; cvinitmatheader (&m, 1, 2, CV_32FC1, a); ret = svm.predict (&m); switch ((int) ret) { case 1: rcolor = CV_RGB (100, 0, 0); break; case 2: rcolor = CV_RGB (0, 100, 0); break; case 3: rcolor = CV_RGB (0, 0, 100); break; cvset2d (img, i, j, rcolor); // (7) 트레이닝데이터의재그리기 for (i = 0; i < s; i++) { CvScalar rcolor; switch (res[i]) { case 1: rcolor = CV_RGB (255, 0, 0); break; case 2: rcolor = CV_RGB (0, 255, 0); break; case 3: rcolor = CV_RGB (0, 0, 255); break; cvline (img, cvpoint (pts[i].x - 2, pts[i].y - 2), cvpoint (pts[i].x + 2, pts[i].y + 2), rcolor); cvline (img, cvpoint (pts[i].x + 2, pts[i].y - 2), cvpoint (pts[i].x - 2, pts[i].y + 2), rcolor); 328
// (8) 서포트벡터의그리기 sv_num = svm.get_support_vector_count (); for (i = 0; i < sv_num; i++) { support = svm.get_support_vector (i); cvcircle (img, cvpoint ((int) (support[0] * size), (int) (support[1] * size)), 5, CV_RGB (200, 200, 200)); // (9) 이미지의표시 cvnamedwindow ("SVM", CV_WINDOW_AUTOSIZE); cvshowimage ("SVM", img); cvwaitkey (0); cvdestroywindow ("SVM"); cvreleaseimage (&img); return 0; // (1) 이미지영역의확보와초기화이미지영역을확보해, 제로클리어 ( 흑색으로초기화 ) 한다. // (2) 트레이닝데이터의생성 2 차원의트레이닝데이터를랜덤에생성해, 그값을 CvPoint 형태의배열 pts[] 에격납한다. 또, 각트레이닝데이터의클래스를반응을일으키는최소의물리량에의해서결정해, 배열 res[] 에격납한다. // (3) 트레이닝데이터의표시생성된트레이닝데이터를, 최초로확보한이미지상에그리기해, 표시한다. 클래스 1-3 까지가, 적, 록, 청의각색으로나타난다. 여기서, 무엇인가키가밀릴때까지기다린다. // (4) 학습파라미터의생성 SVM 의학습파라미터를이하와같이결정한다. svm 의종류 :CvSVM::C_SVC 커넬의종류 :CvSVM::RBF degree:10.0( 이번은, 이용되지않는다 ) gamma:8.0 coef0:1.0( 이번은, 이용되지않는다 ) C:10.0 nu:0.5( 이번은, 이용되지않는다 ) p:0.1( 이번은, 이용되지않는다 ) 또, 트레이닝데이터를정규화해,CvMat 형태의행렬에격납한다. // (5)SVM 의학습트레이닝데이터와결정할수있던학습파라미터를이용하고,SVM 의학습을행한다. 329
// (6) 학습결과의그리기학습결과를나타내기위해서, 이미지영역내의모든픽셀 ( 특징벡터 ) 을입력으로서클래스분류를실시한다. 또, 입력픽셀을, 그것이속하는클래스에대응한색으로그리기한다. // (7) 트레이닝데이터의재그리기트레이닝데이터를, 결과이미지우에에겹쳐그리기한다. // (8) 서포트벡터의그리기트레이닝데이터가운데, 서포트벡터를흰동그라미로둘러싸표시한다. // (9) 이미지의표시실제로, 처리결과의이미지를표시해, 무엇인가키가밀리면종료한다. 실행결과예 [ 좌 ] 학습샘플,[ 우 ] 분류결과 이미지의각픽셀치를특징벡터로했다 SVM 의학습 학습용의이미지를읽어들여, 그픽셀치를특징벡터로서 SVM 의학습을실시한다 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> #include <ml.h> #include <stdio.h> int main (int argc, char **argv) { int i, j, ii, jj; int width = 28, height = 30; /* 샘플이미지사이즈 */ int image_dim = width * height; int pimage_num = 500; /* 포지티브샘플수 */ int nimage_num = 1000; /* 네가티브샘플수 */ int all_image_num = pimage_num + nimage_num; IplImage *img_org; 330
IplImage *sample_img; int res[all_image_num]; float data[all_image_num * image_dim]; CvMat data_mat, res_mat; CvTermCriteria criteria; CvSVM svm = CvSVM (); CvSVMParams param; char filename[64]; // (1) 포지티브샘플의읽기 for (i = 0; i < pimage_num; i++) { sprintf (filename, "positive/%03d.png", i); img_org = cvloadimage (filename, CV_LOAD_IMAGE_GRAYSCALE); sample_img = cvcreateimage (cvsize (width, height), IPL_DEPTH_8U, 1); cvresize (img_org, sample_img); cvsmooth (sample_img, sample_img, CV_GAUSSIAN, 3, 0, 0, 0); for (ii = 0; ii < height; ii++) { for (jj = 0; jj < width; jj++) { data[i * image_dim + (ii * width) + jj] = float ((int) ((uchar) (sample_img->imagedata[ii * sample_img->widthstep + jj])) / 255.0); res[i] = 1; // (2) 네가티브샘플의읽기 j = i; for (i = j; i < j + nimage_num; i++) { sprintf (filename, "negative/%03d.jpg", i - j); img_org = cvloadimage (filename, CV_LOAD_IMAGE_GRAYSCALE); sample_img = cvcreateimage (cvsize (width, height), IPL_DEPTH_8U, 1); cvresize (img_org, sample_img); cvsmooth (sample_img, sample_img, CV_GAUSSIAN, 3, 0, 0, 0); for (ii = 0; ii < height; ii++) { for (jj = 0; jj < width; jj++) { data[i * image_dim + (ii * width) + jj] = float ((int) ((uchar) (sample_img->imagedata[ii * sample_img->widthstep + jj])) / 255.0); res[i] = 0; // (3)SVM 학습데이터와파라미터의초기화 cvinitmatheader (&data_mat, all_image_num, image_dim, CV_32FC1, data); 331
cvinitmatheader (&res_mat, all_image_num, 1, CV_32SC1, res); criteria = cvtermcriteria (CV_TERMCRIT_EPS, 1000, FLT_EPSILON); param = CvSVMParams (CvSVM::C_SVC, CvSVM::RBF, 10.0, 0.09, 1.0, 10.0, 0.5, 1.0, NULL, criteria); // (4)SVM 의학습과데이터의보존 svm.train (&data_mat, &res_mat, NULL, NULL, param); svm.save ("svm_image.xml"); cvreleaseimage (&img_org); cvreleaseimage (&sample_img); return 0; // (1) 포지티브샘플의읽기포지티브샘플이되는이미지군을읽어들여, 각픽셀의값을 float 형태의배열로변환한다. 여기에서는간단을위해서, 포지티브샘플 ("positive/ 디렉토리이하에있다 ")( 은 ) 는, 3 자리수연번의파일명의이미지로서미리준비되어있다고한다. 우선, 읽어들인각이미지를동일한사이즈 ( 여기에서는,28 30) 에리사이즈해, 노이즈의영향을경감하기위해서스무딩을실시한다. 다음에, 그이미지의각픽셀의휘도치 ( 여기서, 이미지는그레이스케일이미지로서읽히고있다 ) 를특징벡터로서이용하기위해서, 배열로변환한다. 즉,1 개의이미지에대한특징벡터는, 이미지의높이 이미지의폭, 이되어, 그것이샘플이미지의매수분만큼준비된다. 판별치로서 "1"( 을 ) 를이용하고있다. 또, 포지티브샘플로서 500 매의얼굴이미지 ( 거의정면, 기울기없음 ) 을이용하고있다. OpenCV 에는,haar-like 특징을이용한물체검출수법이실장되고있어그쪽을이용한얼굴검출이정도도처리속도도좋기때문에, 얼굴이미지를검출하는의미는별로없지만, 샘플의입수의하기쉬움이라고알기쉬움으로부터이번은얼굴이미지를이용한학습을실시했다. // (2) 네가티브샘플의읽기네가티브샘플이되는이미지군을읽어들여, 포지티브샘플과같게배열로변환한다. 판별치로서 "0"( 을 ) 를이용하고있다. 또, 네가티브샘플로서 1000 매의임의이미지 ( 얼굴이외의이미지 ) 을이용하고있다. // (3)SVM 학습데이터와파라미터의초기화샘플이미지의픽셀치의배열과판별치의배열을, 행렬로변환한다. 또,SVM 의학습을위한파라미터를초기화한다. 여기에서는, 꽤적당하게파라미터를지정해있으므로, 필요에따라서적절한파라미터를설정할필요가있다. // (4)SVM 의학습과데이터의보존포지티브, 네가티브샘플의픽셀치, 및지정된파라미터를이용하고,svm.train() 메소드에의해 SVM 의학습을실시한다. 샘플수는, 포지티브 500, 네가티브 1000, 특징벡터는,28 30=840 차원이다. 또, 학습되었다 SVM 의파라미터를,svm.save() 메소드에의해 XML 형식의파일로서보존한다. 이페이지의최초로도말한것처럼,save 및 load 의기능을이용하기위해서는,OpenCV 의소스를수정할필요가있다. 332
실행결과예 svm_image_xml.zip 하지만, 실제로보존되는파라미터의일례이다. 여기에서는,XML 형식을이용했지만,YAML 형식에서 보존하는것도가능하다. 이미지의각픽셀치를특징벡터로했다 SVM 에의한물체검출 학습되었다 SVM 파라미터를읽어들여, 주어진이미지중으로부터물체를검출한다 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> #include <ml.h> #include <stdio.h> int main (int argc, char **argv) { int i, j; int width = 28, height = 30; /* 샘플이미지사이즈 */ int image_dim = width * height; CvMat m; float a[image_dim]; float ret = -1.0; float scale; IplImage *src, *src_color, *src_tmp; int sx, sy, tw, th; int stepx = 3, stepy = 3; double steps = 1.2; int iterate; CvSVM svm = CvSVM (); // (1) 이미지의읽기 if (argc < 2 (src = cvloadimage (argv[1], CV_LOAD_IMAGE_GRAYSCALE)) == 0 (src_color = cvloadimage (argv[1], CV_LOAD_IMAGE_COLOR)) == 0) { return -1; 333
// (2)SVM 데이터의읽기 svm.load ("svm_image.xml"); /* 읽어들인이미지를부분이미지마다처리 */ cvinitmatheader (&m, 1, image_dim, CV_32FC1, NULL); tw = src->width; th = src->height; for (iterate = 0; iterate < 1; iterate++) { // (3) 이미지를축소해, 현재의부분이미지를행렬에변경 src_tmp = cvcreateimage (cvsize ((int) (tw / steps), (int) (th / steps)), IPL_DEPTH_8U, 1); cvresize (src, src_tmp); tw = src_tmp->width; th = src_tmp->height; for (sy = 0; sy <= src_tmp->height - height; sy += stepy) { for (sx = 0; sx <= src_tmp->width - width; sx += stepx) { for (i = 0; i < height; i++) { for (j = 0; j < width; j++) { a[i * width + j] = float ((int) ((uchar) (src_tmp->imagedata[(i + sy) * src_tmp->widthstep + (j + sx)])) / 255.0); cvsetdata (&m, a, sizeof (float) * image_dim); // (4)SVM 에의한판정과결과의그리기 ret = svm.predict (&m); if ((int) ret == 1) { scale = (float) src->width / tw; cvrectangle (src_color, cvpoint ((int) (sx * scale), (int) (sy * scale)), cvpoint ((int) ((sx + width) * scale), (int) ((sy + height) * scale)), CV_RGB (255, 0, 0), 2); cvreleaseimage (&src_tmp); // (5) 검출결과이미지의표시 cvnamedwindow ("svm_predict", CV_WINDOW_AUTOSIZE); cvshowimage ("svm_predict", src_color); cvwaitkey (0); cvdestroywindow ("svm_predict"); 334
cvreleaseimage (&src); cvreleaseimage (&src_color); cvreleaseimage (&src_tmp); return 0; // (1) 이미지의읽기 SVM 에의한판별의대상이되는이미지를, 커멘드인수로지정된파일로부터읽어들인다. 처리는그레이스케일이미지에대해서행해지지만, 마지막결과표시를위해서칼라이미지도별도준비해둔다. // (2)SVM 데이터의읽기미리학습되었다 SVM 의파라미터를,svm.load() 메소드에의해파일 ( 여기에서는,"xvm_image.xml")( 으 ) 로부터읽어들인다. 이페이지의최초로도말한것처럼,save 및 load 의기능을이용하기위해서는,OpenCV 의소스를수정할필요가있다. // (3) 이미지를축소해, 현재의부분이미지를행렬에변경읽힌이미지를부분이미지마다처리하기위해서,stepx=3, stepy=3 픽셀마다,width height=28 30 의사이즈의부분이미지의픽셀치를배열에대입한다. 또,SVM( 을 ) 를적용하기위해서, 함수 cvsetdata() 에의해, 그배열을 1 (28 30) 의행렬의데이터로서세트한다. // (4)SVM 에의한판정과결과의그리기 svm.predict() 메소드에의해, 행렬에변환된부분이미지가, 어느클래스에속하는지를판별한다. 여기에서는, 전술의학습파라미터파일을이용하고있으므로, 처리대상이되는부분이미지가그학습된물체 ( 얼굴 ) 인지아닌지를판별하고있다. 얼굴이라면판별된부분이미지는, 붉은구형으로표시된다. 파라미터의최적화나, 특별한처리상의궁리 ( 처리영역의선정이나특징벡터의추출등 ) 를하고있는것도없기때문에, 검출정도는높지않다. 게다가이미지전체에대해서처리를행하는경우는, 특징벡터가큰일도있어, 상당한처리시간이필요하다. 또, 이번은,iterate 변수에의한루프를 1 번밖에가서않지만, 이미지중으로부터다른크기의물체를검출하고싶은경우에는, 원이미지를수시축소 ( 여기에서는,1/steps=1/1.2) 해같은처리를실시한다. // (5) 검출결과이미지의표시검출결과적으로붉은구형이그리기된이미지를표시해, 무엇인가키가밀릴때까지기다린다. 실행결과예 위제트 ( 컨트롤 ) OpenCV-1.0.0 그리고이용할수있는위제트 ( 컨트롤 ) 는, 이미지를표시하기위한윈도우, 및트럭바뿐이다. 복잡한 G 335
UI디자인이나이벤트를적절히읽어날린다고하는처리가어렵고, 카메라캡쳐냄새나도해상도의변경이나 frame r ate의설정등에난점하지만있으므로, 간단한테스트프로그램이외에서는, 다른라이브러리를이용하는편이무난하다. 샘플 트럭바의이용 cvcreatetrackbar, cvgettrackbarpos, cvsettrackbarpos 트럭바의작성, 그위치의취득과설정 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> #include <stdio.h> /* 글로벌변수 */ CvFont font; IplImage *img = 0; /* 프로토타입선언 */ void on_trackbar1 (int val); void on_trackbar2 (int val); int main (int argc, char *argv[]) { // (1) 이미지영역을확보해, 초기화한다 img = cvcreateimage (cvsize (400, 200), IPL_DEPTH_8U, 3); cvzero (img); cvinitfont (&font, CV_FONT_HERSHEY_DUPLEX, 1.0, 1.0); // (2) 윈도우, 및트럭바를작성한다 cvnamedwindow ("Image", CV_WINDOW_AUTOSIZE); cvcreatetrackbar ("Trackbar1", "Image", 0, 100, on_trackbar1); cvcreatetrackbar ("Trackbar2", "Image", 0, 100, on_trackbar2); cvshowimage ("Image", img); cvwaitkey (0); cvdestroywindow ("Image"); cvreleaseimage (&img); return 0; 336
/* 콜백함수 */ void on_trackbar1 (int val) { char str[64]; // (3) 트럭바 2( 을 ) 를, 트럭바 1 에동기시킨다 cvsettrackbarpos ("Trackbar2", "Image", val); // (4) 트럭바 1 의값을그리기한다 cvrectangle (img, cvpoint (0, 0), cvpoint (400, 50), cvscalar (0), CV_FILLED); snprintf (str, 64, "%d", val); cvputtext (img, str, cvpoint (15, 30), &font, CV_RGB (0, 200, 100)); cvshowimage ("Image", img); void on_trackbar2 (int val) { char str[64]; int pos1, pos2; // (5) 트럭바 2 의이동범위를, 트럭바 1 의값 ±20( 으 ) 로한정한다 pos1 = cvgettrackbarpos ("Trackbar1", "Image"); if (pos1 > val) pos2 = pos1-20 < val? val : pos1-20; else pos2 = pos1 + 20 > val? val : pos1 + 20; cvsettrackbarpos ("Trackbar2", "Image", pos2); // (6) 트럭바 2 의값을그리기한다 cvrectangle (img, cvpoint (0, 50), cvpoint (400, 100), cvscalar (0), CV_FILLED); snprintf (str, 64, "%d", val); cvputtext (img, str, cvpoint (15, 80), &font, CV_RGB (200, 200, 0)); cvshowimage ("Image", img); // (1) 이미지영역을확보해, 초기화한다이미지영역을확보해, 초기화한다. 또, 후에결과의문자열을그리기하기위한폰트구조체를초기화한다. // (2) 윈도우, 및트럭바를작성한다윈도우를작성후, 함수 cvcreatetrackbar() 에의해, 윈도우상에트럭바를추가한다. 트럭바작성시에, 트럭바이벤트에대한콜백함수 (on_trackbar1,on_trackbar2)( 을 ) 를설정한다. 337
// (3) 트럭바 2( 을 ) 를, 트럭바 1 에동기시킨다함수 cvsettrackbarpos()( 을 ) 를이용하고, 트럭바 2 의값을, 트럭바 1 의현재의값과동기시킨다. // (4) 트럭바 1 의값을그리기한다트럭바 1 의현재의값을, 이미지영역상부에그리기한다. // (5) 트럭바 2 의이동범위를, 트럭바 1 의값 ±20( 으 ) 로한정한다함수 cvgettrackbarpos()( 을 ) 를이용하고, 트럭바 1 의현재의값을취득해, 그값의 ±20 의범위내에서트럭바 2 하지만움직이도록 ( 듯이 ) 설정한다. // (6) 트럭바 2 의값을그리기한다트럭바 2 의현재의값을, 이미지영역하부에그리기한다. 실행결과예 이벤트 OpenCV 그럼, 윈도우상에서발생한마우스이벤트, 및윈도우가액티브한상태로밀린키코드를취득할수있다. 샘플 마우스이벤트의취득 cvsetmousecallback 마우스이벤트를취득하고, 윈도우에표시한다 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> #include <stdio.h> /* 글로벌변수 */ CvFont font; IplImage *img = 0; /* 프로토타입선언 */ void on_mouse (int event, int x, int y, int flags, void *param); int main (int argc, char *argv[]) { int c; 338
// (1) 이미지영역을확보해, 초기화한다 img = cvcreateimage (cvsize (640, 480), IPL_DEPTH_8U, 3); cvzero (img); cvinitfont (&font, CV_FONT_HERSHEY_DUPLEX, 0.4, 0.4); // (2) 윈도우를작성해, 마우스이벤트에대한콜백함수를등록한다 cvnamedwindow ("Image", CV_WINDOW_AUTOSIZE); cvsetmousecallback ("Image", on_mouse); cvshowimage ("Image", img); // (3)'Esc' 키가밀렸을경우에종료한다 while (1) { c = cvwaitkey (0); if (c == '\x1b') return 1; cvdestroywindow ("Image"); cvreleaseimage (&img); return 0; /* 콜백함수 */ void on_mouse (int event, int x, int y, int flags, void *param = NULL) { char str[64]; static int line = 0; const int max_line = 15, w = 15, h = 30; // (4) 마우스이벤트를취득 switch (event) { case CV_EVENT_MOUSEMOVE: snprintf (str, 64, "(%d,%d) %s", x, y, "MOUSE_MOVE"); break; case CV_EVENT_LBUTTONDOWN: snprintf (str, 64, "(%d,%d) %s", x, y, "LBUTTON_DOWN"); break; case CV_EVENT_RBUTTONDOWN: snprintf (str, 64, "(%d,%d) %s", x, y, "RBUTTON_DOWN"); break; case CV_EVENT_MBUTTONDOWN: snprintf (str, 64, "(%d,%d) %s", x, y, "MBUTTON_DOWN"); break; 339
case CV_EVENT_LBUTTONUP: snprintf (str, 64, "(%d,%d) %s", x, y, "LBUTTON_UP"); break; case CV_EVENT_RBUTTONUP: snprintf (str, 64, "(%d,%d) %s", x, y, "RBUTTON_UP"); break; case CV_EVENT_MBUTTONUP: snprintf (str, 64, "(%d,%d) %s", x, y, "MBUTTON_UP"); break; case CV_EVENT_LBUTTONDBLCLK: snprintf (str, 64, "(%d,%d) %s", x, y, "LBUTTON_DOUBLE_CLICK"); break; case CV_EVENT_RBUTTONDBLCLK: snprintf (str, 64, "(%d,%d) %s", x, y, "RBUTTON_DOUBLE_CLICK"); break; case CV_EVENT_MBUTTONDBLCLK: snprintf (str, 64, "(%d,%d) %s", x, y, "MBUTTON_DOUBLE_CLICK"); break; // (5)mouse button, 수식키를취득 if (flags & CV_EVENT_FLAG_LBUTTON) strncat (str, " + LBUTTON", 64); if (flags & CV_EVENT_FLAG_RBUTTON) strncat (str, " + RBUTTON", 64); if (flags & CV_EVENT_FLAG_MBUTTON) strncat (str, " + MBUTTON", 64); if (flags & CV_EVENT_FLAG_CTRLKEY) strncat (str, " + CTRL", 64); if (flags & CV_EVENT_FLAG_SHIFTKEY) strncat (str, " + SHIFT", 64); if (flags & CV_EVENT_FLAG_ALTKEY) strncat (str, " + ALT", 64); // (6) 마우스좌표, 이벤트, 수식키등을이미지에그리기, 표시 if (line > max_line) { cvgetrectsubpix (img, img, cvpoint2d32f (320-0.5, 240-0.5 + h)); cvputtext (img, str, cvpoint (w, 20 + h * max_line), &font, CV_RGB (0, 200, 100)); else { cvputtext (img, str, cvpoint (w, 20 + h * line), &font, CV_RGB (0, 200, 100)); line++; cvshowimage ("Image", img); 340
// (1) 이미지영역을확보해, 초기화한다이미지영역을확보해, 초기화한다. 또, 후에결과의문자열을그리기하기위한폰트구조체를초기화한다. // (2) 윈도우를작성해, 마우스이벤트에대한콜백함수를등록한다초기화한이미지를표시해, 함수 cvsetmousecallback()( 을 ) 를이용하고, 마우스이벤트에대한콜백함수 (on_mouse)( 을 ) 를등록한다. // (3)'Esc' 키가밀렸을경우에종료한다 "Esc" 키가밀렸을경우에종료한다.'\x1b'( 은 ) 는,"Esc" 키를나타내지만, 이문자코드는 "27" 그래서, if( c == 27 ) ( 이 ) 라고해도좋다. 또, 그외의키의경우는, 이하와같이지정하면좋다. if( c == 'q' ) // (4) 마우스이벤트를취득마우스이벤트를취득한다. 취득할수있는마우스이벤트는, 마우스의이동과좌, 오른쪽, 중앙버튼의 DOWN,UP, 더블클릭이다. 덧붙여서, 더블클릭을실시했을때의마우스이벤트는, 버튼 DOWN, 버튼 UP, 버튼 DOWN, 버튼더블클릭, 버튼 UP 의순서로발생한다. // (5)mouse button, 수식키를취득이벤트시에밀리고있는 mouse button, 수식키 (CTRL,ALT,SHIFT)( 을 ) 를취득한다. 이러한플래그는, 각이벤트플래그의논리합으로표현된다. 또, mouse button 플래그는, 버튼 DOWN 시간에는발생하지않지만, 버튼 UP 시간에는발생한다. // (6) 마우스좌표, 이벤트, 수식키등을이미지에그리기, 표시현재의마우스좌표, 마우스이벤트, 수식키모두를이미지에그리기해, 표시한다. 또, 함수 cvgetrectsubpix() ( 을 ) 를이용하고, 화면을스크롤시킨다. 실행결과예 카메라 OpenCV 그럼, 실제의카메라나동이미지파일로부터캡쳐를실시하거나반대로이미지열을동이미지파일로서써내 거나하는것이가능하다. 카메라로부터의이미지캡쳐 cvcapturefromcam, cvqueryframe 지정된번호의카메라로부터이미지를캡쳐해표시한다 341
표시의변환 샘플코드 #include <cv.h> #include <highgui.h> #include <ctype.h> int main (int argc, char **argv) { CvCapture *capture = 0; IplImage *frame = 0; double w = 320, h = 240; int c; // (1) 커멘드인수에의해서지정된번호의카메라에대한캡쳐구조체를작성한다 if (argc == 1 (argc == 2 && strlen (argv[1]) == 1 && isdigit (argv[1][0]))) capture = cvcreatecameracapture (argc == 2? argv[1][0] - '0' : 0); /* 이설정은, 이용하는카메라에의존한다 */ // (2) 캐프체사이즈를설정한다. cvsetcaptureproperty (capture, CV_CAP_PROP_FRAME_WIDTH, w); cvsetcaptureproperty (capture, CV_CAP_PROP_FRAME_HEIGHT, h); cvnamedwindow ("Capture", CV_WINDOW_AUTOSIZE); // (3) 카메라로부터이미지를캡쳐한다 while (1) { frame = cvqueryframe (capture); cvshowimage ("Capture", frame); c = cvwaitkey (10); if (c == '\x1b') break; cvreleasecapture (&capture); cvdestroywindow ("Capture"); return 0; // (1) 커멘드인수에의해서지정된번호의카메라에대한캡쳐구조체를작성한다커멘드인수로서주어진번호의카메라에대한, 캡쳐구조체 CvCapture( 을 ) 를작성한다. 인수가지정되지않는경우는, 디폴트의값이다 "0" 하지만이용된다. 342
// (2) 캐프체사이즈를설정한다함수 cvsetcaptureproperty() 에의해, 캡쳐를행할때의화면사이즈 ( 폭과높이 ) 를지정한다. 다만, 실제의카메라가서포트하고있지않는캐프체사이즈는지정할수없다. // (3) 카메라로부터이미지를캡쳐한다루프블록중에함수 cvqueryframe() ( 을 ) 를호출하는것으로, 실제로캡쳐를실시해, 그것을표시한다. 캡쳐안에 "Esc" 키가밀렸을경우는, 종료한다. 실행결과예 CLOSE 카메라, 비디오파일 OpenCV그럼, 실제의카메라나동이미지파일로부터캡쳐를실시하거나반대로이미지열을동이미지파일로서써내거나하는것이가능하다. 카메라로부터의이미지캡쳐 cvcreatecameracapture, cvqueryframe 지정된번호의카메라로부터이미지를캡쳐해표시한다 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> #include <ctype.h> int main (int argc, char **argv) { CvCapture *capture = 0; IplImage *frame = 0; double w = 320, h = 240; int c; // (1) 커멘드인수에의해서지정된번호의카메라에대한캡쳐구조체를작성한다 if (argc == 1 (argc == 2 && strlen (argv[1]) == 1 && isdigit (argv[1][0]))) capture = cvcreatecameracapture (argc == 2? argv[1][0] - '0' : 0); /* 이설정은, 이용하는카메라에의존한다 */ // (2) 캐프체사이즈를설정한다. 343
cvsetcaptureproperty (capture, CV_CAP_PROP_FRAME_WIDTH, w); cvsetcaptureproperty (capture, CV_CAP_PROP_FRAME_HEIGHT, h); cvnamedwindow ("Capture", CV_WINDOW_AUTOSIZE); // (3) 카메라로부터이미지를캡쳐한다 while (1) { frame = cvqueryframe (capture); cvshowimage ("Capture", frame); c = cvwaitkey (10); if (c == '\x1b') break; cvreleasecapture (&capture); cvdestroywindow ("Capture"); return 0; // (1) 커멘드인수에의해서지정된번호의카메라에대한캡쳐구조체를작성한다함수 cvcreatecameracapture()( 을 ) 를이용하고, 커멘드인수로서주어진번호의카메라에대한캡쳐구조체 CvCapture( 을 ) 를작성한다. 인수가지정되지않는경우는, 디폴트의값이다 "0" 하지만이용된다. OpenCV-1.0.0 의샘플프로그램에서는,cvCreateCameraCapture 대신에, cvcapturefromcam 하지만이용되고있지만, 실제는 highgui.h 안에서, #define cvcapturefromcam cvcreatecameracapture ( 와 ) 과같이정의되고있어이러한함수는등가이다. 여기에서는, 메뉴얼에따라함수 cvcreatecameracapture ( 을 ) 를 이용한다. // (2) 캐프체사이즈를설정한다함수 cvsetcaptureproperty() 에의해, 캡쳐를행할때의화면사이즈 ( 폭과높이 ) 를지정한다. 다만, 실제의카메라가서포트하고있지않는캐프체사이즈는지정할수없다. // (3) 카메라로부터이미지를캡쳐한다루프블록중에함수 cvqueryframe() ( 을 ) 를호출하는것으로, 실제로캡쳐를실시해, 그것을표시한다. 캡쳐안에 "Esc" 키가밀렸을경우는, 종료한다. 실행결과예 CLOSE 344
동영상으로서파일에써낸다 cvcreatevideowriter, cvwriteframe, cvreleasevideowriter 카메라로부터캡쳐한프레임을, 차례차례파일에써낸다 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> #include <ctype.h> #include <stdio.h> int main (int argc, char **argv) { CvCapture *capture = 0; IplImage *frame = 0; CvVideoWriter *vw; double w = 320, h = 240; int c, num = 0; CvFont font; char str[64]; // (1) 커멘드인수에의해서지정된번호의카메라에대한캡쳐구조체를작성한다 if (argc == 1 (argc == 2 && strlen (argv[1]) == 1 && isdigit (argv[1][0]))) capture = cvcapturefromcam (argc == 2? argv[1][0] - '0' : 0); // (2) 캐프체사이즈를설정한다 cvsetcaptureproperty (capture, CV_CAP_PROP_FRAME_WIDTH, w); cvsetcaptureproperty (capture, CV_CAP_PROP_FRAME_HEIGHT, h); // (3) 비디오라이터구조체를작성한다 vw = cvcreatevideowriter ("cap.avi", CV_FOURCC ('X', 'V', 'I', 'D'), 15, cvsize ((int) w, (int) h)); cvinitfont (&font, CV_FONT_HERSHEY_COMPLEX, 0.7, 0.7); cvnamedwindow ("Capture", CV_WINDOW_AUTOSIZE); // (4) 카메라로부터이미지를캡쳐해, 파일에써낸다 while (1) { frame = cvqueryframe (capture); snprintf (str, 64, "%03d[frame]", num); 345
cvputtext (frame, str, cvpoint (10, 20), &font, CV_RGB (0, 255, 100)); cvwriteframe (vw, frame); cvshowimage ("Capture", frame); num++; c = cvwaitkey (10); if (c == '\x1b') break; // (5) 기입을종료해, 구조체를해방한다 cvreleasevideowriter (&vw); cvreleasecapture (&capture); cvdestroywindow ("Capture"); return 0; // (1) 커멘드인수에의해서지정된번호의카메라에대한캡쳐구조체를작성한다전술의캡쳐샘플과같게, 구조체를작성한다. // (2) 캐프체사이즈를설정한다전술의캡쳐샘플과같게, 캐프체사이즈를설정한다. // (3) 비디오라이터구조체를작성한다파일에써내기위한비디오라이터구조체를작성한다. 인수에는, 순서에, ( 파일명, 코덱지정자, frame rate, 사이즈 )( 을 ) 를지정한다. 여기에서는, 코덱의지정에,CV_FOURCC('X','V','I','D')(XVID)( 을 ) 를이용하고있지만, 레퍼런스메뉴얼에있도록 ( 듯이 ),CV_FOURCC('M','J','P','G')( 모션 JPEG)( 이 ) 나 CV_FOURCC('P','I','M','1')(MPEG- 1)( 을 ) 를이용할수도있다. 비압축으로의보존을희망하는경우는,CV_FOURCC('D','I','B',' ')( 을 ) 를지정하면좋다. 물론, 이용되는코덱이인스톨되고있을필요가있다. 또,MacOSX 그럼, 파일명을풀패스로지정하든가, 혹은,touch 커멘드등에서미리파일작성해둘필요가있는것같다. // (4) 카메라로부터이미지를캡쳐해, 파일에써낸다카메라로부터이미지를캡쳐해, 그이미지에대해서문자열을쓴후에, 함수 cvwrtiteframe() 에의해서차례차례파일에써낸다. // (5) 기입을종료해, 구조체를해방한다 "Esc" 키로루프를빠진후에는, 함수 cvreleasevideowriter() 에의해서, 기입을종료시켜, 구조체를해방한다. 실행결과예 346
CLOSE 라벨링 OpenCV자체에는 (OpenCV-1.0.0 시점에있고 ) 라벨링을실시하는함수는존재하지않지만, OpenCV Library Wiki그리고소개되고있다 "Blob extraction library"( 을 ) 를이용하고, 이미지의라벨링을실시할수있다. 이라이브러리는,MIL (Matrox Imaging Library) 에있어서의,MIL blob detection 모듈의기능을대체하는것을,OpenCV( 을 ) 를이용해실장되어있다. Win32환경에서동작하는것과Linux환경에서동작하는것이, 각각배포되고있다. 디폴트에서는, 스태틱라이브러리 (.lib 혹은.a) 의형식에서제공되지만, 소스로부터다이나믹링크 ( 혹은, 공유 ) 라이브러리를작성할수도있다. blob( 이 ) 란, 데이타베이스로말할곳의형태가아니고," 작은덩어리 " 그렇다고한다의미로이용되고있다 ( 컴퓨터그래픽스나비전의세계에서는, 메타보르를표현할때에이용되기도한다 ). 즉, 이미지중으로부터개별의작은덩어리 (blob)( 을 ) 를추출하는작업 ( 라벨링) ( 을 ) 를행하기위한클래스이며, 이하의특징이있다 ( 자세한것은, 상술의홈페이지를참조하는것 ). 2 치이미지혹은그레이스케일이미지로부터,8 근방에있어서연결되어있는영역을추출한다. 이러한영역은 blob ( 으 ) 로서참조된다. 이미지중의주목오브젝트를얻기위해서, 취득했다 blob 집합에대해서필터링을실시한다. 이처리는,CBlobResult 의 Filter 메소드에의해서실현된다. 샘플 라벨링 CBlobResult 입력이미지에대해서라벨링을실시해, 면적과주위장을표시한다 표시의변환 샘플코드 #include <cv.h> #include <highgui.h> #include "BlobResult.h" int main (int argc, char **argv) { int i; IplImage *src_img = 0, *dst_img = 0; CBlobResult blobs; CBlob blob; CvPoint p1, p2; CvFont font; char text[64]; CBlob blobwithbiggestperimeter, blobwithlessarea; // (1) 이미지를읽어들인다 if (argc!= 2 (src_img = cvloadimage (argv[1], CV_LOAD_IMAGE_COLOR)) == 0) 347
return -1; dst_img = cvcreateimage (cvsize (src_img->width, src_img->height), IPL_DEPTH_8U, 1); cvcvtcolor (src_img, dst_img, CV_BGR2GRAY); cvinitfont (&font, CV_FONT_HERSHEY_SIMPLEX, 0.5, 0.5, 0, 1, CV_AA); // (2) 라벨링을실시한다 blobs = CBlobResult (dst_img, NULL, 100, false); // (3) 면적으로필터링 blobs.filter (blobs, B_INCLUDE, CBlobGetArea (), B_INSIDE, 1000, 50000); for (i = 0; i < blobs.getnumblobs (); i++) { // (4) 각 blob 의최소포함구형 ( 기울기없음 ) 을요구한다 blob = blobs.getblob (i); p1.x = (int) blob.minx (); p1.y = (int) blob.miny (); p2.x = (int) blob.maxx (); p2.y = (int) blob.maxy (); // (5) 각 blob 의포함구형, 인덱스, 면적, 주장을그리기 cvrectangle (src_img, p1, p2, CV_RGB (255, 0, 0), 2, 8, 0); sprintf (text, "[%d] %d,%d", blob.label (), (int) blob.area, (int) blob.perimeter ()); cvputtext (src_img, text, cvpoint (p1.x, p1.y - 5), &font, cvscalarall (255)); // (6) 최대의주장을가진다 blob( 을 ) 를, 녹색으로전부칠한다 blobs.getnthblob (CBlobGetPerimeter (), 0, blobwithbiggestperimeter); blobwithbiggestperimeter.fillblob (src_img, CV_RGB (0, 255, 0)); // (7) 최소의면적을가진다 blob( 을 ) 를, 청색으로전부칠한다 blobs.getnthblob (CBlobGetArea (), blobs.getnumblobs () - 1, blobwithlessarea); blobwithlessarea.fillblob (src_img, CV_RGB (0, 0, 255)); // (8) 이미지를표시, 키가밀렸을때에종료 cvnamedwindow ("Labeling", CV_WINDOW_AUTOSIZE); cvshowimage ("Labeling", src_img); cvwaitkey (0); cvdestroywindow ("Labeling"); cvreleaseimage (&src_img); cvreleaseimage (&dst_img); return 0; 348
// (1) 이미지를읽어들인다입력이미지를, 칼라이미지로서읽어들인다. 또, 라벨링대상이되는이미지는, 그레이스케일혹은 2 치이미지일필요가있으므로, 이미지의변환을실시한다. // (2) 라벨링을실시한다 1 번째의인수에, 라벨링대상이되는이미지를준다. 2 번째의인수는, 마스크이미지,3 번째의인수는,blob 인가그이외인지를결정하는반응을일으키는최소의물리량 (0-255), 4 번째의인수는, 각 blob 모멘트의계산을실시하는지아닌지의플래그이다. 반환값으로서오브젝트 CBlobResult( 을 ) 를돌려준다. // (3) 면적으로필터링 Filter() 메소드는, 지정된프롭퍼티에의해,blob( 을 ) 를필터링한다. 1 번째의인수는, 전의라벨링처리로얻을수있던오브젝트. 2 번째의인수는, 필터링작업 ( 조건에들어맞았다 blob( 을 ) 를, 포함한다 "B_INCLUDE" 인가, 포함하지않는다 "B_EXCLUDE" 인가 ). 3 번째의인수는, 필터링에이용하는프롭퍼티를결정하는함수. 4 번째의인수는, 필터링조건 (B_EQUAL,B_NOT_EQUAL,B_GREATER,B_LESS,B_GREATER_OR_EQUAL,B_LESS_OR_EQU AL,B_INSIDE,B_OUTSIDE) 5 번째와 6 번째의인수는, 하한및상한 ( 이것은, 지정되지않는것도있다 ) 이되는반응을일으키는최소의물리량. 이번은, 면적이,1000-50000 [pixel] 의범위에있다 blob 만을남기고있다. // (4) 각 blob 의최소포함구형 ( 기울기없음 ) 을요구한다각 blob 에두어최소, 최대의 x,y 좌표를가지는픽셀을참조하는것으로, 그 blob( 을 ) 를포함하는구형을요구한다. // (5) 각 blob 의포함구형, 인덱스, 면적, 주위장을그리기각 blob 의포함구형, 및 blob 인덱스, 면적, 주위장을그리기한다. 여기서,blob 의주위장은,blob 의경계가되는픽셀의개수이며, Edges() 메소드에의해참조 (CvSeq* 형태 ) 할수있다. 다만, 이러한픽셀은, 래스터스캔되었을때의주사순서에줄지어있을뿐 ( 만큼 ) 이므로, 그대로윤곽을그리는일은할수없다. // (6) 최대의주위장을가진다 blob( 을 ) 를, 녹색으로전부칠한다 CBlobGetPerimeter() 에의해서얻을수있었다 blob 의주위의길이가, 최대 (0 번째 ) 의것을, CBlob 오브젝트로서취득해,FillBlob() 메소드에의해서전부칠한다. // (7) 최소의면적을가진다 blob( 을 ) 를, 청색으로전부칠한다 (6)( 와 ) 과같게,CBlobGetArea() 에의해서얻을수있었다 blob 의면적이, 최소 (blobs.getnumblobs()-1 번째, 즉최후 ) 의것을, CBlob 오브젝트로서취득해,FillBlob() 메소드에의해서전부칠한다. // (8) 이미지를표시, 키가밀렸을때에종료이미지를실제로표시해, 무엇인가키가밀릴때까지기다린다. 실행결과예 [ 좌 ] 처리전 [ 우 ] 처리후 349
350