Histogram Equaliztion(히스토그램 평활화)을 알아보자

2018, Jun 27    

히스토그램 평활화란 명암 값의 분포가 한쪽으로 치우친 영상이 있을 때, 명암값을 고르게 분포시켜주기 위한 처리 방법을 말한다. 예를 들어, 어두운영역에서 세밀한 영상이 있을 때 히스토그램평활화를 이용하면 보다 밝은 영상으로 세밀한 부분을 볼 수 있을 것이다.

히스토그램 평활화는 다음의 단계를 거친다.

  1. 그림의 히스토그램을 구한다.
  2. 히스토그램의 누적 히스토그램의 구하고 정규화 한다.
  3. 이미지에 정규화된 누적 히스토그램의 값을 적용한다.

위 기능을 구현할 수 있는 함수를 정의하자. 헤더파일 선언부는 생략하겠다.

ImageProc.cpp

void ImageProc::CreateCumulativeHistogramSingleChannel(unsigned char* image_input,
	unsigned char* image_output, const int width, const int height)
{
	// 1. 히스토그램을 만든다.
	float histogram[256] = { 0.f };
	for (int i = 0; i < width*height; i++)
		histogram[image_input[i]]++;

	// 2. 누적히스토그램을 만든다.
	float cumulative_histogram[256] = { 0.f };
	float sum = 0.f;
	for (int i = 0; i < 256; i++)
	{
		sum += histogram[i];
		cumulative_histogram[i] = sum;
	}

	// 누적히스토그램을 정규화한다.
	// 정규화 = (값 / 최대값(이미지 사이즈)) * 픽셀최대값(255)
	for (int i = 0; i < 256; i++)
		cumulative_histogram[i] = (cumulative_histogram[i] / (width * height)) * 255 + 0.5f;

	// 3. 이미지에 적용한다.
	for (int i = 0; i < width*height; i++)
		image_output[i] = static_cast<unsigned char>(cumulative_histogram[image_input[i]]);

}

위는 단일 채널에서만 가능한 함수이므로, 칼라이미지에서 평활화 가능한 함수를 정의한다.

ImageProc.cpp

 void ImageProc::CreateHistogramEqualization(unsigned char* image_input,
	unsigned char* image_output, const int width, const int height)
{
	unsigned char* img_R = new unsigned char[width*height];
	unsigned char* img_G = new unsigned char[width*height];
	unsigned char* img_B = new unsigned char[width*height];

	SplitChannels_ColorToRGB(img_R, img_G, img_B, image_input, width, height);

	unsigned char* equal_R = new unsigned char[width*height];
	unsigned char* equal_G = new unsigned char[width*height];
	unsigned char* equal_B = new unsigned char[width*height];

	//SplitChannels_ColorToRGB(equal_R, equal_G, equal_B, image_output, width, height);

	CreateCumulativeHistogramSingleChannel(img_R, equal_R, width, height);
	CreateCumulativeHistogramSingleChannel(img_G, equal_G, width, height);
	CreateCumulativeHistogramSingleChannel(img_B, equal_B, width, height);

	MergeChannels_RGBToColor(equal_R, equal_G, equal_B, image_output, width, height);

	delete[] img_R;
	delete[] img_G;
	delete[] img_B;

	delete[] equal_R;
	delete[] equal_G;
	delete[] equal_B;
}
 

이벤트 처리기를 정의하자. 그전에 SetDrawImage 를 아래와 같이 수정한다. 수정하는 이유는 기존에 흑백이미지만 오른쪽 화면에 나올 수 있는데, 이제 칼라이미지도 나올 수 있게 하기 위해서 이다.

ImageProcessingView.cpp

 void CImageProcessingView::SetDrawImage(unsigned char* image, unsigned char* image_gray,
	const int width, const int height, const int byte)
{
	m_ImgWidth = width * 2;
	m_ImgHeight = height;

	if (m_Image)
		delete[] m_Image;

	// 화면을 두 배로 써야 하기 때문에 *2를 해준다.
	m_Image = new unsigned char[width * 2 * height * 4];

	for (int i = 0; i<width; i++)
	{
		for (int j = 0; j < height; j++)
		{
			m_Image[(m_ImgWidth*j + i) * 4 + 0] = image[(width*j + i) * 4 + 0];
			m_Image[(m_ImgWidth*j + i) * 4 + 1] = image[(width*j + i) * 4 + 1];
			m_Image[(m_ImgWidth*j + i) * 4 + 2] = image[(width*j + i) * 4 + 2];
		}
	}

	// color image 다음에 gray image 가 오면 흑백이미지 처리를 해준다.
	if (byte == 1)
	{
		for (int i = 0; i<width; i++)
		{
			for (int j = 0; j < height; j++)
			{
				m_Image[(m_ImgWidth*j + i + width) * 4 + 0] = image_gray[(width*j + i)];
				m_Image[(m_ImgWidth*j + i + width) * 4 + 1] = image_gray[(width*j + i)];
				m_Image[(m_ImgWidth*j + i + width) * 4 + 2] = image_gray[(width*j + i)];
			}
		}
	}
    // color image 가 다음에 나오면 color image 처리를 해준다.
	else
	{
		for (int i = 0; i<width; i++)
		{
			for (int j = 0; j < height; j++)
			{
				m_Image[(m_ImgWidth*j + i + width) * 4 + 0] = image_gray[(width*j + i)*byte + 0];
				m_Image[(m_ImgWidth*j + i + width) * 4 + 1] = image_gray[(width*j + i)*byte + 1];
				m_Image[(m_ImgWidth*j + i + width) * 4 + 2] = image_gray[(width*j + i)*byte + 2];
			}
		}
	}
	
}
 

이벤트 처리기를 단다.

ImageProcessingDoc.cpp

 void CImageProcessingDoc::OnHistogramHistogramequalization()
{
	// TODO: 여기에 명령 처리기 코드를 추가합니다.
	histogram_equalization_image =
		new unsigned char[m_Images[cur_index].width * m_Images[cur_index].height *4]; 
        ( 클래스 변수로 설정하고 소멸자에서 해당 버퍼를 delete 하는 기능을 추가한다. )

	memset(histogram_equalization_image, 0, sizeof(unsigned char)
		*m_Images[cur_index].width * m_Images[cur_index].height * 4);

	ImageProc::CreateHistogramEqualization(m_Images[cur_index].image_color,
		histogram_equalization_image, m_Images[cur_index].width, m_Images[cur_index].height);

	CImageProcessingView* pView = (CImageProcessingView*)((CMainFrame*)(AfxGetApp()->m_pMainWnd))->GetActiveView();

	pView->SetDrawImage(m_Images[cur_index].image_color, histogram_equalization_image,
		m_Images[cur_index].width, m_Images[cur_index].height, 4);

	pView->OnInitialUpdate();
}
 

실행하면 다음과 같이 이미지의 명암이 고루 분포된다. histogram-equalization-hijy

histogram-equalization-jy