디자이너가 웹에서 가끔보는 Tab 형태의뷰를 아래와 같이 가이드를 해주었다.

위에 처럼 해달라고.. ㅜㅜ

윈도우 어플리케이션이라 힘들텐데............ 라고 말을 했지만 존심은 허락하지 않음 ㅋ

위의 효과를 주기위해 #QTabBar 물어 뜯어보기로................ ㅋ

아무리 찾아봐도 답이 보이지 않았는데..... 구글링의 결과 아래와 같은 속성이 있었다.

QTabBar::tab:top:first // first 라는 속성이 있을 줄이야. 이것이 첫번째 버튼이다......

#StyleSheet를 완성해보자....

상단 TabBar를 기준으로 하였다.

QTabBar::tab:top { color: #4f4f4f; border: 1px solid #F0F0F0; border-top: 1px solid #E0E0E0; border-bottom: 2px transparent black; background-color: #ffffff; padding: 5px; min-width: 110px; border-top-left-radius: 1px; border-top-right-radius: 1px; height: 27px; } QTabBar::tab:top:selected { color: #0f0f0f; background-color: #E0E0E0; border: 1px solid #E0E0E0; border-top: 1px solid #E0E0E0; border-bottom: 2px solid #3daee9; border-top-left-radius: 1px; border-top-right-radius: 1px; } QTabBar::tab:top:!selected:hover { border-bottom: 1px solid #C0C0C0; background-color: #C0C0C0; } QTabBar::tab:top:!selected { border-bottom: 1px solid #E0E0E0; background-color: #ffffff; } QTabBar::tab:top:first { color: #4f4f4f; border: 1px solid #F0F0F0; border-top: 1px solid #E0E0E0; border-bottom: 1px solid #E0E0E0; background-color: #ffffff; padding: 5px; min-width: 27px; border-top-left-radius: 1px; border-top-right-radius: 1px; height: 27px; } QTabBar::tab:top:first:selected { color: #0f0f0f; background-color: #E0E0E0; border: 1px solid #E0E0E0; border-top: 1px solid #E0E0E0; border-bottom: 1px solid #E0E0E0; border-top-left-radius: 1px; border-top-right-radius: 1px; } QTabBar::tab:top:first:!selected:hover { border-bottom: 1px solid #C0C0C0; background-color: #C0C0C0; } QTabBar::tab:top:first:!selected { border-bottom: 1px solid #ffffff; background-color: #ffffff; }

위처럼 #QTabBar 의 first 속성만 따로 지정한다.

추가로 첫번째 버튼의 x 버튼은 소스상에서 아래와 같이 수정하면 x 버튼을 숨길수 있다.......

QWidget *closeBtn = ui->tabMainWidget->tabBar()->tabButton(0, QTabBar::RightSide); closeBtn->hide();

.

.

.

.

.

.

.

두둥

.

.

.

드뎌 완성!!!!!!

비슷하죠~~~~~~~~~~~~~~ ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ

좀더 다듬으면.... 디자이너가 이야기한데로 똑같이 되겠네요 ㅋㅋㅋ

블로그 이미지

맨오브파워

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

,

1.시스템 업데이트 및 필수 패키지 설치

sudo apt update
sudo apt upgrade
sudo apt install build-essential cmake git pkg-config
sudo apt install libjpeg-dev libtiff-dev libpng-dev
sudo apt install libavcodec-dev libavformat-dev libswscale-dev libv4l-dev
sudo apt install libxvidcore-dev libx264-dev
sudo apt install libgtk-3-dev
sudo apt install libatlas-base-dev gfortran
sudo apt install python3-dev

 

2.OpenCV 및 OpenCV Contrib 소스 코드 다운로드

git clone https://github.com/opencv/opencv.git
git clone https://github.com/opencv/opencv_contrib.git

 

3.OpenCV 빌드 디렉토리 생성

cd ~/opencv
mkdir build
cd build

cd ~/opencv는 git  다운 받은 경로로  생각하면 된다.

 

4.CMake를 사용하여 OpenCV 설정

cmake -D CMAKE_BUILD_TYPE=Release \
      -D CMAKE_INSTALL_PREFIX=/usr/local \
      -D OPENCV_EXTRA_MODULES_PATH=~/opencv_contrib/modules \
      -D BUILD_EXAMPLES=ON ..

~/opencv_contrib/modules도 git  다운 받은 경로로  변경한다.

 

5.OpenCV 컴파일 및 설치

make -j4
sudo make install
sudo ldconfig

 

 

 

블로그 이미지

맨오브파워

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

,

윈도우 또는 리눅스환경에서 Qt프레임웍기반의 안드로이드를 위한 프로그램을 개발할 수 있다. 이글에서는 윈도우 환경에서 개발하는 방법을 설명한다.

 

먼저 안드로이드를 위한 컴파일된 Qt를 설치해야하는데 Qt 홈페이지를 방문해서 Qt Online Installer for Windows를 다운로드 받아 실행하거나 Qt가 설치되어 있다면 MaintenanceTool 을 실행한다.

 

Select Components에서 Android x86 및 Android ARMv7을 선택하고 설치한다.

 

안드로이드용 Qt설치가 끝났다면 안드로이드 개발관련 도구들을 다운로드받아야하는데 Android 스튜디오를 다운받을 수 도 있지만 이 글에서는 명령줄 도구로된 것을 사용할 것이다.

 

참고로 Android SDK 도구 버전 25.3.0 이상에서는 SDK 패키지 관리를위한 sdkmanager 와 AVD (Android Virtual Device) 관리를 위한 avdmanager를 명령 줄 도구로만 제공한다는 점을 알아두기 바란다.

 

그리고 한가지 더 중요한 사항은 Qt 버전을 v5.9 이하로 개발할 경우 SDK 도구 패키지 v25.2.5 이하를 사용한다.

 

Android SDK 다운로드 및 설치

 

Android NDK 다운로드 및 설치

Java JDK 다운로드 및 설치

 

윈도우에서는 기본 USB드라이버로 디버깅할 수 없으므로 Google USB드라이버를 설치한다.

CMD(명령프롬프트)를 실행하고 sdkmanager 폴더로 이동한 후 sdkmanager.bat "extras;google;usb_driver" 실행.

(리눅스나 맥에서는 필요치 않다. 자세한 내용은 이곳을 참고)

 

QtCreator설정

Option - Devices의 Android탭에서 JDK, SDK, NDK의 설치 및 폴더 경로를 설정한다. 

 

SDK 25.3 이후  SDK Manager, AVD Manager는 더이상 GUI를 제공하지 않지만 Qt Creator는 Android package목록을 제공하므로 간단하게 패키지를 설치할 수 있다.

 

패키지 설치 

 

이제 안드로이드 디바이스를 연결하고

다음과 같은 디버깅을 허용할지 묻는 팝업이 뜨면 확인을 누른다.

 

프로젝트를 만들때 kit은 아래처럼 처음에 설치한 Android kit을 선택한다.

 

프로젝트 생성 완료 후 Run을 실행하고 그림처럼 연결된 안드로이드 디바이스를 선택하면 잠시 후  선택한 장치에서 프로그램이 실행된다.

 

아래 캡쳐 이미지는 Qt기본 예제(webView)를 안드로이폰에서 실행한 것이다. 

블로그 이미지

맨오브파워

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

,

전에 Qt4.8 버전에서의 정적빌드하는 방법에 대해 설명했는데,

이번에는 Qt5.4버전에서의 정적빌드에 대해 알아보겠습니다.

Qt4.8버전과 대략적으로 비슷하지만 약간 달라진 부분이 있습니다.

이제 설명을 시작하겠습니다.


http://www.qt.io/download-open-source/# 


먼저 위의 링크로 접속해서 Qt5.4 offline을 다운받아야 합니다.


 


하단에 Windows Host 제목의 Qt5.4.0 for Windows 32bit (MinGW 4.9.1, 852MB) 를 다운받아 설치합니다.

설치과정은 별다른 설정이 없으므로 생략합니다.


http://download.qt.io/official_releases/qt/5.4/5.4.0/single/

이제 위의 링크로 접속해서 소스코드를 다운받습니다.


qt-everywhere-opensource-src-5.4.0.zip


과 같은 이름의 파일이 있습니다.

해당 파일을 다운받습니다.


위의 Qt5.4 오프라인 버전을 설치하면 C:\Qt\Qt5.4.0\5.4폴더가 있습니다.

이 위치에 qt5-opensource-src-5.4.0.zip파일을 압축해제 해줍니다.


 


그러면 위와 같이 폴더가 존재하게 됩니다.

(위의 android_armv7은 아마 안보일겁니다. 제가 안드로이드버전을 설치해서 보이는 겁니다.)


C:\Qt\Qt5.4.0\5.4\qt-everywhere-opensource-src-5.4.0\qtbase\mkspecs\win32-g++


위의 경로에 보면 qmake.conf파일이 있습니다.

해당 파일을 메모장으로 열어줍니다.


중간에 QMAKE_LFLAGS가 있습니다.


QMAKE_LFLAGS            = -static -static-libgcc


다음과 같이 수정해줍니다.

그리고 저장한 후 닫아줍니다.


 


시작에서 Qt 프롬프트를 실행합니다.

위의 사진과 같은 곳에 Qt5.4.0 프롬프트가 존재할겁니다.(사진은 퍼온겁니다.)


콘솔창에 다음과 같이 입력합니다.


cd "C:\Qt\Qt5.4.0\5.4\qt-everywhere-opensource-src-5.4.0\qtbase"

그리고 다음을 입력합니다.


configure -static -release -opengl desktop -opensource


그리고 묻는 창에 y를 입력하여 configure를 진행합니다.

1분 내외로 작업이 끝나는데 이후에 다음과 같이 입력합니다.


mingw32-make sub-src


그러면 30분 내외의 시간동안 정적빌드에 들어갑니다.

 


전부 완료되면 다음과 같이 결과가 보입니다.


이제 창을 닫고 Qt Creator를 실행합니다.

메뉴바에서 Tool - options를 클릭합니다.




왼쪽의 Build & Run탭에서 Qt Versions 탭으로 간 다음, Add를 클릭합니다.



C:\Qt\Qt5.4.0\5.4\qt-everywhere-opensource-src-5.4.0\qtbase\bin


위의 경로에 보면 qmake.exe가 있습니다.

이것을 선택하고 열기를 누릅니다.

그리고 Version name을 적절히 변경해줍니다. 저는 Qt5.4.0(qt-static)으로 지었습니다.

 

다시 Kits 탭으로 가서 Add를 클릭합니다.

그리고 Name을 Qt5 static으로 입력하고, Qt version을 방금 만든 Qt5.4.0(qt-static)으로 선택하고 OK를 클릭합니다.


 

이제 새로운 프로젝트를 만들어서 Release모드로 실행해보면 다음과 같이 제대로 빌드 후 실행이 되는 것을 확인할 수 있습니다.


 


빌드된 폴더에 들어가서 실행파일을 보니 용량이 14메가바이트로 굉장히 늘었음을 확인할 수 있습니다.

이제 이 파일을 Qt를 설치하지 않은 다른 컴퓨터에서 원활하게 실행할 수 있게됩니다.

여기까지가 Qt5.4버전에서의 정적빌드 방법입니다.


여기서 조금 더 개선된 방식을 사용하자면 용량을 줄이는 방법이 있습니다.


제가 첨부파일로 올린 upx391w.zip을 다운받아 압축을 풉니다.

저는 이곳에 upx.exe파일이 있습니다.

C:\Users\remoc_000\Desktop\upx391w\upx391w\upx.exe


그리고 방금 빌드한 파일은 다음 경로에 있습니다.


C:\Users\유저\Documents\Qt\build-qt_static_001-Qt5_static-Release\release\qt_static_001.exe


이제 명령 프롬프트를 실행합니다.

명령프롬프트는 시작 - 보조프로그램에 있습니다.


명령 프롬프트에 다음과 같이 입력합니다.

"C:\Users\유저\Desktop\upx391w\upx391w\upx.exe" -9 -o "C:\Users\유저\Documents\Qt\build-qt_static_001-Qt5_static-Release\release\result.exe" "C:\Users\유저\Documents\Qt\build-qt_static_001-Qt5_static-Release\release\qt_static_001.exe"

 


그러면 다음과 같이 압축이 진행됩니다.

사용하지 않는 코드는 버리는 과정입니다.


 그러면 이렇게 완료가 됩니다. 


그리고 result.exe파일이 생성되면서 용량은 5.4메가바이트로 1/3로 줄어듭니다.

이제 네이버 블로그에도 올릴 수 있게되었네요.


여기까지가 Qt5.4 정적빌드 방법입니다.

블로그 이미지

맨오브파워

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

,

OpenCV IplImage를 사용해서 Labeling 함수 구현


1. Labeling이란?

  우선 1-channel의 gray scale 영상이 필요합니다. 이진화된 아래의 이미지처럼 0 or 255(1-channel)의 값을 갖을 경우 인접한 영역끼리 그룹을 짓는 것을 Labeling(레이블링)이라고 합니다.

 

[그림 1]


아래의 그림처럼 인접 영역끼리 label Number를 매겨 그룹화를 하게됩니다.

 

[그림 2]


 여기서 인접한 pixel을 탐색할 때 8-neighbor 방식을 사용하였습니다. neighbor는 '이웃'이라는 뜻처럼 8-neighbor는 현재 pixel을 기준으로 몇 개의 이웃 pixel들을 탐색하는지를 뜻합니다. 아래의 왼쪽 그림은 현재 pixel을 기준으로 위, 아래, 왼쪽, 오른쪽 총 4방향을 탐색하는 4-neighbor 탐색입니다. 오른쪽 그림은 4-neighbor에 4개의 대각방향을 더 탐색하는 8-neighbor 탐색입니다. 저는 8-neighbor를 사용하였기 때문에 [그림 2]에서 label number가 '4'인 대각 픽셀이 그룹된 것을 확인할 수 있습니다. 이렇게 인접한 영역들을 Grouping한 것이 Labeling입니다.

[그림 3] 4-neighbor & 8-neighbor


2. Labeling 구현 전략

1) 이미지에서 화소 값(255)이 있는 지점까지 탐색

