ㅇ 히스토그램 매칭 (Histogram matching)
  - 이미지의 색분포를 다른 이미지와 유사하게 하는 것을 말합니다.
  - color mapping 또는 color transfer라고도 합니다.
  - 예를 들어 아래와 같이 두 장의 이미지가 주어졌다고 합시다.
    . 왼쪽의 이미지는 바닷가를 촬영한 이미지이고,  푸른색 계통이 주요 색입니다.
    . 오른쪽은 포도밭을 촬영한 것이고, 녹색이 주요 색입니다.
    . 이제, 두 사진간에 히스토그램 매칭을 시도해 봅시다.

 


ㅇ 아래는 포도밭 이미지를 바닷가 이미지의 주요색으로 바꾼 것입니다.


ㅇ 아래 이미지는 반대로, 바닷가 이미지를 포도밭의 주요색으로 바꾼 것입니다.
  - 옥색 같았던 바다가  포도밭의 녹색과 노랑색으로 바뀌었습니다. 녹조가 생긴 것처럼 말이죠


ㅇ 또 다른 예를 보겠습니다.
  - 왼쪽은 도시야경 이미지이고, 주요색이 검정색입니다.
  - 오른쪽은 정원 이미지이고, 주요색이 녹색입니다.

   


ㅇ 히스토그램 매칭을 한 아래 이미지는
  - 정원 이미지에 도시야경의 주요 색인 검정색을 씌운 것입니다.
  - 신록으로 푸르렀던 나뭇잎들이 밤하늘 처럼 되어버렸습니다.


ㅇ 이번에는 반대의 경우입니다. 
  - 도시야경 이미지에 정원의 주요 색인 녹색을 씌운 것입니다.
  - 어두웠던 밤하늘이 녹색 계열로 바뀌었습니다. 묘한 분위기를 느낄 수 있습니다.


ㅇ 히스토그램 매칭의 원리
  - 이것은 이미지 (T)의 주요색을 다른 이미지 (R)의 주요 색로 바꾸는 것으로 
  - T의 색을 발생빈도수에 따라서 R의 색으로 바꾸는 것입니다.
  - 예를 들어, T에서 가장 많이 나타나는 색을 R에서 가장 빈도수가 높은 색으로 바꾼다고 생각하면 됩니다.
  - 마찬가지로 두 번째로 자주 나타나는 색을 R에서 빈도수가 두 번째로 높은 색으로 바꾸고,
  - 이런 식으로 빈도수의 순서에 따라서 바꿔나가면 되는 것입니다.

ㅇ 함수 LUT( )
  - OpenCV에서는 히스토그램 매칭을 쉽게 할 수 있도록  함수 LUT( )를 제공합니다.
  - 이 함수는 Lookup Table의 약자입니다.
  - 함수 원형은 아래 그림와 같습니다.
    . src는 입력이미지이고,
    . lut는 크기 256인 1차원 배열이고
    . dst는 결과이미지입니다.
    . 여기서 중요한 것은 lut인데,
    . lut[i]는 i 번째 색에 대해서 대체할 색을 의미합니다.
    . 예를 들어 설명해 보겠습니다.
    . src(x,y)의 색이 i라면, dst(x,y)의 색은 lut[i]가 되는 것입니다.
    


ㅇ 히스토그램 매칭 상세과정
  - 컬러이미지에 대해서 히스토그램 매칭을 수행하는 과정을 살펴보겠습니다.
  - 아래 소스코드에서 해당 라인 번호도 참고로 적어 놓습니다.

  - 121-133:
    . 두 장의 이미지를 입력으로 받습니다.
    . 하나는 히스토그램 매칭이 수행되어야 할 타겟 (T)이미지이고,
    . 다른 하나는 주요색을 추출할 레퍼런스 (R) 이미지 입니다.

  - 14-17:
    . T와 R 이미지 모두 컬러이미지이므로 R, G, B 채널별로 나눕니다.

  - 29-44:
    . 그리고, 채널별로 히스토그램을 구합니다. 이 때 함수 calcHist( )를 사용합니다.
    . 계산된 히스토그램에 대해서 normalize를 수행해서 최대값이 1.0이 되도록 합니다. 

  - 49-67:
    . Normalized된 히스토그램에 대해서 cumulative distribution function (CDF)를 계산합니다.
    . CDF에 대해서 다시 normalize를 수행해서 최대값이 1.0이 되도록 합니다.

  - 72-93:
    . 이것을 이용해서 Lookup table을 만드는데,
    . 빈도수 비율이 가장 유사한 색끼리 매칭한다고 보면됩니다.
    . 보다 정확하게는
    . 이미지 T 색 c의 빈도수에 해당하다는 이미지 R의 색 d를 결정해서
    . lookup 테이블 lut에 채워넣습니다.
    . 이것을 코드로 쓰면 lut[c] = d; 와 같이 됩니다.

  - 95:
    . 함수 LUT()를 이용해서 채널별로 히스토그램 매칭을 수행

  - 98: 
    . 완성된 채널을 합성해서 이미지를 만들어 내는 과정

