본문 바로가기

Computer Vision by OpenCV

OpenCV: HLS영상 히스토그램(histogram)의 장점

반응형




사진에서 어떤 계열의 색이 가장 많은지 알아보자.


RGB모델을 사용하는 영상에서는 이 질문에 쉽게 답하기가 어렵다. 왜냐하면 R, G, B 각각이 256가지씩 값을 가질 수가 있어서 

즉 이만큼의 색 각각에 대해서 몇 개의 픽셀들이 그 값을 갖는지를 알아봐야 하기 때문이다.


HLS모델 영상이라면 문제가 조금은 쉬워진다. 우선 HLS모델이 어떤 것인지는  여기를 보면 된다. 사진에서 어떤 계통의 색이 가장 많은지는 픽셀들이 가지고 있는 Hue, Luminance, Saturation 중에서 Hue 정보만 보면 된다. 게다가 이것은 0 ~ 360까지 값을 갖기 때문에 픽셀들을 이것에 따라 분류하기만 하면 된다. 물론 Hue 정보만 가지고는 정확히 어떤 색이라고 말할 수는 없다. 다만 어떤 계열( 빨간색, 파란색, 초록색 등)의 색인지 만을 판별할 수 있다. 이 정도 정보만 하더라도 이미지를 검색하거나 분류할 때 요긴하게 사용할 수 있다.


좀 더 구체적으로 얘기를 하자면 Hue값에 대해서 사진의 히스토그램을 구하고, 가장 높은 빈도수를 갖는 Hue 값 영역의 색을 사진의 대표 색깔로 하면 되는 것이다. 


복숭아와 레몬

예를 들어, 그림 1은 복숭아와 레몬을 찍은 사진이다. 사람은 대표 색깔을 쉽게 알 수 있는데, 사진에서 빨간색과 노란색 계열이 가장 많다. Hue채널에 대한 히스토그램도 그렇게 나왔을까? 그림 2는 해당하는 Hue채널에 대한 히스토그램으로, 예상한대로 빨간 색 계통의 빈도수가 가장 높고, 그 다음은 노란색이라는 것을 알 수 있다. 히스토그램 선을 그릴 때, 해당 Hue 채널의 색으로 표시했기 때문에 그 영역이 무슨 색인지를 쉽게 알 수 있다. 


<그림 1> 복숭아와 레몬을 찍은 사진. 


<그림 2> 그림 1의 Hue채널에 대한 히스토그램. 각 Hue영역에 해당하는 빈도수를 해당하는 색으로 그렸다.


포도밭

그림 3은 포도밭 사진으로 녹색 계열의 색이 대표색이라고 할 수 있다. 물론 지붕의 빨간색, 하늘의 파란 색도 있다. 이 사진에 대한 Hue 히스토그램은 그림 4이다. 가장 많은 색은 붉은 색, 노란색과 하늘색이고, 오히려 녹색은 그다지 많지 않다. 결과가 직관적으로 이해되지는 않지만, 일단 넘어가자.


<그림 3> 포도밭을 찍은 사진


<그림 4> 그림 3 사진의 Hue채널에 대한 히스토그램


해변

그림 5는 해변 이미지이고, 그림 6은 해당하는 히스토그램이다. 결과에서 보듯이 하늘색 계통이 압도적으로 가장 많다는 것을 알 수 있다.

<그림 5> 해변사진


<그림 6> 그림 5 해변사진에 대한 Hue 히스토그램


녹색실험

그림 3(포도밭)에 대한 히스토그램 (그림 4) 결과가 미심쩍어서 한 가지 실험을 해보았다. 그림 7과 같이 녹색 이미지에 대해서 Hue 히스토그램을 구해본 것이다. 포도밭 녹색을 제대로 검출할 수 있는지 검증하기 위한 목적이다. 결과 히스토그램에서는 녹색이 압도적으로 많이 나왔기 때문에 녹색을 검출하는데 문제가 없었다는 것을 확인할 수 있다. 그렇다면 왜 포도밭에서는 붉은색과 노란색이 왜 그리 많이 나왔던 것일까? 좀 더 생각해 볼 문제다. 

<그림 7> 실험용 녹색 이미지

<그림 8> 그림 7에 대한 Hue 히스토그램


소스코드

위의 실험에 사용된 소스 프로그램이다. 


  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
148
149
150
151
152
153