2) 이미 Labeling된 화소인지 확인

3) 아니라면 stack에 현재 x, y 좌표를 저장

4) 8-neighbor 탐색하면서 픽셀화소가 255이면서 Labeling이 안된 지역을     발견하면 stack에 모두 넣는다. (8-neighbor 모두 탐색한다.)

5) stack의 맨 위 좌표를 받아 4)번 과정을 반복한다.

6) 더이상 grouping할 행렬이 없을 때(stack이 비었을 때)까지 수행

7) 이미지의 다음 화소 값이 있는 곳까지 탐색하는 1) 과정부터 다시 시작


3. Labeling 구현 설명

제일 첫 좌표를 (0, 0)이라고 할 때 이미지에서 화소 값(255)이 있는 지점인 (3, 3)까지 오게된다. 이 점을 stack에 push한다.

[그림 4]


[그림 5] stack


  (3, 3)을 기준으로 8-neighbor 탐색을 한다. 해당 pixel에 값이 있고 Labeling이 안된 영역을 stack에 push 한다. [그림 6]을 보면 (3, 3)을 기준으로 3칸이 그림영역이고 현재 Labeling이 안되었기 때문에 [그림 7]처럼 stack에 push 한다.

[그림 6]


  stack의 top인 (4, 3)를 기준으로 다시 8-neighbor 탐색을 하면서 Label Number를 매기면 된다. 

