이번 글에서는 히스토그램 평활화(histogram equalization)에 대해 알아보겠습니다.
히스토그램 평활화는 스트레칭과 마찬가지로, 영상의 픽셀 값이 영상 영역 전체에 골고루 분포하도록 하는 알고리즘입니다.
아래와 같이 명암비가 낮은 이미지와 히스토그램이 있습니다.

명암비가 낮고 분포가 한곳에 몰려있는 영상

명암비가 낮고 분포가 한곳에 몰려있는 영상
영상의 픽셀값이 골고루 퍼져있지 않은 것도 문제지만, 특정 픽셀 값에 분포가 너무 몰려 있습니다.
이 경우에 스트레칭으로 늘려도 한 부분에 몰려있다는 문제가 있습니다.

히스토그램 스트레칭

히스토그램 스트레칭
이런 상황에서는 히스토그램 평활화를 사용하면 됩니다.
히스토그램 평활화는 특정 픽셀값 부근에 분포가 너무 뭉쳐있는 경우 넓게 펼쳐주는 방식입니다.
그 과정은 아래의 4 X 4 영상으로 간단히 설명드리겠습니다.

위와 같은 영상에서 0의 값을 가지는 픽셀은 3개, 1은 3개, 2는 4개, 3은 0개, 4는 2개, 5는 1개, 6은 2개, 7은 1개가 있습니다.
이것을 표로 표시하면 아래와 같습니다.

누적값은 아래 수식을 사용한 값입니다.

누적 함수: H(g) = Σ h(i)
이 누적 값에 픽셀 최댓값 / 픽셀 수를 곱해줍니다. 예제에서는 픽셀 최댓값은 7, 픽셀 수는 16입니다.

이렇게 각 픽셀 값이 존재하는 위치에 결과값을 넣어주면 되는데 영상에서는 소수점을 사용하지 않으므로 반올림을 해줍니다.

이 경우 아래와 같이 픽셀값이 변경됩니다.

전체 과정을 수식으로 표현하면 아래와 같습니다.

dst(x, y) = round ( H( src(x, y) ) * L(max) / N )
위 모든 과정은 equalizeHist 함수를 사용하면 됩니다.
/**
@param src 소스 영상. 8-bit single channel image.
@param dst 결과 영상. src와 같은 사이즈, 같은 타입이여야함
*/
void equalizeHist( InputArray src, OutputArray dst );
샘플 코드는 아래와 같습니다.
// 히스토그램 구하기
Mat src = imread("mountine.jpg", IMREAD_GRAYSCALE);
if (src.empty())
{
cerr << "image open error" << endl;
return -1;
}
Mat dst;
equalizeHist(src, dst);
int iImages = 1;
int iChannels[] ={0};
Mat histogram;
int iDims = 1;
const int iHistogramSize[] ={256};
float graylevel[] ={0, 256};
const float *pRanges[] ={graylevel};
calcHist(&dst, iImages, iChannels, noArray(), histogram, iDims, iHistogramSize, pRanges);
// histogram 그리기
double dbMax; // 히스토그램에서 제일 큰 값
minMaxLoc(histogram, 0, &dbMax);
Mat histogram_img(100, 256, CV_8UC1, Scalar(255, 255, 255));
for (int iRow = 0 ; iRow < 256 ; iRow++)
{
float val_hist = histogram.at<float>(iRow, 0);
int iPos = cvRound(val_hist * 100 / dbMax);
line(histogram_img, Point(iRow, 100), Point(iRow, 100 - iPos), Scalar(0, 0, 0));
}
imshow("src", src);
imshow("dst", dst);
imshow("histogram", histogram_img);
waitKey();
destroyAllWindows();
return 0;
결과는 아래와 같습니다.

히스토그램 평활화

히스토그램 평활화
스트레칭과 다르게 픽셀 분포가 넓어진 것을 볼 수 있습니다.