해당 포스트는 "열혈강의 영상처리 프로그래밍" 책의 내용을 요약한 것이다.
github => https://github.com/fbf7290/ImageProcessing/tree/ImageCalculator
※ 영상의 사칙 연산
영상과 영상 사이의 연산은 다른 부분을 발견하고자 뺄셈을 수행한다거나, 영상의 픽셀 자체 값이 아닌 픽셀이 가지는 어떠한 특성값을 계산할 때 자주 사용한다. 영상과 상수 사이의 연산은 영상의 밝기나 명암을 조절하는 데 사용할 수 있고, 픽셀에 어떠한 가중치 값을 곱할 때도 사용할 수 있다.
일단, 대화상자 기반 프로젝트 "ImageCalculator"을 만들고 이전 포스트에서 사용했던 CMyImage 폴더 내 파일들을 추가한다. 그리고 CMyImage 폴더 안에 MyImageFunc.cpp, MyImageFunc.h 파일을 생성한다.
template <typename T1, typename T2, typename TO> void AddImage(const CMyImage<T1>& src1, const CMyImage<T2>& src2, CMyImage<TO>& dst) { int nWStep = dst.GetWidth()*dst.GetChannel(); int nHeight = dst.GetHeight(); int r, c; if (sizeof(TO) == 1) // BYTE형 영상 { for (r = 0; r<nHeight; r++) { T1* pSrc1 = src1.GetPtr(r); T2* pSrc2 = src2.GetPtr(r); TO* pDst = dst.GetPtr(r); for (c = 0; c<nWStep; c++) { pDst[c] = CLIP(pSrc1[c] + pSrc2[c]); } } } else // BYTE형 이외의 영상 { for (r = 0; r<nHeight; r++) { T1* pSrc1 = src1.GetPtr(r); T2* pSrc2 = src2.GetPtr(r); TO* pDst = dst.GetPtr(r); for (c = 0; c<nWStep; c++) { pDst[c] = pSrc1[c] + pSrc2[c]; } } } }
위 AddImage 메서드는 두 개의 입력 영상을 더해서 출력 영상을 반환하는 메서드다. MyImageFunc.h 에 정의되어 있고, MyImageFunc.cpp 파일에 정의하지 않은 이유는 CMyImage 클래스가 템플릿으로 정의되어 있기 때문이다. 위 메서드를 보면 nWStep 멤버 변수를 이전 포스트들과 다르게 4의 배수로 맞추지 않고 그냥 너비에 채널을 곱했다. 그 이유는 두 입력 영상을 곱하고 출력을 할 때 nWStep 값을 4의 배수로 함으로써 생기는 Dummy data는 연산을 할 필요가 없기 때문이다. 그리고 연산을 하는 과정에서 템플릿의 타입이 BYTE 인것과 BYTE 아닌 것으로 나눴다. 이유는 BYTE 형끼리 덧셈을 하다가 연산의 결과가 255를 넘어가면 데이터에 잘못된 값이 입력되기 때문이다. 그래서 앞 포스트에서 정의한 CLIP 매크로를 사용해 255값을 넘지 않도록 해준다. BYTE 형이 아닌 다른 자료형들은 결과값이 해당 데이터 형의 표현 범위를 벗어나는 일이 거의 일어나지 않고 CLIP 매크로 함수를 자주 호출하면 프로그램의 수행 속도를 저하하는 요인이 되므로 CLIP 매크로를 쓰지 않도록 한다. 또한, 매개변수를 보면 입력 영상의 경우 변경할 일이 없으므로 const로 선언하였고 모든 매개변수에 참조값을 넘겨 주었다. 영상의 데이터 크기는 다른 변수들에 비해 매우 크기 때문에 값에 의한 호출 보다는 참조에 의한 호출로 인자 값을 전달하는 게 좋다.
template <typename T1, typename T2> CMyImage<T1> operator+(const CMyImage<T1<& src1, const CMyImage<T2>& src2) { int nWidth1 = src1.GetWidth(); int nHeight1 = src1.GetHeight(); int nWidth2 = src2.GetWidth(); int nHeight2 = src2.GetHeight(); int nChan1 = src1.GetChannel(); int nChan2 = src2.GetChannel(); ASSERT(nWidth1 == nWidth2 && nHeight1 == nHeight2 && nChan1 == nChan2); CMyImage<T1> ret(nWidth1, nHeight1, nChan1); AddImage(src1, src2, ret); return ret; }
위 메서드는 Operator overloading을 한 메서드이다. 보면 assert 메서드로 입력 영상의 너비, 높이, 채널 값을 비교하고 있는 데 그 이유는 연산을 할 입력 영상들의 크기값이 같아야 하기 때문이다. operator+ 메서드는 AddImage 메서드와 다르게 결과 영상을 담을 변수를 매개변수로 전달하지 못하기 때문에 operator+ 메서드 내부에서 결과 영상을 담을 변수를 선언한다. 그래서 operator+ 메서드를 반복할 경우 메모리에 결과 영상의 변수 할당 작업이 계속 일어나 프로그램 속도가 저하될 수 있다.
template <typename T1, typename T2, typename TO> void AddConst(const CMyImage<T1>& src, const T2 val, CMyImage<TO>& dst) { int nWStep = dst.GetWidth()*dst.GetChannel(); int nHeight = dst.GetHeight(); int r, c; if (sizeof(TO) == 1) // BYTE형 영상 { for (r = 0; r<nHeight; r++) { T1* pSrc = src.GetPtr(r); TO* pDst = dst.GetPtr(r); for (c = 0; c<nWStep; c++) { pDst[c] = CLIP(pSrc[c] + val); } } } else // BYTE형 이외의 영상 { for (r = 0; r<nHeight; r++) { T1* pSrc = src.GetPtr(r); TO* pDst = dst.GetPtr(r); for (c = 0; c<nWStep; c++) { pDst[c] = pSrc[c] + val; } } } }
위 메서드는 상수와 입력 영상 사이의 연산이다. AddImage 메서드를 이해 했다면 위 메서드도 이해 될 것이다.
위에서 +연산에 관한 영상과 영상끼리의 메서드, 연산자 다중 정의, 영상과 상수의 연산을 보았다. + 뿐만 아니라 -,*,/ 도 똑같이 구현하면 된다. 포스트의 첫 부분에 있는 github 참조 하길 바란다.
※ 영상의 논리 연산
영상의 논리 연산은 사칙 연산보다 활용빈도가 떨어지지만, 때때로 유용하다. 예를 들어, 영상의 특정 영역만 선택하고자 할 때, 선택하려는 영역의 픽셀값이 255인 마스크 영상을 만들어 이를 원본 영상과 논리곱 연산을 하면 결과 영상에서 마스크와 일치하는 부분만 남기는 효과를 얻을 수 있다.
영상의 논리 연산도 산술 연산과 똑같이 하면 되는 데 논리 연산의 경우 픽셀 데이터가 Byte형 경우에만 의미가 있어 템플릿을 사용하지 않고 오직 Byte 데이터 형으로 함수를 선언 및 정의한다. 그래서 산술 연산과 달리 MyImageFunc.cpp 파일에 정의해도 된다. 하지만 MyImageFunc.h 파일에 함수에 대한 선언을 해주어야 한다. 구체적인 소스코드는 github를 참조하길 바란다.
'영상처리 프로그래밍' 카테고리의 다른 글
영상의 광학적 변환(2) - 영상 감마 보정 (0) | 2017.06.10 |
---|---|
영상의 광학적 변환(1) - 밝기와 명암 조절 (0) | 2017.06.10 |
영상 입출력 프로그램 만들기(4) - 대화상자기반 영상 출력 (0) | 2017.06.09 |
영상 입출력 프로그램 만들기(3) - MDI에서 화면 출력 (0) | 2017.06.07 |
영상 입출력 프로그램 만들기(2) - 비트맵 파일 읽기와 쓰기 (0) | 2017.06.06 |
영상 입출력 프로그램 만들기(4) - 대화상자기반 영상 출력
해당 포스트는 "열혈강의 영상처리 프로그래밍" 책의 내용을 요약한 것이다.
※ 대화상자 기반 영상 출력
(github => https://github.com/fbf7290/ImageProcessing/tree/DialogViewer, 저의 미숙함으로 CImage 폴더를 제외한 파일이 날라갔습니다. 이번 예제는 CImage 폴더 내 소스 파일들이 핵심이기 때문에 상관 없을겁니다. )
새로운 대화상자 기반 프로젝트 "DialogViewer" 를 만들고 위와 같이 버튼 두 개를 배치했다. 그리고 이전 예제에서 사용했던 CMyImage 폴더와 폴더 내 파일들을 추가해준다. 먼저 영상 출력 버튼을 클릭했을 시 새로운 창이 떠서 이미지를 보여줘야 한다, 그래서 [클래스 추가] 대화 상자를 통해 CFrameWnd 클래스를 상속받는 CImageFrameWnd 클래스와 CView 클래스를 상속한 CImageView 클래스를 CMyImage 폴더 안에 생성한다. 원래는 CView만 생성해도 되지만 CFrameWnd 까지 생성한 이유는 CView 만 생성할 시 화면만 생성되고 창을 제어하는 닫기/숨기기/최대화 단추들이 없기 때문이다.
- CImageView 클래스
void CImageView::OnDraw(CDC* pDC) { CDocument* pDoc = GetDocument(); // TODO: 여기에 그리기 코드를 추가합니다. if (m_image.IsEmpty()) return; _DrawImage(pDC->m_hDC, m_image, 0, 0, m_image.GetWidth(), m_image.GetHeight(), 0, 0, m_image.GetWidth(), m_image.GetHeight()); }
CImageView 클래스는 CView 클래스를 상속한 클래스이기에 자동적으로 OnDraw 메서드가 정의되어 있다. 이전 포스트인 MDI에서 영상 출력에서와 같이 OnDraw() 메서드 내부에서 _DrawImage 메서드를 호출한다. 또한 이전 포스트와 같이 CImageView.cpp 파일 안에 아래의 코드를 추가해준다.
static BYTE bmiBuf[sizeof(BITMAPINFOHEADER) + 1024]; static const RGBQUAD GrayPalette[256] = { { 0, 0, 0, 0 },{ 1, 1, 1, 0 },{ 2, 2, 2, 0 },{ 3, 3, 3, 0 },{ 4, 4, 4, 0 },{ 5, 5, 5, 0 },{ 6, 6, 6, 0 },{ 7, 7, 7, 0 },{ 8, 8, 8, 0 },{ 9, 9, 9, 0 }, { 10, 10, 10, 0 },{ 11, 11, 11, 0 },{ 12, 12, 12, 0 },{ 13, 13, 13, 0 },{ 14, 14, 14, 0 },{ 15, 15, 15, 0 },{ 16, 16, 16, 0 },{ 17, 17, 17, 0 },{ 18, 18, 18, 0 },{ 19, 19, 19, 0 }, { 20, 20, 20, 0 },{ 21, 21, 21, 0 },{ 22, 22, 22, 0 },{ 23, 23, 23, 0 },{ 24, 24, 24, 0 },{ 25, 25, 25, 0 },{ 26, 26, 26, 0 },{ 27, 27, 27, 0 },{ 28, 28, 28, 0 },{ 29, 29, 29, 0 }, { 30, 30, 30, 0 },{ 31, 31, 31, 0 },{ 32, 32, 32, 0 },{ 33, 33, 33, 0 },{ 34, 34, 34, 0 },{ 35, 35, 35, 0 },{ 36, 36, 36, 0 },{ 37, 37, 37, 0 },{ 38, 38, 38, 0 },{ 39, 39, 39, 0 }, { 40, 40, 40, 0 },{ 41, 41, 41, 0 },{ 42, 42, 42, 0 },{ 43, 43, 43, 0 },{ 44, 44, 44, 0 },{ 45, 45, 45, 0 },{ 46, 46, 46, 0 },{ 47, 47, 47, 0 },{ 48, 48, 48, 0 },{ 49, 49, 49, 0 }, { 50, 50, 50, 0 },{ 51, 51, 51, 0 },{ 52, 52, 52, 0 },{ 53, 53, 53, 0 },{ 54, 54, 54, 0 },{ 55, 55, 55, 0 },{ 56, 56, 56, 0 },{ 57, 57, 57, 0 },{ 58, 58, 58, 0 },{ 59, 59, 59, 0 }, { 60, 60, 60, 0 },{ 61, 61, 61, 0 },{ 62, 62, 62, 0 },{ 63, 63, 63, 0 },{ 64, 64, 64, 0 },{ 65, 65, 65, 0 },{ 66, 66, 66, 0 },{ 67, 67, 67, 0 },{ 68, 68, 68, 0 },{ 69, 69, 69, 0 }, { 70, 70, 70, 0 },{ 71, 71, 71, 0 },{ 72, 72, 72, 0 },{ 73, 73, 73, 0 },{ 74, 74, 74, 0 },{ 75, 75, 75, 0 },{ 76, 76, 76, 0 },{ 77, 77, 77, 0 },{ 78, 78, 78, 0 },{ 79, 79, 79, 0 }, { 80, 80, 80, 0 },{ 81, 81, 81, 0 },{ 82, 82, 82, 0 },{ 83, 83, 83, 0 },{ 84, 84, 84, 0 },{ 85, 85, 85, 0 },{ 86, 86, 86, 0 },{ 87, 87, 87, 0 },{ 88, 88, 88, 0 },{ 89, 89, 89, 0 }, { 90, 90, 90, 0 },{ 91, 91, 91, 0 },{ 92, 92, 92, 0 },{ 93, 93, 93, 0 },{ 94, 94, 94, 0 },{ 95, 95, 95, 0 },{ 96, 96, 96, 0 },{ 97, 97, 97, 0 },{ 98, 98, 98, 0 },{ 99, 99, 99, 0 }, { 100, 100, 100, 0 },{ 101, 101, 101, 0 },{ 102, 102, 102, 0 },{ 103, 103, 103, 0 },{ 104, 104, 104, 0 },{ 105, 105, 105, 0 },{ 106, 106, 106, 0 },{ 107, 107, 107, 0 },{ 108, 108, 108, 0 },{ 109, 109, 109, 0 }, { 110, 110, 110, 0 },{ 111, 111, 111, 0 },{ 112, 112, 112, 0 },{ 113, 113, 113, 0 },{ 114, 114, 114, 0 },{ 115, 115, 115, 0 },{ 116, 116, 116, 0 },{ 117, 117, 117, 0 },{ 118, 118, 118, 0 },{ 119, 119, 119, 0 }, { 120, 120, 120, 0 },{ 121, 121, 121, 0 },{ 122, 122, 122, 0 },{ 123, 123, 123, 0 },{ 124, 124, 124, 0 },{ 125, 125, 125, 0 },{ 126, 126, 126, 0 },{ 127, 127, 127, 0 },{ 128, 128, 128, 0 },{ 129, 129, 129, 0 }, { 130, 130, 130, 0 },{ 131, 131, 131, 0 },{ 132, 132, 132, 0 },{ 133, 133, 133, 0 },{ 134, 134, 134, 0 },{ 135, 135, 135, 0 },{ 136, 136, 136, 0 },{ 137, 137, 137, 0 },{ 138, 138, 138, 0 },{ 139, 139, 139, 0 }, { 140, 140, 140, 0 },{ 141, 141, 141, 0 },{ 142, 142, 142, 0 },{ 143, 143, 143, 0 },{ 144, 144, 144, 0 },{ 145, 145, 145, 0 },{ 146, 146, 146, 0 },{ 147, 147, 147, 0 },{ 148, 148, 148, 0 },{ 149, 149, 149, 0 }, { 150, 150, 150, 0 },{ 151, 151, 151, 0 },{ 152, 152, 152, 0 },{ 153, 153, 153, 0 },{ 154, 154, 154, 0 },{ 155, 155, 155, 0 },{ 156, 156, 156, 0 },{ 157, 157, 157, 0 },{ 158, 158, 158, 0 },{ 159, 159, 159, 0 }, { 160, 160, 160, 0 },{ 161, 161, 161, 0 },{ 162, 162, 162, 0 },{ 163, 163, 163, 0 },{ 164, 164, 164, 0 },{ 165, 165, 165, 0 },{ 166, 166, 166, 0 },{ 167, 167, 167, 0 },{ 168, 168, 168, 0 },{ 169, 169, 169, 0 }, { 170, 170, 170, 0 },{ 171, 171, 171, 0 },{ 172, 172, 172, 0 },{ 173, 173, 173, 0 },{ 174, 174, 174, 0 },{ 175, 175, 175, 0 },{ 176, 176, 176, 0 },{ 177, 177, 177, 0 },{ 178, 178, 178, 0 },{ 179, 179, 179, 0 }, { 180, 180, 180, 0 },{ 181, 181, 181, 0 },{ 182, 182, 182, 0 },{ 183, 183, 183, 0 },{ 184, 184, 184, 0 },{ 185, 185, 185, 0 },{ 186, 186, 186, 0 },{ 187, 187, 187, 0 },{ 188, 188, 188, 0 },{ 189, 189, 189, 0 }, { 190, 190, 190, 0 },{ 191, 191, 191, 0 },{ 192, 192, 192, 0 },{ 193, 193, 193, 0 },{ 194, 194, 194, 0 },{ 195, 195, 195, 0 },{ 196, 196, 196, 0 },{ 197, 197, 197, 0 },{ 198, 198, 198, 0 },{ 199, 199, 199, 0 }, { 200, 200, 200, 0 },{ 201, 201, 201, 0 },{ 202, 202, 202, 0 },{ 203, 203, 203, 0 },{ 204, 204, 204, 0 },{ 205, 205, 205, 0 },{ 206, 206, 206, 0 },{ 207, 207, 207, 0 },{ 208, 208, 208, 0 },{ 209, 209, 209, 0 }, { 210, 210, 210, 0 },{ 211, 211, 211, 0 },{ 212, 212, 212, 0 },{ 213, 213, 213, 0 },{ 214, 214, 214, 0 },{ 215, 215, 215, 0 },{ 216, 216, 216, 0 },{ 217, 217, 217, 0 },{ 218, 218, 218, 0 },{ 219, 219, 219, 0 }, { 220, 220, 220, 0 },{ 221, 221, 221, 0 },{ 222, 222, 222, 0 },{ 223, 223, 223, 0 },{ 224, 224, 224, 0 },{ 225, 225, 225, 0 },{ 226, 226, 226, 0 },{ 227, 227, 227, 0 },{ 228, 228, 228, 0 },{ 229, 229, 229, 0 }, { 230, 230, 230, 0 },{ 231, 231, 231, 0 },{ 232, 232, 232, 0 },{ 233, 233, 233, 0 },{ 234, 234, 234, 0 },{ 235, 235, 235, 0 },{ 236, 236, 236, 0 },{ 237, 237, 237, 0 },{ 238, 238, 238, 0 },{ 239, 239, 239, 0 }, { 240, 240, 240, 0 },{ 241, 241, 241, 0 },{ 242, 242, 242, 0 },{ 243, 243, 243, 0 },{ 244, 244, 244, 0 },{ 245, 245, 245, 0 },{ 246, 246, 246, 0 },{ 247, 247, 247, 0 },{ 248, 248, 248, 0 },{ 249, 249, 249, 0 }, { 250, 250, 250, 0 },{ 251, 251, 251, 0 },{ 252, 252, 252, 0 },{ 253, 253, 253, 0 },{ 254, 254, 254, 0 },{ 255, 255, 255, 0 } }; bool _DrawImage(HDC hdc, const CByteImage& image, int nSrcX, int nSrcY, int nSrcW, int nSrcH, int nDstX, int nDstY, int nDstW, int nDstH) { if (!image.GetPtr()) return false; int nWidth = image.GetWStep() / image.GetChannel(); int nHeight = image.GetHeight(); int nBPP = image.GetChannel() * 8; ASSERT((BITMAPINFO*)bmiBuf && nWidth > 0 && nHeight > 0 && (nBPP == 8 || nBPP == 24 || nBPP == 32)); BITMAPINFOHEADER *bmih = &(((BITMAPINFO*)bmiBuf)->bmiHeader); memset(bmih, 0, sizeof(*bmih)); bmih->biSize = sizeof(BITMAPINFOHEADER); bmih->biWidth = nWidth; bmih->biHeight = -nHeight; bmih->biPlanes = 1; bmih->biBitCount = nBPP; bmih->biCompression = BI_RGB; if (nBPP == 8) memcpy(((BITMAPINFO*)bmiBuf)->bmiColors, GrayPalette, 256 * sizeof(RGBQUAD)); SetStretchBltMode(hdc, COLORONCOLOR); ::StretchDIBits( hdc, nDstX, nDstY, nDstW, nDstH, nSrcX, nSrcY, nSrcW, nSrcH, image.GetPtr(), // lpBits (LPBITMAPINFO) &(((BITMAPINFO*)bmiBuf)->bmiHeader), // lpBitsInfo DIB_RGB_COLORS, // iUsage SRCCOPY); // dwRop return true; }
OnDraw() 메서드 안에서 _DrawImage()를 호출할 때 View상에 그릴 이미지 객체가 필요하므로 CImageView.h 헤더파일에 출력용 영상을 멤버 변수로 선언한다.
protected: CByteImage m_image;
또한 m_image 멤버 변수에 영상을 입력해줄 setImage 메서드를 추가한다. 이 함수는 CImageFrameWnd 클래스가 멤버 변수인 CImageView 클래스에 영상을 지정하는 데 사용한다. 또한 CImageFrameWnd 클래스에서 CImageView 클래스의 뷰를 생성할 수 있게 해야해 CImageVIew 클래스 내부에 외부에서 CImageView를 생성할 수 있도록 하는 함수를 추가해야 한다.
void CImageView::SetImage(const CByteImage& input) { m_image = input; } BOOL CImageView::CreateEx(CWnd * pWnd) { return Create(NULL, NULL, WS_CHILD | WS_VISIBLE | WS_MAXIMIZE, CRect(0, 0, 0, 0), pWnd, AFX_IDW_PANE_FIRST, NULL); }
- CImageFrameWnd 클래스
먼저 CImageFrameWnd 클래스에서 CImageView를 생성해야 하므로 CImageView에 접근할 CImageView 변수를 선언한다.
protected: CImageView m_view;
CImageFrameWnd 클래스에서 CImageView 클래스 생성을 통한 화면에 영상 출력 기능은 생성자에서 구현한다.
static int nFrameWndCount = 0; CImageFrameWnd::CImageFrameWnd(const CByteImage &image, const char * name) { m_view.SetImage(image); CString wndName; if (name) wndName = name; else wndName.Format(_T("Image view %d"), nFrameWndCount); CRect rect(30 * nFrameWndCount, 30 * nFrameWndCount, image.GetWidth() + 30 * nFrameWndCount, image.GetHeight() + 30 * nFrameWndCount); Create(NULL, wndName, WS_OVERLAPPEDWINDOW, rect); nFrameWndCount++; ShowWindow(SW_SHOW); }
m_view.setImage(image) 를 통해서 CImageView 클래스 멤버 변수인 CByteImage 객체에 영상을 저장한다. 그 다음 wndName 창 이름을 지정하고 ShowWindow를 통해 최종적으로 영상을 출력해줄 창이 보여진다. nFrameWndCount는 영상을 여러 개 출력할 때 출력 뷰의 창 위치를 조정하기 위해서 만든 변수이다.
CImageFrameWnd * pImageWnd = new CImageFrameWnd(inputImage, "영상 출력");
위와 같이 생성자를 이용해서 inputImage 영상을 출력할 수 있는 창이 보여진다.
CImageFrameWnd를 통해 영상을 출력할 수 있는 창이 보여지지만 아직 영상을 실제로 출력하는 되지는 않는다. 왜나하면 CImageView 클래스에 영상 데이터만 입력했지 실제로 View를 생성하고 화면에 보여지는 우리가 위에서 정의한 CreateEx 메서드를 호출하지 않았기 때문이다. 그래서 CreateEx 메서드를 호출해야 하는 데 이 메서드를 CImageFrameWnd 클래스가 생성하고 나서 호출 돼야 하기 때문에 CimageFrameWnd 클래스의 OnCreate 메서드 내에서 호출하기로 하자. OnCreate() 함수는 CImageFrameWnd 클래스를 생성하고 나서 WM_CREATE 메시지에 의해 호출이 된다. 그래서 OnCreate 메서드를 재정의해야 하는데 재정의 하는 방법은 클래스 마법사 메뉴를 통해서 재정의 할 수 있다.
int CImageFrameWnd::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; // TODO: 여기에 특수화된 작성 코드를 추가합니다. m_view.CreateEx(this); return 0; }
위와 같이 재정의한 OnCreate 메서드 내부에 CImageView 클래스의 CreateEx 메서드를 호출했다. 그럼으로써 CImageView에서 View를 생성해 실제로 영상을 출력해준다.
- DialogViewerDlg 클래스
"영상 출력", "창 닫기" 버튼에서 실제 영상 창을 생성하고 닫기 위해서는 CImageFrameWnd 클래스에 접근해야 하므로 CImageFrameWnd 클래스 포인터 변수를 멤버변수로 선언한다.
CImageFrameWnd* m_pImageWnd;
그리고 "영상 출력", "창 닫기" 버튼에 대한 이벤트 처리 함수를 만들어 준다.
void CDialogViewerDlg::OnBnClickedButtonView() { // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다. CByteImage image = LoadImageFromDialog();
m_pImageWnd = new CImageFrameWnd(image, "영상 출력"); } void CDialogViewerDlg::OnBnClickedButtonClose() { // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다. delete m_pImageWnd;
}
※ 다중 영상 출력 관리하기
위 예제는 영상 하나 출력할 때 마다 CImageFrameWnd 인스턴스를 생성해야 해서 번거롭다. 특히, 여러 개의 영상을 출력하고 닫으려면 여러 개의 CImageFrameWnd 클래스를 관리해야 한다. 따라서 여러 개의 CImageFrameWnd 객체들을 관리하고 한 번에 여러 창을 띄우고 닫을 수 있도록 별도의 CImageFrameWndManager 클래스를 CMyImage 폴더 안에 생성한다. 해당 클래스는 CImageFrameWnd 클래스의 포인터를 입력 받아 배열에 추가하고 특정 CImageFrameWnd 객체를 소멸하고 배열에서 삭제하는 기능을 가진다.
class CImageFrameWndManager { public: CImageFrameWndManager(void); ~CImageFrameWndManager(void); void Add(CImageFrameWnd* pImageWnd); CImageFrameWnd* FindWnd(const char* name); void Delete(CImageFrameWnd* pImageWnd); void CloseAll(); protected: std::vectorm_vpImageWnd; }; extern void ShowImage(const CByteImage& input, const char *name = NULL); extern void CloseImage(const char* name); extern void CloseAllImages();
위 코드는 CImageFrameWndManager의 헤더 파일이다. 전역함수를 비롯해 각 함수에 대해서 알아보자.
CImageFrameWndManager::CImageFrameWndManager(void) { } CImageFrameWndManager::~CImageFrameWndManager(void) { CloseAll(); } void CImageFrameWndManager::Add(CImageFrameWnd* pImageWnd) { m_vpImageWnd.push_back(pImageWnd); }
소멸자를 보면 열려있는 영상 출력 창을 모두 닫는 기능이 있는 CloseAll 메서드가 호출된다. Add 메서드는 출력 창을 관리하기 위해서 동적으로 생성된 CImageFrameWnd 포인터를 입력받아 vector 배열에 데이터를 넣는 역할을 한다.
CImageFrameWnd* CImageFrameWndManager::FindWnd(const char* name) { CImageFrameWnd* ret = NULL; if (name) { for (unsigned int i = 0; i<m_vpImageWnd.size(); i++) { CString wName; m_vpImageWnd.at(i)-<GetWindowText(wName); if (wName == name) // 일치하는 문자열 발견 { ret = m_vpImageWnd.at(i); break; } } } return ret; }
FindWnd 메서드는 입력된 문자열과 같은 이름을 가진 CImageFrameWnd 객체를 찾는 기능을 한다. 여러 개의 열려있는 창을 관리하려면 특정 창 객체를 찾아서 사용해야 하는 데 그 때 사용되는 메서드이다.
void CImageFrameWndManager::Delete(CImageFrameWnd* pImageWnd) { for (unsigned int i = 0; i<m_vpImageWnd.size(); i++) { if (m_vpImageWnd.at(i) == pImageWnd) { delete m_vpImageWnd.at(i); m_vpImageWnd.erase(m_vpImageWnd.begin() + i); break; } } } void CImageFrameWndManager::CloseAll() { for (int i = m_vpImageWnd.size() - 1; i >= 0; i--) { delete m_vpImageWnd.at(i); m_vpImageWnd.erase(m_vpImageWnd.begin() + i); } }
Delete 메서드는 특정 창을 닫을 때 사용하는 메서드이고 CloseAll 메서드는 모든 창을 닫을 때 사용하는 메서드이다.
CImageFrameWndManager gImageFrameWndManager; void ShowImage(const CByteImage& input, const char *name) { CImageFrameWnd *pImageWnd = gImageFrameWndManager.FindWnd(name); if (pImageWnd) // 같은 이름의 창 발견 { pImageWnd->GetImageView().SetImage(input); // 기존 창의 영상 갱신 pImageWnd->GetImageView().Invalidate(false); // 화면 갱신 함수 호출 } else // 같은 이름의 창이 없을 시 새 창을 생성하고 배열에 추가 { gImageFrameWndManager.Add(new CImageFrameWnd(input, name)); } } void CloseImage(const char* name) { CImageFrameWnd* pImageWnd = gImageFrameWndManager.FindWnd(name); gImageFrameWndManager.Delete(pImageWnd); } void CloseAllImages() { gImageFrameWndManager.CloseAll(); }
영상을 출력하는 모든 부분에서 CImageFrameWndManager 클래스에 접근해야 하므로 편리성을 위해 CImageFrameWndManager 전역 변수를 선언했다. 그리고 나머지 ShowImage, CloseImage, CloseAllImages 메서드 또한 편리성을 위해 전역변수로 정의했다.
-DialogViewDlg 클래스
void CDialogViewerDlg::OnBnClickedButtonView() { // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다. CByteImage image = LoadImageFromDialog(); ShowImage(image, "Image"); } void CDialogViewerDlg::OnBnClickedButtonClose() { // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다. CloseImage("Image"); }
최종적으로 CImageFrameWndManager을 다 구현했을 시 "영상 출력", "창 닫기" 버튼 이벤트 처리기 구현이다.
'영상처리 프로그래밍' 카테고리의 다른 글
영상의 광학적 변환(1) - 밝기와 명암 조절 (0) | 2017.06.10 |
---|---|
픽셀 단위 영상 처리 - 영상 사칙/논리 연산 (0) | 2017.06.09 |
영상 입출력 프로그램 만들기(3) - MDI에서 화면 출력 (0) | 2017.06.07 |
영상 입출력 프로그램 만들기(2) - 비트맵 파일 읽기와 쓰기 (0) | 2017.06.06 |
영상 입출력 프로그램 만들기(1) - 비트맵 구조 (0) | 2017.06.06 |