6 분 소요

1. 클리핑

1.1 클리핑이란?

픽셀 밝기값이 표현 범위를 넘어가는 경우, 표현 범위내로 값을 변환

일반적인 영상의 경우, 픽셀이 8비트로 표현됨. (HDR은 8비트 이상이다)

2^8= 256이다. ⇒ 따라서 밝기 값의 범위는 0~255이다.

image-20241206021949914

1.2 클리핑 예시

image-20241206022006017

// 클리핑 함수 정의

/* 
픽셀의 값이 255를 넘거나 0보다 작으면 그 값을 각각 255 또는 0으로 설정하고, 
그렇지 않으면 원래 값을 유지한다. -> return value
*/

int clip_value(int value) {
    if (value > 255) {
        return 255;
    } else if (value < 0) {
        return 0;
    } else {
        return value;
    }
}

// 3x3 이미지 클리핑 처리
void clip_image(int img[3][3], int img_out[3][3]) {
    for (int y = 0; y < 3; ++y) {
        for (int x = 0; x < 3; ++x) {
            img_out[y][x] = clip_value(img[y][x]);
        }
    }
}

1.3 밝기값 표현 범위를 벗어나는 영상 만들기

int main()
{
	int height, width;
	int** img = (int**)ReadImage((char* )"barbara.png", &height, &width);
	int** img_out1 = (int**)IntAlloc2(height, width); // 밝기 증가한 이미지 저장
	int** img_out2 = (int**)IntAlloc2(height, width); // 밝기 감소한 이미지 저장

	for (int y = 0; y < height; y++) {
		for (int x = 0; x < width; x++) {
			img_out1[y][x] = img[y][x] + 50; // 밝기 값을 50 증가
			img_out2[y][x] = img[y][x] - 50; // 밝기 값을 50 감소
		}
	}

	// 문자열 리터럴을 const char*로 전달
	ImageShow((char*)"출력1 영상보기", img_out1, height, width); // 밝기 증가한 이미지 출력
	ImageShow((char*)"출력2 영상보기", img_out2, height, width); // 밝기 감소한 이미지 출력

	return 0;
}

value값==50

밝기값을 50씩 증가시킨 코드

void ShiftImage(int value, int** img, int height, int width)
{
	for (int y = 0; y < height; y++) {
		for (int x = 0; x < width; x++) {
			img[y][x] = img[y][x] + value;
		}
	}
}

int main()
{
	int height, width;
	int** img = ReadImage((char*)"./images/lena.png", &height, &width);
	int** img_out = (int**)IntAlloc2(height, width);
	
	ShiftImage(50, img, height, width);
//포인터(예: img, img_out): 배열 자체가 이미 메모리 주소를 가리키므로, 
//포인터를 그대로 전달하면 된다. 이때 &를 붙이지 않아도 된다

	ImageShow((char*)"input", img, height, width);

	
}

결과가 이상하게 나온다.

밝기값 표현범위를 벗어나는 경우 색이 반전되는것을 확인할 수 있다.

image-20241206022034397

  • 밝기값 257은 일반적으로 8비트 시스템에서 표현할 수 있는 범위를 벗어난 값이다. 8비트에서는 0부터 255까지의 값만 사용할 수 있는데, 257은 255보다 크기 때문에 이를 표현할 수 없다.
  • 이진수 변환: 그림에서는 257을 이진수로 변환하여 보여주고 있다. 257을 2진수로 나타내면 100000001이 된다.
  • 표현 가능한 범위: 하지만, 8비트 시스템에서는 가장 오른쪽 8비트만 사용하기 때문에, 상위 비트(첫 번째 비트 1)는 잘리고 나머지 값만 사용된다. 즉, 00000001이 남게 되며, 이는 십진수로 1에 해당한다.
  • 결과: 257이라는 값이 표현될 수 없는 상황에서, 시스템은 이를 잘라서 1로 변환하는 것이다.

