반응형

OpenCV에서 캡처한 웹캠 영상을 MFC의 Picture Control에 보여주기 위해 필요한 과정을 설명합니다.

OpenCV 4.5.3과 Visual Studio Community 2019를 사용해서 진행했지만 다른 버전에서도 거의 동일하게 동작할 듯합니다. 



다이얼로그기반 MFC 프로젝트 생성부터 예제 코드 실행까지 단계별로 설명합니다.



1. MFC 응용 프로그램 생성

 

2. UI 구성

 

3. OpenCV 관련 코드 추가

 

4. 실행 결과

 

5. 참고




2015. 11.  19

최초 작성



2018. 10. 22

그레이스케일 영상으로 변환 후, 영상출력이 제대로 안되는 문제가 있었네요..

BITMAPINFO에 palette를 추가해서 해결했습니다. 



2021. 9. 25

Visual Studio 2019, OpenCV 4.5.3에서 테스트



1. MFC 응용 프로그램 생성

 

1-0. 새 프로젝트 만들기를 클릭합니다. 

 

 

1-1. MFC 앱을 선택하고 다음을 클릭합니다. ( Visual Studio 2019를 최근 버전으로 업데이트후 진행한거라 항목이 다를 수 있습니다.) 

 




1-2. 적당한 프로젝트 이름을 적은 후, 만들기를 클릭합니다. 

Visual Studio 2019 버전 16.11.3에선 에러가 발생하니  프로젝트 이름을 영어대소문자만으로 구성해야 합니다. 

 




1-3. 애플리케이션 종류를 대화 상자 기반으로 변경하고 마침을 클릭합니다. 

 




Visual Studio 2019 버전 16.11.3에서 프로젝트 생성시 다음과 같은 에러가 발생할 수 있습니다. 

COM 구성 요소 호출에서 HRESULT E_FAIL 오류가 반환되었습니다.





아래 포스트에 따로 작성한 구글에서 찾아본 방법들을 진행해보았는데도 해결이 안되어 혹시나해서 영어대소문자만으로 프로젝트 이름을 만드니 에러가 안납니다. 

확인한 바로는 _ 또는 빈칸을 프로젝트 이름에 추가한 경우 에러가 발생합니다. 


Visual Studio 2019 16.11.3에서 MFC 프로젝트 생성시 "COM 구성 요소 호출에서 HRESULT E_FAIL 오류가 반환되었습니다" 에러 발생
https://webnautes.tistory.com/1539 





1-3. 툴바의 솔루션 플랫폼 x64로 변경합니다. 

 




1-4. 다음 포스팅을 참고하여 프로젝트에 OpenCV 관련 설정을 추가합니다. 9 ~ 19번을 진행하면 됩니다.

 

Visual Studio 2017/2019에서 OpenCV 4.5.3를 사용하는 방법

http://webnautes.tistory.com/1132 





2. UI 구성

OpenCV에서 캡쳐한 영상이 보여질 영역이 필요합니다. 



2-1. 아래 스크린샷에 보이는 리소스 뷰의 다이얼로그를 더블 클릭합니다.  

 

리소스 뷰가 보이지 않으면 메뉴에서 보기 > 다른 창 > 리소스 뷰  또는 보기 > 리소스 뷰 를 선택하세요.

같은 비주얼 스튜디오 2017이라도 업데이트 정도에 따라 다르군요..

 




2-2. 우선 다이얼로그에 있는 모든 구성요소를 제거하세요. 

그리고나서 도구 상자를 클릭 후  Picture Control을 드래그하여 다이얼로그에 추가합니다.

 




2-3.  Picture Control을 배치한 결과입니다. 

 




2-4. 다이얼로그 위에 있는 Picture Control을 선택한 상태에서 마우스 우클릭하여 보이는 메뉴에서 속성을 클릭합니다.

 

ID IDC_PICTURE로 수정하고 엔터를 눌러 변경합니다. 

