본문 바로가기

Computer Vision by OpenCV

Histogram Matching, Color mapping, Color Transfer

반응형






ㅇ 히스토그램 매칭 (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;
}









반응형