OpenGL 렌더링 결과를 보여주기 위해 필요한 윈도우(UI)OpenGL 컨텍스트 생성을 MFC로 처리하는 방법을 설명합니다.


64비트 윈도우(x64)에서 OpenGL 2.x  API를 사용하여 프로그래밍을 하는 경우를 대상으로 하고 있습니다.

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




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

MFC 응용 프로그램 생성


GLEW 라이브러리 추가


UI 구성


OpenGL 관련 코드 추가


테스트


관련 포스팅


참고한 사이트



MFC 응용 프로그램 생성

Visual Studio 2017을 실행시키고 메뉴에서 파일 > 새로 만들기 > 프로젝트를 선택합니다.

MFC 응용 프로그램을 선택하고 프로젝트 이름을 지정해 줍니다.




MFC 응용 프로그램 마법사가 시작됩니다.

현재 프로젝트 설정을 보여주는데 추가 설정을 위해 다음을 클릭합니다.



대화 상자 기반을 선택하고 마침을 클릭합니다.



메뉴 아래에 보이는 솔루션 구성 Debug, 솔루션 플랫폼 x64로 변경합니다.

프로젝트에 추가하게 되는  GLEW 라이브러리가  Release용이지만  MFC 디버깅을 위해 Debug로 설정합니다.-




이제부터 Visual Studio 2017에서 OpenGL 개발을 하기위해 필요한 라이브러리를 프로젝트에 추가하는 방법을 설명합니다.

프로젝트 이름 OpenGL with MFC이고 프로젝트 위치  C:\Users\사용자이름\Documents\Visual Studio 2017\Projects\OpenGL with MFC인 경우로 가정하고 설명합니다.

사용하시는 환경에 맞추어 적절히 변경하시면 됩니다.



GLEW 라이브러리 추가

OpenGL 3.x 이상에서는 호출 가능한 OpenGL API 함수들이 런타임에 결정됩니다.

GLEW를 사용하여 런타임OpenGL API 함수를 호출합니다.  


아래 링크에서 GLEW를 다운로드 받을 수  있습니다.

http://glew.sourceforge.net/



Windows 32-bit and 64-bit를 클릭하여 미리 라이브러리를  컴파일해둔 바이너리를 다운로드 받습니다.



압축을 풀은 후, include 폴더를 복사하여



프로젝트 폴더의 경로  OpenGL with MFC\OpenGL with MFC붙여넣기해줍니다.

기존에 생성된 include 폴더에 추가로 복사되어 집니다.



압축을 풀은 폴더의 경로 glew-2.0.0-win32\glew-2.0.0\lib\Release\x64에 있는  glew32.lib, glew32.lib를 복사하여



프로젝트 폴더의 경로  OpenGL-Example\OpenGL-Example\lib에 붙여넣기 해줍니다.



GLEW 라이브러리를 사용하여 컴파일한 실행 파일과 같이 glew32.dll 파일을 배포해야 합니다.


아직 실행 파일이 저장되는 폴더가 생성되어 있지 않기 때문에 dll 파일을 복사해줄 위치가 아직 없습니다.

아직 코드를 추가하지 않았지만 Visual Studio에서 Ctrl + F7를 눌러 컴파일을 해주면 실행파일이 저장될 위치를 위한 폴더들이 생성됩니다.



압축을 풀은 폴더의 경로 glew-2.0.0-win32\glew-2.0.0\bin\Release\x64에 있는  glew32.dll 파일을  복사하여



프로젝트 폴더의 경로  OpenGL with MFC\x64\Debug에 붙여넣기 해줍니다.



UI 구성

OpenGL의 렌더링 결과가 보여질 영역이 필요합니다.

오른쪽 끝에 위치한 도구 상자를 클릭 후  Picture Control을 드래그하여 다이얼로그 추가합니다.



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

버튼은 이후 코드 수정시 사용하게 될 거 같아서 남겨두었습니다.



다이얼로그 위에 있는 Picture Control을 선택한 상태에서 속성을 클릭합니다.

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



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

멤버 변수 추가 마법사 창의 변수 이름 항목에 m_picture를 입력하고 마침을 클릭합니다.



