반응형


히스토그램 평활화는 히스토그램을 이용하여 이미지의 명암 대비를 개선시키는 방법입니다.  


그레이스케일 영상의 경우 픽셀이 가질 수 있는 값의 범위는 0 ~ 255 사이의 값입니다.  이미지 상에서 픽셀값이 0인 갯수, 픽셀값이 1인 갯수, ... , 픽셀값이 255인 갯수를 세어서 배열에 저장한 것이 히스토그램입니다. 왼쪽 이미지에 대해 히스토그램을 구하여 그래프로 그려보면 중앙의 좁은 범위에 픽셀들이 몰려있는 것을 볼 수 있습니다.  그래프에서 x축은 0~255사이의 픽셀값 범위이며 y축은 픽셀 갯수입니다. 



히스토그램 평활화를 적용시키면 이미지의 픽셀값이 0~255 범위내에 골고루 분산되어 이미지의 명암대비가 개선됩니다.  




구현 과정

1. 입력 영상에 대한 히스토그램과 누적 히스토그램을 계산합니다. 누적 히스토그램은 현재 픽셀값까지의 히스토그램을 더해주는 것입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    //입력 그레이스케일 영상의 히스토그램 계산
    int histogram[256= { 0, };
 
    for (int y = 0; y < img_input.rows; y++)
    {
        for (int x = 0; x < img_input.cols; x++)
        {
            int value = img_gray.at<uchar>(y, x);
            histogram[value] += 1;
        }
    }
 
    //입력 그레이스케일 영상의 누적 히스토그램 계산
    int cumulative_histogram[256= { 0, };
    int sum = 0;
 
    for (int i = 1; i < 256; i++)
    {
        sum += histogram[i];
        cumulative_histogram[i] = sum;
    }
cs


2. 정규화된 누적 히스토그램을 구하여 입력 그레이스케일 영상에 히스토그램 평활화를 적용합니다. 

P_new = N[ P_old ] * 255

N : 정규화된 누적 히스토그램

P_old : 입력 영상의 픽셀값

P_new: 결과 영상의 픽셀값

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
    //입력 그레이스케일 영상의 정규화된 누적 히스토그램 계산
    float normalized_cumulative_histogram[256= { 0.0, };
    int image_size = img_input.rows * img_input.cols;
 
    for (int i = 0; i < 256; i++)
    {
        normalized_cumulative_histogram[i] = cumulative_histogram[i] / (float)image_size;
    }
 
 
    //히스토그램 평활화 적용 및 결과 영상의 히스토그램 계산
    img_result = Mat(img_input.rows, img_input.cols, CV_8UC1);
    int histogram2[256= { 0, };
    for (int y = 0; y<img_input.rows; y++)
    {
        for (int x = 0; x < img_input.cols; x++)
        {
            img_result.at<uchar>(y, x) = normalized_cumulative_histogram[img_gray.at<uchar>(y, x)] * 255;
            histogram2[img_result.at<uchar>(y, x)] += 1;
        }
    }
 
    
    //결과 영상의 누적 히스토그램 계산
    int cumulative_histogram2[256= { 0, };
    sum = 0;
 
    for (int i = 1; i < 256; i++)
    {
        sum += histogram2[i];
        cumulative_histogram2[i] = sum;
    }
cs


입력 영상의 경우 픽셀들이 중앙에 몰려 있었는데 


이미지 평활화 적용 후, 0~255사이에 넓게 분포하게 되었습니다.  누적 히스토그램도 점차적으로 증가하게 되었습니다.



전체 소스코드입니다.

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;


int main()
{
    Mat img_input, img_gray, img_result, img_histogram, img_histogram2;

    //이미지 파일을 읽어와서 img_input에 저장
    img_input = imread("211.jpg", IMREAD_COLOR);
    if (img_input.empty())
    {
        cout << "파일을 읽어올수 없습니다." << endl;
        exit(1);
    }


    //입력영상을 그레이스케일 영상으로 변환
    img_gray = Mat(img_input.rows, img_input.cols, CV_8UC1);

    for (int y = 0; y < img_input.rows; y++)
    {
        for (int x = 0; x < img_input.cols; x++)
        {
            //img_input으로부터 현재 위치 (y,x) 픽셀의
            //blue, green, red 값을 읽어온다.
            uchar blue = img_input.at<Vec3b>(y, x)[0];
            uchar green = img_input.at<Vec3b>(y, x)[1];
            uchar red = img_input.at<Vec3b>(y, x)[2];

            //blue, green, red를 더한 후, 3으로 나누면 그레이스케일이 된다.
            uchar gray = (blue + green + red) / 3.0;

            //Mat타입 변수 img_gray에 저장한다.
            img_gray.at<uchar>(y, x) = gray;
        }
    }


    //입력 그레이스케일 영상의 히스토그램 계산
    int histogram[256] = { 0, };

    for (int y = 0; y < img_input.rows; y++)
    {
        for (int x = 0; x < img_input.cols; x++)
        {
            int value = img_gray.at<uchar>(y, x);
            histogram[value] += 1;
        }
    }

    //입력 그레이스케일 영상의 누적 히스토그램 계산
    int cumulative_histogram[256] = { 0, };
    int sum = 0;

    for (int i = 1; i < 256; i++)
    {
        sum += histogram[i];
        cumulative_histogram[i] = sum;
    }

    //입력 그레이스케일 영상의 정규화된 누적 히스토그램 계산
    float normalized_cumulative_histogram[256] = { 0.0, };
    int image_size = img_input.rows * img_input.cols;

    for (int i = 0; i < 256; i++)
    {
        normalized_cumulative_histogram[i] = cumulative_histogram[i] / (float)image_size;
    }


    //히스토그램 평활화 적용 및 결과 영상의 히스토그램 계산
    img_result = Mat(img_input.rows, img_input.cols, CV_8UC1);
    int histogram2[256] = { 0, };
    for (int y = 0; y<img_input.rows; y++)
    {
        for (int x = 0; x < img_input.cols; x++)
        {
            img_result.at<uchar>(y, x) = normalized_cumulative_histogram[img_gray.at<uchar>(y, x)] * 255;
            histogram2[img_result.at<uchar>(y, x)] += 1;
        }
    }

   
    //결과 영상의 누적 히스토그램 계산
    int cumulative_histogram2[256] = { 0, };
    sum = 0;

    for (int i = 1; i < 256; i++)
    {
        sum += histogram2[i];
        cumulative_histogram2[i] = sum;
    }


    //히스토그램 그리기
    img_histogram = Mat( 300, 600, CV_8UC1, Scalar(0));
    img_histogram2 = Mat(300, 600, CV_8UC1, Scalar(0));
   
    int max = -1;
    for (int i = 0; i < 256; i++)
        if (max < histogram[i]) max = histogram[i];

    int max2 = -1;
    for (int i = 0; i < 256; i++)
        if (max2 < histogram2[i]) max2 = histogram2[i];

    for (int i = 0; i<256; i++)
    {
        int histo = 300 * histogram[i] / (float)max;
        int cumulative_histo = 300 * cumulative_histogram[i] / (float)cumulative_histogram[255];

        line(img_histogram, Point(i + 10, 300), Point(i + 10, 300 - histo), Scalar(255,255,255));
        line(img_histogram, Point(i + 300, 300), Point(i + 300, 300 - cumulative_histo), Scalar(255, 255, 255));


        int histo2 = 300 * histogram2[i] / (float)max2;
        int cumulative_histo2 = 300 * cumulative_histogram2[i] / (float)cumulative_histogram2[255];

        line(img_histogram2, Point(i + 10, 300), Point(i + 10, 300 - histo2), Scalar(255, 255, 255));
        line(img_histogram2, Point(i + 300, 300), Point(i + 300, 300 - cumulative_histo2), Scalar(255, 255, 255));
    }


    //화면에 결과 이미지를 보여준다.
    imshow("입력 영상", img_input);
    imshow("입력 그레이스케일 영상", img_gray);
    imshow("결과 그레이스케일 영상", img_result);
    imshow("입력 영상의 히스토그램", img_histogram);
    imshow("평활화 후 히스토그램", img_histogram2);

    //아무키를 누르기 전까지 대기
    while (waitKey(0) == 0);

    //결과를 파일로 저장
    imwrite("img_gray.jpg", img_gray);
    imwrite("img_result.jpg", img_result);
    imwrite("img_histogram.jpg", img_histogram);
    imwrite("img_histogram2.jpg", img_histogram2);
}




반응형

포스트 작성시에는 문제 없었지만 이후 문제가 생길 수 있습니다.
개선 사항을 댓글로 남겨주면 가능한 빨리 반영하도록 하겠습니다.

여러분의 응원으로 좋은 컨텐츠가 만들어집니다.
지금 본 내용이 도움이 되었다면 유튜브 구독 부탁드립니다. 감사합니다 ~~

유튜브 구독하기


제가 쓴 책도 한번 검토해보세요 ^^

  1. 초보코딩 2019.11.25 02:52

    평활화 연습해보고 싶어서 전체 소스코드 보고 똑같이 입력해봤는데
    식별자 line, cvPoint, cvWaitKey가 정의되지 않았다고 에러가 뜨네요 ㅠㅠ
    혹시 해결방안을 알려주실수 있나요?

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.11.25 08:42 신고

      코드를 수정해놓았습니다. 다시 확인해보세요.

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.11.25 08:42 신고

      cvPoint를 Point로 ..
      cvWaitKey를 waitKey로 변경한 후..
      #include <opencv2/opencv.hpp> 헤더파일을 추가하면 됩니다.

  2. 초보코딩 2019.11.28 21:39

    코드 수정해봤는데 이런 에러가 뜹니다 ㅠㅠ

    c:\Users\김준혁\Desktop\영상처리 연습\C_C++_Projects\.vscode\Histogram1.cpp:1:30: fatal error: opencv2/opencv.hpp: No such file or directory
    #include <opencv2/opencv.hpp>
    ^
    compilation terminated.
    터미널 프로세스가 종료 코드 1(으)로 종료되었습니다.

    터미널이 작업에서 다시 사용됩니다. 닫으려면 아무 키나 누르세요.

    환경설정에 문제가 있는걸까요?

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.11.28 21:55 신고

      코드는 문제 없는거 확인했기 때문에 환경 설정 문제가 아닐까 싶습니다.

+ Recent posts