[그림 7]


4. Iplimage로 구현한 Labeling 함수

stl stack을 사용하기 위해 #include <stack> 헤더파일을 선언한다. 
- 함수 호출방법: Labeling(src, dst);
- 함수 인자설명: src(원본이미지), dst(출력이미지)
(labelNumber 값이 출력이미지로 바로 출력되어 잘 안보일 수 있다. 눈에 보이게 하고 싶으면 labelNumber = 100; 이렇게 초기 값을 설정하면 잘 보일 것이다.)

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
#include <stack>
 
void    Labeling    (const IplImage *src, IplImage *dst)
{
    // Only 1-Channel
    if( src->nChannels != 1 )
        return;
 
    // image size load
    int height    = src->height;
    int width    = src->width;
 
    // input image, result image를 image size만큼 동적할당
    unsigned char*    inputImage    = new unsigned char    [height * width];
    int*            resultImage    = new int            [height * width];    
 
    // before labeling prcess, initializing 
    forint y = 0; y < height; y++ ){
        forint x = 0; x < width; x++ ){
            // image copy
            inputImage[width * y + x] = src->imageData[width * y + x];
 
            // initialize result image
            resultImage[width * y + x] = 0;
        }
    }
 
    //// 8-neighbor labeling
    // Labeling 과정에서 stack overflow 방지를 위한 stl <stack>사용 
    stack<Point> st;
    int labelNumber = 0;
    forint y = 1; y < height - 1; y++ ){
        forint x = 1; x < width - 1; x++ ){
            // source image가 255일 경우 + Labeling 수행되지 않은 픽셀에서만 labeling process 시작
            if( inputImage[width * y + x] != 255 || resultImage[width * y + x] != 0 ) continue;
            
            labelNumber++;
            
            // 새로운 label seed를 stack에 push
            st.push(Point(x, y));
 
            // 해당 label seed가 labeling될 때(stack이 빌 때) 까지 수행
            while( !st.empty() ){
                // stack top의 label point를 받고 pop
                int ky = st.top().y;
                int kx = st.top().x;
                st.pop();
 
                // label seed의 label number를 result image에 저장
                resultImage[width * ky + kx] = labelNumber;
 
                // search 8-neighbor
                forint ny = ky - 1; ny <= ky + 1; ny++ ){
                    // y축 범위를 벗어나는 점 제외
                    if( ny < 0 || ny >= height ) continue;
                    forint nx = kx - 1; nx <= kx + 1; nx++ ){
                        // x축 범위를 벗어나는 점 제외
                        if( nx < 0 || nx >= width ) continue;
                        
                        // source image가 값이 있고 labeling이 안된 좌표를 stack에 push
                        if( inputImage[width * ny + nx] != 255 || resultImage[width * ny + nx] != 0 ) continue;
                        st.push(Point(nx, ny));
                        
                        // 탐색한 픽셀이니 labeling
                        resultImage[width * ny + nx] = labelNumber;
                    }
                }
            }        
        }
    }
 
    // dst image에 복사
    forint y = 0; y < height; y ++ ){
        forint x = 0; x < width; x++ ){
            dst->imageData[width * y + x] = resultImage[width * y + x];
        }
    }
 
    // 메모리 해제
    delete[] inputImage;
    delete[] resultImage;
}



