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 오류가 반환되었습니다. 아래 포스트에 따로 작성한 구글에서 찾아본 방법들을 진행해보았는데도 해결이 안되어 혹시나해서 영어대소문자만으로 프로젝트 이름을 만드니 에러가 안납니다. 확인한 바로는 _ 또는 빈칸을 프로젝트 이름에 추가한 경우 에러가 발생합니다. |
1-3. 툴바의 솔루션 플랫폼을 x64로 변경합니다.
1-4. 다음 포스팅을 참고하여 프로젝트에 OpenCV 관련 설정을 추가합니다.
Visual Studio 2017/2019/2022에서 OpenCV 4.5.5를 사용하는 방법
https://webnautes.tistory.com/2038
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
시간날때마다 틈틈이 이것저것 해보며 블로그에 글을 남깁니다.
블로그의 문서는 종종 최신 버전으로 업데이트됩니다.
여유 시간이 날때 진행하는 거라 언제 진행될지는 알 수 없습니다.
영화,책, 생각등을 올리는 블로그도 운영하고 있습니다.
https://freewriting2024.tistory.com
제가 쓴 책도 한번 검토해보세요 ^^
그렇게 천천히 걸으면서도 그렇게 빨리 앞으로 나갈 수 있다는 건.
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!