[컴퓨터 비전] 영상의 밝기와 명암비 조절

Ogu·2023년 10월 10일
0
post-thumbnail

영상의 밝기 조절

그레이스케일 영상 다루기

영상 파일을 그레이스케일 형태로 불러오는 경우

imread() 함수의 두번째 인자에 IMREAD_GRAYSCALE 플래그를 설정합니다.

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

그레이스케일 영상을 저장할 새로운 Mat 객체 생성

CV_8UC1 타입의 객체를 생성합니다. 다음 ㅁ코드는 모든 픽셀 값이 0으로 초기화된 640 X 480 그레이스케일 영상을 생성합니다.

Mat img2(480, 640, CV_8UC1, Scalar(0));

이미 3채널 컬러 영상을 가지고 있고, 이 영상을 그레이스케일 영상으로 변환

cvtColor()함수를 사용해 3채널 컬러 영상을 1채널 그레이스케일 영상으로 변환합니다.

cvtColor() 함수는 Mat 객체에 저장된 색상 정보를 변경할 때 사용하는 함수이며,
COLOR_BGR2GRAY 는 BGR 3채널 컬러 영상을 1채널 그레이스케일 영상으로 변환할때 사용합니다.

Mat img3 = imread("lenna.bmp", IMREAD_COLOR);
Mat img4;
cvtColor(img3, img4, COLOR_BGR2GRAY);

영상의 밝기 조절

영상의 밝기(brightness) 조절이란 영상의 전체적인 밝기를 조절하여 좀 더 밝거나 어두운 영상을 만드는 작업입니다.
입력 영상의 모든 픽셀에 일정 값을 더하거나 빼는 작업을 수행합니다.
양수를 더하면 밝아지고, 빼면 어두워집니다.

수식으로 표현하면 다음과 같습니다.

dst(x, y) = src(x, y) + n

src : 입력 영상, dst : 출력 영상, n : 조절할 밝기 값

영상의 밝기 조절 함수 그래프


255보다 큰 값이나 0보다 작은 값으로 설정해도 최댓값은 255, 최솟값은 0으로 설정됩니다.

덧셈, 뺄셈 연산으로 밝기 조절

Mat 객체와 C/C++ 기본 자료형과의 덧셈 및 뺄셈 연산 가능합니다.
아래 코드는 src 행렬의 모든 원소에 각각 100을 더하고, 포화 연산까지 수행한 결과를 dst 행렬에 저장합니다.

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

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

	Mat dst = src - 100;

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

	destroyAllWindows();
}

만약, 영상의 밝기 조절 결과를 dst같은 새로운 영상에 저장하는 것이 아니고, 자기 자신에게 저장하려면 += 연산자 재정의를 사용할 수 있습니다.

Mat img = imread("lenna.bmp", IMREAD_GRAYSCALE);
img += 100;

영상의 밝기 조절 직접 구현

Mat 행렬의 원소 값을 참조하며 입력 영상의 모든 픽셀을 방문하며 픽셀값에 일정한 상수를 더하거나 빼면 밝기 조절 적용을 할 수 있습니다.

Mat::at() 함수의 첫번째 인자가 y축 좌표에 해당하는 j이고, 두번째 인자가 x축 좌표에 해당하는 i입니다.

포화 연산을 고려하지 않은 밝기 증가 직접 구현

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;
		}
	}

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

	destroyAllWindows();
}

하지만 포화 연산을 고려하지 않았기 때문에 다음과 같이 나타납니다.

포화 연산을 고려한 영상의 밝기 증가 직접 구현

변수 v는 int형이기 때문에 255보다 큰 수도 저장할 수 있습니다.
삼항 조건 연산자를 이용해 v>255가 참이면 255를 대입하고, 거짓이면 다시 v<0 조건을 검사하여 v<0이 참이면 0을 대입하고, 그렇지 않으면 v를 결과 영상 픽셀값으로 대입합니다.

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) = v > 255 ? 255 : v < 0 ? 0 : v;
		}
	}

그러나 이처럼 그레이스케일 값 범위에 맞게끔 결과 영상 픽셀 값을 설정하는 작업은 매우 빈번하기 때문에, OpenCV에서는 이러한 포화 연산을 수행하는 saturate_cast() 라는 이름의 캐스팅 함수를 지원합니다.

template<> inline 
uchar saturate_cast<uchar>( int v );
  • v : int형 자료형이 표현할 수 있는 범위의 정수
  • 반환값 : 0 ~ 255 사이의 정수
