[컴퓨터 비전] 5.3히스토그램 - Week7_2

Ogu·2023년 10월 14일
0

5.3 히스토그램 분석

주어진 영상의 픽셀 밝기 분포를 조사해 밝기 및 명암비를 적절하게 조절

  1. 영상의 픽셀 밝기 값 분포를 나타내는 히스토그램

  2. OpenCV에서 히스토그램을 구하는 방법

  3. 히스토그램 분석을 통해 영상의 밝기 및 명암비 자동 조절

    → 히스토그램 스트레칭, 히스토그램 평활화

5.3.1 히스토그램 구하기

히스토그램이란?

영상의 픽셀 값 분포를 그래프 형태로 표현한 것

  • 그레이스케일 → 각 그레이스케일 값에 해당하는 픽셀의 개수를 구하고 이를 막대 그래프 형태로 표현

  • 컬러 → 세 개의 색상 성분 조합에 따른 픽셀 개수를 계산하여 히스토그램을 구함

  • 아래 4 X 4 입력 영상은 각 픽셀이 0~7 사이의 밝기를 가짐

  • 각각의 밝기에 해당하는 픽셀 개수를 세어서 막대 그래프 형태로 표현한 히스토그램

  • 히스토그램 그래프의 가로축 : 히스토그램의 빈(bin)
  • 위 그래프의 빈 개수는 8
  • 그레이스케일 → 빈 개수 : 256개

but, 히스토그램의 빈 개수가 항상 픽셀 값 범위와 같아야 하는 것은 X

  • 히스토그램의 빈 개수를 픽셀 값 범위보다 작게 설정 → 히스토그램이 표현하는 영상의 픽셀 값 분포 모양이 좀 더 대략적인 형태로 바뀜
  • 빈 개수 ⇊ - 픽셀 값 분포 모양 대략적 ↔ 빈 개수 ⇈ - 세밀한 픽셀 값 표현

calcHist()

OpenCV에서 영상의 히스토그램을 구하는 함수

  • 한장의 또는 여러장의 영상으로부터 히스토그램을 구할 수 O
  • 여러 채널로부터 O
  • 히스토그램의 빈 개수 조절 O

💡 calcHist()

void calcHist(const Mat* images, int nimages,
              const int* channels, InputArray mask,
              OutputArray hist, int dims, const int* histSize,
              const float** ranges, bool uniform = true, bool accumulate = false);
• images입력 영상의 배열 또는 입력 영상의 주소. 영상의 배열인 경우, 모든 영상의 크기와 깊이는 같아야 함
• nimages입력 영상 개수
• channels히스토그램을 구할 채널을 나타내는 정수형 배열
• mask마스크 영상. 입력 영상과 크기가 같은 8비트 배열이어야 함.마스크 행렬의 원소 값이 0이 아닌 좌표의 픽셀만 히스토그램 계산에 사용됨. mask 인자에 Mat() 또는 noArray()를 지정하면 입력 영상 전체에 대해 히스토그램을 구함.
• hist출력 히스토그램. CV_32F 깊이를 사용하는 dims-차원의 행렬
• dims출력 히스토그램의 차원 수
• histSize각 차원의 히스토그램 배열 크기를 나타내는 배열(즉, 각 차원의 히스토그램 빈 개수를 나타내는 배열)
• ranges각 차원의 히스토그램 범위. 등간격 히스토그램이면(uniform = true), ranges[i]는 각 차원의 최솟값과 최댓값으로 구성된 배열이고 [최솟값, 최댓값)2 범위를 나타냄. 비등간격 히스토그램이면(uniform = false), ranges[i]는 각각의 구역을 나타내는 histSize[i]+1개의 원소로 구성된 배열
• uniform히스토그램 빈의 간격이 균등한지를 나타내는 플래그
• accumulate누적 플래그. 이 값이 true이면 hist 배열을 초기화하지 않고 누적하여 히스토그램을 계산
  • 모두 10개의 인자 (맨 뒤의 uniform, accumulate는 default값 O)
  • 최소 8개 인자 설정
  • uniform의 default = true, accumulate = false → hist 배열을 0으로 초기화한 후 등간격 히스토그램 계산
  • 등간격 히스토그램 : 빈이 표현하는 밝기 값 간격이 균일

그레이스케일 입력 영상 → 256개의 빈으로 구성된 히스토그램 구하기

