컴공지식/컴퓨터비전

히스토그램이란?

개강한 공대생 2024. 9. 8. 23:17

히스토그램은 이미지에서 각 픽셀의 밝기 수준(=인텐시티 값)이 얼마나 자주 나타나는지를 나타내는 그래프다.

이제 다음 변수를 살펴보자

h(r_k)

이게 무엇을 의미하냐면 r_k라는 인텐시티값을 가진 픽셀의 수를 나타내는 함수다.

그러니까 만약 r_k가 20이고(인텐시티값이 20) 이 인텐시티값을 가진 픽셀이 100개 있으면 h(r_k)는 100이 되는거다.

그러니까.. 이 함수의 값들을 알고 있으면 인텐시티의 분포를 볼 수 있는거다.

예를 들어, 밝은 부분이 많으면 높은 인텐시티 값 쪽의 h(r_k) 값이 클 거고, 어두운 부분이 많으면 낮은 인텐시티 쪽의 값들이 커지는거다.

3X3 매트릭스에는 각 픽셀의 인텐시티 값이 적혀있다.

20인텐시티를 가진 픽셀의 값은 4개이고, 50의 인텐시티 값을 가진 픽셀은 2개이고, 120은 2개, 150은 1개가 있다.

이를 히스토그램으로 나타내면 다음 그림과 같이 되는 것이다.

 

이제 히스토그램 정규화( histogram normalization )에 대해서 알아보자

여기서 말하는 정규화는, 히스토그램의 각 값을 전체 픽셀 수로 나눠서 계산하는 거다.

이렇게 하면 히스토그램의 각 값이 전체 이미지에서 차지하는 비율을 알 수 있게 된다.

그래서 이 정규화된 히스토그램은 확률 함수(probability function)처럼 볼 수 있다.

간단히 말하면, 이미지에서 특정 인텐시티 값이 나올 확률을 구하는 것이다.

 

여기서 bin이라는 개념이 나오는데 bin은 히스토그램에서 데이터를 그룹으로 나누는 구간을 뜻한다.

밝기 값들을 특정 범위로 나누는 칸이라고 보면 된다.

 

이제 히스토그램 정규화의 예를 살펴보자

만약 인텐시티 값이 16(0~15)이고

bin이 4라고 가정하자

그리고 픽셀들의 값은 다음과 같다. (총 30개의 픽셀)

1번 bin[0~3]: 밝기 값이 0에서 3 사이인 픽셀 수가 28개이니 28/30이 된다.

2번 bin[4~7]: 1/30

3번 bin[8~11]: 1/30

4번 bin[12~15]: 이 구간에는 아무것도 없으므로 0/30

 

대부분의 값이 0에서 3 사이에 몰려있는 것을 확인할 수 있다.

그래서 첫 번째 bin에 많은 픽셀이 있는 거다.

분포를 쉽게 파악할 수 있다.

 

이제 히스토그램 평활화( Histogram Equalization) 에 대해서 알아보자

히스토그램 평활하는 이미지를 더 선명하고 뚜렷하게 보이게 하기 위해 대조(contrast)를 조정하는 방법이다.

이미지에서 밝기(혹은 색상) 차이를 확실하게 만들어서, 물체를 더 잘 구별할 수 있게 해주는 거다.

 

히스토그램 평활화를 하게된다면 다음 이미지가

다음과 같이 변하게 된다.

평활화된 히스토그램 그래프를 확인할 수 있다.

 

다음은 히스토그램 평활화를 하는 코드다

 

int main() {
    Mat image;
    Mat hist_equalized_image;

    image = imread("lena.png", 0); // 이미지를 그레이스케일로 읽기
    if (!image.data) exit(1);      // 이미지가 제대로 불러와지지 않으면 종료

    equalizeHist(image, hist_equalized_image); // 히스토그램 평활화

    imshow("Input Image", image);  // 원본 이미지 출력
    imshow("Hist Equalized Image", hist_equalized_image); // 평활화된 이미지 출력

    waitKey(0);  // 키 입력 대기
    return 0;
}

 

히스토그램 평활화가 일어나는 부분은 주석을 보면 알겠지만

equalizeHist(image, hist_equalized_image);

이 부분인데 image가 평활화되어 hist_equalized_image에 저장된다.

 

히스토그램 평활화가 항상 이미지 품질을 향상시키는 건 아니라는 것이다.

특히 특정 범위의 데이터가 우세할 때는 문제가 생길 수 있다.

 

다음은 히스토그램의 그래프도 OpenCV에서 함께 보여주는 코드의 예시다.

int main() {
    Mat image;
    Mat hist_equalized_image;
    Mat hist_graph;
    Mat hist_equalized_graph;

    image = imread("lena.png", 0);  // 이미지를 흑백으로 불러옴
    if (!image.data) exit(1);  // 이미지가 제대로 불러와지지 않으면 프로그램 종료

    equalizeHist(image, hist_equalized_image);  // 히스토그램 평활화 적용

    hist_graph = drawHistogram(image);  // 원본 이미지의 히스토그램 그리기
    hist_equalized_graph = drawHistogram(hist_equalized_image);  // 평활화된 이미지의 히스토그램 그리기

    imshow("Input Image", image);  // 원본 이미지 출력
    imshow("Hist Equalized Image", hist_equalized_image);  // 히스토그램 평활화된 이미지 출력
    imshow("Hist Graph", hist_graph);  // 원본 이미지의 히스토그램 출력
    imshow("Hist Equalized Graph", hist_equalized_graph);  // 평활화된 이미지의 히스토그램 출력

    waitKey(0);  // 키 입력 대기
    return 0;
}

 

 

여기서 drawHistogram함수는 openCV에서 제공하는 함수가 아니기 때문에 우리가 직접 구현해야 한다.

drawHistogram함수의 구현은 아래 코드와 같다.

 

Mat drawHistogram(Mat src) {
    Mat hist, histImage;

    // Establish the number of bins and histogram range
    int i, hist_w, hist_h, bin_w, histSize;
    float range[] = { 0, 256 };  // 히스토그램의 범위 (0 ~ 255)
    const float* histRange = { range };

    hist_w = 512;  // 히스토그램 이미지의 너비
    hist_h = 400;  // 히스토그램 이미지의 높이
    histSize = 256;  // 히스토그램의 크기 (0 ~ 255)
    bin_w = cvRound((double) hist_w / histSize);  // 각 bin(히스토그램 막대)의 너비 계산

    // Draw the histogram on a white background
    histImage = Mat(hist_h, hist_w, CV_8UC3, Scalar(255, 255, 255));

    // Compute the histograms
    calcHist(&src, 1, 0, Mat(), hist, 1, &histSize, &histRange);  // 이미지에서 히스토그램 계산

    // Fit the histogram to the image height (normalize)
    normalize(hist, hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());

    // Draw the histogram on the image
    for (i = 0; i < histSize; i++) {
        rectangle(histImage, Point(bin_w * i, hist_h),
                  Point(bin_w * (i + 1), hist_h - cvRound(hist.at<float>(i))),
                  Scalar(0, 0, 0), -1);  // 검은색으로 막대 그리기
    }

    return histImage;
}

 

이 함수는 입력 이미지 src로부터 히스토그램을 계산하고, 그 히스토그램을 시각적으로 그려서 반환한다.

calcHist()로 히스토그램을 계산하고, normalize()로 히스토그램 값을 이미지 크기에 맞게 조정한 후, rectangle() 함수로 각 막대를 그린 다음 그 결과를 반환하는 구조다.