image-20241206022101125

  • 밝기값 -2는 8비트 표현 범위에서 음수이므로, 표현할 수 없는 값이다.
  • **2의 보수 사용**: 이 경우 음수는 2의 보수법을 사용해 양수로 변환된다. -2를 8비트 이진수로 변환하면 11111110이 된다. 이 값은 **2의 보수**를 사용한 결과로, 사실상 254에 해당하는 값이다.
  • 결과: 따라서, 시스템은 -2라는 값을 받아들일 수 없기 때문에 이를 2의 보수로 변환하여 254로 처리한다.

image-20241206022120945

아래의 사진에서 확인할 수 있듯이 -1 ⇒ 사실상 255와 같다.

따라서, 밝게 나오는 것이다.

image-20241206022135260

2. 해결방법: 클리핑

void ClippingImage(int value, int** img, int height, int width, int** img_out)
{
	for (int y = 0; y < height; y++) {
		for (int x = 0; x < width; x++) {
			if (img[y][x] > 255) {
				img_out[y][x] = 255;
			}
			else if (img[y][x] < 0) {
				img_out[y][x] = 0;
			}
			else {
				img_out[y][x] = img[y][x];
			}
		}
	}

}

int main()
{
	int height, width;
	int** img = ReadImage((char*)"./images/lena.png", &height, &width);
	int** img_out = (int**)IntAlloc2(height, width);
	ClippingImage(50, img, height, width, img_out);

	ImageShow((char*)"input", img, height, width);
	ImageShow((char*)"output", img_out, height, width);

	
}
  • 매크로 사용
// 대소를 비교하는 구문
#define GetMax(x, y) ((x>y) ? x : y)
#define GetMin(x, y) ((x<y) ? x : y)

void ImageClipping(int** img, int height, int width, int** img_out)
{
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            // img_out[y][x] = (img[y][x]>255) ? 255 : img[y][x];
            // img_out[y][x] = (img[y][x]<0) ? 0 : img[y][x];
            img_out[y][x] = GetMin(img[y][x], 255);
            img_out[y][x] = GetMax(img[y][x], 0);
        }
    }
}

#define NUM 100

int ex0925_1(void)
{
	int x = 100, y = 200;
	int A, B;

	//A = (x > y) ? x : y;
	//B = (x < y) ? x : y;
	
	A = GetMax(x, 0); //큰값
	B = GetMin(y, 255); //작은값

	return 0;

}
#define NUM 100

int ex0924_3()
{
	int height, width;
	int** img = ReadImage((char*)"./images/lena.png", &height, &width);
	int** img_out = (int**)IntAlloc2(height, width);
	
	ImageClipping(50, img, height, width, img_out);

	ImageShow((char*)"input", img, height, width);
	ImageShow((char*)"output", img_out, height, width);

	return 0;
}

int main(void)
{
	ex0924_3();
}
  • 중첩해서 사용하기
int ex0925_2(void)
{
	int A = 100, B = 200, C = 300;
	//int D = GetMax(A,B);
	int E = GetMax(GetMax(A, B), C);
	return 0;
}
int findMaxvalue(int** img, int height, int width)
{
	int max_value = img [0][0]; 
	for (int y = 0; y < height; y++)
	{
		for (int x = 0; x < width; x++)
		{
			max_value = GetMax(max_value, img[y][x]);
		}
	}

	return max_value;
}
int findMinvalue(int** img, int height, int width)
{
	int min_value = img[0][0];
	for (int y = 0; y < height; y++)
	{
		for (int x = 0; x < width; x++)
		{
			min_value = GetMin(min_value, img[y][x]);
		}
	}

	return min_value;
}

int main(void)
{
	int A[7] = { 1,-1,3,8,2,9,10 };

	int max_value = A[0];
	int min_value = A[0];

	for (int n = 1; n < 7; n++)
	{
		max_value = GetMax(max_value, A[n]);
		min_value = GetMin(min_value, A[n]);
	}

	int height, width;
	int** img = ReadImage((char*)"./images/lena.png", &height, &width);
	max_value=findMaxvalue(img, height, width);
	min_value = findMinvalue(img, height, width);

	return 0;
}

3. 영상 혼합

두개의 영상을 적절한 가중치를 주어 합하는 처리

  • 각 영상에 가중치를 곱하여 합하며, 가중치의 합은 일반적으로 1이다.

가중치를 0.5, 0.5로 해보자.