OpenGL 관련 코드 추가

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

클래스 이름을 COpenGLwithMFCDlg로 변경합니다.

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



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



OpenGL with MFCDlg.h 파일에 GLEW 헤더파 일과 라이브러리 파일를 지정해줍니다.

// OpenGL with MFCDlg.h : 헤더 파일
//

#pragma once
#include "afxwin.h"

//상대경로로 헤더파일을 지정합니다.
#include "./include/GL/glew.h"
#include "./include/GL/wglew.h"

//사용할 라이브러리를 지정해줍니다.
#pragma comment(lib, "OpenGL32.lib")
#pragma comment(lib, "./lib/glew32.lib")



OpenGL with MFCDlg.h 파일 끝에 필요한 멤버함수 및 멤버 변수를 추가해줍니다.

public:
CStatic m_picture;

public:
afx_msg void OnDestroy();
afx_msg void OnTimer(UINT_PTR nIDEvent);

protected:
virtual BOOL GetOldStyleRenderingContext(void);

private:
//OpenGL Setup
BOOL GetRenderingContext();
//Rendering Context and Device Context Pointers
HGLRC     m_hRC;
CDC*      m_pDC;

float angle;
};



OpenGL with MFCDlg.cpp 파일의 OnInitDialog 함수에 붉은색 줄을 추가합니다.

// TODO: 여기에 추가 초기화 작업을 추가합니다.
//OpenGL context 생성
if (!GetRenderingContext())
{
AfxMessageBox(CString("OpenGL 초기화중 에러가 발생하여 프로그램을 실행할 수 없습니다."));
return -1;
}

angle = 0;
SetTimer(1000, 30, NULL);

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



OnDestroy 함수에 다음 붉은색 줄을 추가합니다.

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

// TODO: 여기에 메시지 처리기 코드를 추가합니다.

if (FALSE == ::wglDeleteContext(m_hRC))
{
AfxMessageBox(CString("wglDeleteContext failed"));
}
}



OnTimer 함수에 붉은색 코드를 추가합니다.

void COpenGLwithMFCDlg::OnTimer(UINT_PTR nIDEvent)
{
// TODO: 여기에 메시지 처리기 코드를 추가 및/또는 기본값을 호출합니다.
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glLoadIdentity();

//z축을 중심으로 설정된 angle로 회전한다.
glRotatef(angle, 0.0f, 0.0f, 1.0f);

//중앙이 원점에 오도록 삼각형을 그린다.
glBegin(GL_TRIANGLES); //3점이 하나의 삼각형을 구성한다. 반시계 방향으로 3점의 vertex를 지정해줘야 한다.
glColor3f(1.0f, 0.0f, 0.0f); //빨간색 지정
glVertex3f(-0.5f, -0.5f, 0.0f);    // 왼쪽 아래 vertex
glColor3f(0.0f, 1.0f, 0.0f); //녹색 지정
glVertex3f(0.5f, -0.5f, 0.0f);    // 오른쪽 아래 vertex
glColor3f(0.0f, 0.0f, 1.0f); //파란색 지정
glVertex3f(0.0f, 0.5f, 0.0f);    // 위쪽 vertex
glEnd();

//삼각형 회전각 증가
angle += 0.5f;

//화면 업데이트
SwapBuffers(m_pDC->GetSafeHdc());
}



OpenGL with MFCDlg.cpp 파일에 GetRenderingContext 함수를 추가합니다.

