대메뉴 바로가기 본문 바로가기

데이터 기술 자료

데이터 기술 자료 상세보기
제목 엔비디아 젯슨 TK1에서의 OpenCV 프로그래밍
등록일 조회수 8645
첨부파일  

임베디드 시스템을 위한 모바일 수퍼컴퓨터

엔비디아 젯슨 TK1에서의 OpenCV 프로그래밍



OpenCV(Open Computer Vision)는 실시간 이미지 프로세싱에 중점을 둔 C 언어 기반 오픈소스 라이브러리다. 젯슨 TK1 역시 이를 지원하고 있다. 엔비디아 젯슨 TK1으로 하는 GPGPU, 그 마지막 시간인 이 글에서는 젯슨 TK1에서의 OpenCV 프로그래밍에 대해 알아본다.



영상이나 이미지는 픽셀 기반의 데이터로 구성된, x 축과 y 축을 가진 행렬 데이터다. 일반적인 CPU는 이러한 행렬 데이터 처리에 최적화돼 있지 않아 GPU에 비해 처리 속도가 느리다. 이러한 연산은 GPU로 보다 빠르게 처리할 수 있다. 젯슨 TK1는 이러한 GPU 가속 기능을 지원한다.



OpenCV

영상 처리를 하기 위해서는 관련 이론이나 알고리듬에 대해 훤히 꿰뚫고 있어야 한다. 관련 지식 없이 프로그래밍이 어렵지만, OpenCV는 이를 가능케 한다. OpenCV는 영상 처리, 컴퓨터 비전, 이미지 학습 등을 지원하는 오픈소스 라이브러리로, 윈도우, 맥OS, 리눅스는 물론 안드로이드, iOS 등의 모바일 플랫폼을 지원한다. BSD 라이선스에 의해 opencv.org에서 배포되고 있는데, 초기에는 인텔의 주도로 개발됐다. C 언어에 기반을 두고 있으나 OpenCV 2.0부터는 C++, 파이썬, 자바로까지 지원이 확대됐다. 고급 영상처리를 위한 다양한 알고리즘을 비롯해 카메라 입력, 영상 출력 등의 기능도 제공한다. 영상 처리를 위한 패턴 인식, 컴퓨터 비전 알고리즘 등을 구현한 다양한 함수는 물론 영상 인식과 처리, 색상공간의 변환 필터링, DFT나 FFT, 주파수 변환 등의 기능을 제공하는 것도 OpenCV의 특징. OpenCV는 최근 로봇, 무인자동차 운전 제어, 스트릿 뷰 영상정합, 의학영상 분석 등 다양한 분야에 활용되고 있다.

● 쿠다와 OpenCV
OpenCV에 쿠다를 함께 쓰면 단일 CPU로 처리하는 것에 비해 상당한 속도 향상 효과를 얻을 수 있다. 인텔이 개발을 주도해서인지 안드로이드 등에는 최적화가 다소 미진하나 테그라 기반의 CPU에서는 보다 나은 성능을 발휘하는 것으로 알려져 있다.



● TK1에서 OpenCV 사용하기
TK1에서 OpenCV를 쓰기 위해서는 라이브러리가 필요하다. 젯슨 TK1에는 우분투 14.04 기반의 Linux4Tegra(L4T)가 설치돼 있는데, 올바르게 설치된 상태라면 사용자 홈디렉토리(/home/ubuntu)에 OpenCV4Tegra라는 디렉토리가 있을 것이다. 이 안에는 libopencv4tegra-repo_l4t-r21_2.4.10.1_armhf.deb과 ocv.sh 파일이 있다. 여기서 ocv.sh 파일을 실행하면 TK1에 OpenCV를 설치할 수 있다. 참고로 설치 시에는 인터넷 연결이 필요하다.



OpenCV 프로그래밍

OpenCV로 GUI를 프로그래밍하기 위해서는 별도의 HighGUI 라이브러리를 이용해야 한다. OpenGL의 GLUT 라이브러리와 유사하다. 지금부터는 몇 가지 OpenCV 예제들을 통해 기본 구조에 대해 살펴보자.