Ctrl + S를 눌러서 저장합니다. 

 




2-5. 다시 다이얼로그 위에 있는 Picture Control을 선택하고 마우스 오른쪽 버튼을 클릭하여 메뉴에서 변수 추가를 선택합니다.

 

제어 변수 추가 창의 이름 항목m_picture를 입력하고 마침을 클릭합니다. 

 



버전 차이로 다음처럼 보일 수 도 있습니다. 

 




3. OpenCV 관련 코드 추가

 

3-1. Ctrl + Shift + X를 눌러서 클래스 마법사를 실행합니다.




3-2. 클래스 이름을 COpenGLwithMFCDlg로 변경합니다. 끝부분이 Dlg인 것을 선택하면 됩니다. 

메시지 탭을 선택하고 리스트에서 WM_DESTROY를 선택 후 처리기 추가 버튼을  클릭합니다.

 




3-3. 같은 방식으로 WM_TIMER도 선택해서 추가하고 확인 버튼을 클릭합니다.

 




3-4. OpenCVwithMFCDlg.h 파일에 OpenCV 헤더파일을 추가합니다. 

 

#include "opencv2/opencv.hpp" 

 

using namespace cv;

 

 

3-5. OpenCVwithMFCDlg.h 파일에 멤버 변수를 추가해줍니다. 

 

VideoCapture *capture;

Mat mat_frame;

CImage cimage_mfc;

 



3-6. OpenCVwithMFCDlg.cpp 파일의 OnInitDialog 함수에 코드를 추가합니다.

 

capture = new VideoCapture(0);

if (!capture->isOpened())

{

MessageBox(_T("웹캠을 열수 없습니다. \n"));

}

 

//웹캠 크기를  320x240으로 지정    

capture->set(CAP_PROP_FRAME_WIDTH, 320);

capture->set(CAP_PROP_FRAME_HEIGHT, 240);

 

SetTimer(1000, 30, NULL);

 



3-7. OpenCVwithMFCDlg.cpp 파일의 OnTimer 함수에 코드를 추가합니다.

 

OnTimer 함수에서 영상을 가져온 후  CImage로 변환해서 화면에 출력합니다. 

이때 출력되는 화면의 크기는 다이얼로그에 올려둔 picture 컨트롤러의 크기입니다.

