관리 메뉴

공부 기록장 💻

[OpenCV/C++] 영상의 기하학적 변환 1 - 어파인 변환의 원리 (Geomtetric Transformation of Image - Affine Transformation) 본문

# Tech Studies/Computer Vision • OpenCV

[OpenCV/C++] 영상의 기하학적 변환 1 - 어파인 변환의 원리 (Geomtetric Transformation of Image - Affine Transformation)

dream_for 2022. 12. 1. 23:28

OpenCV 4로 배우는 컴퓨터 비전과 머신 러닝 CH8. 영상의 기하학적 변환 정리

 

 

영상의 기하학적 변환

 

영상의 기하학적 변환 (Geometric Transform) 은 영상을 구성하는 픽셀의 배치 구조를 변경함으로써 전체 영상의 모양을 바꾸는 작업이다. 앞서 배운 영상의 밝기와 명암비 조절, 필터링 등은 픽셀 위치가 고정된 상태에서 값이 변경되었던 반면, 기하학적 변환은 픽셀 값은 그대로 유지하면서 위치를 변경하는 작업이다.

 

입력 영상에서 (x,y) 좌표의 픽셀을 결과 영상의 (x', y') 좌표로 변환하는 방법은 다음과 같이 고유의 함수 형태로 나타낼 수 있다.

 

1. 어파인 변환

 

영상의 기하학적 변환 중에서 어파인 변환 (Affine Transform) 이란 영상을 평행 이동시키거나 회전, 크기 변환 등을 통해 만들 수 있는 변환을 통칭한다. 또는 엿앙을 한쪽 방향으로 밀어서 만든 것 같은 전단 변환도 어파인 변환에 포함된다.

 

아래 그림을 통해 원본 영상 (a)에 대한 어파인 변환을 이해해보면,

(b)는 입력 영상을 x, y축 방향으로 일정 크기만큼 평행 이동시킨 결과,

(c)는 y좌표가 증가함에 따라 x축 시작 위치를 조금씩 오른쪽으로 이동한 전단 변환 결과,

(d)는 입력 영상을 가로 방향으로는 축소, 세로 방향으로는 확대한 크기 변환 결과,

(e)는 영상 원점을 기준으로 시계 방향으로 10도 회전한 결과,

(f)는 앞서 설명한 이동, 크기, 회전, 전단 변환을 복합적으로 적용하여 만든 어파인 변환 결과 이다.

 

 

 

어파인 변환은 모두 6개의 파라미터를 이용한 수식으로 정의할 수 있는데, 입력 영상의 좌표 (x,y)가 (x', y')로 이동하는 수식은 다음과 같이 1차 다항식으로 표현된다.

위 수식에서 a,b,c,d,e,f, 가 어파인 변환을 결정하는 6개의 파라미터로, 두 수식으로 표현된 어파인 변환은 행렬을 이용해 하나의 수식으로 표현할 수 있다.

 

즉, 입력 영상의 좌표를 나타내는 행렬 앞에 2x2 행렬을 곱하고, 그 뒤에 2x1 행렬을 더하는 형태로 어파인 변환을 표현한다.

수학적 편의를 위해 입력 영상의 좌표 (x,y)에 가상의 좌표 1을 하나 추가하여 (x,y,1)의 형태로 바꾸면, 위 행렬 수식을 다음과 같이 하나의 행렬 곱셈 형태로 바꿀 수 있다.

위 수식에서 6개의 파라미터로 구성된 2x3 행렬을 어파인 변환 행렬 (Affine Transformation Matrix)라고 부른다. 즉, 어파인 변환은 2x3 실수형 행렬 하나로 표현할 수 있다.

 

따라서 입력 영상과 어파인 변환 결과 영상으로부터 어파인 변환 행렬을 구하기 위해서는 최소 세 점의 이동 관계를 알아야 한다. 점 하나의 이동 관계로부터 x, y좌표에 대한 변환 수식 2개를 얻을 수 있으므로, 점 3개의 이동 관계로부터 총 6개의 방정식을 구할 수 있다. 따라서 점 3개의 이동관계를 알고 있다면 6개의 원소로 정의되는 어파인 변환 행렬을 구할 수 있다는 의미이다.

 

 

아래 그림을 보면, 점 3개의 이동 관계에 의해 결정되는 어파인 변환을 보여준다.

왼쪽 직사각형 꼭지 점 3개를 빨강, 보라, 초록색으로 표시하였고, 이 점들이 이동한 위치를 오른쪽 그림에 같은 색상으로 나타냈다. 어파인 변환에 의해 직사각형 영상이 평행사변형 형태로 변환될 수 있기 때문에 입력 영상의 좌측 하단 모서리 점이 이동하는 위치는 자동으로 결정된다.

세개의 점을 이용해 2x3 어파인 변환 행렬을 이용해 어파인 변환 수행

따라서 어파인 변환은 점 3개의 이동 관계만으로 정의 가능하다.

 

 

getAffineTransform(), warpAffine()

 