<리스트 1> HelloOpenCV.cpp #include < stdio.h> #include < math.h> /* OpenCV를 위한 헤더파일 */ #include < cv.h> #include < highgui.h> int main(int argc, char *argv[]) { IplImage* img = 0; /* OpenCV에서 영상 처리를 위한 구조체 */ int width, height, channels, step; uchar *data; if(argc < 2) { printf("Usage: main < image-file-name> "); return -1; } /* 이미지 불러오기 */ img = cvLoadImage(argv[1]); if(!img){ printf("Could not load image file: %s ",argv[1]); return -1; } /* 이미지의 정보 가져오기 */ width = img->width; // 이미지의 넓이 height = img->height; // 이미지의 높이 channels = img->nChannels; // 이미지의 채널 수 step = img->widthStep; // 이미지의 총 넓이(채널 수 * 바이트수) data = (uchar *)img->imageData; // 실제 이미지 데이터 printf("Processing a %dx%d image with %d channels ", width, height, channels); /* 출력을 위한 윈도우의 생성 */ cvNamedWindow("mainWin", CV_WINDOW_AUTOSIZE); /* 윈도우의 위치 조정 */ cvMoveWindow("mainWin", 100, 100); /* 이미지의 반전 처리 */ for(int i = 0; i < height; i++) for(int j = 0; j < width; j++) for(int k = 0; k < channels; k++) data[i*step+j*channels+k] = ~data[i*step+j*channels+k]; cvShowImage("mainWin", img ); // 이미지 표시 cvWaitKey(0); // 사용자 키 입력 대기 cvReleaseImage(&img ); // 사용된 자원 반환 return 0; }



OpenCV는 기본적으로 IplImage 구조체를 사용해 영상을 처리한다. IplImage 구조체는 이미지에 대한 기본적인 정보와 이미지 데이터를 가지고 있는데, 구조체 앞의 IPL은 인텔 이미지 프로세싱 라이브러리(Intel Image Processing Library)의 약자다. 이미지에 대한 정보는 IplImage 구조체의 멤버에 기록된다. 이미지의 폭는 width 멤버, 높이는 height, 색상의 깊이는 depth, 채널의 수는 nChannels, 이미지의 크기는 imageSize에 저장돼 있다. 실제 이미지 데이터의 경우 imageData을 통해 가져올 수 있다. <리스트 1>은 imageData 구조체를 이용해 현재의 이미지를 반전 처리한다.




이미지를 불러올 때에는 cvLoadImage() 함수를 쓴다. 이미지를 컬러로 로드하고 싶다면 CV_LOAD_IMAGE_COLOR를, 흑백으로 로드할 때에는 CV_LOAD_IMAGE_GRAYSCALE을 쓰면 된다. 참고로 이미지의GED를 사용하면 된다. cvLoadImage() 함수를 이용해 불러온 이미지는 IplImage 구조체의 포인터 형으로 반환된다. 사용이 끝난 IplImage 데이터는 cvReleaseImage() 함수를 통해 자원을 반환할 수 있다. 불러온 이미지는 HighGUI 라이브러리로 화면에 표시할 수 있다.

cvNamedWindow() 함수로 윈도우를 생성하고, cvMoveWindow() 함수를 통해 윈도우를 원하는 위치로 이동시킬 수 있다. 윈도우에 IplImage 데이터를 표시하는 것은 cvShowImage() 함수로 할 수 있으며, 윈도우의 사용이 끝나면 cvDestroyWindow()로 윈도우를 삭제하면 된다.



일반적으로 main() 함수가 종료되면 애플리케이션이 종료된다. OpenCV로 프로그래밍할 때에는 화면에 윈도우가 표시되는 동안 main() 함수가 종료되지 않아야 하는데, cvWaitKey()로 이를 구현할 수 있다. cvWaitKey() 함수는 사용자 키 입력을 기다리는 함수로, 사용자가 키를 입력하면 다음의 함수를 실행한다. 지금부터는 <리스트 1>을 실제로 빌드해 보자. 이를 빌드하기 위해서는 별도의 이미지 파일를 준비해야 한다. 참고로 <리스트 1>은 C++ 기반이므로 gcc가 아닌 g++로 빌드해야 한다.



<리스트 2> <리스트 1> 빌드 결과 ubuntu@tegra-ubuntu:~$ g++ -o HelloOpenCV HelloOpenCV.cpp ?lopencv_core ?lopencv_imgproc ?lopencv_highgui ubuntu@tegra-ubuntu:~$ ./HelloOpenCV sample1.png Processing a 640x480 image with 3 channels





<리스트 1>을 빌드하려면 OpenCV의 core, imgproc, highgui 라이브러리가 필요하다. 일반적으로 OpenCV 소스 코드를 빌드할 때에는 pkg-config --cflags ?libs opencv와 같이 pkg-config를 이용하면 되지만, 젯슨 TK1에서 빌드할 때에는 라이브러리 이름을 직접 지정해 빌드해야 한다. <리스트 2>로 <리스트 1>을 실행하면 화면에 반전된 이미지 영상이 나타난다. 원본 영상과 비교하면 필름처럼 각 색상이 반전돼 있을 것이다.