출처: http://devmonster.tistory.com/22 [Dev.Monster]

출처: http://devmonster.tistory.com/22 [Dev.Monster]

블로그 이미지

맨오브파워

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

,

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



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

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




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


MFC 응용 프로그램 생성


GLEW 라이브러리 추가


UI 구성


OpenGL 관련 코드 추가


테스트


관련 포스팅


참고한 사이트



최초작성 2017.5.26

마지막 수정 2018.1.29


MFC 응용 프로그램 생성

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

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 폴더에 추가로 복사되어 집니다.

최근에 Visual Studio 2017 Community 버전을 설치했거나 업데이트를 했다면 다음처럼 소스 폴더의 위치가 변경된 것을 볼 수 있습니다.

C:\Users\사용자 이름\source\repos 아래에 프로젝트 폴더가 위치하게 됩니다.





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





프로젝트 폴더의 경로  OpenGL-Example\OpenGL-Example\lib 폴더를 생성하고 붙여넣기 해줍니다.





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


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

아직 코드를 추가하지 않았지만 Visual Studio의 메뉴에서 빌드 > 솔루션 빌드를 선택하여 컴파일을 해주면 실행파일이 저장될 위치를 위한 폴더들이 생성됩니다.





압축을 풀은 폴더의 경로 glew-2.1.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);
virtual BOOL SetupPixelFormat(void);

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

