3학년/OpenCV

[OpenCV] 8장 영상의 기하학적 변환

천도복숭아에이드 2023. 7. 1. 19:08

 

 

8.1 어파인 변환

 

8.1.1 어파인 변환

 

영상의 기하학적 변환(geometric transfor): 영상을 구성하는 픽세르이 배치 구조를 변경함으로써 전체 영상의 모양을 바꾸는 작업

 

어파인 변환(affine transformation): 영상을 평행 이동시키거나 회전, 크기 변환 등을 통해 만들 수 있는 변환을 통칭

 

 

Mat getAffineTransform(const Point2f src[], const Point2f dst[]);
Mat getAffineTransform(InputArray src, InputArray dst);
  • src: 입력 영상에서 세 점의 좌표
  • dst: 결과 영상에서 세 점의 좌표
  • 반환값: 2x3 어파인 변환 행렬. CV_64FC1

 

 

void wrapAffine (InputArray src, OutputArray dst, 
				InputArray M, Size dsize, int flags = INTER_LINEAR,
                int borderMode = BORDER_CONSTANT, const Scalar& borderValue = Scalar());
  • src: 입력 영상
  • dst: 결과 영상. src와 같은 타입이고, 크기는 dsize에 의해 결정
  • M: 2x3 어파인 변환 행렬
  • dsize: 결과 영상 크기
  • flags: 보간법 알고리즘. 만약 OR 연산자를 이용하여 WRAP_INVERSE_MAP 플래그를 함께 지정하면
    역방향으로 변환을 수행
  • borderMode: 가장자리 픽셀 확장 방식. BorderTypes 열거형 상수 중 하나를 지정. 만약 BORDER_TRANSPARENT를 지정하면 입력 영상의 픽셀 값이 복사되지 않는 영역은 dst 픽셀 값을 그대로 유지
  • borderValue: borderMode가 BORDER_CONSTANT일 때 사용할 상수 값. 기본값으로 검은색이 지정되어 있음

 

 

 

void affine_transform()
{
	Mat src = imread("tekapo.bmp");

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

	Point2f srcPts[3], dstPts[3];
	srcPts[0] = Point2f(0, 0);
	srcPts[1] = Point2f(src.cols - 1, 0);
	srcPts[2] = Point2f(src.cols - 1, src.rows - 1);
	dstPts[0] = Point2f(50, 50);
	dstPts[1] = Point2f(src.cols - 100, 100);
	dstPts[2] = Point2f(src.cols - 50, src.rows - 50);

	Mat M = getAffineTransform(srcPts, dstPts);

	Mat dst;
	warpAffine(src, dst, M, Size());

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

	waitKey();
	destroyAllWindows();
}
  • 3행: tekapo.bmp 파일을 3채널 컬러 영상으로 불러와 src에 저장
  • 10행: 입력 영상과 출력 영상에서의 세 점 좌표를 지정할 srcPts와 dstPts 배열을 선언
  • 11~13행: srcPts 배열에 입력 영상의 좌측 상단, 우측 상단, 우측 하단의 좌표를 저장
  • 14~16행: dstPts 배열에 srcPts 점들이 이동할 좌표를 저장
  • 18행: 2x3 어파인 변환 행렬을 M에 저장
  • 21행: 어파인 변환 행렬 M을 이용하여 src 영상을 어파인 변환하여 dst에 저장. warpAffine() 함수의 네 번째 인자에 Size()를 지정하여 dst 영상 크기가 src 영상 크기와 같아지도록 설정

 

 

void transform(InputArray src, OutputArray dst, InputArray m);
  • src: 입력 행렬 또는 vector<Point2f>, 점의 좌표를 다채널로 표현
  • dst: 출력 행렬 또는 vector<Point2f>
  • m: 변환 행렬. 2x2 또는 3x3 실수형 행렬

 

 

8.1.2 이동 변환

 