● OpenCV를 이용한 영상 처리 프로그래밍
이제 OpenCV를 이용해 간단히 영상을 처리해 보자. OpenCV는 색상변환, 확대, 축소, 영상합성, 외각선 검출 등의 일반적인 영상 처리를 지원한다.



<리스트 3> OpenCVImageProcessing.cpp #include < stdio.h> #include < opencv/cv.h> #include < opencv/highgui.h> int main(int argc, char** argv) { /* 이미지 불러오기 */ IplImage *image1 = cvLoadImage("sample1.jpg", CV_LOAD_IMAGE_COLOR); IplImage *image2 = cvLoadImage("sample2.jpg", CV_LOAD_IMAGE_COLOR); /* 영상처리할 이미지 데이터 저장을 위한 공간 */ IplImage *image_add = cvCreateImage(cvGetSize(image1), IPL_DEPTH_8U, 3); IplImage *image_sub = cvCreateImage(cvGetSize(image1), IPL_DEPTH_8U, 3); IplImage *image_mul = cvCreateImage(cvGetSize(image1), IPL_DEPTH_8U, 3); IplImage *image_div = cvCreateImage(cvGetSize(image1), IPL_DEPTH_8U, 3); IplImage *image_gray1 = cvCreateImage(cvGetSize(image1), IPL_DEPTH_8U, 1); IplImage *image_gray2 = cvCreateImage(cvGetSize(image1), IPL_DEPTH_8U, 1); IplImage *image_white = cvCreateImage(cvGetSize(image1), IPL_DEPTH_8U, 1); IplImage *image_gray_sub = cvCreateImage(cvGetSize(image1), IPL_DEPTH_8U, 1); /* 윈도우를 생성한다. */ cvNamedWindow("IMAGE_1", CV_WINDOW_AUTOSIZE); cvNamedWindow("IMAGE_2", CV_WINDOW_AUTOSIZE); cvNamedWindow("IMAGE_ADDITION", CV_WINDOW_AUTOSIZE); cvNamedWindow("IMAGE_SUBTRACTION", CV_WINDOW_AUTOSIZE); cvNamedWindow("IMAGE_MULTIPLICATION", CV_WINDOW_AUTOSIZE); cvNamedWindow("IMAGE_DIVISION", CV_WINDOW_AUTOSIZE); cvNamedWindow("IMAGE_GRAY1", CV_WINDOW_AUTOSIZE); cvNamedWindow("IMAGE_GRAY2", CV_WINDOW_AUTOSIZE); cvNamedWindow("IMAGE_WHITE", CV_WINDOW_AUTOSIZE); /* 영상 처리를 진행한다. */ cvAdd(image1, image2, image_add, NULL); cvSub(image1, image2, image_sub, NULL); cvMul(image1, image2, image_mul, 1); cvDiv(image1, image2, image_div, 1); cvCvtColor(image1, image_gray1, CV_RGB2GRAY); cvCvtColor(image2, image_gray2, CV_RGB2GRAY); cvAbsDiff(image_gray1, image_gray2, image_gray_sub); cvThreshold(image_gray_sub, image_white, 100, 255, CV_THRESH_BINARY); /* 화면에 처리된 결과를 표시한다. */ cvShowImage("IMAGE_1", image1); cvShowImage("IMAGE_2", image2); cvShowImage("IMAGE_ADDITION", image_add); cvShowImage("IMAGE_SUBTRACTION", image_sub); cvShowImage("IMAGE_MULTIPLICATION", image_mul); cvShowImage("IMAGE_DIVISION", image_div); cvShowImage("IMAGE_GRAY1", image_gray1); cvShowImage("IMAGE_GRAY2", image_gray2); cvShowImage("IMAGE_WHITE", image_white); cvWaitKey(0); // 키 입력까지 대기 /* 앞에서 사용할 리소드들을 해제한다. */ cvReleaseImage(&image1); cvReleaseImage(&image2); cvReleaseImage(&image_add); cvReleaseImage(&image_sub); cvReleaseImage(&image_mul); cvReleaseImage(&image_div); cvReleaseImage(&image_gray1); cvReleaseImage(&image_gray2); cvReleaseImage(&image_white); cvReleaseImage(&image_gray_sub); cvDestroyAllWindows(); // 모든 윈도우 삭제 return 0; }