ㅇ OpenCV 소스코드
  - 위에서 설명한 히스토그램 매칭과정을 C++ 소스코드로 구현한 것입니다.

  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
144
145
146
147
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

// reference와 target은 변경시키지 않을 것이므로 const
// result는 변경결과를 담는다.
void histMatch(const Mat &reference, const Mat &target, Mat &result)
{
	const float HISTMATCH = 0.000001;  // threshold of histogram difference
	double min, max;

	vector<Mat> ref_channels;  // 벤치마킹 이미지를 색채널별로 분리해서 저장
	split(reference, ref_channels);
	vector<Mat> tgt_channels;  // 색변경 대상 이미지를 채널별로 분리
	split(target, tgt_channels);
	
	int histSize = 256;  // histogram을 256 level로 작성
	float range[] = { 0, 256 };  // 최소와 최대 bin
	const float *histRange = { range };
	bool uniform = true;

	for (int i = 0; i < 3; i++)
	{
		Mat ref_hist, tgt_hist;  // 생성된 히스토그램 저장
		Mat ref_hist_accum, tgt_hist_accum;  // 히스토그램의 CDF계산

		calcHist(&ref_channels[i], 1, 0, Mat(), ref_hist, 1, &histSize, &histRange, uniform, false);
		calcHist(&tgt_channels[i], 1, 0, Mat(), tgt_hist, 1, &histSize, &histRange, uniform, false);

		minMaxLoc(ref_hist, &min, &max);  // 히스토그램에서 최소값과 최대값을 구한다.
		if (max == 0)
		{
			cout << "ERROR: max is 0 in ref_hist" << endl;
		}
		normalize(ref_hist, ref_hist, min / max, 1.0, NORM_MINMAX);  // 히스토그램 값을 0 ~ 1까지로 normalize한다.

		minMaxLoc(tgt_hist, &min, &max);  // 히스토그램에서 최소값과 최대값을 구한다.
		if (max == 0)
		{
			cout << "ERROR: max is 0 in tgt_hist" << endl;
		}
		normalize(tgt_hist, tgt_hist, min / max, 1.0, NORM_MINMAX);  // 히스토그램 값을 0 ~ 1까지로 normalize한다.

		//
		// CDF를 계산한다.
		//
		ref_hist.copyTo(ref_hist_accum);  // 복사본을 만든다.
		tgt_hist.copyTo(tgt_hist_accum);

		float *src_cdf_data = ref_hist_accum.ptr<float>();  // pointer로 access하면 성능을 높일 수 있다.
		float *dst_cdf_data = tgt_hist_accum.ptr<float>();

		for (int j = 1; j < 256; j++)
		{
			src_cdf_data[j] += src_cdf_data[j - 1];
			dst_cdf_data[j] += dst_cdf_data[j - 1];
		}

		//
		// 계산된 CDF를 normalize한다.
		//
		minMaxLoc(ref_hist_accum, &min, &max);
		normalize(ref_hist_accum, ref_hist_accum, min / max, 1.0, NORM_MINMAX);
		minMaxLoc(tgt_hist_accum, &min, &max);
		normalize(tgt_hist_accum, tgt_hist_accum, min / max, 1.0, NORM_MINMAX);

		// 
		// Histogram matching을 수행
		//
		Mat lut(1, 256, CV_8UC1);  // Lookup table을 만든다.
		uchar *M = lut.ptr<uchar>();
		uchar last = 0;
		for (int j = 0; j < tgt_hist_accum.rows; j++)
		{
			float F1 = dst_cdf_data[j];

			//
			// 벤치마킹이미지에서 유사한 CDF 값을 갖는 픽셀 intensity를 찾는다.
			//
			for (uchar k = last; k < ref_hist_accum.rows; k++)
			{
				float F2 = src_cdf_data[k];
				if (abs(F2 - F1) < HISTMATCH || F2 > F1)  // 유사한 CDF이거나, 
				{
					M[j] = k;  // 변경대상 이미지의 intensity j는 intensity k로 변환
					last = k;  // 다음 검색을 시작할 위치
					break;  // 다음 intensity로
				}

			}
		}

		LUT(tgt_channels[i], lut, tgt_channels[i]);  // Lookup table을 이용한 색깔 변화
	}  // end of for

	merge(tgt_channels, result);  // 3개 채널들을 합쳐서 이미지를 재생성

}

