관리 메뉴

공부 기록장 💻

[OpenCV/C++] 영상의 기하학적 변환 3 - 투시 변환 (Perspective Transform of Image) 본문

# Tech Studies/Computer Vision • OpenCV

[OpenCV/C++] 영상의 기하학적 변환 3 - 투시 변환 (Perspective Transform of Image)

dream_for 2022. 12. 2. 01:05

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

영상의 투시 변환


영상의 기하학적 변환 중, 어파인 변환보다 자유도가 높은 투시 변환이 있다.
투시 변환 (Perspective Transform) 은 직사각형 형태의 영상을 임의의 블록 사각형 형태로 변경할 수 있는 변환이다. 원본 영상에 있던 직선은 투시 변환에 의해 결과 영상에서 그대로 직진성이 유지되지만, 두 직선의 평행 관계는 깨어질 수 있다.

투시 변환은 보통 3x3 크기의 실수 행렬로 표현하며, 8개의 파라미터로 표현할 수 있지만, 좌표 계산의 편의상 9개의 원소를 갖는 3x3 행렬을 사용한다. 투시 변환을 표현하는 행렬을 Mp라고 하면, 입력 영상의 픽셀 좌표 (x,y)가 행렬 Mp에 의해 이동하는 결과 영상 픽셀 좌표 (x',y')는 다음과 같이 계산된다


위의 행렬 수식에서 입력 좌표와 출력 좌표를 (x,y,1), (wx', wy', w) 형태로 표현한 것을 동차 좌표계(homogenous coordinates)라고 하며, 좌표 계산의 편의를 위해 사용하는 방식이다.
여기서 w는 결과 영상의 좌표를 표현할 때 사용되는 비례 상수이다. 결국 x' y'는 다음과 같이 구할 수 있다.



getPerspectiveTransform(), warpPerspective() 함수


OpenCV에서 제공하는 두 함수, 투시 변환 행렬을 구하는 함수 getPerspectiveTransform()과 실제 영상을 투시 변환하는 함수 warpPerspective() 를 통해 투시 변환을 할 수 있다.
이는 입력 영상에서의 네 점의 좌표와 이 점들이 이동한 결과 영상의 좌표 4개를 입력으로 받아 3x3 투시 변환 행렬을 계산하여 투시 변환 영상을 생성한다.

즉, 어파인 변환과 유사한 방식으로, 어파인 변환에서는 2x3 어파인 변환 행렬을 사용했다면 투시 변환의 경우 3x3 투시 변환 행렬을 적용하여 투시 변환된 결과 영상을 생성하게 되는 것이다.

getPerpsectiveTransform() 함수의 원형은 다음과 같다.

3x3 크기의 투시 변환 행렬을 구하는 getPerspectiveTransform() 함수

src에 저장된 네 점을 dst의 좌표의 점으로 옮기는 투시 변환 행렬을 반환한다.
반환하는 Mat 객체는 CV_64FC1 타입을 사용하는 3x3 크기의 투시 변환 행렬이다.

3x3 투시 변환 행렬을 통해 투시 변환한 결과 영상을 생성하는 warpPerspective() 함수


warpPerspective() 함수는 투시 변환 행렬 M을 이용해 결과 영상 dst 를 생성한다.



네 점의 이동 관계로부터 투시 변환 행렬을 구하고, 이를 이용해 실제 영상을 투시 변환하는 예제를 살펴보자.
card, bmp 트럼프 카드 영상에서 사용자가 마우스로 카드 모서리 네 좌표를 선택하면, 해당 카드를 반듯한 직사각형 형태로 투시 변환하여 화면에 출력하는 예제이다.


우선 전체 코드는 다음과 같다.

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

using namespace cv;
using namespace std;

// 투시 변환 예제

Mat src;
Point2f srcQuad[4], dstQuad[4]; // 입력, 출력 영상에서의 네 점 좌표를 저장할 배열 선언

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