OpenCV는 어파인 변환 행렬을 구하는 함수와 어파인 변환 행렬을 이용해 실제 영상을 어파인 변환하는 함수를 모두 제공한다. 어파인 변환 행렬을 구하는 함수는 getAffineTransform() 으로, 입력 영상에서 세 점의 좌표와 이 점들이 이동한 결과 영상의 좌표 3개를 입력 받아 2x3 어파인 변환 행렬을 계산한다.

 

입력 영상의 세 점, 결과 영상의 이동한 세 점을 입력 받아 2x3 어파인 변환 행렬을 계산하는 getAffineTransform() 함수

 

세 점의 좌표를 담는 src와 dst 배열에는 크기가 3인 Point2f 배열, 또는 vector<Point2f> 자료형 변수를 사용해도 된다.

함수가 반환하는 Mat 객체는 CV_64FC1 타입을 사용하는 2x3크기의 어파인 변환 행렬이다.

 

위에서 구한 2x3 어파인 변환 행렬을 통해 어파인 변환 결과 영상을 생성하려면 warAffine() 함수를 사용해야 한다.

 

2x3 크기의 어파인 변환 행렬 M은 CV_32FC1 또는 CV_64FC1 타입이어야 한다.

어파인 결과 영상의 크기 dsize는 어파인 변환 성격에 따라 사용자가 적절하게 지정해야 하며, Size()를 전달하는 경우 입력 영상과 같은 크기의 결과 영상을 생성한다.

 

이제 세 점의 이동 관계로부터 어파인 변환 행렬을 구하고, 이를 이용해 실제 영상을 어파인 변환하는 예제를 살펴보자.

 

우선 입력 영상의 세 점의 좌표 배열과, 임의로 지정한 결과 영상의 세 점의 좌표 배열을 다음과 같이 지정해보자.

 

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

 

srcPts[0]은 입력 영상의 좌측 상단 꼭지점 좌표로, (0,0) 이다. dstPts[0,0]은 (50,50)으로 지정되어 (50,50)만큼 이동함을 나타낸다.

srcPts[1]은 (src.cols-1, 0) 좌표로 우측 상단의 꼭지점 좌표를 나타낸다. dstPts[1]은 (src.cols-100, 100)으로 우측 상단 꼭지점이 좌측으로 100만큼, 아래로 100만큼 이동이 됨을 알 수 있다.

srcPts[2]는 (src.cols-1, src.rows-1) 좌표로 우측 하단의 꼭지점 좌표를 나타낸다. dstPts[2]는 (src.cols-50, src.rows-50)으로 좌측 상단이 (50,50) 만큼 이동한 것과 동일한 양상이 나타난다.

 

결국, 세 점의 좌표 이동을 통해 직사각형이 평행 사변형으로 어파인 변환이 일어난다는 것을 확인할 수 있다.

자세한 예제 코드는 아래를 살펴보자.

 

// 영상의 어파인 변환
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);

	// 2x3 어파인 변환 행렬을 M에 저장
	Mat M = getAffineTransform(srcPts, dstPts); // 총 6개의 파라미터, 3개의 점를 전달해야 함

	Mat dst;
	warpAffine(src, dst, M, Size()); // 어파인 변환 결과 영상 생성

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

 

 

 

 

어파인 변환 행렬에 사용된 세 꼭짓점을 다시 살펴보자.

입력 영상과 결과 영상에 대응되는 좌표를 나타내면 다음과 같다.

 

 

transform() 함수

 

참고로 어파인 변환 행렬을 가지고 있을 때, 영상 전체를 어파인 변환하는 것이 아니라 일부 점들이 어느 위치로 이동하는지 알고 싶은 경우 transform() 함수를 사용할 수 있다.

 

3개의 좌표를 정의하여 srcPoints 벡터에 담고,

transform() 함수를 이용해 어파인 변환 행렬 M을 3번째 인자로 주어 transform() 함수를 호출한 뒤,

src와 dst 영상에 대해 srcPoints와 변환된 결과 영상에 각각 대응하는 dstPoints에 빨간색 원을 그려보자.

 

	vector<Point2f> srcPoints = { Point2f(100,20),Point2f(200,50), Point2f(500,40) };
	vector<Point2f> dstPoints;
	
	transform(srcPoints, dstPoints, M);
	
	cout << "srcPoints -> dstPoints " << endl;
	for (int i = 0;i < dstPoints.size();i++) {
		cout << srcPoints[i] << " -> " << dstPoints[i] << endl;
		circle(src, srcPoints[i], 5, Scalar(0, 0, 255));
		circle(dst, dstPoints[i], 5, Scalar(0, 0, 255));
	}

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

 

우선 세 점 srcPoints와 각각에 대응하는 결과 영상의 dstPoints를 출력하면 다음과 같다.

 

 

src와 dst 영상 위에 세 점에 대해 각각 빨간색 작은 원이 그려짐을 통해

어파인 변환에 의해 각 점이 이동한 것을 확인할 수 있다.

 

 

 

 

728x90
반응형
Comments