OpenCV 클리핑과 영상혼합
1. 클리핑
1.1 클리핑이란?
픽셀 밝기값이 표현 범위를 넘어가는 경우, 표현 범위내로 값을 변환
일반적인 영상의 경우, 픽셀이 8비트로 표현됨. (HDR은 8비트 이상이다)
2^8= 256이다. ⇒ 따라서 밝기 값의 범위는 0~255이다.

1.2 클리핑 예시

// 클리핑 함수 정의
/*
픽셀의 값이 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);
}
결과가 이상하게 나온다.
밝기값 표현범위를 벗어나는 경우 색이 반전되는것을 확인할 수 있다.

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

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

아래의 사진에서 확인할 수 있듯이 -1 ⇒ 사실상 255와 같다.
따라서, 밝게 나오는 것이다.

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로 해보자.

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

- 또는 다음과 같이 코드 작성 (함수화)
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);
🦒
- 5.4 같은 수의 경우:
- 0.5를 더하면
5.4 + 0.5 = 5.9가 되고, 버림 처리하면5가 된다. - 즉, 원래
5.4는 반올림 시5로 가는 것이 맞다.
- 0.5를 더하면
- 5.5 같은 수의 경우:
- 0.5를 더하면
5.5 + 0.5 = 6.0이 되고, 버림 처리하면6이 된다. - 즉, 원래
5.5는 반올림 시6으로 가는 것이 맞다.
- 0.5를 더하면
- 5.9 같은 수의 경우:
- 0.5를 더하면
5.9 + 0.5 = 6.4가 되고, 버림 처리하면6이 된다. - 즉, 원래
5.9는 반올림 시6으로 가는 것이 맞다.
- 0.5를 더하면
따라서, 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의 이미지가 강해진다.

댓글남기기