관리 메뉴

공부 기록장 💻

[OpenCV/C++] 히스토그램 분석: 히스토그램 구하기 (Histogram) 본문

# Tech Studies/Computer Vision • OpenCV

[OpenCV/C++] 히스토그램 분석: 히스토그램 구하기 (Histogram)

dream_for 2022. 11. 30. 01:39

 

Open CV 4로 배우는 컴퓨터 비전과 머신러닝 CH5 영상의 밝기와 명암비 조절 정리

 

 

히스토그램 분석

픽셀 값 변환을 통해 밝기와 명암비를 조절했다면, 주어진 영상의 픽셀 밝기 분포를 조사하여 밝기 및 명암비를 적절하게 조절하는 방법에 대해 알아보자.

 

히스토그램 구하기

그레이스케일 영상의 경우, 각 그레이스케일 값에 해당하는 픽셀의 개수를 구하고 이를 막대 그래프 형태로 표현함으로써 히스토그램을 구할 수 있다. 컬러 영상의 경우, 3개의 색상 성분 조합에 따른 픽셀 개수 각각 계산하여 히스토그램 구성한다.

컴퓨터 비전에서의 히스토그램(histogram)이란,  영상의 픽셀 값 분포를 그래프 형태로 표현한 것이라 할 수 있다.

 

 

위의 4x4 입력 영상은 각 픽셀이 0부터 7 사이의 밝기를 가질 수 있는 단순한 형태의 영상으로, 각각의 픽셀 값(밝기)에 대하여 그 개수를 막대 그래프로 표현하였다.

히스토그램 그래프의 가로축을 빈(bin) 이라 하고, 위의 그래프는 픽셀 값 0부터 7까지 표현한 것으로 빈의 개수가 총 7개라 할 수 있다. 그레이스케일의 경우 256개의 빈을 갖는 히스토그램을 구하는 것이 일반적이다.

 

 

히스토그램의 빈 개수가 항상 픽셀 값 범위와 같아야 하는 것은 아니다. 경우에 따라 히스토그램의 빈 개수를 픽셀 값 범위보다 작게 설정할 수도 있는데, 아래 그림의 경우 8개의 밝기 값을 가지는 영상에서 히스토그램 빈 개수를 4로 설정할 수도 있다.

0번 빈의 경우는 픽셀 값이 0과 1인 픽셀 개수, 1번 빈의 경우 픽셀 값이 2 또는 3인 픽셀의 개수를 나타냈다.

일반적으로 히스토그램의 빈 개수가 줄어들면, 히스토그램이 표현하는 영상의 픽셀 값 분포 모양이 좀 더 대략적인 형태로 바뀐다. 반대로 빈 개수가 많으면 세밀한 픽셀 값 분포 표현이 가능하다.

 

calcHist()

OpenCV에서 영상의 히스토그램을 구하려면 calcHist() 함수를 사용한다. 이는 한 장의 영상 뿐 아니라 여러 장의 영상 그리고 여러 채널로부터 히스토그램을 구할 수 있다. 히스토그램 빈 개수도 조절이 가능하다.

 

모두 10개의 인자를 가지고 있으며, 마지막의 uniform, accumulate 값은 각각 true, false로 기본 값을 가지고 있으므로 총 8개의 인자를 필수적으로 지정해줘야 한다.

입력 영상, 입력 영상의 개수, 히스토그램을 구할 채널의 배열, 마스크 행렬(noArray()인 경우 입력 영상 전체로 지정), 출력 히스토그램 행렬, 출력 히스토그램의 차원 수, 히스토그램의 범위를 지정해줘야 한다. 이때  ranges는 등간격(uniform=true 지정) 히스토그램을 표현할 것이기 때문에 최솟값과 최댓값으로 구성된 배열을 넘겨주도록 한다. 

 

우선 그레이스케일 영상으로부터 256개의 빈으로 구성된 히스토그램을 생성하는 함수는 다음과 같다.

 

// 그레이스케일 영상으로부터 256개의 빈을 갖는 히스토그램 행렬 생성
Mat calcGrayHist(const Mat& img) {
	// img 영상이 그레이스케일 영상인지 검사 (아닌 경우 에러 발생 후 프로그램 종료)
	CV_Assert(img.type() == CV_8UC1);

	Mat hist;
	int channels[] = { 0 }; // 그레이스케일 1채널
	int dims = 1;	// 1차원 행렬
	const int histSize[] = { 256 }; // 첫번째 채널 값의 범위를 256개 빈으로 나눔
	float graylevel[] = { 0,256 }; // 그레이스케일 값의 최솟값, 최댓값 지정
	const float* ranges[] = { graylevel };

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

	return hist;
}

 