// test 1
#define REF_IMG "img_osaka_night.jpg"
#define TGT_IMG "img2_garden.jpg"

// test 2
//#define REF_IMG "img2_garden.jpg"
//#define TGT_IMG "img_osaka_night.jpg"

// test 3
//#define REF_IMG "img2_beach.jpg"
//#define TGT_IMG "img2_grapefarm.jpg"

// test 4
//#define REF_IMG "img2_grapefarm.jpg"
//#define TGT_IMG "img2_beach.jpg"

int main(int argc, char** argv)
{

	Mat ref = imread(REF_IMG, IMREAD_COLOR); 	// 색깔을 벤치마킹할 이미지, BGR 포맷으로 읽는다.
	if (ref.empty() == true)
	{
		cout << "Unable to read reference image" << endl;
		return -1;
	}
	Mat tgt = imread(TGT_IMG, IMREAD_COLOR); 	// 색깔을 변경할 이미지
	if (tgt.empty() == true)
	{
		cout << "Unable to read target image" << endl;
		return -1;
	}
	Mat dst = tgt.clone();  // 색깔 변경 결과를 담을 이미지

	namedWindow("Reference", WINDOW_KEEPRATIO);  // 벤치마킹 이미지 표시창, 비율따라 크기 조정
	namedWindow("Target", WINDOW_KEEPRATIO); // 변경대상 이미지
	namedWindow("Result", WINDOW_KEEPRATIO);  // 변경결과를 담을 이미지
	
	imshow("Reference", ref);
	imshow("Target", tgt);

	histMatch(ref, tgt, dst);
	imshow("Result", dst);

	waitKey();
	return 0;
}









반응형
LIST






ㅇ Contrast Enhancement
  - 이미지에서 너무 밝은 부분이나, 어두운 부분은 자세한 내용을 보기가 어렵다.
  - 비슷한 intensity를 갖는 픽셀들이 한 곳에 모여있기 때문이다.
    . intensity란, 예를 들어, gray image에서 픽셀은 0(검정색)부터 255(흰색) 값을 갖는데, 이 값을 의미하는 것이다.
    . color image는 RGB, 3개의 채널을 갖는다면, 하나의 픽셀은 각 채널별로 intensity를 갖게 된다.
    . 여기서는 gray image만 생각하자.
  - 이를 개선하는 것을 contrast enhancement라고 한다.
  - 차이 또는 대조 (contrast)를 개선 (enhance)한다는 뜻으로,
  - 차이가 안 나던 픽셀들의 intensity를 바꿔서 차이가 나도록 한다는 것이다.
  - 이것을 위해서 사용되는 방법이 histogram equalization인데, 이에 대해서 살펴보면,,,

ㅇ 히스토그램 (Histogram)
  - 이것은 intensity별로 픽셀들의 개수를 나타내는 그래프이다.

ㅇ Histogram modeling, Histogram transfer
  - 픽셀들의 intensity를 변경하게 되면,
  - 이에 따라 히스토그램도 변경되는데, 이를 histogram modeling 또는 histogram transfer라고 한다.
  - 이러한 변경은 앞에서 설명했듯이, 잘 안 보이는 부분을 잘 보이게 한다든지 하는 contrast enhancement를 하기 위해서이다.

ㅇ Histogram equalization
  - histogram modeling (tranfer)는 intensity를 변경하는 것이기 때문에,
  - 여러 가지 방법들을 생각해 볼 수 있는데,
  - 그 중에서도 contrast enhancement를 목적으로 하는 대표적인 방법이 histogram equalization이다.
  