for (int j = 0; j < src.rows; j++) {
		for (int i = 0; i < src.cols; i++) {
			dst.at<uchar>(j, i) = saturate_cast<uchar>(src.at<uchar>(j, i) + 100);
		}
	}

위의 코드로 실행하면, 포화 연산이 추가되어 레나 영상의 밝기가 정상적으로 밝아지는 것을 확인 할 수 있습니다.

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

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);

	waitKey();
	destroyAllWindows();
}

영상의 명암비 조절

앞의 영상의 밝기 조절이 기본적으로 덧셈 연산을 사용한다면, 영상의 명암비 조절은 전체 픽셀에 절절한 실수를 곱하는 곱셈 연산을 사용합니다.

기본적인 명암비 조절 방법

명암비란 영상에서 밝은 영역과 어두운 영역 사이에 드러나는 밝기 차이의 강도를 의미합니다.
명암 대비 또는 contrast 라고도 부릅니다.

일반적으로 어둡거나, 또는 전반적으로 밝은 픽셀로만 구성된 경우, 명암비가 낮다고 표현합니다.
밝은 영역과 어두운 영역이 골고루 섞여 있는 영상은 명암비가 높다고 합니다.

아래의 왼쪽 사진은 명암비가 낮아 흐릿한 느낌을 주고, 오른쪽은 명암비가 높아 선명한 느낌을 줍니다.

명암비 조절 수식

dst(x,y) = saturate(s * src(x, y))

위의 수식은 상수 s가 1보다 작은 경우 명암비가 낮아지고, s가 1보다 큰 경우 명암비가 높아지는 효과가 있습니다.
그러나 결과 영상이 전반적으로 어두워지거나, 영상의 밝기가 너무 쉽게 포화되는 단점이 잇습니다.

(a)는 픽셀이 가질 수 있는 범위가 0~128로 제한되어 전체적으로 어두워지며 명암비가 감소합니다.

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

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

	float s = 2.f;
	Mat dst = s * src;
	imshow("src", src);
	imshow("dst", dst);

	waitKey();
	destroyAllWindows();
}

위 코드는 Mat 행렬의 모든 원소에 주어진 실수 값을 곱한 결과 행렬을 반환합니다.
이때, 결과 행렬에 대해 포화 연산도 함께 수행됩니다.

결과를 보면, 전체적으로 픽셀 값이 포화되어 흰색으로 나타나는 영역이 많아 사물의 윤곽 구분이 더 어려워졌습니다. 사실상 픽셀 값에 일정 상수를 단순히 곱하여 명암비를 조절하는 방식은 잘 사용되지 않습니다.

효과적인 명암비 조절 방법

명암비를 효과적으로 높이기 위해서는 밝은 픽셀은 더욱 밝게, 어두운 픽셀은 더욱 어두워지게 변경해야 합니다.
픽셀 값이 밝고 어두운 기준을 어떻게 설정하는지에 따라 명암비 조절 결과 영상의 품질 차이를 가져올 수 있습니다.

그레이스케일 범위 중간값인 128을 기준으로 설정하거나, 입력 영상의 평균 밝기를 기준으로 삼을 수도 있습니다.

우리는 그레이스케일 범위 중간값인 128을 기준으로 명암비를 조절해보겠습니다.

명암비를 높이려면, 입력 영상의 픽셀 값이 128보다 크면 더욱 밝게, 128보다 작으면 더 작게 만듭니다.

반대로, 명암비를 감소시키려면 128보다 큰 픽셀 값은 좀 더 작게, 128보다 작은 픽셀 값은 오히려 128에 가깝게 증가시킵니다.

픽셀 값 변경 방식 수식

dst(x,y) = src(x, y) + src(x, y - 128) * α
α 는 -1보다 같거나 큰 실수입니다. 이 수식은 항상 (128, 128) 좌표를 지나가고, α에 의해 기울기가 변경되는 직선의 방정식입니다.

  • -1 ≤ α ≤ 0 : 기울기가 0부터 1 사이의 직선이고, 명암비 감소
  • -α ≥ 0 : 기울기>1 직선, 명암비 감소

하지만 위 수식은 0보다 작거나 255보다 커지는 경우가 발생할 수 있습니다.

포화 연산을 포함한 픽셀 값 변경 방식 수식

dst(x,y) = saturate(src(x, y) + src(x, y)-128 * α

void contrast2()
{
	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;

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

}

위 코드는 α값 1.0을 사용하여 레나 영상의 명암비를 증가시킵니다.
그 결과 아래와 같이 좀 더 자연스럽게 명암비가 증가되었습니다.

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

0개의 댓글