Mat calcGrayHist(const Mat& img)
{
    CV_Assert(img.type() == CV_8UC1);

    Mat hist;
    int channels[] = { 0 };
    int dims = 1;
    const int histSize[] = { 256 };
    float graylevel[] = { 0, 256 };
    const float* ranges[] = { graylevel };

    calcHist(&img, 1, channels, noArray(), hist, dims, histSize, ranges);

    return hist;
}
  • 3행 CV_Assert() : calcGrayHist() 함수로 전달된 img 영상이 그레이스케일인지 검사
  • 5~10행 12행에서 호출하는 calcHist() 함수에 전달할 인자를 생성하는 구문
  • 5행 히스토그램 정보를 저장할 Mat 타입의 변수 hist를 선언
  • 6행 히스토그램을 구할 채널 번호를 담은 channels 배열을 생성. 그레이스케일 영상은 한 개의 채널을 가지고 있고, 채널 번호는 0부터 시작하므로 channels 배열은 0 하나만 원소로 가짐
  • 7행 dims 변수에 1을 대입 → 하나의 채널에 대해서만 히스토그램을 구함, 결과로 구해지는 hist 행렬이 1차원 행렬
  • 8행 histSize 배열 원소에 256을 하나 지정 → 입력 영상의 첫 번째 채널 값의 범위를 256개 빈으로 나누어 히스토그램을 구함
  • 9~10행 graylevel 배열의 원소에는 그레이스케일 값의 최솟값과 최댓값인 0과 256을 차례대로 지정. ranges 배열은 graylevel 배열 이름을 원소로 갖는 배열.
  • 12행 calcHist() 함수를 이용하여 img 영상의 히스토그램을 구하고, 그 결과를 hist 변수에 저장
  • 14행 구해진 히스토그램 hist를 반환

→ 반환되는 hist : CV_32FC1 타입을 갖는 256 X 1 크기의 행렬

→ hist 행렬의 행 개수 = 256, 열 개수 = 1

히스토그램 행렬 → 막대 그래프

hist 행렬을 참조하여 막대 그래프 영상을 생성

그래프에서 최대 빈도수를 표현하는 막대 그래프 길이가 100픽셀이 되도록 그래프를 그림

01    Mat getGrayHistImage(const Mat& hist)
02    {
03        CV_Assert(hist.type() = = CV_32FC1);
04        CV_Assert(hist.size() = = Size(1, 256));
05     
06        double histMax;
07        minMaxLoc(hist, 0, &histMax);
08     
09        Mat imgHist(100, 256, CV_8UC1, Scalar(255));
10        for (int i = 0; i < 256; i++) {
11            line(imgHist, Point(i, 100),
12                Point(i, 100 - cvRound(hist.at<float>(i, 0)*100/histMax)), Scalar(0));
13        }
14     
15        return imgHist;
16    }
  • 3~4행 | getGrayHistImage() 함수의 인자로 전달된 hist 행렬이 256개의 빈으로 구성된 히스토그램 행렬인지 검사
  • 6~7행 | minMaxLoc() : hist 행렬 원소의 최댓값을 histMax 변수에 저장
                최솟값은 관심이 없으므로 두번째 인자는 0으로 설정
  • 9행 | 흰색으로 초기화된 256×100 크기의 새 영상 imgHist를 생성
  • 10~13행 | for 반복문과 **line()함수**를 이용하여 각각의 빈에 대한 히스토그램 그래프를 그림
  • 15행 | hist 행렬로부터 구한 256×100 크기의 히스토그램 영상 imgHist를 반환
  • 히스토그램 행렬의 최댓값 위치에서 100픽셀에 해당하는 검은색 직선을 그림
  • 나머지 히스토그램 막대그래프는 100픽셀보다 짧은 길이의 직선

히스토그램 화면 출력

// 히스토그램 행렬, 그래프 변수 저장
Mat src = imread("camera.bmp", IMREAD_GRAYSCALE);
Mat hist = calcGrayHist(src);
Mat hist_img = getGrayHistImage(hist);
 
imshow("src", src);
imshow("srcHist", hist_img);
// 단순히 히스토그램 그래프 영상 화면 출력
Mat src = imread("camera.bmp", IMREAD_GRAYSCALE);

imshow("src", src);
imshow("srcHist", getGrayHistImage(calcGrayHist(src)));

영상 특성에 따른 히스토그램 분석

히스토그램 픽셀 분포 그래프 → 밝기와 명암비를 가늠할 수 있는 유용한 도구로 사용할 수 O

  • (a) - 원본

  • (b) - 밝기 증가, 그래프 전체적으로 오른쪽 이동

  • (c) - 밝기 감소, 그레프 전체적으로 왼쪽 이동

  • (d) - 명암비 증가 - 그래프가 그레이스케일 값 범위 전체 구간에 골고루 나타남

  • (e) - 명암비 낮음 - 히스토그램 그래프가 가운데 일부 구간에 몰려서 나타남

5.3.2 히스토그램 스트레칭

히스토그램 스트레칭(histogram stretching)

영상의 히스토그램이 그레이스케일 전 구간에 걸쳐서 나타나도록 변경하는 선형 변환 기법

  • 명암비가 낮은 영상 → 히스토그램이 특정 구간에 집중되어 나타남
  • 히스토그램을 마치 고무줄을 잡아 늘리듯이 펼쳐서 히스토그램 그래프가 그레이스켕리 전 구간에서 나타나도록 변환하는 기법
  • 히스토그램 스트레칭 → 명암비가 높아짐 → 대체로 보기 좋은 사진
  • 히스토그램 스트레칭