ㅇUniform histogram 
  - histogram equalization의 목적은 intensity들이 골고루 사용되도록 하는데 있다.
  - 비슷한 intensity들만 많이 사용되면, 이미지에서 그 부분은 잘 안보이기 때문이다.
  - 그래서, 궁극적인 목적이자, 가장 바람직인 equalization은
  - intensity들이 모두 동일한 빈도수로 사용되는 것이다. (하지만 이것은 목표일 뿐이고, 실제는 그렇지 않다.)

ㅇ Histogram Equalization의 효과

- 아래와 같은 히스토그램이 있다고 하자.
- x축은 intensity이고, y축은 값들의 빈도수이다.
- 예를 들어, intensity 2는 빈도수가 30이고,
- 최대 빈도수를 갖는 intensity는 4인 것을 알 수 있다.
- 그런데,
- intensity들이 2 ~ 4에 몰려있는 것을 볼 수 있다. (1번으로 표시)
- Histogram equalization은 이러한 집중을 완화시키는 것으로, 다음 그림을 보자.


- 위의 히스토그램을 완벽하게 equalization하면, 
- 아래와 같이 모든 intensity들이 동일한 빈도수를 갖게된다.
- 하지만, 실제에서는 이러한 것을 달성하기가 어렵고,
- OpenCV에서는 CDF (cumulative distribution function)을 이용한 방법을 이용한다.


ㅇ OpenCV의 histogram equalization 지원
  - equalizeHist( )라는 함수를 제공하는데,
  - 이 함수에 이미지를 인수로 주면, equalized된 이미지를 돌려준다.

void equalizeHist( InputArray src, OutputArray dst): gray image, src 8 bit single channel image
  - 함수 링크
 - src: 입력이미지, dst: equalized된 출력이미지

  - 이 함수는 Cumulative Distribution Function (CDF)를 이용해서 equalization을 수행한다.


ㅇ CDF를 이용한 Histogram Equalization 원리
  - 단계별 수행내용을 그림으로 나타냈다.

- 1 단계
- Histogram에 대해 cumulative sum을 계산한다.
- 좌측에서 우측으로 가면서,
- 각 값들에 대해, 자신과 이전값들의 빈도수 합을 구하는 것이다.
- 값이 커지면서, 빈도수가 점차 증가하는데,
- 이는 이전값들의 빈도수에 자신의 빈도수를 더해 나가기 때문이다.


- 2단계
- 위의 결과에 대해 y축의 값을 normalize한 것으로,
- 빈도수들을 0 ~ 10까지로 rescaling한 것이다.
- 예를 들어, 위의 그램에서 값3에 해당하는 빈도수가 80정도 였는데,
- 이는 4로 normalize된다.
- 이렇게 하는 이유는.... 다음 그림을 보면


- 3단계
- 위에서 normalization을 하면
- 값에 대한 맵핑(mapping)을 얻을 수 있는데,
- 무슨 의미냐 하면,
- 아래 그림과 같이, 
- 값 2를 mapping해서 2로, (번호 1)
- 값 3은 4로 (번호 2)
- 값 4는 10으로 (번호 3)으로 바꾸는 대응을 알 수 있다는 것이다.
- 이 mapping을 이용해서,
- 기존 값을 새로운 값으로 변경시키는 것이다.
- 왜, 이런 mapping을 수행하냐면,,,, 다음 그림에서


- 새로운 값으로 바꾸고 난 후에,
- 히스토그램을 다시 그리면, 아래 그림과 같다.
- 기존에는 값 2 ~ 4에 모두 몰려있었는데,
- 새로운 그림에서는 2 ~ 10까지 펼쳐져 있게 된다.
- 즉, 완벽한 equalization은 아니지만, 
- 그래도, 히스토그램이 전체적으로 퍼지는 효과를 얻게 되는 것이다.



ㅇ MATLAB 소스코드
  - 위의 결과들을 얻기 위한 프로그램
 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
%
% 히스토그램 equalization
%
clear all;
close all;

% 테스트 데이터
% intensity - value
% data = [0 1;
%     1 2;
%     2 30;
%     3 50;
%     4 2;
%     5 2;
%     6 2;
%     7 30;
%     8 50;
%     9 2;
%     10 1;
%     ];

data = [0 1;
    1 2;
    2 30;
    3 50;
    4 100;
    5 2;
    6 2;
    7 1;
    8 1;
    9 2;
    10 1;
    ];

max_intensity = size(data,1)-1;

% 바그래프로 데이터를 출력
figure;
bar(data(:,1), data(:,2));
title('Original Histogram');
ylim([0, max(data(:,2)+10)]);

