[OpenCV] 8장 영상의 기하학적 변환
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 실수형 행렬