본문 바로가기
OpenCV

OpenCV ] 영상의 밝기 조절 + 명암비 조절 + 히스토그램 분석

by eteo 2022. 8. 23.

자세한 설명은 주석처리함

 

출처 : 책, OpenCV 4 로 배우는 컴퓨터 비전과 머신러닝

 

영상의 밝기를 100만큼 증가하기

void brightness1(){
	Mat img3 = imread("lenna.bmp", IMREAD_COLOR);
	Mat img4;
	cvtColor(img3, img4, COLOR_BGR2GRAY);		// 컬러 영상으로 변환


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

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

	// 밝기 조절과 같은 작업을 수행할 때 덧셈 연산자 재정의를 사용하여 코드를 작성하는 것이 더욱 빠르고 간편

	Mat dst = src + 100;	// 내부적으로 포화연산이 일어남. x > 255 일 때 x=255
	//dst = src - 100;		// x < 0 일 때 x=0

	imshow("src", src);
	imshow("dst", dst);

	waitKey(0);

}

 

 

 

 

 

 

 

 

포화연산을 고려하지 않은 영상의 밝기 증가

void brightness2() {

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

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

	Mat dst(src.rows, src.cols, src.type());
    // 사용자가 직접 결과 영상의 픽셀 값을 설정하려면 적절한 크기와 타입의 결과 영상을 미리 생성해야함

	// 포화연산을 고려하지 않은 영상의 밝기 증가
	for (int j = 0; j < src.rows; j++) {
		for (int i = 0; i < src.cols; i++) {
			dst.at<uchar>(j, i) = src.at<uchar>(j, i) + 100;
            // Mat 행렬의 원소 값 참조 방법을 사용하면 어렵지 않게 밝기 조절을 직접 구현할 수 있지만
            // 포화연산이 자동으로 적용되진 않음
		}
	}
	// 밝은 픽셀 주변에서 급격하게 어두운 픽셀이 나타나는 것은 오버 플로우가 발생했기 때문
	// 정수 256을 16진수로 표현하면 0x100이고, 이 값을 unsigned char 자료형에 대입하면
	// 하위 1바이트만 대입되기 때문에 변수 a에는 0x00이 저장됨. 257을 대입하려고 하면 실제로는 0x01이 저장됨
	// 255보다 큰 값이 되는 픽셀은 오히려 0에 가까운 어두운 픽셀로 바뀌게 되는 것

	imshow("src", src);
	imshow("dst", dst);


	waitKey(0);

}

 

 

 

 

 

 

 

 

 

 

 

 

포화연산을 고려한 영상의 밝기 증가

void brightness3() {

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

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

	Mat dst(src.rows, src.cols, src.type());

	// 포화연산을 고려한 영상의 밝기 증가
	for (int j = 0; j < src.rows; j++) {
		for (int i = 0; i < src.cols; i++) {
			//int v = src.at<uchar>(j, i) + 100;
			//dst.at<uchar>(j, i) = saturate_cast<uchar>(v);
			dst.at<uchar>(j, i) = saturate_cast<uchar>(src.at<uchar>(j, i) + 100);
		}
	}

	imshow("src", src);
	imshow("dst", dst);


	waitKey(0);

}

 

 

 

 

 

 

 

 

 

트랙바를 이용한 영상의 밝기 조절

void on_brightness(int pos, void* userdata) {

	Mat src = *(Mat*)userdata;
	Mat dst = src + pos;

	imshow("dst", dst);
}

void brightness4() {

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

	if (src.empty()) {
		cerr << "Image load failed!" << endl;
		return;
	}
	
	namedWindow("dst");
	createTrackbar("Brightness", "dst", 0, 100, on_brightness, (void*)&src);
	on_brightness(0, (void*)&src);	// 트랙바를 조절하지 않더라도 프로그램 실행시에 imshow()구문이 실행되도록 강제로 콜백함수 호출

	waitKey(0);
	destroyAllWindows();

}

 

 

 

 

 

 

 

기본적인 영상의 명암비 증가

void contrast1() {

	// 명암비란 영상에서 밝은 영역과 어두운 영역 사이에 드러나는 밝기 차이의 강도를 의미함
	// 명암 대비 또는 콘트라스트(contrast)라고도 함
	// 영상이 전반적으로 어둡거나 또는 전반적으로 밝은 픽셀로만 구성된 경우, 명암비가 낮다고 표현함
	// 반면에 밝은 영역과 어두운 영역이 골고루 섞여 있는 영상은 명암비가 높다고 말함
	// 명암비가 낮은 영상은 객체 간의 구분이 잘 되지 않아서 전반적으로 흐릿하게 느껴짐
	// 명암비가 높은 영상은 사물의 구분이 잘 되며 선명한 느낌을 줌

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

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

	float s = 2.f;
	Mat dst = s * src;
	// OpenCV는 C/C++ 실수형 자료형과 Mat 객체 사이의 곱셈 연산자 재정의도 제공함
	// 이때 결과 행렬에 대해 포화 연산도 함께 수행함
    // 흰색으로 나타나는 영역이 너무 많아 사물의 윤곽 구분이 더 어려워짐
	// 사실상 픽셀 값에 단순히 상수를 곱하여 명암비를 조절하는 방식은 실전에서는 잘 사용되지 않음

	imshow("src", src);
	imshow("dst", dst);

	waitKey(0);
	destroyAllWindows();
}

 

 

 

 

 

 

 

 

 

