반응형

MFC에서 VFW(Video For Window)를 사용하여 웹캠의 영상을 가져온 후, 그레이스케일 영상으로 변환하여 

PIcture Control에 원본과 그레이스케일 영상을 보여주도록 만들어 보았습니다. 

 

ps. 프로젝트가 32비트인 경우에만 정상적으로  동작하는 듯합니다. 64비트로 변경시 capDriverConnect 함수에서 에러가 나네요. 

2020.11.20 다시 테스트해보니 64비트 에서도 정상적으로 웸캠 영상을 가져옵니다. 하지만 일부 웹캠에서 사용하는 픽셀 포맷과 안맞아서 검은 화면이 나오는 듯합니다. 

 




OpenCV를 사용하여 얻은 영상을 Picture Control에 보여주는 방식도 있습니다.  다음 포스트를 참고하세요. 

 

OpenCV와 MFC 연동해서 웹캠 영상을 출력하기

https://webnautes.tistory.com/2039 



이미지를 OpenCV의 포맷으로 변환하는 것은 나중에 해볼까 합니다. 




1. 우선 Visual Studio에서 MFC 프로젝트를 만듭니다.  포스트에서는 Visual Studio 2019를 사용하였습니다. 

 

MFC 항목이 보이지 않는다면 다음 포스트를 참고하여 추가 패키지를 설치하세요.



C/C++, Win32 API, MFC 개발을 위해 Visual Studio Community 2019 설치하는 방법

https://webnautes.tistory.com/1328

 




응용 프로그램 종류로 “대화 상자 기반”을 선택하고 SDL은 체크 해제합니다. 

추가로 바꿀 옵션은 없습니다.  마침 버튼을 클릭합니다. 

 




2. 왼쪽에 보이는 프로젝트 창에서  리소스 뷰를 선택합니다 

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

 

Dialog 항목에서 다이얼로그를 선택합니다. IDD_ABOUTBOX 아래에 있는 것을 선택하면 됩니다. 

 




3. 마우스로 모서리를 클릭후 드래그하여 다이얼로그의 크기를 적절히 조정한 후..

비주얼 스튜디오 오른쪽에 보이는  도구 상자를 클릭하여 Picture Control을 드래그하여 두 개 가져옵니다. 

 

Picture Control의 크기를 적절히 조정한 후,  스크린샷처럼 나란히 배치합니다. 

 



각각의 Picture Control을 선택한 후, 마우스 우클릭하여 보이는 메뉴에서 속성을 선택합니다. 

각각 ID를 ICD_PICTURE1, IDC_PICTURE2로 변경합니다. 

굳이 안해줘도 되지만 해주는 편이 좋을 듯합니다. 

 

 




각각의 Picture Control을 선택한 후,  마우스 우클릭하여 보이는 메뉴에서 변수 추가를 선택합니다. 

각각 변수 이름을 m_picture1, m_picture2로 추가한 후, 마침 버튼을 클릭하면 됩니다. 

 




4. 프로젝트 창에서 솔루션 탐색기를 선택한 후,

소스 파일 항목에서 Dlg.cpp로 끝나는 파일을 선택합니다.  

본 포스트에서는 webcam_exampleDlg.cpp입니다. 

 




5. 소스 파일 상단에 다음 코드를 추가합니다. 

 

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


#include <vfw.h>

#pragma comment (lib,"vfw32.lib")


HWND hWindow;
int iBitmapFlag = 0;
CBitmap capture_map;
unsigned char *arrImage1;
unsigned char *arrImage2;




6. OnInitDialog 함수에 다음 코드를 추가합니다

 

      // TODO: 여기에 추가 초기화 작업을 추가합니다.


hWindow = capCreateCaptureWindow(NULL, WS_CHILD | WS_VISIBLE, 0, 0, 640, 480, m_hWnd, 1);


bool ret = capDriverConnect(hWindow, 0);
if (ret == false)
{
AfxMessageBox(L"Webcam not found", MB_ICONERROR);

return false;
}


// picture control에 영상이 보이도록 합니다.
CRect wndRC;
m_picture1.GetClientRect(wndRC);
m_picture1.MapWindowPoints(this, wndRC);
wndRC.DeflateRect(1, 1, 1, 1);

::SetWindowPos(hWindow, NULL, wndRC.left, wndRC.top, wndRC.Width(), wndRC.Height(), SWP_NOZORDER);
::ShowWindow(hWindow, SW_SHOW);

// 일정 주기로 웹캠으로부터 영상을 가져오기 위해 타이머를 사용합니다.
SetTimer(1, 30, 0);

return TRUE;  // 포커스를 컨트롤에 설정하지 않으면 TRUE를 반환합니다.




7. 메뉴에서 프로젝트 > 클래스 마법사를 선택합니다. 

 

클래스 이름이 Dlg로 끝나는 클래스인지 확인한 후,  왼쪽에 보이는 탭에서 메시지를 선택합니다. 

메시지 목록에서 WM_TIMER를 선택 한 후, 오른쪽에 보이는 처리 추가 버튼을 클립합니다.  

 



기존  처리기에 OnTimer 함수가 추가된 것을 볼 수 있습니다. 

 




8. 메시지 항목에서 WM_DESTROY를 선택하고 처리기 추가 버튼을 클릭합니다. 

 




OnDestroy 함수가 추가된 것을 볼 수 있습니다. 확인 버튼을 클릭합니다.

 