disp(sum(data(:,2)))

% cumulative 합을 계산
% intensity - cumulative 합
data_temp = data;
data_temp(:,2) = cumsum(data(:,2));
data1 = data;
data1(:,2) = round(max_intensity*cumsum(data(:,2))/sum(data(:,2)));

figure;
bar(data_temp(:,1), data_temp(:,2));
title('Cumulative Sum');
ylim([0, max(data_temp(:,2)+10)]);

% 바그래프로 cumulative합을 출력
figure;
bar(data1(:,1), data1(:,2));
title('Normalized Cumulative Sum');
ylim([0,max_intensity+1]);

% equalized data
equData = data;
equData(:,2) = zeros(max_intensity+1, 1);

for k=1:max_intensity+1
    newIntensity = data1(k,2);
    newIntensityIdx = newIntensity+1;
    equData(newIntensityIdx, 2) = equData(newIntensityIdx,2)+ data(k,2); 
end

figure;
bar(equData(:,1), equData(:,2));
title('Equalized Histogram');

disp(sum(equData(:,2)))






반응형
LIST

Histogram을 이용한 이미지 유사도 측정: 

Evaluating image similarity using histogram


히스토그램 (histogram)을 이용하면 사진들이 서로 얼마나 비슷한지 측정할 수 있다. 히스토그램간의 유사도를 측정하면 된다. OpenCV는 이러한 측정을 위해 함수 compareHist( )를 제공한다. 비교대상인 두 개의 히스토그램을 인자로 전달하면 유사도를 수치로 반환한다. 비교방식은 7가지가 있는데, 주로 사용되는 것은 correlation, chi-square, intersection과 Bhattachayya가 있다.


 

예를 들어 아래와 같이 다섯 장의 사진 (위에 있는 것이 이름)이 주어졌을 때, 각 사진간의 유사도를 측정해 보자.


img_dotonbori.jpg

img_eiffel.jpg

img2_beach.jpg

img2_garden.jpg

img2_grapefarm.jpg


우선 결과부터 제시하면 아래와 같이 img2_garden(위에서부터 4번째)과 img2_grapefarm(마지막 사진) 이 사용한 4가지 방법 모두에서 가장 유사한 사진으로 나온다.



이러한 비교에 사용된 프로그램은 아래와 같다.

순서도

  1. 비교대상 사진 5장을 읽어들이면서 HLS format으로 전환한다. (24~32)

  2. 각 HLS사진에 대해서 3차원 histogram을 구한다. (40 ~ 52)

  3. 각 histogram끼리 유사도를 계산한다. (54 ~ 62)


 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
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

#define SCALE 0.2
#define NUM 5
#define BINS 8

int main(int argc, char** argv)
{
	// the names of images to be compared each other
	string imgNames[NUM] = { "img_dotonbori.jpg", "img_eiffel.jpg", "img2_beach.jpg", "img2_garden.jpg", "img2_grapefarm.jpg" };

	//for (int i = 0; i < NUM; i++)
	//{
	//	cout << imgNames[i] << endl;
	//}

	// read all images and convert to HLS format
	Mat imgs[NUM];
	Mat imgsHLS[NUM];
	for (int i = 0; i < NUM; i++)
	{
		imgs[i] = imread(imgNames[i], IMREAD_COLOR);
		if (imgs[i].data == 0)
		{
			cout << "Unable to read " << imgNames[i] << endl;
			return 0;
		}
		cvtColor(imgs[i], imgsHLS[i], COLOR_BGR2HLS);
	}

	//cout << "Succeeded to read all images" << endl;

	// compute 3D histogram
	Mat histogram[NUM];

	int channel_numbers[] = { 0, 1, 2 };
	for (int i = 0; i < NUM; i++)
	{
		int* number_bins = new int[imgsHLS[i].channels()];
		for (int ch = 0; ch < imgsHLS[i].channels(); ch++)
		{
			number_bins[ch] = BINS;
		}
		float ch_range[] = { 0.0, 255.0 };
		const float *channel_ranges[] = { ch_range, ch_range, ch_range };
		calcHist(&imgsHLS[i], 1, channel_numbers, Mat(), histogram[i], imgsHLS[i].channels(), number_bins, channel_ranges);
		normalize(histogram[i], histogram[i], 1.0);
	}

	cout << "Image Comparison by HISTCMP_CORREL   " << endl;
	for (int i = 0; i < NUM; i++)
	{
		for (int j = i + 1; j < NUM; j++)
		{
			double matching_score = compareHist(histogram[i], histogram[j], HISTCMP_CORREL);
			cout << imgNames[i] << "-" << imgNames[j] << ", " << 1matching_score << endl;
		}
	}
	return 0;
}