효과적인 영상의 명암비 조절

void contrast2() {
	// 효과적인 명암비 조절 방법 : 명암비를 효과적으로 높이기 위해서는 밝은 픽셀은 더욱 밝게, 어두운 픽셀은 더욱 어두워지게 변경
	// 그레이스케일 범위 중간값인 128을 기준으로 설정할 수도 있고, 입력 영상의 평균 밝기를 구하여 기준으로 삼을 수도 있음
	// 입력 영상의 픽셀 값이 128보다 크면 더욱 밝게 만들고, 128보다 작으면 픽셀 값을 더 작게 만드는 방식

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

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

	float alpha = 1.f;
	Mat dst = src + (src - 128) * alpha;

	// 이 수식은 src 픽셀값이 128일 때 dst가 128이어서 항상 (128, 128) 좌표를 지나감
	// 그리고 alpha 에 의해 기울기가 변경됨
	// α  > 0이면 기울기가 1보다 큰 직선의 방정식이며, 이는 명암비를 증가시키는 변환 함수임



	//float alpha = 1.f;
	//cout << "mean(src)" << mean(src) << endl;
	//Mat dst = src + (src - mean(src)) * alpha;
	//α 의 범위가  α > 0이면 기울기가 1보다 큰 직선의 방정식이며, 이는 명암비를 증가시키는 변환 함수


	//float alpha = 2.f;
	//Mat dst = src + (src - 128) * alpha;

	//float alpha = -0.5f;
	//Mat dst = src + (src - 128) * alpha;
	//α의 범위가 - 1 < α < 0이면 기울기가 0부터 1 사이의 직선이 되며, 이는 명암비를 감소시키는 변환 함수

	imshow("src", src);
	imshow("dst", dst);

	waitKey(0);
	destroyAllWindows();
}

 

 

 

 

 

 

 

 

 

 

그레이스케일 영상의 히스토그램 그래프 그리기

// 영상의 히스토그램(Histogram)이란 영상의 픽셀값 분포를 그래프 형태로 표현한 것을 의미함
// 히스토그램 그래프에서 가로축을 히스토그램의 빈(bin)이라고 함
// 그레이스케일 영상의 경우 256개의 빈을 갖는 히스토그램을 구하는 것이 일반적

Mat calcGrayHist(const Mat& img) {
	
	CV_Assert(img.type() == CV_8UC1);
	// 매크로 함수. 인자가 false이면 에러가 발생하며 프로그램 종료

	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);
	/*
	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 : 마스크 영상. mask 인자에 Mat() 또는 noArray()를 지정하면 입력 영상 전체에 대해 히스토그램을 구함
	// hist : 출력 히스토그램. CV_32F 깊이를 사용하는 dims-차원의 행렬
	// dims : 출력 히스토그램의 차원 수
	// histSize : 각 차원의 히스토그램 배열 크기를 나타내는 배열(각 차원의 히스토그램 빈 개수를 나타내는 배열)
	// ranges : 각 차원의 히스토그램 범위. 등간격(uniform=true) 히스토그램이면 ranges[i]는 각 차원의 최솟값과 최댓값으로 구성된 배열
	// [최솟값, 최대값)범위 : 대괄호는 포함을 의미하고 소괄호는 포함하지 않음을 의미. ex.변수 x의 범위가 [a,b) 라는 것은 a <= x < b
	// 비등간격(uniform=false) 히스토그램이면 ranges[i]는 각각의 구역을 나타내는 histSize[i]+1개의 원소로 구성된 배열
	// uniform : 히스토그램 빈의 간격이 균등한지 나타내는 플래그. default true
	// accumulate : 누적 플래그, 이 값이 true이면 hist 배열을 초기화하지 않고 누적하여 히스토그램을 계산. default false


	return hist;	// 반환되는 hist는 CV_32FC1 타입을 갖는 256×1 크기의 행렬
}

Mat getGrayHistImage(const Mat& hist) {
	// 히스토그램 행렬을 막대그래프 형태로 나타내려면 직접 hist 행렬을 참조하여 막대그래프 영상을 생성해야함
	
	CV_Assert(hist.type()==CV_32FC1);
	CV_Assert(hist.size() == Size(1, 256));

	double histMax;
	minMaxLoc(hist, 0, &histMax);	// hist 행렬 원소의 최댓값을 histMax 변수에 저장. 최솟값은 관심이 없으므로 두번째 인자는 0

	Mat imgHist(100, 256, CV_8UC1, Scalar(255));
	// 흰색으로 초기화된 100x256(막대그래프 길이가 100픽셀이 되도록) 크기의 새영상 imgHist를 생성

	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));
		// 반복문과 line함수를 사용하여 각각의 빈에 대한 히스토그램 그래프를 그림
		// 최댓값은 100픽셀에 해당하는 검은색 직선이 그려지고 나머지 막대는 100픽셀보다 짧은 길이의 직선으로 표현됨
	}

	return imgHist;	// hist 행렬로부터 구한 히스토그램 영상을 반환
}

void histogram() {

	Mat src = imread("lenna.bmp", IMREAD_GRAYSCALE);
	//Mat hist = calcGrayHist(src);
	//Mat hist_img = getGrayHistImage(hist);

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

	waitKey(0);
	destroyAllWindows();

}