본문 바로가기
런닝머신 (ML)

[컴퓨터 비전 & 머신러닝] 히스토그램 분석

728x90

히스토그램 분석을 통해 영상의 밝기 및 명암비를 자동으로 조절하는 히스토그램 스트레칭과 히스토그램 평활화 기법에 대해 알아보겠다

 

히스토그램이란 영상의 픽셀 값 분포를 그래프 형태로 표현한 것을 의미한다

그레이스케일의 영상일 경우 각 영상 값에 해당하는 픽셀의 개수를 구한다음 이를 막대 그래프 형태로 표현함으로써 구할 수 있다

 

0부터 7 픽셀로 구성된 4 x 4 영상을 히스토그램으로 표현한 것이다

히스토그램 그래프에서 가로축을 히스토그램의 빈이라고 한다

첫번째 히스토그램 그래프는 8개의 빈으로 구성되어 있고, 두번째 히스토그램은 4개의 빈으로 만들어졌다

빈의 개수가 많을수록 세밀한 픽셀 값 표현이 가능하고, 줄어들수록 대략적인 형태로 변한다

 


calcHist() 함수 : 영상의 히스토그램을 구하는 함수, 한 장의 영상뿐만 아니라 여러장의 영상으로부터 히스토그램을 구할 수 있다

Mat calcGrayHist(const Mat& img)
{
	CV_Assert(img.type() == CV_8UC1); //매크로 함수를 이용해여 그레이스케일 영상인지 검사

	Mat hist;
	int channels[] = { 0 }; //그레이스케일 영상은 한 개의 채널을 가지고 있으므로, 채널 번호는 0부터 시작
	int dims = 1; //하나의 채널에서만 히스토그램을 구하기 때문에 1차원 행렬로 설정
	const int histSize[] = { 256 }; //256개의 빈으로 나누어 히스토그램을 구한다
	float graylevel[] = { 0, 256 }; //최솟값인 0과 최댓값은 256을 차례대로 지정
	const float* ranges[] = { graylevel }; //배열 이름 설정

	//CV_32FC1타입을 갖는 256 x 1 크기의 행렬 반환
	calcHist(&img, 1, channels, noArray(), hist, dims, histSize, ranges); 

	return hist;
}
Mat getGrayHistImage(const Mat& hist)
{
	CV_Assert(hist.type() == CV_32FC1);
	CV_Assert(hist.size() == Size(1, 256)); //hist 행렬이 256개의 빈으로 구성된 히스토그램 행렬인지 검사

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

	Mat imgHist(100, 256, CV_8UC1, Scalar(255)); //흰색으로 초기화된 256 x 100 크기의 새 영상 imgHist 생성
	
	//for 반복문과 line() 함수를 사용해 빈에 대한 히스토그램 그래프 그림
	for (int i = 0; i < 256; i++) {
		line(imgHist, Point(i, 100),
			Point(i, 100 - cvRound(hist.at<float>(i, 0) * 100 / histMax)), Scalar(0));
	}

	return imgHist; //256 x 100 크기의 영상 반환
}
void lenna_histogram()
{
	Mat src = imread("lenna.bmp", IMREAD_GRAYSCALE);

	if (src.empty()) {
		cerr << "Image load failed!" << endl;
		return;
	}

	//Mat hist = calcGrayHist(src);
	//Mat hist_img = getGrayHistImage(hist);

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

	waitKey();
	destroyAllWindows();

}

calcHist() 함수를 이용해 그레이스케일 레나 사진의 히스토그램을 출력해보았다

 

사진 밝기에 따른 히스토그램 차이

오른쪽으로 갈 수록 점점 사진이 어두워지는데 히스토그램 그래프가 왼쪽으로 이동하는 것을 볼 수 있다

 

 

명암비 차이에 따른 히스토그램 차이

오른쪽으로 갈수록 명암비가 높아지는데, 명암비가 낮은 사진은 가운데에 그래프에 몰려있고, 높은 사진은 전체 구간에 골고루 퍼져 있다

 


히스토그램 스트레칭 : 영상의 히스토그램이 그레이스케일 전 구간에 걸쳐서 나타나도록 변경하는 선형 변환 기법, 명암비가 낮은 영상은 히스토그램이 특정 구간에 집중되어 나타나는데 이러한 히스토그램을 고무줄을 잡아 늘리듯이 펼쳐서 고르게 나타나게 변환

히스토그램 스트레칭 수식

 

히스토그램 스트레칭을 위한 함수는 OpenCV에서 따로 제공하지는 않는다

그러나 minMaxLoc() 함수를 사용하면 쉽게 구할 수 있다

void histgoram_stretching()
{
	Mat src = imread("hawkes.bmp", IMREAD_GRAYSCALE);

	if (src.empty()) {
		cerr << "Image load failed!" << endl;
		return;
	}

	//입력 영상 src에서 그레이스케일 최솟값과 최댓값을 구하여 gmin과 gmax에 저장
	double gmin, gmax;
	minMaxLoc(src, &gmin, &gmax);

	//히스토그램 스트레칭 수식을 그대로 적용하여 결과 영상 dst를 생성
	Mat dst = (src - gmin) * 255 / (gmax - gmin);

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

	imshow("dst", dst);
	imshow("dstHist", getGrayHistImage(calcGrayHist(dst)));

	waitKey();
	destroyAllWindows();
}

입력 영상 src가 전체적으로 뿌옇게 보이는 것과 달리, 히스토그램 스트레칭이 수행된 결과 영상 dst는 어두운 영역과 밝은 영역이 골고루 분포하는 명암비가 높은 영상으로 바뀌었다

 


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

h(g)는 영상에서 그레이스케일 값이 g인 픽셀 개수를 나타내고, H(g)는 h(g)로부터 누적 함수이다

N은 입력 영상의 픽셀 개수이다

L_max는 영상이 가질 수 있는 최대 밝기 값을 의미하고 일반적인 그레이스케일 영상일 경우 255가 최대값이다

round() 는 반올림 함수이다

 

 

OpenCV에서는 그레이스케일 영상의 히스토그램 평활화를 수행하는 equalizeHist() 함수를 제공한다

void histgoram_equalization()
{
	Mat src = imread("hawkes.bmp", IMREAD_GRAYSCALE);

	if (src.empty()) {
		cerr << "Image load failed!" << endl;
		return;
	}

	Mat dst;
	equalizeHist(src, dst); //히스토그램 평활화를 수행한 결과를 dst에 저장

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

	imshow("dst", dst);
	imshow("dstHist", getGrayHistImage(calcGrayHist(dst)));

	waitKey();
	destroyAllWindows();
}

히스토그램 형활화 수행 결과 영상이 전체적으로 밝은 영역과 어두운 영역의 대비가 크게 증가한 것을 확인할 수 있다. 히스토그램 그래프를 살펴보면 scrHist 그래프에서 큰 값이 몰려 있던 부분이 히스토그램 그래프가 disHist 그래프에서는 그레이스케일 전체 범위로 넓게 펼쳐진 것을 확인할 수 있다

728x90