IplImage 객체는 이미지 로드뿐 아니라 영상 처리된 데이터의 저장에도 사용된다. OpenCV는 두 영상을 합산하는 cvAdd() 함수, 두 영상의 차를 구하는 cvSub(), 두 영상을 곱하는 cvMul() 함수, 두 영상을 나누는 cvDiv() 함수, 색상을 변환시키는 cvCvtColor() 함수 등을 제공한다.



<리스트 3>을 빌드할 때에는 OpenCV 라이브러리를 링크해야 한다. 코드 실행을 위해서는 같은 크기의 sample1.jpg와 sample2.jpg가 필요하다. <리스트 3>의 실행 결과는 <그림 4>와 같다.





OpenCV와 GPU의 사용

TK1은 리눅스로 동작하는 만큼 Video4Linux2를 통해 카메라를 사용할 수 있다. Video4Linux2는 리눅스에서 카메라, TV수신카드, 라디오 등의 인터페이스 카드로부터 데이터를 가져올 수 있는 API다. TK1은 USB 기반 카메라를 쓸 수 있다. 로지텍, 마이크로소프트 등의 벤더에서 제조한 키넥틱(Kinetic) 등의 다양한 USB 웹캠을 사용할 수 있다. TK1에 지원 가능한 카메라인 경우 연결 시 /dev /video0라는 파일이 생성된다.

● OpenCV와 USB 카메라
OpenCV는 카메라 영상으로 자동으로 가져올 수 있다. OpenCV는 리눅스뿐 아니라 윈도우, 맥OS 등의 다양한 플랫폼을 지원한다. 이 덕분에 Video4Linux2가 지원되지 않는 시스템에서도 사용 가능하다. 지금부터는 OpenCV를 이용해 카메라 영상을 가져와 화면에 출력하는 윈도우 애플리케이션을 개발해 보자.



<리스트 4> OpenCVWebCam.c #include < stdio.h> #include < opencv/cv.h> #include < opencv/highgui.h> int main(int argc, char** argv) { CvCapture* capture; // 카메라를 위한 변수 IplImage *frame; // 영상을 위한 변수 /* 캡처한 카메라를 지정한다. */ capture = cvCaptureFromCAM(0); if(capture == 0) { perror("OpenCV : open WebCam "); return -1; } /* 캡처할 영상의 속성을 설정한다. */ cvSetCaptureProperty(capture, CV_CAP_PROP_FRAME_WIDTH, 320); cvSetCaptureProperty(capture, CV_CAP_PROP_FRAME_HEIGHT, 240); /* 윈도우를 생성한다. */ cvNamedWindow("CAMERA_IMAGE", CV_WINDOW_AUTOSIZE); cvResizeWindow( "T9-camera", 320, 240); cvGrabFrame(capture); // 카메라로부터 한 장의 영상 획득 frame = cvRetrieveFrame(capture); // 카메라 영상에서 이미지 획득 cvShowImage("CAMERA_IMAGE", frame); // 화면에 처리 결과 표시 cvWaitKey(0); // 키 입력까지 대기 cvReleaseImage(&frame); // 사용란 리소스 해제 cvDestroyAllWindows(); // 모든 윈도우 삭제 return 0; }



OpenCV에서 카메라를 사용할 때에는 CvCapture 클래스를 쓴다, 카메라로부터 가져온 영상은 IplImage 클래스로 얻을 수 있다. 현재의 카메라는 cvCaptureFromCAM() 함수를 사용하면 되는데, 함수의 인자는 카메라의 번호다. 일반적으로 카메라가 1개인 경우 0번을 사용한다. 카메라는 여러 장의 이미지를 연속적으로 가져올 수 있으며, 이미지 표시는 한 장씩만 처리 가능하다. 카메라에서 한 장의 영상은 cvRetrieveFrame() 함수를 통해 IplImage 클래스의 객체로 가져오고 이를 윈도우에 출력하면 된다. <리스트 4>를 빌드하면 <그림 5>와 같은 결과를 얻을 수 있다.



● TK1과 OpenCV
OpenCV는 일반적으로 CPU에서 처리된다. 이러한 처리를 GPU로 처리하면 속도를 높이는 동시에 전력 소모도 줄일 수 있다. OpenCV를 GPU로 처리하는 예제는 <리스트 5>와 같다.