image-20241206022205225

int main()
{
	int height, width;
	int** img1 = ReadImage((char*)"./images/lena.png", &height, &width);
	int** img2 = ReadImage((char*)"./images/barbara.png", &height, &width);
	int** img_out = (int**)IntAlloc2(height, width);

	float alpha = 0.5;
	for (int y = 0; y < height; y++)
	{
		for (int x = 0; x < width; x++)
		{
			img_out[y][x] = alpha * img1[y][x] + (1.0 - alpha) * img2[y][x];
		}
	}
	ImageShow((char*)"output", img_out, height, width);

}

image-20241206022223765

  • 또는 다음과 같이 코드 작성 (함수화)
void MixingImages(int** img1, int** img2, float alpha, int height, int width, int** img_out)
{

	for (int y = 0; y < height; y++)
	{
		for (int x = 0; x < width; x++)
		{
			img_out[y][x] = alpha * img1[y][x] + (1.0 - alpha) * img2[y][x];
		}
	}

}

int main()
{
	int height, width;
	int** img1 = ReadImage((char*)"./images/lena.png", &height, &width);
	int** img2 = ReadImage((char*)"./images/barbara.png", &height, &width);
	int** img_out = (int**)IntAlloc2(height, width);

	float alpha = 0.5;
	MixingImages(img1, img2, alpha, height, width, img_out);

	ImageShow((char*)"output", img_out, height, width);

}

소수점 처리 문제

이 코드에서 img_out[y][x]는 정수형 배열이므로, 계산 결과가 소수점 아래 값이 있을 경우 자동으로 버림 처리가 이루어진다. 예를 들어, 125.8이 계산되면 125로 저장된다. 만약 반올림 처리를 하고 싶다면, 다음과 같은 방식으로 코드를 수정할 수 있다:

img_out[y][x] = (int)(alpha * img1[y][x] + (1.0 - alpha) * img2[y][x] + 0.5);

🦒

  1. 5.4 같은 수의 경우:
    • 0.5를 더하면 5.4 + 0.5 = 5.9가 되고, 버림 처리하면 5가 된다.
    • 즉, 원래 5.4는 반올림 시 5로 가는 것이 맞다.
  2. 5.5 같은 수의 경우:
    • 0.5를 더하면 5.5 + 0.5 = 6.0이 되고, 버림 처리하면 6이 된다.
    • 즉, 원래 5.5는 반올림 시 6으로 가는 것이 맞다.
  3. 5.9 같은 수의 경우:
    • 0.5를 더하면 5.9 + 0.5 = 6.4가 되고, 버림 처리하면 6이 된다.
    • 즉, 원래 5.9는 반올림 시 6으로 가는 것이 맞다.

따라서, 0.5를 더한 후 정수형으로 변환하는 방식은 소수점 첫째 자리에서의 반올림 효과를 내는 방법이다. 이것은 소수점 자리를 단순히 잘라내는 버림 대신에 0.5를 더해 다음 정수로 넘어가게 하는것이다.

  • MicingImage()함수를 이용해서 알파값이 0.1, 0.2, ….0.8이 되는 영상을 출력하도록 코드를 구성해보자.

알파값을 for문으로 변경

void MixingImages(int** img1, int** img2, float alpha, int height, int width, int** img_out)
{

	for (int y = 0; y < height; y++)
	{
		for (int x = 0; x < width; x++)
		{
			img_out[y][x] = alpha * img1[y][x] + (1.0 - alpha) * img2[y][x];
		}
	}

}

int main()
{
	int height, width;
	int** img1 = ReadImage((char*)"./images/lena.png", &height, &width);
	int** img2 = ReadImage((char*)"./images/barbara.png", &height, &width);
	int** img_out = (int**)IntAlloc2(height, width);

	

	for (float alpha = 0.1; alpha < 1.0; alpha += 0.1)
	{
		MixingImages(img1, img2, alpha, height, width, img_out);

		ImageShow((char*)"output", img_out, height, width);

	}
	
}

알파값이 커질수록 barbara의 이미지가 강해진다.

image-20241206022248765

태그:

카테고리:

업데이트:

댓글남기기