크기 변환과 보간법

  • 영상의 크기를 원본 영상 보다 크게 또는 작게 만드는 변환
  • x축과 y축 방향으로의 스케일 비율을 지정

순방향 매핑(forward mapping)

image

  • 그레이스케일 영상을 가로, 세로 2배 확대하기
void resize1()
{
	Mat src = imread("camera.bmp", IMREAD_GRAYSCALE);

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

	Mat dst = Mat::zeros(src.rows * 2, src.cols * 2, CV_8UC1);

	for (int y = 0; y < src.rows; y++) {
		for (int x = 0; x < src.cols; x++) {
			int x_ = x * 2;
			int y_ = y * 2;

			dst.at<uchar>(y_, x_) = src.at<uchar>(y, x);
		}
	}

	imshow("src", src);
	imshow("dst", dst);
	waitKey();
}
  • 아래와 같이 확대된 camera.bmp 사이사이에 검정색 점들이 있는 것을 확인할 수 있음

image

  • 영상 확대시 채워지지 않은 빈 공간이 발생함
    • (0,0) -> (0,0) / (1,0) -> (2,0) / (0,1) -> (0,2) / (1,1) -> (2,2) 로 가는데 4 x 4 배열의 나머지 값들은 빈 공간이 됨

image

  • 이러한 문제점을 해결하기 위해 역방향 매핑(backward mapping)을 사용

역방향 매핑(backward mapping)

void resize2()
{
	Mat src = imread("camera.bmp", IMREAD_GRAYSCALE);

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

	Mat dst = Mat::zeros(src.rows * 2, src.cols * 2, src.type());

	for (int y_ = 0; y_ < dst.rows; y_++) {
		for (int x_ = 0; x_ < dst.cols; x_++) {
			int x = x_ / 2;
			int y = y_ / 2;
			dst.at<uchar>(y_, x_) = src.at<uchar>(y, x);
		}
	}

	imshow("src", src);
	imshow("dst", dst);
	waitKey();
}
  • 빈 곳 마다 검정색 점은 없지만, 그림을 확대하면 픽셀 하나가 크기가 커진 정사각형 형태로 보이고 다른 픽셀과 같이 보면 계단식 형태처럼 보임
    • 1 x 1 픽셀의 값이 2 x 2 모두에 채워지면서 하나의 픽셀 값을 4개의 똑같은 픽셀 값으로 채워 이러한 문제가 발생

image

보간법

  • 역방향 매핑에 의한 크기 변환시, 참조해야할 입력 영상의 (x, y) 좌표가 실수 좌표라면?
    • (x,y)와 가장 가까운 정수 좌표의 픽셀 값을 참조
    • 또는 (x,y) 근방의 정수 좌표 픽셀 값을 이용하여 실수 좌표 위치의 픽셀 값을 추정
  • 보간법이라는 실수 좌표 상에서의 픽셀 값을 결정하기 위해 주변 픽셀 값을 이용하여 값을 추정하는 방법

  • 주요 보간법
    • 최근방 이웃 보간법(nearest neighbor interpolation)
    • 양선형 보간법(bilinear interpolation)
    • 3차 보간법(cubic interpolation)
    • 스플라인 보간법(spline interpolation)
    • 란쵸스 보간법(lanczos interpolation)
최근방 이웃 보간법(nearest neighbor interpolation)
  • 가장 가까운 위치에 있는 픽셀의 값을 참조하는 방법
  • 예 : (50.2, 32.8) -> (50, 33)
    • 장점 : 빠르고 구현하기 쉽다
    • 단점 : 계단 현상(블록 현상)
  • 계단현상이 심하게 나타는 것을 확인할 수 있음

image

양선형 보간법(bilinear interpolation)
  • 실수 좌표를 둘러싸고 있는 4 개 픽셀 값에 가중치를 곱한 값들의 선형 합으로 결과 영상의 픽셀 값을 구하는 방법
    • 장점 : 비교적 빠르며 계단 현상이 크게 감소
    • 단점 : 최근방 이웃 보간법에 비해서는 느림
  • 계단현상이 줄어든 것을 확인할 수 있음

image

  • 양선형 보간법 구현 방법
    • 실수 좌표를 둘러싸고 있는 4 개의 픽셀 값을 이용

image

image