<리스트 5> OpenCVwithGPU.cpp #include < iostream> #include < string> #include < opencv2/core/core.hpp> #include < opencv2/highgui/highgui.hpp> #include < opencv2/gpu/gpu.hpp> /* 네임 스페이스의 사용 */ using namespace std; using namespace cv; using namespace cv::gpu; enum Method {FGD_STAT, MOG, MOG2, GMG}; int main(int argc, const char** argv) { /* 명령행 인수 분석 */ cv::CommandLineParser cmd(argc, argv, "{ c | camera | false | use camera }" "{ f | file | 768x576.avi | input video file }"); bool useCamera = cmd.get< bool>("camera"); string file = cmd.get< string>("file"); Method m = MOG2; VideoCapture cap; /* 카메라 또는 파일 사용 */ (useCamera)?cap.open(0):cap.open(file); if (!cap.isOpened()) { cerr < < "can not open camera or video file" < < endl; return -1; } /* CPU에 저장되는 이미지 : cv::Mat */ Mat frame; cap >> frame; /* Mat과 동일하나 GPU에 이미지와 프레임 저장 : cv::gpu::GpuMat */ GpuMat d_frame(frame); MOG2_GPU mog2; // 영상 분석을 위한 Mixture of Gaussians GpuMat d_fgmask, d_fgimg, d_bgimg; Mat fgmask, fgimg, bgimg; switch(m) { case MOG2: mog2(d_frame, d_fgmask); break; } /* 윈도우 생성 */ namedWindow("image", WINDOW_NORMAL); namedWindow("foreground mask", WINDOW_NORMAL); namedWindow("foreground image", WINDOW_NORMAL); if (m != GMG) { namedWindow("mean background image", WINDOW_NORMAL); } for(;;) { cap >> frame; if (frame.empty()) break; /* 이미지를 GPU로 불러오기 */ d_frame.upload(frame); if(fgimg.empty()) fgimg.create(frame.size(), frame.type()); /* 현재의 모델 업데이트 */ switch(m) { case MOG2: mog2(d_frame, d_fgmask); mog2.getBackgroundImage(d_bgimg); break; } d_fgimg.create(d_frame.size(), d_frame.type()); d_fgimg.setTo(Scalar::all(0)); d_frame.copyTo(d_fgimg, d_fgmask); /* CPU로 다운로드 */ d_fgmask.download(fgmask); d_fgimg.download(fgimg); if (!d_bgimg.empty()) d_bgimg.download(bgimg); imshow("image", frame); imshow("foreground mask", fgmask); imshow("foreground image", fgimg); if (!bgimg.empty()) imshow("mean background image", bgimg); /* ESC 키를 누르면 종료 */ int key = waitKey(30); if (key == 27) break; } return 0; }



OpenCV를 GPU로 처리하려면 네임스페이스를 써야 한다. OpenCV에서 명령행 인수를 파싱하기 위해서는 Command LineParer를 이용하면 된다. <리스트 5>에서 카메라를 이용하는 경우 ‘c’를 입력하면 된다. 카메라로부터 영상을 가져오는 데에는 cv::Mat를 이용한다. cv::Mat는 영상을 CPU에서 처리하고 메모리에 저장한다. GPU에서 처리되게 하기 위해서는 cv::gpu:: GpuMat를 써 메모리 공간을 할당하면 된다. 이미지를 GPU로 불러올 때에는 upload() 함수, GPU에서 처리된 영상 데이터를 CPU로 다운로드할 때에는 d_image.download() 함수를 사용한다. <리스트 5>는 MOG(Mixture of Gaussians)를 이용해 영상을 분석하는데, 이를 빌드하기 위해서는 GPU 처리와 관련된 몇 개의 라이브러리 링크가 필요하다(<리스트 6> 참조). <리스트 5>을 실행하면 <그림 6>처럼 원본 영상과 분석된 영상이 함께 출력될 것이다.



<리스트 6> <리스트 5> 빌드에 필요한 라이브러리 ubuntu@tegra-ubuntu:~$ g++ -o OpenCVwithGPU OpenCVwithGPU.cpp ?lopencv_core ?lopencv_imgproc ?lopencv_highgui ?lopencv_gpu





쿠다는 건축, 유전학, 수학 등의 다분야 활용되는 추세다. 192개의 그래픽 코어를 가진 젯슨 TK1은 라즈베리 파이나 인텔 에디슨에 비해 더 빠른 이미지 프로세싱 성능을 제공한다. 여타의 임베디드 보드는 영상 처리에 충분한 성능을 제공하지 않아 실시간 처리에 한계를 보이는 반면, 젯슨 TK1은 OpenCV를 통해 영상 처리, 얼굴 인식 등을 실시간으로 처리할 수 있다. 지금까지 살펴본 것들과 이러한 특징을 잘 활용해 GPGPU를 보다 다양한 분야에 적용해 보길 바란다.



출처 : 마이크로소프트웨어 10월호

제공 : 데이터 전문가 지식포털 DBguide.net