영상의 이동 변환 (translation transformation): 영상을 가로 또는 세로 방향으로 일정 크기만큼 이동시키는 연산. 시프트 연산이라고도 한다. 

  • 노란색 사각형: w x h 크기의 원본 영상
  • 녹색 사각형: 가로 방향으로 a, 세로 방향으로 b 만큼 이동 변환된 결과 영상

 

void affine_translation()
{
	Mat src = imread("tekapo.bmp");

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

	Mat M = Mat_<double>({ 2, 3 }, { 1, 0, 150, 0, 1, 100 });

	Mat dst;
	warpAffine(src, dst, M, Size());

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

	waitKey();
	destroyAllWindows();
}

 

  • 3행:  tekapo.bmp 파일을 3채널 컬러 영상으로 불러와 src에 저장
  • 10행: 가로로 150 픽셀, 세로로 100 픽셀 이동하는 어파인 변환 행렬 M을 생성
  • 13행: src 영상을 이동 변환하여 dst 영상을 생성. dst 영상 크기는 src 영상과 같게 설정

입력 영상의 픽셀 값이 복사되지 않은 영역은 검은색으로 채워져있다

 

8.1.3 전단 변환

 

전단 변환 (shear transformation): 직사각 형태의 영상을 한쪽 방향으로 밀어서 평행사변형 모양으로 변형되는 변환.
층밀림 변환이라고도 한다.

 

 

 

 

void affine_shear()
{
	Mat src = imread("tekapo.bmp");

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

	double mx = 0.3;
	Mat M = Mat_<double>({ 2, 3 }, { 1, mx, 0, 0, 1, 0 });

	Mat dst;
	warpAffine(src, dst, M, Size(cvRound(src.cols + src.rows * mx), src.rows));

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

	waitKey();
	destroyAllWindows();
}
  • 10~11행: 가로 방향으로 밀림 정도를 0.3으로 설정한 전단 변환 행렬 M을 생성
  • 14행: 행렬 M을 이용하여 어파인 변환을 수행. 전단 변환에 의해 입력 영상의 일부가 잘리지 않도록 결과 영상 가로 크기를 cvRound(src.cols + src.rows * mx) 형태로 지정

 

 

8.1.4 크기 변환

 

영상의 크기 변환(scale transformation): 영상의 전체적인 크기를 확대 또는 축소하는 변환

 

 

void resize (InputArray src, OutputArray dst, Size dsize, 
			double fx = 0, double fy = 0, int interpolation = INTER_LINEAR);
  • src: 입력 영상
  • dst: 결과 영상
  • dsize: 결과 영상 크기
  • fx: x축 방향으로의 크기 변환 비율. dsize에 Size()를 지정한 경우에 사용
  • fy: y축 방향으로의 크기 변환 비율. dsize에 Size()를 지정한 경우에 사용
  • interpolation: 보간법 지정. INTER_NEAREST, INTER_LINEAR, INTER_CUBIC, INTER_AREA, INTER_LANCZ054 중 하나를 지정

 

 

 

void affine_scale()
{
	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();
	destroyAllWindows();
}

 

  • 3행: rose.bmp 파일을 3채널 컬러 영상으로 불러와 src에 저장
  • 11행: src 영상을 x 방향으로 4배, y 방향으로 4배 확대하여 dst1을 생성. src 영상의 크기가 480 x 320 이므로 결과 영상 dst1의 크기는 1920x1280으로 결정. 보간법은 최근방 이웃 보간법을 사용
  • 12행: src 영상을 1920x1280 크기로 확대하여 dst2를 생성. 보간법을 따로 지정하지 않았으므로 기본값인 양선형 보간법을 사용. 
  • 13행: src 영상을 1920x1280 크기로 확대하여 dst3을 생성. 보간법은 3차 회선 보간법을 사용
  • 14행: src 영상을 1920x1280 크기로 확대하여 dst4를 생성, 보간법은 란초스 보간법을 사용
  • 16~20행: 입력 영상 src와 확대 변환 결과 영상을 화면에 출력. 확대 변환 결과 영상은 (400,500) 좌표부터 400x400 크기의 부분 영상을 화면에 출력

 

 

 

 