BOOL COpenGLwithMFCDlg::GetRenderingContext()
{
//픽처 컨트롤에만 그리도록 DC 생성
//참고 https://goo.gl/CK36zE
CWnd* pImage = GetDlgItem(IDC_PICTURE);
CRect rc;
pImage->GetWindowRect(rc);
m_pDC = pImage->GetDC();


if (NULL == m_pDC)
{
AfxMessageBox(CString("Unable to get a DC"));
return FALSE;
}


if (!GetOldStyleRenderingContext())
{
return TRUE;
}


//Get access to modern OpenGL functionality from this old style context.
glewExperimental = GL_TRUE;
if (GLEW_OK != glewInit())
{
AfxMessageBox(CString("GLEW could not be initialized!"));
return FALSE;
}


//참고 http://gamedev.stackexchange.com/a/30443
GLint attribs[] =
{
//OpenGL 2.0 사용
WGL_CONTEXT_MAJOR_VERSION_ARB, 2,
WGL_CONTEXT_MINOR_VERSION_ARB, 0,
// Uncomment this for forward compatibility mode
//WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
// Uncomment this for Compatibility profile
WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB,
// We are using Core profile here
//WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
0
};


HGLRC CompHRC = wglCreateContextAttribsARB(m_pDC->GetSafeHdc(), 0, attribs);
if (CompHRC && wglMakeCurrent(m_pDC->GetSafeHdc(), CompHRC))
m_hRC = CompHRC;

return TRUE;
}




OpenGL with MFCDlg.cpp 파일에 GetOldStyleRenderingContext 함수를 추가합니다.

BOOL COpenGLwithMFCDlg::GetOldStyleRenderingContext()
{
//A generic pixel format descriptor. This will be replaced with a more
//specific and modern one later, so don't worry about it too much.
static PIXELFORMATDESCRIPTOR pfd =
{
sizeof(PIXELFORMATDESCRIPTOR),
1,
PFD_DRAW_TO_WINDOW |            // support window
PFD_SUPPORT_OPENGL |            // support OpenGL
PFD_DOUBLEBUFFER,               // double buffered
PFD_TYPE_RGBA,                  // RGBA type
32,                             // 32-bit color depth
0, 0, 0, 0, 0, 0,               // color bits ignored
0,                              // no alpha buffer
0,                              // shift bit ignored
0,                              // no accumulation buffer
0, 0, 0, 0,                     // accum bits ignored
24,                        // 24-bit z-buffer
0,                              // no stencil buffer
0,                              // no auxiliary buffer
PFD_MAIN_PLANE,                 // main layer
0,                              // reserved
0, 0, 0                         // layer masks ignored
};

// Get the id number for the best match supported by the hardware device context
// to what is described in pfd
int pixelFormat = ChoosePixelFormat(m_pDC->GetSafeHdc(), &pfd);

//If there's no match, report an error
if (0 == pixelFormat)
{
AfxMessageBox(CString("ChoosePixelFormat failed"));
return FALSE;
}

//If there is an acceptable match, set it as the current
if (FALSE == SetPixelFormat(m_pDC->GetSafeHdc(), pixelFormat, &pfd))
{
AfxMessageBox(CString("SetPixelFormat failed"));
return FALSE;
}

//Create a context with this pixel format
if (0 == (m_hRC = wglCreateContext(m_pDC->GetSafeHdc())))
{
AfxMessageBox(CString("wglCreateContext failed"));
return FALSE;
}

//Make it current.
if (FALSE == wglMakeCurrent(m_pDC->GetSafeHdc(), m_hRC))
{
AfxMessageBox(CString("wglMakeCurrent failed"));
return FALSE;
}
return TRUE;
}




테스트

F5를 눌러서 실행시켜 봅니다.






관련 포스팅

OpenGL과 MFC 연동 예제( GLEW 사용, Dialog 기반, OpenGL 3.x 코드)

http://webnautes.tistory.com/1109


참고한 사이트

http://www.cs.uregina.ca/Links/class-info/315/WWW/Lab1/MFC/

 

 

출처 : http://webnautes.tistory.com/1108?category=702754

블로그 이미지

맨오브파워

한계를 뛰어 넘어서..........

,


컨볼루션(Convolution)

입력 영상을 스캔하면서 현재 위치의 픽셀과 마스크 크기 내에 포함되는 주변 픽셀을 마스크 배열과 컨볼루션하여 결과 영상의 현재 위치값으로 결정합니다.  

마스크에서 정의한 가중치에 따라 이미지를 흐리기(blurring) 만들거나 선명하게(sharpening) 만들 수 있습니다. 또는 이미지 상에 있는 에지를 검출하는데 사용할 수 있습니다. 마스크는 3,5,7,9,11.. 처럼 홀수크기를 갖습니다.

 


