[2023-04-24] 2. 크기 변환과 보간법
크기 변환과 보간법
- 영상의 크기를 원본 영상 보다 크게 또는 작게 만드는 변환
- x축과 y축 방향으로의 스케일 비율을 지정
순방향 매핑(forward mapping)
- 그레이스케일 영상을 가로, 세로 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 사이사이에 검정색 점들이 있는 것을 확인할 수 있음
- 영상 확대시 채워지지 않은 빈 공간이 발생함
- (0,0) -> (0,0) / (1,0) -> (2,0) / (0,1) -> (0,2) / (1,1) -> (2,2) 로 가는데 4 x 4 배열의 나머지 값들은 빈 공간이 됨
- 이러한 문제점을 해결하기 위해 역방향 매핑(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개의 똑같은 픽셀 값으로 채워 이러한 문제가 발생
보간법
- 역방향 매핑에 의한 크기 변환시, 참조해야할 입력 영상의 (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)
- 장점 : 빠르고 구현하기 쉽다
- 단점 : 계단 현상(블록 현상)
- 계단현상이 심하게 나타는 것을 확인할 수 있음
양선형 보간법(bilinear interpolation)
- 실수 좌표를 둘러싸고 있는 4 개 픽셀 값에 가중치를 곱한 값들의 선형 합으로 결과 영상의 픽셀 값을 구하는 방법
- 장점 : 비교적 빠르며 계단 현상이 크게 감소
- 단점 : 최근방 이웃 보간법에 비해서는 느림
- 계단현상이 줄어든 것을 확인할 수 있음
- 양선형 보간법 구현 방법
- 실수 좌표를 둘러싸고 있는 4 개의 픽셀 값을 이용
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); // 실수 값으로 나오므로 반올림 함
}
}
}
3차 보간법(cubic interpolation)
- 실수 좌표를 둘러싸고 있는 16개의 픽셀 값에 3차 함수를 이용한 가중치를 부여하여 결과 영상 픽셀의 값을 계산함
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개의 픽셀을 사용하므로 연산량의 차이는 큼
- 영상 축소시 고려할 사항
- 한 픽셀로 구성된 선분들은 영상을 축소할 때 사라지는 경우가 발생함
- 입력 영상을 부드럽게 필터링 후 축소하거나 다단계 축소를 권장
- OpenCV의 resize() 함수에서는 INTER_AREA 플래그를 사용