8.1.5 회전 변환

 

영상의 회전 변환 (rotation transformation): 특정 좌표를 기준으로 영상을 원하는 각도만큼 회전하는 변환

 

 

 

Mat getRotationMatrix2D(Point2f center, double angle, double scale);
  • center: 회전 중심 좌표
  • angle: 회전 각도. 양수는 반시계 방향. 음수는 시계 방향을 의미
  • scale; 회전 후 추가적으로 확대 또는 축소할 비율. 크기를 그대로 유지하려면 1을 지정
  • 반환값: 2x3 어파인 변환 행렬 (CV_64F0

 

 

void affine_rotation()
{
	Mat src = imread("tekapo.bmp");

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

	Point2f cp(src.cols / 2.f, src.rows / 2.f);
	Mat M = getRotationMatrix2D(cp, 20, 1);

	Mat dst;
	warpAffine(src, dst, M, Size());

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

	waitKey();
	destroyAllWindows();
}
  • 3행: tekapo.bmp 호수 영상을 3채널 컬러 영상으로 불러와 src 변수에 저장
  • 10행: 영상의 중심 좌표를 가리키는 변수 cp를 선언
  • 11행: cp 좌표를 기준으로 반시계 방향으로 20도 회전하는 변환 행렬 M을 생성
  • 13~14행: 변환 행렬 M을 이용하여 src 영상을 어파인 변환하고,  그 결과를 dst에 저장. dst 영상 크기는 src와 동일

 

 

 

 

void rotate(InputArray src, OutputArray dst, int rotateCode);
  • src: 입력 행렬
  • dst: 출력 행렬
  • rotateCode: 회전 각도 지정 플래그. ROTATE_90_CLOCKWISE, ROTATE_180, ROTATE_90_COUNTERCLOCKWISE 세 개의 상수 중 하나를 지정

 

 

 

 

 

8.1.6 대칭 변환

 

 

 

void flip(InputArray src, OutputArray dst, int flipCode);
  • src: 입력 영상
  • dst: 결과 영상 src와 같은 크기, 타입
  • flipCode: 대칭 방법 지정 플래그. flipCode가 양수이면 좌우 대칭, 0이면 상하 대칭, 음수이면 상하 대칭과 좌우 대칭을 모두 수행한다. 

 

void affine_flip()
{
	Mat src = imread("eastsea.bmp");

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

	imshow("src", src);

	Mat dst;
	int flipCode[] = { 1, 0, -1 };
	for (int i = 0; i < 3; i++) {
		flip(src, dst, flipCode[i]);

		String desc = format("flipCode: %d", flipCode[i]);
		putText(dst, desc, Point(10, 30), FONT_HERSHEY_SIMPLEX, 1.0, Scalar(255, 0, 0), 1, LINE_AA);

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

	destroyAllWindows();
}
  • 13행: flip() 함수에 전달할 flipCode 세 개를 정수형 배열에 저장
  • 15행: flipCode 배열에 저장된 정수 값을 이용하여 대칭 변환을 수행
  • 17~18행: 대칭 변환 결과 영상 위에 flipCode 값을 출력

 

 

 

8.2 투시 변환

 

 

투시 변환 (perspective transform): 어파인 변환보다 자유도가 높다. 직사각형 형태의 영상을 임의의 볼록 사각 형태로 변경할 수 있는 변환. 직선성이 유지되지만 두 직선 사이의 평행 관계는 깨어질 수 있다. 

 

Mat getPerspectiveTransform (const Point2f src[], const Point2f dst[], int solveMethod = DOCOMP_LU);
Mat getPerspectiveTransform (InputArray src, InputArray dst, int solveMethod = DECOMP_LU);
  • src: 입력 영상에서 네 점의 좌표
  • dst: 결과 영상에서 네 점의 좌표
  • solveMethod: 계산 방법 지정 DecompTypes 열거형 상수 중 하나를 지정
  • 반환값: 3x3 크기의 투시 변환 행렬

 

 

void warpPerspecitve(InputArray src, OutputArray dst, InputArray M, Size dsize, int flages = INTER_LINEAR,
				int borderMode = BORDER_CONSTANT, const Scalar& borderValue = Scalar());
  • src: 입력 영상
  • dst: 결과 영상. src와 같은 타입. 크기는 dsize
  • M: 3x3 투시 변환 행렬
  • dsize: 결과 영상 크기
  • flags: 보간법 알고리즘. 만약 OR 연산자를 이용하여 WARP_INVERSE_MAP 플래그를 함께 지정하면 역방향으로 변환을 수행
  • borderMode: 가장자리 픽셀 확장 방식. BorderTypes 열거형 상수 중 하나를 지정. 만약 BORDER_TRANSPARENT 를 지정하면 입력 영상의 픽셀 값이 복사되지 않는 영역은 dst 픽셀 값을 그대로 유지
  • borderValue: borderMode가 BORDER_CONSTANT 일 때 사용할 상수 값. 기본값으로 검은색이 지정됨

 

#include "opencv2/opencv.hpp"
#include <iostream>

using namespace cv;
using namespace std;

Mat src;
Point2f srcPts[4], dstPts[4];

void on_mouse(int event, int x, int y, int flags, void* userdata);

int main()
{
	src = imread("card.bmp");

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

	namedWindow("src");
	setMouseCallback("src", on_mouse);

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

	return 0;
}

void on_mouse(int event, int x, int y, int flags, void*)
{
	static int cnt = 0;

	if (event == EVENT_LBUTTONDOWN) {
		if (cnt < 4) {
			srcPts[cnt++] = Point2f(x, y);

			circle(src, Point(x, y), 5, Scalar(0, 0, 255), -1);
			imshow("src", src);

			if (cnt == 4) {
				int w = 200, h = 300;

				dstPts[0] = Point2f(0, 0);
				dstPts[1] = Point2f(w - 1, 0);
				dstPts[2] = Point2f(w - 1, h - 1);
				dstPts[3] = Point2f(0, h - 1);

				Mat pers = getPerspectiveTransform(srcPts, dstPts);

				Mat dst;
				warpPerspective(src, dst, pers, Size(w, h));

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

 

  • 7행: 입력 영상을 저장할 변수 src를 전역 변수로 선언
  • 8행: 입력 영상과 출력 영상에서의 네 점 좌표를 저장할 srcQuad와 dstQuad 배열을 선언
  • 21~22행: "src" 창을 미리 생성한 후 "src" 창에 마우스 콜백 함수를 등록
  • 32행: cnt 는 마우스 왼쪽이 눌린 횟수를 저장하는 변수
  • 34행: 마우스 이벤트 중에서 마우스 왼쪽 버튼이 눌려지는 이벤트에 대해서만 처리
  • 36행: "src" 창에서 마우스 왼쪽 버튼을 눌려진 좌표를 srcQuad 배열에 저장. 그리고 cnt 값을 1만큼 증가
  • 38행: 마우스 왼쪽 버튼이 눌려진 위치에 반지름이 5인 빨간색 원을 그림
  • 41행: 마우스 왼쪽 버튼이 네 번 눌러지면 if문 블록을 수행
  • 42행: 투시 변환하여 만들 결과 영상의 가로와 세로 크기를 w와 h 변수에 저장
  • 44~47행: "src" 창에서 사용자가 마우스로 선택한 사각형 꼭지점이 이동할 결과 영상 좌표를 설정
  • 49행: 3x3 투시 변환 행렬을 pers 변수에 저장
  • 51~52행: 투시 변환을 수행하여 w x h 크기의 결과 영상 dst 를 생성

 

 

 

void perspectiveTransform(InputArray src, OutputArray dst, InputArray m);
  • src: 입력 행렬 또는 vector<Point2f> 점의 좌표를 다채널로 표현
  • dst: 출력 행렬 또는 vector<Point2f> 
  • m: 변환행렬 3x3 또는 4x4 실수형 행렬