void resize3()
{
	Mat src = imread("camera.bmp", IMREAD_GRAYSCALE);

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

	Mat dst;
	resizeBilinear(src, dst, Size(600, 300));

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

void resizeBilinear(const Mat& src, Mat& dst, Size size) 
// Mat 참조로 입력 영상과 출력 영상, 출력 영상이 가져야할 사이즈를 객체로 받음
{
	dst.create(size.height, size.width, CV_8U); // dst 사이즈 크기만큼 생성함

	int x1, y1, x2, y2;	double rx, ry, p, q, value;
	double sx = static_cast<double>(src.cols - 1) / (dst.cols - 1); // 
	double sy = static_cast<double>(src.rows - 1) / (dst.rows - 1); // 

	for (int y = 0; y < dst.rows; y++) { // 출력 영상의 세로 크기만큼 동작
		for (int x = 0; x < dst.cols; x++) { // 출력 영상의 가로 크기만큼 동작
			rx = sx * x;			ry = sy * y; // 실수 좌표를 rx, ry 
			x1 = cvFloor(rx);		y1 = cvFloor(ry); // rx, ry보다 작은 정수 값을 x1, y1
			x2 = x1 + 1; if (x2 == src.cols) x2 = src.cols - 1; // rx, ry보다 큰 정수 값을 x2, y2
			y2 = y1 + 1; if (y2 == src.rows) y2 = src.rows - 1;
			p = rx - x1;			q = ry - y1; // 아래의 그림처럼 변수를 지정함

			value = (1. - p) * (1. - q) * src.at<uchar>(y1, x1) // 위의 수식을 코드로 작성
				+ p * (1. - q) * src.at<uchar>(y1, x2)
				+ (1. - p) * q * src.at<uchar>(y2, x1)
				+ p * q * src.at<uchar>(y2, x2);

			dst.at<uchar>(y, x) = static_cast<uchar>(value + .5); // 실수 값으로 나오므로 반올림 함 
		}
	}
}

image

3차 보간법(cubic interpolation)
  • 실수 좌표를 둘러싸고 있는 16개의 픽셀 값에 3차 함수를 이용한 가중치를 부여하여 결과 영상 픽셀의 값을 계산함

image

OpenCV resize() 함수

void resize(InputArray src, OutputArray dst, Size dsize, double fx = 0, double fy = 0, int interpolation = INTER_LINEAR)
  • src : 입력 영상
  • dst : 출력 영상
  • dsize : 결과 영상의 크기. Size()로 지정하면 fx, fy에 의해 자동 결정됨
  • fx : x 방향 스케일 비율 (dsize 값이 0일 때 유효)
  • fy : y 방향 스케일 비율 (dsize 값이 0일 때 유효)
  • interpolation : 보간법 지정 상수
    • INTER_NEAREST : 최근방 이웃 보간법
    • INTER_LINEAR : 양선형 보간법(2x2 이웃 픽셀 참조)
    • INTER_CUBIC : 3차회선 보간법(4x4 이웃 픽셀 참조)
    • INTER_LANCZOS4 : Lanczos보간법(8x8 이웃 픽셀 참조)
    • INTER_AREA : 영상 축소 시 효과적
void resize4()
{
	Mat src = imread("rose.bmp");

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

	Mat dst1, dst2, dst3, dst4;
	resize(src, dst1, Size(), 4, 4, INTER_NEAREST);
	resize(src, dst2, Size(1920, 1280));
	resize(src, dst3, Size(1920, 1280), 0, 0, INTER_CUBIC);
	resize(src, dst4, Size(1920, 1280), 0, 0, INTER_LANCZOS4);

	imshow("src", src);
	imshow("dst1", dst1(Rect(400, 500, 400, 400)));
	imshow("dst2", dst2(Rect(400, 500, 400, 400)));
	imshow("dst3", dst3(Rect(400, 500, 400, 400)));
	imshow("dst4", dst4(Rect(400, 500, 400, 400)));
	waitKey();
}
  • dst1 과 dst2 사이에서는 분명하게 화질이 많이 부드러워진 것을 확인할 수 있음
  • dst2, dst3, dst4 사이에서는 확연한 차이는 없지만 dst4로 갈수록 조금 부드러워지는 것을 확인할 수 있음
  • dst2 에서는 4개의 픽셀, dst3 에서는 16개의 픽셀 dst4에서는 64개의 픽셀을 사용하므로 연산량의 차이는 큼

image

  • 영상 축소시 고려할 사항
    • 한 픽셀로 구성된 선분들은 영상을 축소할 때 사라지는 경우가 발생함
    • 입력 영상을 부드럽게 필터링 후 축소하거나 다단계 축소를 권장
    • OpenCV의 resize() 함수에서는 INTER_AREA 플래그를 사용

image