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


그레이스케일 영상의 경우 픽셀이 가질 수 있는 값의 범위는 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

블로그 이미지

맨오브파워

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

,


윈도우에서 FFmpeg을 컴파일하려면 Visual Studio에서 하는 방법과 MinGW의 gcc로 컴파일하는 방법이 있다.

Visual Studio를 사용하는 방법이 쉽다고 되어있는데, 막상 해보니 잘 되지 않고…

윈도우 환경에서 gcc로 컴파일 하려면 MinGWyasm이 필요하다.



1. MinGW 다운 및 설치


MinGW를 설치하려면 여기에서 mingw-get-setup.exe를 다운받아 실행하면 된다.

설치 화면에서 mingw32-basemsys-base를 선택한 뒤에 Installation - Apply Changes를 클릭하면 설치가 진행된다.

설치가 되는 기본 폴더의 위치는 c:\MinGW이다.




2. pr.exe 설치


컴파일할 때 pr.exe가 필요한데, 위에서 설치한 내용에선 이 파일이 빠져있다.

여기에서 다운받아 압축을 푼 뒤에 pr.exe를 c:\MinGW\bin에 복사한다.



3. yasm 다운 및 설치


여기에서 yasm을 다운받는다. win32를 다운받으면 무난히 사용할 수 있다.

다운받은 뒤 파일명을 yasm.exe로 바꾼 뒤에 c:\MinGW\bin에 복사하면 된다.



4. FFmpeg 소스 다운로드 및 컴파일 준비


FFmpeg 소스는 여기서 다운받을 수 있다.

다운받은 뒤에 압축은 c:\MinGW\msys\1.0\home\[계정] 아래에 해제하면 작업이 좀 더 쉽다.



5. unistd.h 수정


c:\MinGW\includeunistd.h가 있다.

여기에 _cdecl가 5번 들어있는데, 이를 모두 __cdecl로 바꿔준다.



6. 컴파일


c:\MinGW\msys\1.0에서 msys.bat를 실행한다.

우선 아래와 같이 입력해서 MinGW 폴더를 마운트한다.


1
mount c:/mingw /mingw


여기서 역슬래쉬(\)가 아니라 슬래쉬(/)임에 유의한다.


그 다음은 소스가 저장된 위치로 이동.

4번에서 설명한 내용대로 했으면 현재 폴더의 아래에 있다.

여기서 아래와 같이 입력하면 컴파일된다. 소요 시간은 대략 30분 안팎.


1
2
3
./configure --target-os=mingw32 --enable-cross-compile --arch=i686 --enable-static
make
make install


이렇게 하면 ffmpeg.exe 등이 만들어진 것을 확인할 수 있다.



참조 사이트: 노녁의 볼우물 #1, #2

 



출처: http://teus.me/308 [TEUS.me]

블로그 이미지

맨오브파워

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

,