- src : 입력 영상,  dst : 출력 영상
- Gmin = 입력 영상의 픽셀 값 중 가장 작은 그레이스케일 값
- Gmax = 입력 영상의 픽셀 값 중 가장 큰 그레이스케일 값
  • 즉 양방향으로 늘려 Gmin = 0, Gmax = 255가 되도록 변환 → 히스토그램이 그레이스케일 전체 구간에 대해 나타남

  • 변환 함수 그래프

- ( Gmin, 0)과( Gmax, 255)를 지나가는 직선의 방정식을 구해서 이를 변환 함수로 사용
- 직선의 기울기 = 255 / ( Gmax − Gmin), y 절편 = -255 * Gmin / (Gmax-Gmin)이됨
- y = mx + b (b : y절편)
  • 히스토그램 스트레칭을 위한 함수 → OpenCV 제공 X
  • but 기본적인 산술연산에 대한 연산자 재정의 지원 → 앞 수식을 소스 코드로 변경
  • Gmin, Gmax → minMaxLoc() 함수 사용

히스토그램 스트레칭 예제

01    void histgoram_stretching()
02    {
03        Mat src = imread("hawkes.bmp", IMREAD_GRAYSCALE);
04     
05        if (src.empty()) {
06            cerr << "Image load failed!" << endl;
07            return;
08        }
09     
10        double gmin, gmax;
11        minMaxLoc(src, &gmin, &gmax);
12     
13        Mat dst = (src - gmin) * 255 / (gmax - gmin);
14     
15        imshow("src", src);
16        imshow("srcHist", getGrayHistImage(calcGrayHist(src)));
17     
18        imshow("dst", dst);
19        imshow("dstHist", getGrayHistImage(calcGrayHist(dst)));
20     
21        waitKey();
22        destroyAllWindows();
23    }
  • Mat dst = (src - gmin) * 255 / (gmax - gmin);

→ 영상 dst는 어두운 영역과 밝은 영역이 고루 분포 → 명암비가 높아짐

→ dstHist창의 히스토그램은 입력 영상의 히스토그램이 양 옆으로 늘어난 듯한 형태

5.3.3 히스토그램 평활화

히스토그램 스트레칭(histogram stretching)


히스토그램 스트레칭과 더불어 영상 픽셀 값 분포가 그레이스케일 전체 영역에서 골고루 나타나도록 변경하는 알고리즘

  • 특정 그레이스케일 값 근방에서 픽셀 분포가 너무 많이 뭉쳐져 있는 경우 이를 넓게 펼쳐 주는 방식으로 픽셀 값 분포 조절
  • = 히스토그램 균등화 = 히스토그램 평탄화
  • h(g) = 영상에서 그레이스케일 값이 g인 픽셀 개수 (그레이스케일 값 g)
  • H(g) : 히스토그램 누적 함수 → 히스토그램 평활화 계산

  • H(g) → 픽셀 값 변환 함수
  • H(g) 함수의 최댓값이 255가 되도록 정규화

- N : 입력 영상의 픽셀 개수
- Lmax : 영상이 가질 수 있는 최대 밝기 (일반적인 그레이스케일 : 255)
- round() : 반올림 함수

4 x 4 영상에서 히스토그램 평활화 구현


  • 영상의 픽셀 개수가 16개이므로 H(7) = 16
  • 최대 밝기 값 : 7, 전체 픽셀 개수 : 16 → 정규화 상수 = 7/16

equalizeHist() - 히스토그램 평활화 함수


CV_8UC1 타입을 사용하는 그레이스케일 영상만 입력으로 받음에 주의 ⭐

💡 `equalizeHist()` ****
void equalizeHist( InputArray src, OutputArray dst );
• src입력 영상. 8비트 1채널
• dst출력 영상. src와 크기와 타입이 같음

히스토그램 평활화 예제

01    void histgoram_equalization()
02    {
03        Mat src = imread("hawkes.bmp", IMREAD_GRAYSCALE);
04     
05        if (src.empty()) {
06            cerr << "Image load failed!" << endl;
07            return;
08        }
09     
10        Mat dst;
11        equalizeHist(src, dst);
12     
13        imshow("src", src);
14        imshow("srcHist", getGrayHistImage(calcGrayHist(src)));
15     
16        imshow("dst", dst);
17        imshow("dstHist", getGrayHistImage(calcGrayHist(dst)));
18     
19        waitKey();
20        destroyAllWindows();
21    }

→ srcHist에서 큰 값이 몰려 있던 부분의 히스토그램 그래프가 dstHist 그래프에서는 그레이스케일 전체 범위로 넓게 펼쳐짐

→ 기존 형태를 그대로 가져가지 않고 균등하게 분포

profile
私はゲームと日本が好きなBackend Developer志望生のOguです🐤🐤

0개의 댓글