반응형
LIST



Histogram equalization


영상처리기법 중에 histogram equalization이라는 것이 있다. Histogram은 <<여기>>에서 자세히 설명하고 있다. Equalization이라는 것은 특정값을 가진 픽셀들이 너무 많지 않도록 골고루 퍼트리는 기술이다.  비유를 들자면, 서울에 거주하는 사람이 너무 많으면, 서울과 수도권으로 사람들을 이주시키는 식이다. 자세한 원리는 이 <<블로그를>> 참조하면 된다.




Histogram equalization을 하게 되면, 단일 색으로 보이던 부분들이 구분되어 보여지는 효과가 있다. 예를 들어, 아래 사진을 보자. 어느 도시의 야경을 촬영한 것이다. 왼쪽은 원래 영상이고, 오른쪽이 equalization이 후의 영상이다. 차이점은 밤하늘이 전체적인 검은색 일변도에서 여러 가지 단계를 가진 검정색으로 보이게 된 것이다.




Luminance Histogram Equalization


특히, 영상의 밝기 (Luminance)를 equalization하면 안 보이던 부분들이 아주 잘 보이게 된다. 예를 들어 아래 사진을 보자. 왼쪽 사진에서 집 입구가 전체적으로 어두운 색이서 물체들이 제대로 구분되지 않는다. 이 때  luminance를 기준으로 equalization을 하게 되면 오른쪽 결과와 같이 집 입구가 환해지는 효과를 얻을 수 있다.


이러한 환해지는 효과는 아래 사진들에서도 확인할 수 있다. 다양한 상황을 촬영한 사진들에 대해 Luminance histogram equalization을 수행한 결과들이다.




반대로 너무 환해서 잘 보이지 않았던 부분들은 Luminance histogram equalization을 하게 되면, 어두워지면서 세부특징들을 잘 관찰할 수 있다. 예를 들어 아래 사진에서 바닥의 디테일들이 아주 잘 보이는 것을 알 수 있다.



Implementation


OpenCV가 제공하는 기능들을 이용해서 luminance histogram equalization을 구현해 보자. 우선 영상을 읽어들인다. imread( )는 컬러이미지인 경우 BGR 포맷으로 읽기 때문에, 이것을 HLS 포맷으로 변환할 필요가 있다. 이를 위해 cvtColor( )함수를 사용한다. 라인 36 ~ 39은 HLS 포맷 영상에 대해서 Luminance채널에 대해서만 equalization을 수행하고, 이를 다시 HLS 영상에 넣는 과정이다. 마지막으로 HLS 포맷 영상을 BGR 포맷으로 재변환해야 한다.(라인 40) 왜냐하면 OpenCV는 BGR 영상만 표시가능하기 때문이다. 



 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/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

#define SCALE 0.2


int main(int argc, char** argv)
{
	if (argc != 2)
	{
		cout << " Provide image name to read" << endl;
		return -1;
	}

	Mat inputImg, dispImg, disp2Img;
	Mat hlsImg;

	inputImg = imread(argv[1], IMREAD_COLOR); // read in BGR format

	if (inputImg.data == 0)
	{
		cout << "Unable to read " << argv[1] << endl;
		return 0;
	}

	// inputImg is too large to be shown. 
	// use scaled-down dispImg instead just for display
	// use inputImg for the histogram calculation
	resize(inputImg, dispImg, Size(), SCALE, SCALE, CV_INTER_AREA);

	cvtColor(dispImg, hlsImg, COLOR_BGR2HLS_FULL);

	vector<Mat> channels(hlsImg.channels());
	split(hlsImg, channels);
	equalizeHist(channels[1], channels[1]);
	merge(channels, hlsImg);
	cvtColor(hlsImg, disp2Img, COLOR_HLS2BGR_FULL);

	namedWindow("Original", CV_WINDOW_AUTOSIZE);
	namedWindow("After luminacense equalization", CV_WINDOW_AUTOSIZE);

	moveWindow("Original", 100, 100);
	moveWindow("After luminacense equalization", 120, 120);

	imshow("Original", dispImg);
	imshow("After luminacense equalization", disp2Img);

	waitKey(0);
	return 0;
}