위 함수를 통해 얻은 히스토그램 행렬은 CV_32FC1 타입을 갖는 256 x 1 크기의 행렬이다. 즉, hist 행렬의 행 개수는 256, 열 개수는 1이다. 

 

 

이제 막대그래프의 형태로 나타내기 위해서는 hist 행렬을 참조하여 막대그래프 영상을 생성해야 한다.

256개의 빈을 갖는 hist 행렬로부터 가로가 256픽셀, 세로가 100픽셀인 크기의 히스토그램 영상을 생성해보자.

 

// 히스토그램 행렬을 나타내는 막대 그래프 생성
Mat getGrayHistImage(const Mat& hist) {
	CV_Assert(hist.type() == CV_32FC1);
	CV_Assert(hist.size() == Size(1, 256)); // 256개의 빈으로 구성된 히스토그램 행렬인지 검사

	double histMax;
	minMaxLoc(hist, 0, &histMax); // hist 행렬 원소의 최솟값 0, 최댓값을 histMax에 저장

	Mat imgHist(100, 256, CV_8UC1, Scalar(255)); // 흰색으로 초기화된 256x100 크기의 새 영상 생성
	// 각각의 빈에 대한 히스토그램 그래프 그리기
	for (int i = 0;i < 256;i++) {
		// 히스토그램 막대그래프의 최대 길이를 100픽셀로 설정
		line(imgHist, Point(i, 100),
			Point(i, 100 - cvRound(hist.at<float>(i, 0)*100 / histMax)), Scalar(0));
	}
	return imgHist;
}

 

hist 행렬 원소의 최댓값을 찾기 위해 minMaxLoc() 함수를 사용했다. 

여기서 minMaxLoct() 함수는, 행렬에서 최댓값과 최솟값을 구하는 함수이다.

opencv 공식문서

히스토그램 막대그래프의 최대 길이를 100픽셀로 설정했는데, 이는 행렬의 최댓값 위치에서 100픽셀에 해당하는 검은색 직선을 그리고, 나머지 히스토그램 막대그래프는 100픽셀보다 짧은 길이의 직선으로 표현한다는 것이다.

 

 

앞서 작성한, 히스토그램 행렬을 생성하는 calcGrayHist()와 막대 그래프로 나타내는 getGrayHistImage() 함수를 사용하여 레나 영상의 히스토그램을 화면에 출력해보자.

 

Mat src = imread("lenna256.bmp", IMREAD_GRAYSCALE);

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

 

 

결과는 위와 같다. 레나 영상 자체 만으로는 픽셀 값이 두드러지게 나타나는 부분을 찾기가 어려운데,

아래의 카메라맨 영상는 픽셀 값이 분포된 영역이 두 군데 나타내는 것을 확인할 수 있다.

 

검정색에 가까운 어두운 옷과 머리에 해당하는 부분이 A영역,

그리고 조금 더 밝은 부분에 해당하는 하늘, 잔디밭 부분이 B 영역으로 나타난다고 볼 수 있다.

 

 

밝기와 명암비에 따른 영상과 히스토그램의 상관 관계

 

밝기와 명암비가 다른 여러 레나 영상을 이용해 영상과 히스토그램의 상관관계를 살펴보자.

원본 레나 영상에 밝기를 30만큼 증가시킨 (b)의 경우, 히스토그램의 분포가 전체적으로 오른쪽으로 이동한 것을 확인할 수 있다.

밝기를 낮춘 (c)의 경우는 전체적으로 왼쪽으로 분포도가 이동하였다.

 

 

(d)의 경우 명암비를 증가시켜, 밝은 부분과 어두운 부분의 차이를 증가시켰더니, 히스토그램 그래프가 그레이스케일 값 범위 전체 구간에 골고루 넓게 분포되어 나타났다.

반대로 (e)와 같이 명암비를 감소시켰더니, 그래프 가운데 일부 구간에 몰려 나타났다.

 

 

이를 통해, 히스토그램의 픽셀 분포 그래프는 영상의 밝기와 명암비를 가늠할 수 있는 유용한 도구로 사용될 수 있다.

 

 

728x90
반응형
Comments