void COpenCVwithMFCDlg::OnTimer(UINT_PTR nIDEvent)
{
	// TODO: 여기에 메시지 처리기 코드를 추가 및/또는 기본값을 호출합니다.

	  //mat_frame가 입력 이미지입니다. 
	capture->read(mat_frame);


	//이곳에 OpenCV 함수들을 적용합니다.
	//여기에서는 그레이스케일 이미지로 변환합니다.
	cvtColor(mat_frame, mat_frame, COLOR_BGR2GRAY);



	//화면에 보여주기 위한 처리입니다.
	int bpp = 8 * mat_frame.elemSize();
	assert((bpp == 8 || bpp == 24 || bpp == 32));

	int padding = 0;
	//32 bit image is always DWORD aligned because each pixel requires 4 bytes
	if (bpp < 32)
		padding = 4 - (mat_frame.cols % 4);

	if (padding == 4)
		padding = 0;

	int border = 0;
	//32 bit image is always DWORD aligned because each pixel requires 4 bytes
	if (bpp < 32)
	{
		border = 4 - (mat_frame.cols % 4);
	}



	Mat mat_temp;
	if (border > 0 || mat_frame.isContinuous() == false)
	{
		// Adding needed columns on the right (max 3 px)
		cv::copyMakeBorder(mat_frame, mat_temp, 0, 0, 0, border, cv::BORDER_CONSTANT, 0);
	}
	else
	{
		mat_temp = mat_frame;
	}


	RECT r;
	m_picture.GetClientRect(&r);
	cv::Size winSize(r.right, r.bottom);

	cimage_mfc.Create(winSize.width, winSize.height, 24);


	BITMAPINFO* bitInfo = (BITMAPINFO*)malloc(sizeof(BITMAPINFO) + 256 * sizeof(RGBQUAD));
	bitInfo->bmiHeader.biBitCount = bpp;
	bitInfo->bmiHeader.biWidth = mat_temp.cols;
	bitInfo->bmiHeader.biHeight = -mat_temp.rows;
	bitInfo->bmiHeader.biPlanes = 1;
	bitInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	bitInfo->bmiHeader.biCompression = BI_RGB;
	bitInfo->bmiHeader.biClrImportant = 0;
	bitInfo->bmiHeader.biClrUsed = 0;
	bitInfo->bmiHeader.biSizeImage = 0;
	bitInfo->bmiHeader.biXPelsPerMeter = 0;
	bitInfo->bmiHeader.biYPelsPerMeter = 0;


	//그레이스케일 인경우 팔레트가 필요
	if (bpp == 8)
	{
		RGBQUAD* palette = bitInfo->bmiColors;
		for (int i = 0; i < 256; i++)
		{
			palette[i].rgbBlue = palette[i].rgbGreen = palette[i].rgbRed = (BYTE)i;
			palette[i].rgbReserved = 0;
		}
	}


	// Image is bigger or smaller than into destination rectangle
	// we use stretch in full rect

	if (mat_temp.cols == winSize.width && mat_temp.rows == winSize.height)
	{
		// source and destination have same size
		// transfer memory block
		// NOTE: the padding border will be shown here. Anyway it will be max 3px width

		SetDIBitsToDevice(cimage_mfc.GetDC(),
			//destination rectangle
			0, 0, winSize.width, winSize.height,
			0, 0, 0, mat_temp.rows,
			mat_temp.data, bitInfo, DIB_RGB_COLORS);
	}
	else
	{
		// destination rectangle
		int destx = 0, desty = 0;
		int destw = winSize.width;
		int desth = winSize.height;

		// rectangle defined on source bitmap
		// using imgWidth instead of mat_temp.cols will ignore the padding border
		int imgx = 0, imgy = 0;
		int imgWidth = mat_temp.cols - border;
		int imgHeight = mat_temp.rows;

		StretchDIBits(cimage_mfc.GetDC(),
			destx, desty, destw, desth,
			imgx, imgy, imgWidth, imgHeight,
			mat_temp.data, bitInfo, DIB_RGB_COLORS, SRCCOPY);
	}


	HDC dc = ::GetDC(m_picture.m_hWnd);
	cimage_mfc.BitBlt(dc, 0, 0);


	::ReleaseDC(m_picture.m_hWnd, dc);

	cimage_mfc.ReleaseDC();
	cimage_mfc.Destroy();

	CDialogEx::OnTimer(nIDEvent);
}



4. 실행 결과

 

4-1. F5키를 눌러서 실행한 결과입니다.  현재 코드는 스크린샷과 달리 흑백으로 보입니다. 



3-7에서 다음 코드를 주석처리하면 컬러로 보이게 됩니다.


cvtColor(mat_frame, mat_frame, COLOR_BGR2GRAY);

 





5. 참고

 

http://stackoverflow.com/a/29006218   

 

http://pklab.net/pagefiles/LoadBmp/PkMatToGDI.hpp 

 

https://stackoverflow.com/a/28525365

 

반응형

진행해본 결과물을 기록 및 공유하는 공간입니다.
잘못된 부분이나 개선점을 알려주시면 반영하겠습니다.


소스코드 복사시 하단에 있는 앵커 광고의 왼쪽 위를 클릭하여 닫은 후 해야 합니다.


문제가 생기면 포스트와 바뀐 환경이 있나 먼저 확인해보세요.
질문을 남겨주면 가능한 빨리 답변드립니다.


제가 쓴 책도 한번 검토해보세요 ^^

  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기

댓글을 달아 주세요

TistoryWhaleSkin3.4">
  1. 이전 댓글 더보기