해당 포스트는 "열혈강의 영상처리 프로그래밍" 책의 내용을 요약한 것이다.



※ 히스토그램

영상의 광학적 변환(1) 밝기와 명암 조절 포스트에서 눈에 잘 보이는 영상을 보여주기 위해 특정 상수를 곱하는 방법을 배웠다. 이 방법은 영상이 좋아지는 데 제한이 있다. 영상 안 모든 픽셀 값을 고르게 분포해 주지 못하기 때문이다. 히스토그램은 이를 보완해 모든 픽셀값을 대체로 고르게 사용하도록 개선하여 눈에 더 잘 보이는 영상을 만들어준다. 

왼쪽 그림은 픽셀 값이 고르게 분포하지 못한 영상이고 오른쪽은 고르게 분포한 영상이다. 왼쪽 그림은 픽셀이 고르게 분포하지 못하여 어두운 부분이 세밀하게 보이지 못하지만, 오른쪽 그림은 고르게 분포하여 어두운 부분이 세밀하게 보인다.

두 그래프는 위에 있는 영상에 대한 각각의 히스토그램이다. 히스토그램은 데이터의 분포를 막대 그래프 형태로 나타낸 것으로, x축은 0에서 255까지의 픽셀 값을 y축은 픽셀 수를 나타낸다. 왼쪽 히스토그램은 픽셀 수가 0에서 255까지 고르게 분포하지 않는다. 오른쪽은 픽셀 수가 왼쪽에 비해 고르게 분포한다. 누적 합은 오른쪽이 왼쪽에 비해 선형으로 일직선으로 되는 것을 알 수 있다.



※ 히스토그램 평활화를 이용하여 영상 명암 보정

위에서 살펴보았듯이 우리는 픽셀 값이 고르게 분포하지 않은 영상을 고르게 분포하도록 바꿔야한다. 즉, 히스토그램에서 중간 밝기 영역에 많은 픽셀을 할당하고 어둡고 밝은 밝기 영역은 조금 할당해야 한다. 이를 영상 처리에서 히스토그램 평활화 또는 균등화 작업이라고 한다.(컬러 영상의 히스토그램은 지식이 더 필요해 회색조 영상의 히스토그램에 대해서만 설명한다.)


- 히스토그램 평활화 작업

먼저, 히스토그램 평활화 작업을 하려면 히스토그램을 구해야 한다. 히스토그램은 각 밝기에 대한 픽셀 수를 나타낸다. 따라서 아래 코드와 같이 모든 픽셀을 돌면서 밝기를 배열의 인덱스로 하는 256 크기의 배열 안에 1을 더해 주면 각 밝기에 대한 픽셀수가 나온다.

memset(m_histogram, 0 , 256*sizeof(int));
int r, c;
for(r=0; r < nHeight; r++)
   for(c=0; c < nWidth ; c++)
       m_histogram[pIn[r*nWStep+c]]++;

그 다음 계산한 히스토그램을 가지고 히스토그램 누적 합을 계산한다.

m_histogramCdf[i] = m_histogram[0] + m_histogram[1] + ....... + m_histogram[i];

마지막으로 누적 합 값으로 픽셀 값을 변환한다. 히스토그램 평탄화는 픽셀 값이 0에서 255까지 고르게 분포하도록 하는 것이다. 위에서 본 그래프와 같이 픽셀값(밝기)가 증가할수록 누적합이 일정 비율로 증가해야 한다. 즉, 비례해야 한다.  그래서 다음과 같은 방법을 사용한다. 각 픽셀에 대한 누적합이 0에서 Height*Width까지의 범위를 가진다. 누적합에 255/(Height*Width)를 곱해 0에서 255까지의 범위를 가지게 만든다. 특정 위치(x,y)에 대한 픽셀 값(p)을 픽셀값의 누적합(z)으로 바꾼다. 그러면 픽셀 값(p)를 가지고 있는 영상 내 모든 위치들의 픽셀 값은 z로 바뀌고 해당 픽셀의 누적값은 그대로 z가 된다. 따라서 히스토그램이 y=x 그래프가 되 서로 비례하게 되고 히스토그램 평활화가 완료된다. 아래는 이에 관한 소스 코드이다. 

void CImageEnhancerDlg::_HistogramEqualization() { if (m_imageIn.GetChannel() != 1) { AfxMessageBox("회색조 영상을 입력하세요."); return; } int nWidth = m_imageIn.GetWidth(); int nHeight = m_imageIn.GetHeight(); memset(m_histogram, 0, 256*sizeof(int)); int r, c; for (r=0 ; r<nHeight ; r++) //히스토그램 계산 { BYTE* pIn = m_imageIn.GetPtr(r); for (c=0 ; c<nWidth ; c++) { m_histogram[pIn[c]]++; } } double dNormFactor = 255.0 / (nWidth * nHeight); for (int i=0 ; i<256 ; i++) //누적합이 0에서 255까지 범위를 갖도록 하기 위해 { m_histogramCdf[i] = m_histogram[i]*dNormFactor; } for (int i=1 ; i<256 ; i++)//누적합 도출 { m_histogramCdf[i] = m_histogramCdf[i-1] + m_histogramCdf[i]; } for (r=0 ; r<nHeight ; r++) { BYTE* pIn = m_imageIn.GetPtr(r); BYTE* pOut = m_imageOut.GetPtr(r); for (c=0 ; c<nWidth ; c++) { //픽셀 값을 누적합 값으로 변환 pOut[c] = (BYTE)(m_histogramCdf[pIn[c]]+0.5); } } ShowImage(m_imageOut, "히스토 그램 평활화 결과 영상"); }



+ Recent posts