9. OnTimer 함수에 다음 코드를 추가합니다. 

 

// TODO: 여기에 메시지 처리기 코드를 추가 및/또는 기본값을 호출합니다.

if (nIDEvent == 1)
{
capGrabFrame(hWindow);
}


RECT rect;
m_picture1.GetClientRect(&rect);
int window_width = rect.right;
int window_height = rect.bottom;

int image_width = window_width;
int image_height = window_height;
int b = 4;

arrImage1 = new unsigned char[b * image_width * image_height];
arrImage2 = new unsigned char[b * image_width * image_height];

BITMAPINFO bmpinfo;
bmpinfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmpinfo.bmiHeader.biWidth = image_width;
bmpinfo.bmiHeader.biHeight = -image_height;
bmpinfo.bmiHeader.biPlanes = 1;
bmpinfo.bmiHeader.biBitCount = 32;
bmpinfo.bmiHeader.biCompression = BI_RGB;
bmpinfo.bmiHeader.biSizeImage = 0;
bmpinfo.bmiHeader.biXPelsPerMeter = 0;
bmpinfo.bmiHeader.biYPelsPerMeter = 0;
bmpinfo.bmiHeader.biClrUsed = 0;
bmpinfo.bmiHeader.biClrImportant = 0;




// 웹캠 영상을 배열에 저장합니다.
CDC dc;
HDC hdc = ::GetDC(m_picture1.m_hWnd);
dc.Attach(hdc);

CDC     capture_dc;


if (iBitmapFlag == 0) {
capture_map.CreateCompatibleBitmap(&dc, image_width, image_height);
iBitmapFlag = 1;
}

capture_dc.CreateCompatibleDC(&dc);
capture_dc.SelectObject(&capture_map);
capture_dc.BitBlt(0, 0, image_width, image_height, &dc, 0, 0, SRCCOPY); 



LPBYTE lpBits = arrImage1;  
int err;
int ret = GetDIBits(capture_dc, capture_map, 0, image_height, lpBits, &bmpinfo, DIB_RGB_COLORS);
if (ret == 0)   err = GetLastError();

capture_dc.DeleteDC();
ReleaseDC(&dc);



// 그레이스케일 영상 or 컬러 영상 보여줄지 여부
int iFlagGray = 1;

for (int y = 0; y < image_height; y++)
{

for (int x = 0; x < image_width; x++)
{
int index1 = ((y * image_width) + x);
index1 *= 4;

if (iFlagGray == 1) //grayscale
{

// 그레이스케일로 변환합니다.
// 편의상 그레이스케일 이미지도 32비트 영상입니다.
int grayblue = arrImage1[index1] * 0.11;        // 11%
int graygreen = arrImage1[index1 + 1] * 0.59;   // 59%
int grayred = arrImage1[index1 + 2] * 0.30;     // 30%
int grayall = grayblue + graygreen + grayred;

arrImage2[index1]    = grayall;     // blue
arrImage2[index1 +1] = grayall;     // green
arrImage2[index1 +2] = grayall;     // red
arrImage2[index1 +3] = 0;           // this byte is not used
}
else  //color
{
arrImage2[index1]    = arrImage1[index1];     //blue
arrImage2[index1 +1] = arrImage1[index1 + 1]; //green
arrImage2[index1 +2] = arrImage1[index1 + 2]; //red
arrImage2[index1 +3] = 0;                     // this byte is not used
}
}

}


int bpp = 32;


CImage cimage_mfc;
cimage_mfc.Create(window_width, window_height, 32);


BITMAPINFO *bitInfo = (BITMAPINFO*)malloc(sizeof(BITMAPINFO) + 256 * sizeof(RGBQUAD));
bitInfo->bmiHeader.biBitCount = bpp;
bitInfo->bmiHeader.biWidth = window_width;
bitInfo->bmiHeader.biHeight = -window_height;
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) // 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;
}
}*/



// 이미지 크기와 영상을 보여줄 영역 크기가 같은 경우와 다른 경우를 다르게 처리
if (image_width == window_width  && image_height == window_height)
{

SetDIBitsToDevice(cimage_mfc.GetDC(),
0, 0, window_width, window_height,
0, 0, 0, image_height,
arrImage2, bitInfo, DIB_RGB_COLORS);
}
else
{
int destx = 0, desty = 0;
int destw = window_width;
int desth = window_height;

int imgx = 0, imgy = 0;
int imgWidth = image_width;
int imgHeight = image_height;

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



// picture control에 이미지를 보여줍니다.
HDC dc2 = ::GetDC(m_picture2.m_hWnd);
cimage_mfc.BitBlt(dc2, 0, 0);
::ReleaseDC(m_picture2.m_hWnd, dc2);

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

delete arrImage1;
delete arrImage2;


CDialogEx::OnTimer(nIDEvent);



10. OnDestroy 함수에 다음 코드를 추가합니다. 

 

void CwebcamexampleDlg::OnDestroy()
{
CDialogEx::OnDestroy();

// TODO: 여기에 메시지 처리기 코드를 추가합니다.
capCaptureStop(hWindow);
capDriverDisconnect(hWindow);
}





참고한 곳

http://www.orgler.it/webcam.htm


반응형

문제 발생시 지나치지 마시고 댓글 남겨주시면 가능한 빨리 답장드립니다.

도움이 되셨다면 토스아이디로 후원해주세요.
https://toss.me/momo2024


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

+ Recent posts