#include <opencv2/opencv.hpp> #include <iostream> using namespace cv; using namespace std; #define SCALE 0.2 /* given hue value, which is original_hue/2 calculate a corresponding RGB. It is assumed that luminance is 0.5 (50%), and saturation is 1.0(100%) */ Scalar cvtHLStoRGB(double hue) { double _hue = 2 * hue; double C = 1.0; double X = (double)(1.0 - abs((double)(((int)(_hue/60.0) % 2) - 1))); double m = 0.0; double R = 0.0; double G = 0.0; double B = 0.0; if (_hue >= 0 && _hue < 60) { R = C*255.0; G = X*255.0; } else if (_hue >= 60 && _hue < 120) { R = X*255.0; G = C*255.0; } else if (_hue >= 120 && _hue < 180) { G = C*255.0; B = X*255.0; } else if (_hue >= 180 && _hue < 240) { G = X*255.0; B = C*255.0; } else if (_hue >= 240 && _hue < 300) { R = X*255.0; B = C*255.0; } else if (_hue >= 300 && _hue < 360) { R = C*255.0; B = X*255.0; } return Scalar(B, G, R); } int main(int argc, char** argv) { if (argc != 2) { cout << " Provide image name to read" << endl; return -1; } Mat inputImg, dispImg; Mat hlsImg; inputImg = imread(argv[1], CV_LOAD_IMAGE_COLOR); // 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(inputImg, hlsImg, CV_BGR2HLS); // separate into three images with only one channel, H, L, and S vector<Mat> hls_images(3); split(hlsImg, hls_images); //-------------------------------------- // H, L, S 채널별로 histogram을 계산 //-------------------------------------- // Prepare three histograms for H, L, S MatND* histogram = new MatND[hlsImg.channels()]; // Each image has only one channel. const int* channel_numbers = { 0 }; float channel_range[] = { 0.0, 255.0 }; const float* channel_ranges = channel_range; // 255-bins for histogram int number_bins = 255; // calculate histograms one at a time, H, L, and S // histogram[0] contains H's // histogram[1] contains L's // histogram[2] contains S's for (int chan = 0; chan < hlsImg.channels(); chan++) { calcHist(&(hls_images[chan]), 1, channel_numbers, Mat(), histogram[chan], 1, &number_bins, &channel_ranges); } //------------------------ // Plot the histograms in each image //------------------------ int hist_w = 512; int hist_h = 400; int bin_w = cvRound((double)hist_w / number_bins); // histogram image for H channel Mat histImageH(hist_h, hist_w, CV_8UC3, Scalar(0, 0, 0)); normalize(histogram[0], histogram[0], 0, histImageH.rows, NORM_MINMAX, -1, Mat()); // histogram image for L channel Mat histImageL(hist_h, hist_w, CV_8UC3, Scalar(0, 0, 0)); normalize(histogram[1], histogram[1], 0, histImageL.rows, NORM_MINMAX, -1, Mat()); // histogram image for S channel Mat histImageS(hist_h, hist_w, CV_8UC3, Scalar(0, 0, 0)); normalize(histogram[2], histogram[2], 0, histImageS.rows, NORM_MINMAX, -1, Mat()); for (int i = 1; i < number_bins; i++) { line(histImageH, Point(bin_w*(i - 1), hist_h - cvRound(histogram[0].at<float>(i - 1))), Point(bin_w*(i), hist_h - cvRound(histogram[0].at<float>(i))), cvtHLStoRGB((double)i), 2, 8, 0); line(histImageL, Point(bin_w*(i - 1), hist_h - cvRound(histogram[1].at<float>(i - 1))), Point(bin_w*(i), hist_h - cvRound(histogram[1].at<float>(i))), Scalar(0, 255, 0), 2, 8, 0); line(histImageS, Point(bin_w*(i - 1), hist_h - cvRound(histogram[2].at<float>(i - 1))), Point(bin_w*(i), hist_h - cvRound(histogram[2].at<float>(i))), Scalar(0, 0, 255), 2, 8, 0); } namedWindow("Original", CV_WINDOW_AUTOSIZE); namedWindow("histogramH", CV_WINDOW_AUTOSIZE); namedWindow("histogramL", CV_WINDOW_AUTOSIZE); namedWindow("histogramS", CV_WINDOW_AUTOSIZE); moveWindow("Original", 100, 100); moveWindow("histogramH", 120, 120); moveWindow("histogramL", 140, 140); moveWindow("histogramS", 160, 160); imshow("Original", dispImg); imshow("histogramH", histImageH); imshow("histogramL", histImageL); imshow("histogramS", histImageS); waitKey(0); return 0; }


소스코드 설명


line 13-54

함수 cvtHLStoRGB( )는 Hue값을 BGR 색으로 만들어준다. 이 함수는 Hue 히스토그램에서 Hue영역에 상응하는 색으로 선을 그리는데 사용된다. Hue를 BGR로 변환하는 아래 공식을 프로그램 한 것이다.



line 77-78

HLS영상을 채널별로 구분하여 별도의 영상으로 나누는 과정이다.


line 85-103

채널별로 나뉘어진 영상들에 대해 각각 히스토그램을 계산하는 부분이다. 채널별로 3번에 걸쳐 함수 calcHist( )를 이용하여 히스토그램을 계산하고, 배열 histogram에 저장한다.


line 125-127

Hue 히스토그램에서 해당 Hue 영역의 색으로 선을 그리는 부분이다.

반응형