GLuint vao;
void defineVAO(GLuint &vao, GLuint &shaderProgram);
GLuint create_program();
};




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


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


GLuint shaderProgram;
defineVAO(vao, shaderProgram);

glUseProgram(shaderProgram);
glBindVertexArray(vao);

SetTimer(1000, 30, NULL);

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




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


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

// TODO: 여기에 메시지 처리기 코드를 추가합니다.
glDeleteVertexArrays(1, &vao);

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




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


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

CDialogEx::OnTimer(nIDEvent);

glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);


glDrawArrays(GL_TRIANGLES, 0, 3);

//화면 업데이트
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;
}


//Get a new style pixel format
if (!SetupPixelFormat())
{
return FALSE;
}


//참고 http://gamedev.stackexchange.com/a/30443
GLint attribs[] =
{
//OpenGL 3.3 사용
WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
WGL_CONTEXT_MINOR_VERSION_ARB, 3,
// 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 파일에 defineVAO 함수를 추가합니다.


void COpenGLwithMFCDlg::defineVAO(GLuint &vao, GLuint &shaderProgram)
{
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);

float position[] = {
0.0f,  0.5f, 0.0f, //vertex 1
0.5f, -0.5f, 0.0f, //vertex 2
-0.5f, -0.5f, 0.0f //vertex 3
};

float color[] = {
1.0f, 0.0f, 0.0f, //vertex 1 : RED (1,0,0)
0.0f, 1.0f, 0.0f, //vertex 2 : GREEN (0,1,0)
0.0f, 0.0f, 1.0f  //vertex 3 : BLUE (0,0,1)
};



GLuint position_vbo, color_vbo;

glGenBuffers(1, &position_vbo);
glBindBuffer(GL_ARRAY_BUFFER, position_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(position), position, GL_STATIC_DRAW);

glGenBuffers(1, &color_vbo);
glBindBuffer(GL_ARRAY_BUFFER, color_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(color), color, GL_STATIC_DRAW);


shaderProgram = create_program();

GLint position_attribute = glGetAttribLocation(shaderProgram, "position");
glBindBuffer(GL_ARRAY_BUFFER, position_vbo);
glVertexAttribPointer(position_attribute, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(position_attribute);

GLint color_attribute = glGetAttribLocation(shaderProgram, "color");
glBindBuffer(GL_ARRAY_BUFFER, color_vbo);
glVertexAttribPointer(color_attribute, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(color_attribute);

glBindVertexArray(0);
}




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


GLuint COpenGLwithMFCDlg::create_program() {

const GLchar* vertexShaderSource =
"#version 330 core\n"
"in vec3 position;"
"in vec3 color;"
"out vec3 color_from_vshader;"
"void main()"
"{"
"gl_Position = vec4(position, 1.0);"
"color_from_vshader = color;"
"}";

const GLchar* fragmentShaderSource =
"#version 330 core\n"
"in vec3 color_from_vshader;"
"out vec4 out_color;"
"void main()"
"{"
"out_color = vec4(color_from_vshader, 1.0);"
"}";


GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);

GLint success;
GLchar infoLog[512];

glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
TRACE(CString("ERROR: vertex shader 컴파일 실패 ") + CString(infoLog) + CString("\n"));
}

GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);

glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
TRACE(CString("ERROR: fragment shader 컴파일 실패 ") + CString(infoLog) + CString("\n"));
}


GLuint shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);

glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);

glLinkProgram(shaderProgram);


glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);

TRACE(CString("ERROR: shader program 연결 실패 ") + CString(infoLog) + CString("\n"));
}

return shaderProgram;
}




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;
}




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


BOOL COpenGLwithMFCDlg::SetupPixelFormat()
{
//This is a modern pixel format attribute list.
//It has an extensible structure. Just add in more argument pairs
//befroe the null to request more features.
const int attribList[] =
{
WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
WGL_ACCELERATION_ARB,   WGL_FULL_ACCELERATION_ARB,
WGL_DOUBLE_BUFFER_ARB,  GL_TRUE,
WGL_PIXEL_TYPE_ARB,     WGL_TYPE_RGBA_ARB,
WGL_COLOR_BITS_ARB,     32,
WGL_DEPTH_BITS_ARB,     24,
WGL_STENCIL_BITS_ARB,   8,
0, 0  //End
};


unsigned int numFormats;
int pixelFormat;
PIXELFORMATDESCRIPTOR pfd;

//Select a pixel format number
wglChoosePixelFormatARB(m_pDC->GetSafeHdc(), attribList, NULL, 1, &pixelFormat, &numFormats);

//Optional: Get the pixel format's description. We must provide a
//description to SetPixelFormat(), but its contents mean little.
//According to MSDN:
//  The system's metafile component uses this structure to record the logical
//  pixel format specification. The structure has no other effect upon the
//  behavior of the SetPixelFormat function.
//DescribePixelFormat(m_pDC->GetSafeHdc(), pixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &pfd);

//Set it as the current
if (FALSE == SetPixelFormat(m_pDC->GetSafeHdc(), pixelFormat, &pfd))
{
AfxMessageBox(CString("SelectPixelFormat failed"));
return FALSE;
}

return TRUE;
}



테스트

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




관련 포스팅

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

http://webnautes.tistory.com/1108


참고한 사이트

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

 

 

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

블로그 이미지

맨오브파워

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

,

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

블로그 이미지

