상세 컨텐츠

본문 제목

Rendering to OpenCV window

개발과 트러블슈팅

by 양고 2010. 4. 5. 11:11

본문

문제
OpenGL로 실시간으로 렌더링한 그림을 OpenCV에서 사용하고자 한다.
그러나 일반적으로 사용되는 GLUT은 OpenCV와 마찬가지로 윈도우나 메시지 처리를 위한 자체 프레임워크를 가지고 있어서, OpenCV와 함께 사용하는 것이 어렵다.
따라서 OpenCV에서 제공하는(cvNamedWindow) 윈도우에 OpenGL 렌더링이 가능한 방법을 찾게 되었다.

해결방향
우선 이 글을 참조하기 바란다.
OpenGL in a PROPER Windows APP (NO GLUT!!)
Nehe의 Lesson01도 비슷한 성격이지만, 위 링크가 더 자세한 설명을 제공한다.
렌더링 과정을 요약하면 이렇다. OpenGL drawing → HGLRC → HDC → application window.
이렇게 하려면 Window Class(WC)를 윈도우즈에 등록하고 직접 CreateWindow 해야한다. 그러나 이 방법은 기본적으로 Win32 응용프로그램 환경을 가정하고 있어서, 콘솔 프로그램이면서 윈도 생성 등은 HighGUI로 따로 빼 놓은 OpenCV에서 사용하기는 힘들다.

결론
OpenCV에서 HDC를 얻어서 HGLRC와 연결해 주면 된다. 예제에서는 "RENDERED"라는 이름의 창에 그린다.
다음과 같이 설정한 후, OpenGL에서 drawing해보자.

HINSTANCE hinstance = GetModuleHandle(NULL);
HWND hwnd = (HWND)cvGetWindowHandle("RENDERED");
HDC hdc = GetDC(hwnd);

PIXELFORMATDESCRIPTOR pfd = {0};
pfd.nSize = sizeof( PIXELFORMATDESCRIPTOR );
pfd.nVersion = 1;
pfd.dwFlags = PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 24;
pfd.cDepthBits = 32;
int chosenPixelFormat = ChoosePixelFormat(hdc, &pfd);
if( chosenPixelFormat == 0 )
FatalAppExit( NULL, TEXT("ChoosePixelFormat() failed!") );
printf("You got ID# %d as your pixelformat!\n", chosenPixelFormat);
if(SetPixelFormat(hdc, chosenPixelFormat, &pfd) == NULL)
FatalAppExit( NULL, TEXT("SetPixelFormat() failed!") );

HGLRC hglrc = wglCreateContext(hdc);

wglMakeCurrent(hdc, hglrc);


OpenCV 윈도우에서는 그려짐과 동시에 사라지는데, 이는 HighGUI의 redraw 메커니즘 때문일 것이다.
내용을 유지하고 싶다면, OpenGL drawing 직후 color buffer 내용을 읽어오면 된다.
예제에서는 "image"로 읽어와서 "MODEL" 윈도우에 표시한다.

glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, image->imageData);
GLenum ge = glGetError();
printf("GL Error: %X\n", ge);
cvShowImage("MODEL", image);



주의사항
OpenCV는 BGR 순서로 저장되기 때문에 R과 G가 바뀐다. 위아래도 바뀐다.
나중에 골치아파질 부분이다...
 
추가
화면에 보이는 OpenCV 윈도의 DC를 사용할 경우, 겹쳐진 다른 윈도 등이 해당 DC 위에 그려짐으로써 GL 렌더링 외에 다른 것들이 렌더링 컨텍스트에 함께 그려져 버린다. 이는 윈도를 minimize해도 마찬가지.
이런 문제를 피하기 위해서는 결국 off-screen rendering을 해야 한다. 간단하게 설명한다.
console application이지만 window class를 등록하고 CreateWindow - 아래 코드 참조.
그런 다음 얻어진 HDC를 가지고 HGLRC와 연결.
내 경우 그렇게 해도 잘 안 됐는데, PixelFormatDescriptor에서 PFD_DOUBLEBUFFER를 disable했기 때문으로 밝혀졌다. 반드시 enable해주자.

LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
 return DefWindowProc (hwnd, iMsg, wParam, lParam) ;
}
HWND hwndConsole = NULL;
HWND CreateOffscreenWindow()
{
 const char szClassName[] = "myWindowClass";
 WNDCLASSEX wc;
 hwndConsole = GetConsoleWindow();
 HINSTANCE hinstance = (HINSTANCE)GetWindowLong(hwndConsole, GWL_HINSTANCE);
//Step 1: Registering the Window Class
 wc.cbSize        = sizeof(WNDCLASSEX);
 wc.style         = 0;
 wc.lpfnWndProc   = WndProc; //(WNDPROC)GetWindowLongPtr(hwndConsole, GWLP_WNDPROC); // WndProc;
 wc.cbClsExtra    = 0;
 wc.cbWndExtra    = 0;
 wc.hInstance     = hinstance; //(HINSTANCE)GetWindowLong(hwndConsole, GWL_HINSTANCE); //hInstance;
 wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
 wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
 wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = NULL;
 wc.lpszClassName = szClassName;
 wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);
 if(!RegisterClassEx(&wc))   
 {
  MessageBox(NULL, "Window Registration Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK);
  return 0;
 }
// Step 2: Creating the Window
 HWND hwnd = CreateWindowEx(WS_EX_CLIENTEDGE, szClassName, "The title of my window", WS_OVERLAPPEDWINDOW,
  CW_USEDEFAULT, CW_USEDEFAULT, width, height, NULL, NULL, hinstance, NULL);
 if(hwnd == NULL)
 {
  MessageBox(NULL, "Window Creation Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK);
  return 0;
 }
// ShowWindow(hwnd, SW_SHOW); //nCmdShow);
// UpdateWindow(hwnd);
 return hwnd;
}

관련글 더보기