|
OpenCV를 MFC 화면에 띄우기 위한 방법을 2가지로 보여준다.
1) OpenCV 3.1.0 에서 BitmapInfo를 이용한 MFC로 그리기
2) OpenCV 2.4.11 에서 CvvImage를 이용한 MFC로 그리기
1. Web캠 영상을 OpenCv를 이용해 MFC 화면에 출력하기 (BitmapInfo)
(참고: OpenCV-MFC에 출력 Cvvimage 대체)
* Visual Studio 2022 + OpenCV 4.9.0 사용 샘플
* Mat 이미지를 MFC 화면에 출력하는 예제
단계 | 설명 |
프로젝트 생성 | 대화상자로 프로젝트 생성한다. 프로젝트명: ocvCam OpenCV 설정하기: https://cafe.daum.net/smhan/cczU/61 |
리소스뷰에서 그림을 출력할 화면 추가하기 | m_View는 비트맵 영상을 출력할 부분으로 리소스에서 Picture Control로 선택하여 추가한 다음 변수추가는 CStatic으로 설정하여 추가하면 된다. 제어변수추가 이미지 화면 IDC_VIEW 컨트롤 m_View CStatic 카메라 시작 버튼 IDC_CAM_START 카메라 멈춤 버튼 IDC_CAM_STOP 이미지 읽기 버튼 IDC_IMG_LOAD 이미지 저장 버튼 IDC_IMG_SAVE |
xxxDlg.h | #include "opencv2/opencv.hpp" using namespace cv; public: Mat m_Image; VideoCapture m_Capture; BOOL m_bThreadFlag = FALSE; void FillBitmapInfo(BITMAPINFO* bmi, int width, int height, int bpp, int origin); void DisplayImage(CDC* pDC, CRect rect, Mat& srcimg); |
**Dlg.cpp 필요한 함수 추가 | void CocvCamDlg::FillBitmapInfo(BITMAPINFO* bmi, int width, int height, int bpp, int origin) { assert(bmi&&width >= 0 && height >= 0 && (bpp == 8 || bpp == 24 || bpp == 32)); BITMAPINFOHEADER *bmih = &(bmi->bmiHeader); memset(bmih, 0, sizeof(*bmih)); bmih->biSize = sizeof(BITMAPINFOHEADER); bmih->biWidth = width; bmih->biHeight = origin ? abs(height) : -abs(height); bmih->biPlanes = 1; bmih->biBitCount = (unsigned short)bpp; bmih->biCompression = BI_RGB; if (bpp == 8) { RGBQUAD *palette = bmi->bmiColors; for (int i = 0; i<256; i++) { palette[i].rgbBlue = palette[i].rgbGreen = palette[i].rgbRed = (BYTE)i; palette[i].rgbReserved = 0; } } } void CocvCamDlg::DisplayImage( CDC* pDC, CRect rect, Mat& srcimg ) { Mat img; int step = ((int)(rect.Width() / 4)) * 4; // 4byte 단위조정해야 영상이 기울어지지 않는다. if (srcimg.empty()) return; resize( srcimg, img, Size( step, rect.Height() ) ); uchar buffer[sizeof( BITMAPINFOHEADER ) * 1024]; BITMAPINFO* bmi = (BITMAPINFO*)buffer; int bmp_w = img.cols; int bmp_h = img.rows; int depth = img.depth(); int channels = img.channels(); int bpp = 8*channels; FillBitmapInfo( bmi, bmp_w, bmp_h, bpp, 0 ); int from_x = MIN( 0, bmp_w - 1 ); int from_y = MIN( 0, bmp_h - 1 ); int sw = MAX( MIN( bmp_w - from_x, rect.Width() ), 0 ); int sh = MAX( MIN( bmp_h - from_y, rect.Height() ), 0 ); SetDIBitsToDevice( pDC->m_hDC, rect.left, rect.top, sw, sh, from_x, from_y, 0, sh, img.data + from_y*img.step, bmi, 0 ); img.release(); } |
OnInitDialog() 함수 카메라 설정 | BOOL CocvCamDlg::OnInitDialog() { // TODO: 여기에 추가 초기화 작업을 추가합니다. m_Capture.open(0); // 카메라 연결 if (!m_Capture.isOpened()) AfxMessageBox(_T("There is no camera captured!")); } |
OnPaint() 함수 이미지 출력 | void CocvCamDlg::OnPaint() { if (IsIconic()) { ... else { // Mat 이미지인 m_Image를 화면 m_View에 출력해보자. if (m_Image.data != NULL) { CRect rect; CDC* pDC = m_View.GetDC(); // 출력한 부분의 DC 얻기 m_View.GetClientRect(rect); // 출력할 영역 얻기 DisplayImage(pDC, rect, m_Image); // 카메라에서 읽어들인 영상을 화면에 그리기 ReleaseDC(pDC); } CDialogEx::OnPaint(); } } |
불러오기 | void CocvCamDlg::OnBnClickedLoadBmp() { // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다. wchar_t szFilter[] = _T("Image (*.BMP) | *.BMP;*.GIF;*.JPG | All Files(*.*)|*.*||"); CFileDialog dlg(TRUE, _T("bmp"), _T("test"), OFN_HIDEREADONLY, szFilter); if (dlg.DoModal() == IDOK) { CT2CA ansiStr(dlg.GetPathName().GetBuffer(0)); // Unicode T to Ascii std::string filename( ansiStr ); m_Image = imread(filename,1); // 1 for color Invalidate(FALSE); } } |
저장하기 | void CcvCam2013Dlg::OnBnClickedSaveBmp() { // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다. wchar_t szFilter[] = _T("Image (*.BMP) | *.BMP;*.GIF;*.JPG | All Files(*.*)|*.*||"); CFileDialog dlg(FALSE, _T("bmp"), _T("test"), OFN_HIDEREADONLY, szFilter); if (dlg.DoModal() == IDOK) { // flip(m_Image, m_Image, 0); // vertical std::string filename(CT2CA(dlg.GetPathName())); // 유니코드변환 //CStringA filename(dlg.GetPathName().GetBuffer(0)); imwrite(filename, m_Image); } } |
카메라 시작 - 쓰레드 함수 처리 | // Cam 영상 출력 쓰레드 UINT ThreadImageCaptureFunc(LPVOID param) { CocvCamDlg *pDlg = ( CocvCamDlg *) param; while (pDlg->m_bThreadFlag) { pDlg->m_Capture >> pDlg->m_Image; // 영상 획득 pDlg->Invalidate(FALSE); // OnPaint()에서 이미지 출력 } return 0; } // 시작 버튼 void CocvCamDlg::OnBnClickedCamStart() { // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다. m_bThreadFlag = TRUE; CWinThread *pThread = ::AfxBeginThread(ThreadImageCaptureFunc, this); } |
카메라 멈춤 | // 멈춤 버튼 void CocvCamDlg::OnBnClickedCamStop() { // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다. m_bThreadFlag = FALSE; } |
2. CvvImage를 이용하여 OpenCV의 IplImage를 MFC로 그리기
다음과 같은 영상 캡쳐 프로그램을 작성해 보자. 오른쪽 하단에 조그마한 화면에 이진영상처리한 결과를 보여준다. 이 부분을 통해 어떻게 데이터를 처리하는지 공부할 수 있다.
샘플소스: (Timer 또는 Thread 사용 OpenCV 2.4.11, x86) - cvvimage 사용
1) OpenCV 2.4.11을 설치한다. (설치참고: https://cafe.daum.net/smhan/cczU/61 )
내컴퓨터 - 시스템 속성 - 고급시스템 설정 - 고급 -환경변수 - 시스템 변수에' 탭에서 Path를 수정한다. (재부팅 필요)
PATH ... ;C:\opencv\build\x86\vc12\bin (vc2013)
CLASSPATH ... ;C:\opencv\build\x86\vc12\bin (vc2013)
전처리기 정의에 추가한다. _CRT_SECURE_NO_WARNINGS;
C++ -> General -> Additional Include Directories에 추가한다. C:\opencv\build\include
Linker-> General -> Additional Library Directories에 추가한다. C:\opencv\build\x86\vc12\lib
Linker -> Input -> Additional Dependencies 에 추가한다.
2) 파일 추가
3) 다이얼로그 리소스에서 버튼과 CStatic을 추가한다.
IDC_VIEW // static 캡쳐 영상 띄울 window IDC_VIEW_BIN // static 이진 영상처리한 결과 IDC_CAM_START // button 캡쳐 시작 IDC_CAM_STOP // button 캡쳐 멈춤 IDC_BMP_LOAD // button bmp 파일 일기 IDC_BMP_SAVE // button bmp 파일 쓰기 |
4) ...Dlg.h에 다음을 추가한다.
#include <opencv/cv.h> #include <opencv/highgui.h> #include "CvvImage.h" // 클래스 내에 다음을 추가한다. IplImage* m_Image; CvCapture* m_Capture; CvvImage m_cImage; |
6) ...Dlg.cpp에 다음을 추가한다.
// Timer 대신에 쓰레드를 사용해 보자. 먼저 전역변수와 함수를 다음과 같이 선언한다. // 카메라 영상 캡처 시작 버튼을 누르면 다음과 같이 쓰레드를 실행한다. BOOL m_bThreadFlag; UINT ThreadImageCaptureFunc(LPVOID param); IplImage* m_Image; CvCapture* m_Capture; BOOL CcvCam2013Dlg::OnInitDialog() { ... m_Capture = cvCreateCameraCapture(0); if (!m_Capture) AfxMessageBox(_T("There is no camera captured!")); } void CcvCam2013Dlg::OnPaint() { ... if (m_Image != NULL) { // --------------------------------------------------------------- // 카메라에서 읽어들인 영상을 화면에 그리기 // --------------------------------------------------------------- CRect rect; CDC* pDC; pDC = m_View.GetDC(); m_View.GetClientRect(rect); m_cImage.CopyOf(m_Image); m_cImage.DrawToHDC(pDC->m_hDC, rect); ReleaseDC(pDC); // --------------------------------------------------------------- // 다음 소스를 통해 영상 데이터를 다루는 법을 익힌다. // --------------------------------------------------------------- // 칼라영상을 이진화 한다. pDC = m_BinView.GetDC(); m_BinView.GetClientRect(rect); for (int y = 0; y < m_Image->height; y++) { for (int x = 0; x < m_Image->widthStep; x++) { if ((unsigned char) m_Image->imageData[y*m_Image->widthStep + x] > 100) m_Image->imageData[y*m_Image->widthStep + x] = 255; else m_Image->imageData[y*m_Image->widthStep + x] = 0; } } // --------------------------------------------------------------- m_cImage.CopyOf(m_Image); m_cImage.DrawToHDC(pDC->m_hDC, rect); ReleaseDC(pDC); } } void CcvCam2013Dlg::OnBnClickedCamStart() { SetTimer(1, 30, NULL); } void CcvCam2013Dlg::OnBnClickedCamStop() { KillTimer(1); } void CcvCam2013Dlg::OnDestroy() { CDialogEx::OnDestroy(); KillTimer(1); if (m_Capture) cvReleaseCapture(&m_Capture); } void CcvCam2013Dlg::OnTimer(UINT_PTR nIDEvent) { // 영상 획득 m_Image = cvQueryFrame(m_Capture); Invalidate(FALSE); CDialogEx::OnTimer(nIDEvent); } void CcvCam2013Dlg::OnBnClickedLoadBmp() { wchar_t szFilter[] = _T("Image (*.BMP) | *.BMP;*.GIF;*.JPG | All Files(*.*)|*.*||"); CFileDialog dlg(TRUE, _T("bmp"), _T("test"), OFN_HIDEREADONLY, szFilter); if (dlg.DoModal() == IDOK) { CStringA filename(dlg.GetPathName().GetBuffer(0)); m_Image = cvLoadImage(filename,1); // 1 for color Invalidate(); } } void CcvCam2013Dlg::OnBnClickedSaveBmp() { wchar_t szFilter[] = _T("Image (*.BMP) | *.BMP;*.GIF;*.JPG | All Files(*.*)|*.*||"); CFileDialog dlg(FALSE, _T("bmp"), _T("test"), OFN_HIDEREADONLY, szFilter); if (dlg.DoModal() == IDOK) { cvFlip(m_Image, m_Image, 0); // vertical CStringA filename(dlg.GetPathName().GetBuffer(0)); cvSaveImage(filename, m_Image); } } // 카메라 영상 캡처 시작 버튼을 누르면 다음과 같이 쓰레드를 실행한다. void CcvCam2013Dlg::OnBnClickedCamStart() { m_bThreadFlag = TRUE; CWinThread *pThread = ::AfxBeginThread(ThreadImageCaptureFunc, this); } // 멈춤 버튼을 누르면 다음과 같이 쓰레드 플래그를 false로 만들어 종료시킨다. void CcvCam2013Dlg::OnBnClickedCamStop() { m_bThreadFlag = FALSE; } // 쓰레드 함수는 다음과같이 구성한다. UINT ThreadImageCaptureFunc(LPVOID param) { CcvCam2013Dlg *pDlg = (CcvCam2013Dlg *)param; while (m_bThreadFlag) { m_Image = cvQueryFrame(m_Capture); // 영상 획득 pDlg->Invalidate(FALSE); } return 0; } |
|