Post date: Nov 30, 2016 4:07:30 AM
OpenCV에 대해 살짝 맛만 보는 장이다. 설치하면서 환경설정이 제대로 되어 있는지 확인하고자 몇라인 코딩한걸 바탕으로 OpenCV를 살짝 맛본다.
3장부터 본격적인 OpenCV에 대해 설명하고 있기 때문에 이번 정리에서는 그냥 아~~ 이렇게 사용하는거구나..정도로만 이해하고 넘어 가도록 하자...
참고로 소스는 개인적으로 책을 바탕으로 조금 수정/보완한것입니다.. 소스가지고 토 달지 말아 주세욤 ^^
이번장에서는 간단한 이미지 파일을 View 하고 변환 하는것과 간단한 AVI파일을 재생하면서 다른 이름으로 저장하는것이 목적이다.
1. 그럼 먼저 간단한 Image파일을 로더해서 보여주고 Smothing , 축소 시키는 소스를 보자
/* 현재 실행중인 프로그램의 절대 경로... 별외 소스입니다. */
void CTest_OpenCVDlg::GetMyPath( void )
{
CString strMsg;
TCHAR strBlank[ MAX_PATH ];
memset( m_szPath, 0, sizeof( m_szPath ));
memset( strBlank, 0, sizeof( strBlank );
if (GetModuleFileName( AfxGetInstanceHandle( ), strBlank, MAX_PATH ) == 0 )
{
return;
}
strMsg.Format("%s", strBlank );
strMsg = strMsg.Left(strMsg.ReverseFind('\\'));
sprintf_s(m_szPath, MAX_PATH, "%s", strMsg );
}
/* 실제 이미지 처리 소스 */
void CTest_OpenCVDlg::OnBnClickedButton1()
{
IplImage * Image = NULL;
IplImage * Image_Out = NULL;
IplImage * Image_PyrDown = NULL;
IplImage * Image_Canny = NULL;
/* Image를 보여줄 Window Name 생성 */
/* Original JPEG */
char * ptrWindName = "<< OpenCV >> OrgJPEG Window";
/* Smoothing 처리된 JPEG */
char * ptrWindName2 = "<< OpenCV >> SmothJPEG Window";
/*축소 처리된 JPEG */
char * ptrWindName3 = "<< OpenCV >> PryDownJPEG Window";
/* 캐니 엣지( 그레이스케일 ) 처리된 JPEG */
char * ptrWindName4 = "<< OpenCV >>GreyscaleJPEG Window";
char szFile[ MAX_PATH ];
CvSize cSize;
GetMyPath();<-- 현재 경로를 찾아서
memset( szFile, 0, sizeof( szFile ));
sprintf_s(szFile, strlen( m_szPath ), m_szPath ); <-- JPEG Image가 있는 경로
strcat_s( szFile, MAX_PATH, "\\Sample.jpg" );
/* 원본 이미지를 읽어들일 메모리를 할당하고 */
Image = cvLoadImage( szFile );
/* 원본 이미지를 보여줄 윈도우를 하나 생성하고 */
cvNamedWindow( ptrWindName, CV_WINDOW_AUTOSIZE );
/* Smoothing 처리한 이미지를 보여줄 윈도우 생성하고 */
cvNamedWindow( ptrWindName2, CV_WINDOW_AUTOSIZE );
/* 축소 처리한 이미지를 보여줄 윈도우 생성하고 */
cvNamedWindow( ptrWindName3, CV_WINDOW_AUTOSIZE );
/* Canny 처리한 이미지를 보여줄 윈도우 생성하고 */
cvNamedWindow( ptrWindName4, CV_WINDOW_AUTOSIZE );
/* 원본 이미지를 먼저 보여주고 */
cvShowImage( ptrWindName, Image);
/* Smoothing 처리된 이미지를 저장할 객체 생성
입력이미지와 같은 크기에 부호없는 8bit 자료형( IPL_DEPTH_8U )으로
3개의 채널 즉 ( 한 채널당 8bit 할당한 )을 이용한 IplImage 객체 생성
*/
Image_Out = cvCreateImage( cvGetSize( Image ), IPL_DEPTH_8U , 3 );
/* Gaussian 기법으로 Smoothing 연산하고 ( Gaussian 기법은 6장에서 자세히 )*/
cvSmooth( Image, Image_Out, CV_GAUSSIAN, 3, 3 );
/* Smoothing 처리된 이미지를 보여주고 */
cvShowImage(ptrWindName2, Image_Out );
/* 사이즈를 반씩 쪼개서*/
cSize.width = Image->width / 2;
cSize.height = Image->height /2;
/* 축소 처리된 이미지를 저장할 객체 생성
입력 이미지와 같은 자료형(depth )에 같은 채널수(channels)에 사이즈만 반으로 줄여서 IplImage 객체 생성
*/
Image_PyrDown = cvCreateImage( cSize , Image->depth, Image->nChannels );
/* 축소 연산하고 */
cvPyrDown( Image, Image_PyrDown ) ;
/* 축소된 이미지를 보여주고 */
cvShowImage(ptrWindName3, Image_PyrDown );
/* 원본 이미지가 1채널을 사용한다면 즉, 흑백 이미지라면
제가 가지고 있는 이미지는 단색 이미지가 아니기 때문에 이루틴은 안 탑니다.. 아무것도 안보이는거죠 ^^
*/
if ( Image->nChannels == 1 )
{
/* Canny 처리된 이미지를 저장할 객체 생성
입력이미지와 같은 크기에 부호없는 8bit 자료형( IPL_DEPTH_8U )으로
1개의 채널 즉 Greyscale 한 IplImage 객체 생성
*/
Image_Canny = cvCreateImage( cvGetSize( Image ), IPL_DEPTH_8U , 1);
/* Greyscale 연산하고 */
cvCanny( Image, Image_Canny, 10, 100,3 );
/* Greyscale 된 이미지를 보여주고 */
cvShowImage(ptrWindName4, Image_Canny );
}
/*어떤 KeyEvent가 올때까지 기다리고 */
cvWaitKey( 0 );
/* 할당된 이미지 메모리를 해제하고 */
cvReleaseImage( &Image );
cvReleaseImage( &Image_Out );
cvReleaseImage( &Image_PyrDown );
cvReleaseImage( &Image_Canny );
/*생성된 윈도우를 해제한다 */
cvDestroyWindow(ptrWindName);
cvDestroyWindow(ptrWindName2 );
cvDestroyWindow(ptrWindName3 );
cvDestroyWindow(ptrWindName4 );
}
실행 결과
관련 API를 이용하면 이미지를 보여주고 변환하는 작업은 어렵지 않다..하지만 궁극적인 나 또는 개발자의 목표는 이러한 처리가 어떻게 이루어지는지 그 알고리즘을 알는것이 아닐까 한다.. 소스를 까보면서....^^
여기서 가장 중요한것은 이미지에 대한 모든 정보를 담고 있는 IplImage 구조체가 아닐까 싶다.. 다른건 몰라도 최소한 IplImage 구조체에 어떤 항목들이 있는지는 알고 가자... 항목별 설명은 3장부터해서 설명이 나온다..
typedef struct _IplImage
{
int nSize; // sizeof(IplImage)
int ID; // version (=0)
int nChannels; // Most of OpenCV functions support 1,2,3 or 4 channels
int alphaChannel; // Ignored by OpenCV
int depth; // Pixel depth in bits: IPL_DEPTH_8U, IPL_DEPTH_8S, IPL_DEPTH_16S,
//IPL_DEPTH_32S, IPL_DEPTH_32F and IPL_DEPTH_64F are supported.
char colorModel[4]; // Ignored by OpenCV
char channelSeq[4]; // ditto
int dataOrder; // 0 - interleaved color channels, 1 - separate color channels.
// cvCreateImage can only create interleaved images
int origin; // 0 - top-left origin,
// 1 - bottom-left origin (Windows bitmaps style).
int align; // Alignment of image rows (4 or 8).
// OpenCV ignores it and uses widthStep instead.
int width; // Image width in pixels.
int height; // Image height in pixels.
struct _IplROI *roi; // Image ROI. If NULL, the whole image is selected.
struct _IplImage *maskROI; // Must be NULL.
void *imageId; // " "
struct _IplTileInfo *tileInfo; // " "
int imageSize; // Image data size in bytes
// (==image->height*image->widthStep
// in case of interleaved data)
char *imageData; // Pointer to aligned image data.
int widthStep; // Size of aligned image row in bytes.
int BorderMode[4]; // Ignored by OpenCV.
int BorderConst[4]; // Ditto.
char *imageDataOrigin; // Pointer to very origin of image data
// (not necessarily aligned) -
// needed for correct deallocation
}
IplImage;
2. 다음 AVI 파일을 재생하면서 저장해보자...
AVI가 재생될 때 전체 Frame Count에서 현재 어느 위치에 있는지 보여주는 Tracbar도 붙혀 봤다.. 물론 OpenCV에서 제공하는 Controller다.
당연히 AVI 처리는 이미지(IplImage)처리와 비슷하다...연속된 이미지(IplImage)를 계속해서 보여주는것이 AVI 이기 때문에..
다시말하면 AVI에서 이미지(IplImage)을 뽑아내는 로직만 추가 되었을뿐 뽑아낸 IplImage를 보여주는 방법은 똑 같다는것이다.
보자...
/* AVI 영상 정보를 가지는 전역 객체( CvCapture )와 현재 영상위치를 알려주는 위치 정보 변수 */
CvCapture * g_Cap = NULL;
int g_Slider_Position = 0;
/* Trackbar 변경시 불려지는 Callback Function */
void CTest_OpenCVDlg::onTrackbarSlide( int Pos )
{
int Cur_Pos;
Cur_Pos = cvGetTrackbarPos( "Position","<< OpenCV >> AVI Window");
TRACE("TrackBar Recv Position ( %d ), Cur Postion ( %d ).....\n",Pos, Cur_Pos );
/* 넘어온 Pos 위치로 프레임 이동 */
cvSetCaptureProperty( pThis->g_Cap,CV_CAP_PROP_POS_FRAMES , Pos );
}
void CTest_OpenCVDlg::OnBnClickedButton2()
{
char * ptrWindName = "AVI Window";
char *ptrTrackbarName="Postion"
char szFile[ MAX_PATH ];
char szWritePath[ MAX_PATH ];
IplImage * logpolar_frame = NULL;
CvSize Size;
CvVideoWriter * Writer = NULL;
IplImage * frame;
char c;
memset( szFile, 0, sizeof( szFile ));
memset( szWritePath, 0, sizeof(szWritePath));
/*원본 AVI 파일위치 */
sprintf_s(szFile, MAX_PATH, m_szPath );
strcat_s( szFile, MAX_PATH, "\\Sample.avi" );
/*저장할 파일위치 */
sprintf_s(szWritePath, MAX_PATH, m_szPath);
strcat_s(szWritePath, MAX_PATH, "\\Sample_write.avi");
/* AVI를 보여줄 윈도우를 하나 생성하고 */
cvNamedWindow( ptrWindName, CV_WINDOW_AUTOSIZE );
/*AVI를 읽어드릴 메모리( CvCapture )를 할당하고
CvCapture를 Global로 만든이유는 Tracbar CallbackFunction에서도 객체를 공유하기 때문에
*/
g_Cap = cvCreateFileCapture( szFile);
/* 영상 전체 프레임수를 얻고 */
int Frams = (int)cvGetCaptureProperty( g_Cap, CV_CAP_PROP_FRAME_COUNT );
/*영상의 프레임 Rate 즉, 초당 프레임 수를 얻고 */
double Fps = cvGetCaptureProperty( g_Cap, CV_CAP_PROP_FPS );
/* 영상 사이즈를 구하고 */
Size.width = ( int )cvGetCaptureProperty( g_Cap, CV_CAP_PROP_FRAME_WIDTH);
Size.height = ( int )cvGetCaptureProperty(g_Cap , CV_CAP_PROP_FRAME_HEIGHT);
/* 코덱이 설치 되어 있지않을 경우 아래와 같은 에러 발생
CV_ERROR( CV_StsUnsupportedFormat,
"FFMPEG could not find a codec matching the given FOURCC code. Use fourcc=CV_FOURCC_DEFAULT for auto
selection." );
테스트 PC에 가장 쉽게 코덱을 설치하는 방법은 곰 플레이어를 설치하면된다. 아니면 인터넷에서 공개된 코덱을 다운로드
받아 설치 하기 바란다.
해당 코덱을 이용한 VidoeWriter 객체를 생성하고
OpenCV가 제공해주는 CV_FOURCE 매크로로 사용할 기본 코덱을 지정해 할수 있다
여기서는 MJPG 코덱을 사용하기로 한다.....
*/
Writer = cvCreateVideoWriter( szWritePath, CV_FOURCC('M','J','P','G'), Fps, Size , 1) ;
/*프레임수가 0이 아니면 즉, 정상적인 영상파일이 존재하면ptrTrackbarName으로 AVI 동영상 Window에 최대값 Frams를
가지고 변화가 일어날때 onTracbarSlide를 호출하는 Trackbar를 만들고
*/
if ( Frams != 0 )
{
cvCreateTrackbar( ptrTrackbarName, ptrWindName, &g_Slider_Position, Frams , onTrackbarSlide );
}
/* CvCapture로부터 다음 프레임( IplImage ) 정보를 획득하고 */
frame = cvQueryFrame( g_Cap );
/* logpolar 형식의 IplImage 객체를 얻어서
logpolar 형식이란 사람의 시각이 보는 방법과 유사한 방식정도로만 알고 넘어가자 -.-;;
*/
logpolar_frame = cvCreateImage( Size, IPL_DEPTH_8U, 3);
/* AVI에서 한프레임씩 꺼내도록 Loop를 돌고 */
while( frame == cvQueryFrame( g_Cap) != NULL )
{
/* CvPoint, CvSize는 3장에서 다룬다 */
CvPoint2D32f CvPoint;
CvPoint.x = (float)frame->width;
CvPoint.y = (float)frame->height;
/* 로그폴라 형식으로 변환하고 ..6장에서 자세히 다룬다..*/
cvLogPolar( frame, logpolar_frame,
CvPoint,
40,
CV_INTER_LINEAR + CV_WARP_FILL_OUTLIERS);
/* 변환한 한 프레임을 저장하고 */
cvWriteFrame( Writer, logpolar_frame );
/* 33밀리 세컨드 동안 한 프레임을 보여주고 */
cvShowImage(ptrWindName,frame);
c = cvWaitKey( 33 );
/* 현재 보여주는 영상의 위치를 변경하고 Tracbar에게 프레임 위치가 변경되었음을 알려주고
onTrackbarSlider가 받겠지.....
*/
g_Slider_Position++;
cvSetTrackbarPos( ptrTrackbarName,ptrWindName, g_Slider_Position );
/* Esc Key가 눌러 졌으면 루프를 빠져 나가고 */
if ( c == 27 )
break;
}
/* 생성한 객체를 해제하고 */
cvReleaseVideoWriter(&Writer);
cvReleaseImage(&logpolar_frame);
cvReleaseCapture( &g_Cap );
/*생성된 윈도우를 해제 한다 */
cvDestroyWindow( ptrWindName );
실행결과
여기서도 가장 중요한것은 영상에 대한 모든 정보를 담고 있는 CvCapture 구조체가 아닐까 싶다..
찾아보니 오픈된 Interface가 없고 비됴 Capture 함수들을 위한 파라미터로만 사용된단다... 이게 다다..
아~~ 뭐밍??..다시 찾아 보자...
typedef struct CvCapture CvCapture;
The structure CvCapturedoes not have public interface and is used only as a parameter for video capture functions.
/***************************** CvCapture structure ******************************/
OpenCV가 설치된 내 경로를 기준으로( C:\OpenCV2.0\src\highgui\)에 _highgui.h 라는 Headfile이 존재한다..
struct CvCapture
{
virtual ~CvCapture() {}
virtual double getProperty(int) { return 0; }
virtual bool setProperty(int, double) { return 0; }
virtual bool grabFrame() { return true; }
virtual IplImage* retrieveFrame() { return 0; }
virtual IplImage* queryFrame() { return grabFrame() ? retrieveFrame() : 0; }
};
C로 구현한 OpenCV는 이렇게 struct안에 함수( 함수 포인터 )를 등록시키는 방법으로 Object Oriented 처럼 Design 한다..
참조 : https://code.ros.org/trac/opencv/browser/trunk/opencv/src/highgui/_highgui.h?rev=2209
/*************************** CvVideoWriter structure ****************************/
주의 할것은 생성된 CvCapture 객체는 영상(IplImage)에 대한 메모리를 CvCapture 구조체 내부 항목으로 직접 가지고 있기 때문에 함부로 외부에서 IplImage를 Release하면 안되고 CvCapture를 해제할 때 같이 Release할수 있도록 해야한다.
앞서 말했지만.. 나의 개인적인 방향은 가급적 어떤 API를 어떻게 사용 할 것인가를 알아가는것이 아니라 어떤 알고리즘을 어떻게 구현하고 있는가를 알아가는것이다...
읽고 테스트해보고 정리하고...생각보다 진도도 잘 안 나고 시간도 많이 걸린다... 그래도 한번은 정리를 하고 싶다..
오늘은 2장을 정리하는것으로 끝내고 ^^ .. 다들 즐거운 주말 보내세요 ^^