반응형
LIST


Histogram 그리기


이미지에서 픽셀들이 가지는 값들의 출현빈도를 히스토그램 (histogram)이라고 한다. 

예를 들어, gray 이미지에서 각 픽셀은 0부터 255까지의 값을 갖는다.

이미지의 크기를 300 x 300 이라고 한다면, 총 90,000개의 픽셀들을 0~255 값에 따라 분류하여

각 개별값을 갖는 픽셀들이 몇 개씩인지 알아낸 것이 히스토그램이다.


히스토그램은 contranst enhancement (안 보이는 부분을 잘 보이게) 등에 사용되는데,

이 때 사용되는 기술이 histogram equalization이다. 이것의 원리는 이 <<블로그>>를 참조한다.


OpenCV에서는 이미지의 히스토그램 계산이 쉽도록 함수 calcHist( )를 제공한다.


void cv::calcHist(const Mat * images,
int nimages,
const int * channels,
InputArray mask,
OutputArray hist,
int dims,
const int * histSize,
const float ** ranges,
bool uniform = true,
bool accumulate = false 
)



images: Histogram을 계산할 이미지들에 대한 배열이다.

nimages: images 배열에 포함된 이미지의 개수 이다.

channels: Histogram을 계산할 채널 번호들의 배열이다. 예를 들어, 아래 그림과 같이 BGR 이미지 2장에 대해, 첫 번째 이미지는 B 채널, 두 번째 이미지는 G 채널에 대해서 histogram을 구하고자 한다면 {0, 4}를 배열에 넣어서 전달해야 한다.



mask: Histogram을 계산할 영역을 지정할 수 있다. 옵션사항이고, emptry mask (즉, Mat( ))를 전달하면 아무런 동작도 하지 않는다.

hist: Histogram 계산결과를 저장한다.

dims: Histogram 계산결과를 저장한 hist의 차원을 가리킨다. 

histSize: 각 차원의 bin 개수, 즉 빈도수를 분류할 칸의 개수를 의미한다.

ranges: 각 차원의 분류 bin의 최소값과 최대값을 의미한다.


아래는 gray 이미지에 대해 픽셀값 0 ~ 255에 대해 histogram을 계산하여 그래프로 표시한 결과이다.


<그림 1> Histogram을 계산할 대상 이미지 (gray)


<그림 2> 그림 1에 대한 Histogram 계산결과를 시각화한 결과


위의 결과를 만들어 내기 위한 프로그램의 소스코드이다.


<소스코드 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
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

#define SCALE 0.2

int main(int argc, char** argv)
{

	if (argc != 2)
	{
		cout << " Provide image name to read" << endl;
		return -1;
	}

	Mat inputImg;
	Mat greyImg;

	inputImg = imread(argv[1], CV_LOAD_IMAGE_COLOR);
	resize(inputImg, inputImg, Size(), SCALE, SCALE, CV_INTER_AREA);

	// inputImg를 gray영상(greyImg)으로 변환한다.
	cvtColor(inputImg, greyImg, CV_BGR2GRAY);

	MatND histogram;
	const int* channel_numbers = { 0 };
	float channel_range[] = { 0.0, 255.0 };
	const float* channel_ranges = channel_range;
	int number_bins = 255;

	calcHist(&greyImg, 1, channel_numbers, Mat(), histogram, 1, &number_bins, &channel_ranges);

	// Plot the histogram
	int hist_w = 512; int hist_h = 400;
	int bin_w = cvRound((double)hist_w / number_bins);

	Mat histImage(hist_h, hist_w, CV_8UC1, Scalar(0, 0, 0));
	normalize(histogram, histogram, 0, histImage.rows, NORM_MINMAX, -1, Mat());

	for (int i = 1; i < number_bins; i++)
	{
		line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(histogram.at<float>(i - 1))),
			Point(bin_w*(i), hist_h - cvRound(histogram.at<float>(i))),
			Scalar(255, 0, 0), 2, 8, 0);
	}

	namedWindow("Original", CV_WINDOW_AUTOSIZE);
	namedWindow("Histogram", CV_WINDOW_AUTOSIZE);
	
	moveWindow("Original", 100, 100);
	moveWindow("Histogram", 120, 120);

	imshow("Original", greyImg);
	imshow("Histogram", histImage);

	waitKey(0);
	return 0;
}


반응형
LIST

+ Recent posts