입력 이미지 상의  위치 (1,1)에 3 x 3 크기의 마스크를  컨볼루션하는 예를 들어 보겠습니다.  이해하기 쉽게 현재 위치 (1,1)에 마스크 중앙 (1,1)이 오도록 겹처놓으면,  마스크 배열 항목에 대응되는 이미지 상의 위치들이 있습니다.  컨볼루션 계산은 마스크와 이미지 상에 대응되는 값끼리 곱하여 모두 더하여 구합니다.  결과값을 결과 영상의 현재 위치에 기록하면 됩니다. 

img_output(1,1) = img_input(0,0) x mask(0,0) + img_input(0,1) x mask(0,1) 

                      + img_input(0,2) x mask(0,2) + img_input(1,0) x mask(1,0) 

                      + img_input(1,1) x mask(1,1) + img_input(1,2) x mask(1,2)

                      + img_input(2,0) x mask(2,0) + img_input(2,1) x mask(2,1) 

                      + img_input(2,2) x mask(2,2) 


컨볼루션시 주의할 점은 이미지 테두리에서는 컨볼루션 계산을 할 수 없습니다. 마스크 배열의 일부 항목이 마스크와 대응되지 않기 때문입니다. 


이 문제를 해결하는 여러가지 방법이 있는데 그 중에 한가지 방법이 마스크 크기 / 2 만큼 입력 영상의 테두리 영역을 무시하고 컨볼루션을 계산하는 것입니다.  3x3 마스크의 경우에는 3/2=1.5이므로 1픽셀 크기만큼 입력 영상의 테두리 영역을 무시하게 됩니다.  입력 영상의 테두리 영역에 해당되는 결과 영상의 위치에는 모두 0으로 채우거나 입력 영상의 테두리 영역에 있는 값을 복사해줍니다.


다른 방법은 범위를 벗어난 경우에는 해당 위치의 값을 0으로 처리하는 것입니다. 마스크 크기 3 x 3의 경우 이미지 가장자리에 크기 1인 0으로 채워진 가상의 테두리가 있는 것으로 보는 것입니다. 


또다른 방법은 이미지 테두리에 있는 값을 범위 벗어난 경우에 사용하는 것입니다. 



스무딩(Smoothing)

영상을 흐리게(Blurring)하거나 영상에 존재하는 노이즈를 제거하기 위해 사용됩니다. 


스무딩은 현재 위치의 픽셀 값과 이웃 픽셀 값들의 평균으로 결과 영상의 현재 위치 값을 결정하는 방법입니다. 영상에 적용하면 선명도가 떨어지는 대신 노이즈를 제거하거나 끊어진 에지를 연결하게 됩니다. 


스무딩을 위한 mask로는  모든 픽셀에 똑같은 가중치를 부여하는 mean mask와 


마스크 중앙으로 갈수록 높은 가중치를 부여하는 gaussian mask가 있습니다. 


영상에 mean mask를 적용한 결과입니다.  마스크의 크기를 크게 할 수록  Blurring 효과가 커지며 계산양은 많아 지기 때문에 결과를 얻기까지 시간이 더 걸립니다. 


입력 영상


3 x 3 mean 마스크를 사용한 경우


5 x 5 mean 마스크를 사용한 경우


25 x 25 mean 마스크를 사용한 경우


구현한 소스 코드입니다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#include <opencv2/highgui.hpp>
#include <iostream>
 