int main() {
	src = imread("card.bmp"); // 입력 영상: card 이미지

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

	namedWindow("src");
	setMouseCallback("src", on_mouse); // src창에 대해 마우스 콜백 함수 등록

	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) { // 총 4번 수행
			srcQuad[cnt++] = Point2f(x, y);

			circle(src, Point(x, y), 5, Scalar(0, 0, 255), -1); // 버튼 눌린 곳에 원 그리기
			imshow("src", src);

			// Point 4개 찍고 난 후
			if (cnt == 4) {
				int w = 200, h = 300; // 투시 변환하여 만들 결과 영상의 가로, 세로 크기 지정
				// 투시 변환 결과 영상(직사각형)의 네 꼭짓점
				dstQuad[0] = Point2f(0, 0);
				dstQuad[1] = Point2f(w - 1, 0);
				dstQuad[2] = Point2f(w - 1, h - 1);
				dstQuad[3] = Point2f(0, h - 1);

				Mat pers = getPerspectiveTransform(srcQuad, dstQuad); // 3x3 투시 변환 행렬을 pers에 저장

				Mat dst;
				warpPerspective(src, dst, pers, Size(w, h)); // 투시 변환 후 결과 영상 생성

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


결과는 다음과 같다.


트럼프 카드 4장이 책상 위에 놓여져 있는 src 영상에서 왼쪽 마우스를 클릭하여 한 카드의 네 꼭짓점 좌표에 점을 찍게 되면, 해당 카드가 반듯한 직사각형 형태로 투시 변환된 dst 결과 영상이 나타나는 것을 확인할 수 있다.



마우스 콜백 함수를 조금 더 자세하게 들여다 보면서 투시 변환 행렬을 도출하고 투시 변환한 결과 영상을 생성하는 부분을 이해해보자.

event는 EVENT_LBUTTONDOWN, 왼쪽 마우스가 클릭되었을 때 처리가 된다.
왼쪽 마우스 클릭 이벤트 처리를 총 4번 수행하게 되는데,
srcQuad 배열에는 각각 src 영상 위에 클릭한 위치의 좌표가 저장이 되며, 반지름이 5인 빨간색 원이 영상 위에 그려진다.

그리고 Point를 모두 4개 찍고 난 후에는, 투시 변환 행렬을 만들기 위해 필요한 결과 영상의 좌표 4개에 대한 dstQuad 배열에 값을 지정하게 된다.
본 예제에서는 가로는 200, 세로는 300인 크기의 직사각형의 네 꼭짓점 좌표를 설정하였다.

getPerspectiveTransform() 함수를 실행하여 pers Mat 객체에 3x3 투시 변환 행렬을 저장한 뒤,
warPerspective() 함수를 호출하여 pers 행렬을 이용하여 투시 변환 결과 영상을 dst에 저장하게 된다.

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

	// 왼쪽 버튼 누를 떄 발생하는 이벤트 처리
	if (event == EVENT_LBUTTONDOWN) {
		if (cnt < 4) { // 총 4번 수행
			srcQuad[cnt++] = Point2f(x, y);

			circle(src, Point(x, y), 5, Scalar(0, 0, 255), -1); // 버튼 눌린 곳에 원 그리기
			imshow("src", src);

			// Point 4개 찍고 난 후
			if (cnt == 4) {
				int w = 200, h = 300; // 투시 변환하여 만들 결과 영상의 가로, 세로 크기 지정
				// 투시 변환 결과 영상(직사각형)의 네 꼭짓점
				dstQuad[0] = Point2f(0, 0);
				dstQuad[1] = Point2f(w - 1, 0);
				dstQuad[2] = Point2f(w - 1, h - 1);
				dstQuad[3] = Point2f(0, h - 1);

				Mat pers = getPerspectiveTransform(srcQuad, dstQuad); // 3x3 투시 변환 행렬을 pers에 저장

				Mat dst;
				warpPerspective(src, dst, pers, Size(w, h)); // 투시 변환 후 결과 영상 생성

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

728x90
반응형
Comments