맨오브파워

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

,


영상처리 관련 개념 및 구현을  틈나는대로 정리하여 블로그에 올릴 계획입니다. 편의상 opencv 라이브러리를 사용하여  이미지를 불러와서 Mat 객체에 저장,  이미지를 화면에 보여주기,  파일로 저장 등의 처리를 했습니다. opencv 설치는 아래 글들을 참고하세요. 


[그래픽스&컴퓨터비전/개발환경] - Visual Studio 2015에서 OpenCV 3.1 연동하기

[그래픽스&컴퓨터비전/개발환경] - OpenCV 3.1을 Ubuntu 16.04에 설치

[그래픽스&컴퓨터비전/개발환경] - OpenCV 3.1을 Ubuntu 14.04에 설치

 


첫번째 내용은 컬러영상을 그레이스케일 영상으로 변환하는 것입니다.  OpenCV에서 보통 이미지를 불러올 때, BGR888 포맷을 사용합니다. 한 픽셀당 Blue 8비트, Green 8비트, Red 8비트 정보가 포함되어 있습니다. RGB라 하지 않고 BGR이라 하는 이유는 메모리상에 저장되는 순서가 Blue, Green, Red 이기 때문입니다. 4개의 픽셀을 예로 들면 다음처럼 저장됩니다.

(0,0)                 (0,1)                (0,2)                 (0,3)

Blue Green Red  Blue Green Red  Blue Green Red  Blue Green Red 


화면에 픽셀이 표시될 때 Blue, Green, Red 값에 따라 다른 색으로 표현됩니다. 각 성분의 값은 0~255까지 가능합니다.  각 성분의 크기가 8비트이기 때문에 2의 8승인 256가지(0~255)를 표현할 수 있기 때문입니다.

Blue 

Green 

Red 

픽셀 색 

 255

255 

255 

흰색 

 0

0

검정색 

 128

128

128 

회색

 255

파란색 

 0

255 

녹색 

 0

255 

빨간색 

 0

255 

255 

노란색 


컬러 영상의 모든 픽셀에 대해 blue, green, red를 더하고서  3으로 나누면 그레이 스케일 영상으로 변환이 됩니다. 

실제로 구현한 코드입니다. 컬러 영상을 저장하고 있는 img_input에서 픽셀 접근하는 부분과 그레이 스케일 영상을 저장하는 img_gray에서 픽셀 접근하는 부분은 자주 사용하게 되니 기억해두면 유용합니다. 

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
#include <opencv2/highgui.hpp>
#include <iostream>
 
using namespace std;
using namespace cv;
 
 
int main()
{
    Mat img_input, img_gray;
        
    //이미지 파일을 읽어와서 img_input에 저장
    img_input = imread("input2.jpg", IMREAD_COLOR);
    if (img_input.empty())
    {
        cout << "파일을 읽어올수 없습니다." << endl;
        exit(1);
    }
 
 
    // 1채널, uchar로 생성
    //opencv에선 unsigned char을 uchar로  재정의해서 사용한다.
    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; 
        }
    }
 
 
    //화면에 결과 이미지를 보여준다.
    imshow("입력 영상", img_input);
    imshow("그레이스케일 영상", img_gray);
 
    //아무키를 누르기 전까지 대기
    while (cvWaitKey(0== 0);
 
    //결과를 파일로 저장
    imwrite("img_gray.jpg", img_gray);
}
cs


opencv에서 제공하는 함수를 이용하여 구현한 코드입니다. 

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
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
 
using namespace std;
using namespace cv;
 
 
int main()
{
    Mat img_input, img_gray;
        
    //이미지 파일을 읽어와서 img_input에 저장
    img_input = imread("input2.jpg", IMREAD_COLOR);
    if (img_input.empty())
    {
        cout << "파일을 읽어올수 없습니다." << endl;
        exit(1);
    }
 
 
    cvtColor(img_input, img_gray, COLOR_BGR2GRAY);
 
 
    //화면에 결과 이미지를 보여준다.
    imshow("입력 영상", img_input);
    imshow("그레이스케일 영상", img_gray);
 
    //아무키를 누르기 전까지 대기
    while (cvWaitKey(0== 0);
 
    //결과를 파일로 저장
    imwrite("img_gray.jpg", img_gray);
}

 

 

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

블로그 이미지

맨오브파워

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

,