using namespace std;
using namespace cv;
 
 
int main()
{
    Mat img_input, img_gray, img_result;
 
    //이미지 파일을 읽어와서 img_input에 저장
    img_input = imread("input5.png", IMREAD_COLOR);
    if (img_input.empty())
    {
        cout << "파일을 읽어올수 없습니다." << endl;
        exit(1);
    }
 
 
    //입력영상을 그레이스케일 영상으로 변환
    img_gray = Mat(img_input.rows, img_input.cols, CV_8UC1);
 
    for (int y = 0; y < img_input.rows; y++)
    {
        for (int x = 0; x < img_input.cols; x++)
        {
            //img_input으로부터 현재 위치 (y,x) 픽셀의
            //blue, green, red 값을 읽어온다. 
            uchar blue = img_input.at<Vec3b>(y, x)[0];
            uchar green = img_input.at<Vec3b>(y, x)[1];
            uchar red = img_input.at<Vec3b>(y, x)[2];
 
            //blue, green, red를 더한 후, 3으로 나누면 그레이스케일이 된다.
            uchar gray = (blue + green + red) / 3.0;
 
            //Mat타입 변수 img_gray에 저장한다. 
            img_gray.at<uchar>(y, x) = gray;
        }
    }
 
 
    //mean mask
    int mask1[3][3= { {1,1,1},
                        { 1,1,1 },
                        { 1,1,1 }};
    int mask2[5][5= { { 1,1,1,1,1 },
                        { 1,1,1,1,1 },
                        { 1,1,1,1,1 },
                        { 1,1,1,1,1 },
                        { 1,1,1,1,1 } };
    int mask3[25][25= { 
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
 
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
 
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
 
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 } };
 
 
    long int sum;
    img_result = Mat(img_input.rows, img_input.cols, CV_8UC1);
    int masksize = 3;
        
    for (int y = 0; y < img_input.rows; y++)
    {
        for (int x = 0; x < img_input.cols; x++)
        {
            sum = 0;
            for (int i = -1 * masksize / 2; i <= masksize / 2; i++)
            {
                for (int j = -1 * masksize / 2; j <= masksize / 2; j++)
                {
                    
 
                    //영상 범위를 벗어난 경우 테두리 값을 사용 
                    int new_y = y + i;
                    int new_x = x + j;
 
                    if (new_y < 0) new_y = 0;
                    else if (new_y > img_input.rows - 1) new_y = img_input.rows - 1;
 
                    if (new_x < 0) new_x = 0;
                    else if (new_x > img_input.cols - 1) new_x = img_input.cols - 1;
 
 
                    //선택한 마스크 크기 따라 컨볼루션 계산
                    if ( masksize == 3 )
                        sum += img_gray.at<uchar>( new_y, new_x) * mask1[masksize / 2 +i][masksize / 2 +j];
                    else if ( masksize == 5 )
                        sum += img_gray.at<uchar>(new_y, new_x) * mask2[masksize / 2 + i][masksize / 2 + j];
                    else if ( masksize == 25 )
                        sum += img_gray.at<uchar>(new_y, new_x) * mask3[masksize / 2 + i][masksize / 2 + j];
                }
            }
            
            //마스크 크기로 나누어 주어야 한다. 
            sum = sum / (double)(masksize*masksize);
 
            //0~255 사이값으로 조정
            if (sum > 255) sum = 255;
            if (sum < 0) sum = 0;
 
            img_result.at<uchar>(y, x) = sum;
        }
    }
    
 
    //화면에 결과 이미지를 보여준다.
    imshow("입력 영상", img_input);
    imshow("입력 그레이스케일 영상", img_gray);
    imshow("결과 영상", img_result);
 
    //아무키를 누르기 전까지 대기
    while (cvWaitKey(0== 0);
 
    //결과를 파일로 저장
    imwrite("img_gray.jpg", img_gray);
    imwrite("img_result.jpg", img_result);
}
cs



샤프닝(Sharpening)

영상을 선명하게 하거나 에지를 검출하는데 사용됩니다. 


라플라시안(Laplacian) 마스크입니다.  마스크 배열 항목의 합이 0이 됨을 알 수 있습니다.  영상에 적용시키면 픽셀값이 일정한 영역에서는 0이 되고 픽셀값이 크게 변하는 에지가 있는 영역에서는 값이 커지는 게 됩니다. 


라플라시안 마스크를 영상에 적용시켜보면 에지가 검출됩니다.


입력영상


결과 영상


영상을 선명하게 만드는 효과를 얻기 위해서는 픽셀값이 일정한 영역이 보존되어야 합니다. 그렇게 하기 위해서 마스크 배열의 항목들의 합이 1이 되도록 라플라시안 마스크의 중앙값을 1 증가시켜줍니다. 


영상에 적용시켜 보면 더 선명해진 것을 확인 할 수 있습니다. 


구현한 소스코드입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include <opencv2/highgui.hpp>
#include <iostream>
 
using namespace std;
using namespace cv;
 
 
int main()
{
    Mat img_input, img_gray, img_result;
 
    //이미지 파일을 읽어와서 img_input에 저장
    img_input = imread("input6.png", IMREAD_COLOR);
    if (img_input.empty())
    {
        cout << "파일을 읽어올수 없습니다." << endl;
        exit(1);
    }
 
 
    //입력영상을 그레이스케일 영상으로 변환
    img_gray = Mat(img_input.rows, img_input.cols, CV_8UC1);
 
    for (int y = 0; y < img_input.rows; y++)
    {
        for (int x = 0; x < img_input.cols; x++)
        {
            //img_input으로부터 현재 위치 (y,x) 픽셀의
            //blue, green, red 값을 읽어온다. 
            uchar blue = img_input.at<Vec3b>(y, x)[0];
            uchar green = img_input.at<Vec3b>(y, x)[1];
            uchar red = img_input.at<Vec3b>(y, x)[2];
 
            //blue, green, red를 더한 후, 3으로 나누면 그레이스케일이 된다.
            uchar gray = (blue + green + red) / 3.0;
 
            //Mat타입 변수 img_gray에 저장한다. 
            img_gray.at<uchar>(y, x) = gray;
        }
    }
 
 
    //라플라시안 마스크
    int mask1[3][3= { {-1,-1,-1},  //에지 검출
                        { -1,8,-1 },
                        { -1,-1,-1 }};
 
    int mask2[3][3= { { -1,-1,-1 }, //영상 선명하게
                        { -1,9,-1 },
                        { -1,-1,-1 } };
 
 
    long int sum;
    img_result = Mat(img_input.rows, img_input.cols, CV_8UC1);
    int masksize = 3;
        
    for (int y = 0 + masksize / 2; y < img_input.rows - masksize / 2; y++)
    {
        for (int x = 0 + masksize / 2; x < img_input.cols - masksize / 2; x++)
        {
            sum = 0;
            for (int i = -1 * masksize / 2; i <= masksize / 2; i++)
            {
                for (int j = -1 * masksize / 2; j <= masksize / 2; j++)
                {
                    //sum += img_gray.at<uchar>(y + i, x + j) * mask1[masksize / 2 + i][masksize / 2 + j];
                    sum += img_gray.at<uchar>(y + i, x + j) * mask2[masksize / 2 + i][masksize / 2 + j];
                }
            }
            
            //0~255 사이값으로 조정
            if (sum > 255) sum = 255;
            if (sum < 0) sum = 0;
 
            img_result.at<uchar>(y, x) = sum;
        }
    }
    
 
    //화면에 결과 이미지를 보여준다.
    imshow("입력 영상", img_input);
    imshow("입력 그레이스케일 영상", img_gray);
    imshow("결과 영상", img_result);
 
    //아무키를 누르기 전까지 대기
    while (cvWaitKey(0== 0);
 
    //결과를 파일로 저장
    imwrite("img_gray.jpg", img_gray);
    imwrite("img_result.jpg", img_result);
}
cs

출처 : http://webnautes.tistory.com/1044?category=710857

블로그 이미지

맨오브파워

한계를 뛰어 넘어서..........

,


히스토그램 평활화는 히스토그램을 이용하여 이미지의 명암 대비를 개선시키는 방법입니다.  


그레이스케일 영상의 경우 픽셀이 가질 수 있는 값의 범위는 0 ~ 255 사이의 값입니다.  이미지 상에서 픽셀값이 0인 갯수, 픽셀값이 1인 갯수, ... , 픽셀값이 255인 갯수를 세어서 배열에 저장한 것이 히스토그램입니다. 왼쪽 이미지에 대해 히스토그램을 구하여 그래프로 그려보면 중앙의 좁은 범위에 픽셀들이 몰려있는 것을 볼 수 있습니다.  그래프에서 x축은 0~255사이의 픽셀값 범위이며 y축은 픽셀 갯수입니다. 

 



 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

히스토그램 평활화를 적용시키면 이미지의 픽셀값이 0~255 범위내에 골고루 분산되어 이미지의 명암대비가 개선됩니다.  



 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


구현 과정

1. 입력 영상에 대한 히스토그램과 누적 히스토그램을 계산합니다. 누적 히스토그램은 현재 픽셀값까지의 히스토그램을 더해주는 것입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    //입력 그레이스케일 영상의 히스토그램 계산
    int histogram[256= { 0, };
 
    for (int y = 0; y < img_input.rows; y++)
    {
        for (int x = 0; x < img_input.cols; x++)
        {
            int value = img_gray.at<uchar>(y, x);
            histogram[value] += 1;
        }
    }
 
    //입력 그레이스케일 영상의 누적 히스토그램 계산
    int cumulative_histogram[256= { 0, };
    int sum = 0;
 
    for (int i = 1; i < 256; i++)
    {
        sum += histogram[i];
        cumulative_histogram[i] = sum;
    }
cs


2. 정규화된 누적 히스토그램을 구하여 입력 그레이스케일 영상에 히스토그램 평활화를 적용합니다. 

P_new = N[ P_old ] * 255

N : 정규화된 누적 히스토그램

P_old : 입력 영상의 픽셀값

P_new: 결과 영상의 픽셀값

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    //입력 그레이스케일 영상의 정규화된 누적 히스토그램 계산
    float normalized_cumulative_histogram[256= { 0.0, };
    int image_size = img_input.rows * img_input.cols;
 
    for (int i = 0; i < 256; i++)
    {
        normalized_cumulative_histogram[i] = cumulative_histogram[i] / (float)image_size;
    }
 
 
    //히스토그램 평활화 적용 및 결과 영상의 히스토그램 계산
    img_result = Mat(img_input.rows, img_input.cols, CV_8UC1);
    int histogram2[256= { 0, };
    for (int y = 0; y<img_input.rows; y++)
    {
        for (int x = 0; x < img_input.cols; x++)
        {
            img_result.at<uchar>(y, x) = normalized_cumulative_histogram[img_gray.at<uchar>(y, x)] * 255;
            histogram2[img_result.at<uchar>(y, x)] += 1;
        }
    }
 
    
    //결과 영상의 누적 히스토그램 계산
    int cumulative_histogram2[256= { 0, };
    sum = 0;
 
    for (int i = 1; i < 256; i++)
    {
        sum += histogram2[i];
        cumulative_histogram2[i] = sum;
    }
cs


입력 영상의 경우 픽셀들이 중앙에 몰려 있었는데 


이미지 평활화 적용 후, 0~255사이에 넓게 분포하게 되었습니다.  누적 히스토그램도 점차적으로 증가하게 되었습니다.



전체 소스코드입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#include <opencv2/highgui.hpp>
#include <iostream>
 
using namespace std;
using namespace cv;
 
 
int main()
{
    Mat img_input, img_gray, img_result, img_histogram, img_histogram2;
 
    //이미지 파일을 읽어와서 img_input에 저장
    img_input = imread("input4.jpg", IMREAD_COLOR);
    if (img_input.empty())
    {
        cout << "파일을 읽어올수 없습니다." << endl;
        exit(1);
    }
 
 
    //입력영상을 그레이스케일 영상으로 변환
    img_gray = Mat(img_input.rows, img_input.cols, CV_8UC1);
 
    for (int y = 0; y < img_input.rows; y++)
    {
        for (int x = 0; x < img_input.cols; x++)
        {
            //img_input으로부터 현재 위치 (y,x) 픽셀의
            //blue, green, red 값을 읽어온다. 
            uchar blue = img_input.at<Vec3b>(y, x)[0];
            uchar green = img_input.at<Vec3b>(y, x)[1];
            uchar red = img_input.at<Vec3b>(y, x)[2];
 
            //blue, green, red를 더한 후, 3으로 나누면 그레이스케일이 된다.
            uchar gray = (blue + green + red) / 3.0;
 
            //Mat타입 변수 img_gray에 저장한다. 
            img_gray.at<uchar>(y, x) = gray;
        }
    }
 
 
    //입력 그레이스케일 영상의 히스토그램 계산
    int histogram[256= { 0, };
 
    for (int y = 0; y < img_input.rows; y++)
    {
        for (int x = 0; x < img_input.cols; x++)
        {
            int value = img_gray.at<uchar>(y, x);
            histogram[value] += 1;
        }
    }
 
    //입력 그레이스케일 영상의 누적 히스토그램 계산
    int cumulative_histogram[256= { 0, };
    int sum = 0;
 
    for (int i = 1; i < 256; i++)
    {
        sum += histogram[i];
        cumulative_histogram[i] = sum;
    }
 
    //입력 그레이스케일 영상의 정규화된 누적 히스토그램 계산
    float normalized_cumulative_histogram[256= { 0.0, };
    int image_size = img_input.rows * img_input.cols;
 
    for (int i = 0; i < 256; i++)
    {
        normalized_cumulative_histogram[i] = cumulative_histogram[i] / (float)image_size;
    }
 
 
    //히스토그램 평활화 적용 및 결과 영상의 히스토그램 계산
    img_result = Mat(img_input.rows, img_input.cols, CV_8UC1);
    int histogram2[256= { 0, };
    for (int y = 0; y<img_input.rows; y++)
    {
        for (int x = 0; x < img_input.cols; x++)
        {
            img_result.at<uchar>(y, x) = normalized_cumulative_histogram[img_gray.at<uchar>(y, x)] * 255;
            histogram2[img_result.at<uchar>(y, x)] += 1;
        }
    }
 
    
    //결과 영상의 누적 히스토그램 계산
    int cumulative_histogram2[256= { 0, };
    sum = 0;
 
    for (int i = 1; i < 256; i++)
    {
        sum += histogram2[i];
        cumulative_histogram2[i] = sum;
    }
 
 
    //히스토그램 그리기
    img_histogram = Mat( 300600, CV_8UC1, Scalar(0));
    img_histogram2 = Mat(300600, CV_8UC1, Scalar(0));
    
    int max = -1;
    for (int i = 0; i < 256; i++)
        if (max < histogram[i]) max = histogram[i];
 
    int max2 = -1;
    for (int i = 0; i < 256; i++)
        if (max2 < histogram2[i]) max2 = histogram2[i];
 
    for (int i = 0; i<256; i++)
    {
        int histo = 300 * histogram[i] / (float)max;
        int cumulative_histo = 300 * cumulative_histogram[i] / (float)cumulative_histogram[255];
 
        line(img_histogram, cvPoint(i + 10300), cvPoint(i + 10300 - histo), Scalar(255,255,255));
        line(img_histogram, cvPoint(i + 300300), cvPoint(i + 300300 - cumulative_histo), Scalar(255255255));
 
 
        int histo2 = 300 * histogram2[i] / (float)max2;
        int cumulative_histo2 = 300 * cumulative_histogram2[i] / (float)cumulative_histogram2[255];
 
        line(img_histogram2, cvPoint(i + 10300), cvPoint(i + 10300 - histo2), Scalar(255255255));
        line(img_histogram2, cvPoint(i + 300300), cvPoint(i + 300300 - cumulative_histo2), Scalar(255255255));
    }
 
 
    //화면에 결과 이미지를 보여준다.
    imshow("입력 영상", img_input);
    imshow("입력 그레이스케일 영상", img_gray);
    imshow("결과 그레이스케일 영상", img_result);
    imshow("입력 영상의 히스토그램", img_histogram);
    imshow("평활화 후 히스토그램", img_histogram2);
 
    //아무키를 누르기 전까지 대기
    while (cvWaitKey(0== 0);
 
    //결과를 파일로 저장
    imwrite("img_gray.jpg", img_gray);
    imwrite("img_result.jpg", img_result);
    imwrite("img_histogram.jpg", img_histogram);
    imwrite("img_histogram2.jpg", img_histogram2);
}
cs

출처 : http://webnautes.tistory.com/1043?category=710857

블로그 이미지

맨오브파워

한계를 뛰어 넘어서..........

,