본문 바로가기

프로그래밍 강좌/C++ - OpenCV

[OpenCV][C++] 영상 회전(image rotation)하는 방법 - getRotationMatrix2D(), warpAffine()

이번에는 영상을 특정 각도로 회전시키는 방법에 대해 알아보겠습니다.

2D 회전 행렬은 아래와 같습니다.

OpenCV에서는 회전 중심을 선택하고 영상의 크기를 조절할 수 있는 함수를 제공합니다. OpenCV에서 제공하는 함수의 수식은 아래와 같습니다.

Cx와 Cy는 영상 회전 중심 좌표를 의미하며, s는 scale 값을 의미합니다.

위의 행렬을 계산해주는 함수가 바로 getRotationMatrix2D() 함수 입니다.

함수의 원형은 아래와 같습니다.

center
회전 중심 좌표
angle
회전 각도, degree 단위, (양수: 반시계 방향, 음수: 시계 방향)
scale
영상 스케일 조정 값
반환값
회전 행렬

 

이 행렬을 이용해서 영상을 회전시키기 위해서는 warpAffine() 수를 사용합니다. 이는 Affine 변환을 해주는 함수로 지난번에 Affine Geometry 설명 시 잠깐 언급한 적이 있습니다.

https://blog.naver.com/dorergiverny/223068116929

 

[C++] 직선(선분)의 방정식 표현 총정리 - line homogeneous coordinate 두 점 지나는

이번에는 직선 또는 선분을 나타내는 다양한 방법과 제가 추천하는 방법에 대해 정리해볼까 합니다. 직선 (...

blog.naver.com

 

 

그럼 scale, 회전 심지어는 선분간 각도가 변하는 찌그러짐까지 변환이 가능하다는 얘기입니다.

함수 원형은 아래와 같습니다.

src
입력 영상
dst
출력 영상(회전 변환 결과)
M
회전 (변환) 행렬
dsize
출력 영상의 사이즈
flags
interpolation 방법, 기본: INTER_LINEAR
borderMode
가장자리 확장 방법(채우는 방법), 기본: BORDER_CONSTANT
borderValue
가장자리 채우는 값

그럼 영상을 회전시켜 보도록 하겠습니다.

영상 회전하는 함수를 만들어볼텐데요.

1. 영상의 중앙(회전 중심) 좌표를 계산한다.

cv::Point2f center(src.cols / 2.0, src.rows / 2.0);
 

2. 회전 행렬을 만든다.

getRotationMatrix2D()를 이용하여 회전 중심과 각도를 입력하면 행렬이 나옵니다.

cv::Mat M = cv::getRotationMatrix2D(center, angle, 1.0);
 

3. 회전 영상을 생성한다.

warpAffine()을 이용하여 회전 영상을 생성합니다.

cv::Mat dst;
cv::warpAffine(src, dst, M, src.size());      
 

결과를 보겠습니다.

참고로 소스 영상은 OpenAI DALL-E2가 만들어준 저작권 없는 영상입니다. (저 영상 생성을 위해 결재까지 했어요 ㅋㅋ)

회전 각도에 30을 넣으니 아래와 같은 결과 영상이 나왔습니다. 양수값을 넣어주면 반시계 방향으로 회전하는게 증명이 되었습니다.

전체 소스는 아래와 같습니다.

cv::Mat ImageRotateInner(const cv::Mat src, double angle) {
    cv::Point2f center(src.cols / 2.0, src.rows / 2.0);

    cv::Mat M = cv::getRotationMatrix2D(center, angle, 1.0);

    cv::Mat dst;
    cv::warpAffine(src, dst, M, src.size());      

    return std::move(dst);
}

int main()
{
    cv::Mat src = cv::imread("../../images/girl.png", cv::IMREAD_UNCHANGED);
    cv::Mat src_rot = ImageRotateInner(src, 30);

    return 0;
}
 

그런데 뭔가 영상이 짤린거 같죠? warpAffine()에 원본 영상 사이즈를 넣어서 그렇습니다.

그럼 회전이 되어도 영상이 짤리지 않도록 하려면 어떻게 할까요?

cv::RotatedRect()를 이용해서 회전된 영상의 크기(bounding box)를 알아내면 됩니다.

회전된 영상 크기를 알아내려면 아래와 같이 하시면 됩니다.

cv::Rect bbox = cv::RotatedRect(center, src.size(), angle).boundingRect();
M.at<double>(0, 2) += bbox.width / 2.0 - center.x;
M.at<double>(1, 2) += bbox.height / 2.0 - center.y;
 

전체 소스는 아래와 같습니다.

cv::Mat ImageRotateOuter(const cv::Mat src, double angle) {
    cv::Point2d center(src.cols / 2.0, src.rows / 2.0);
    cv::Mat M = cv::getRotationMatrix2D(center, angle, 1.0);
    cv::Rect bbox = cv::RotatedRect(center, src.size(), angle).boundingRect();
    M.at<double>(0, 2) += bbox.width / 2.0 - center.x;
    M.at<double>(1, 2) += bbox.height / 2.0 - center.y;

    cv::Mat dst;
    cv::warpAffine(src, dst, M, bbox.size());
    return std::move(dst);
}
 

결과는 아래와 같습니다.

위 함수에서 변환 함수 회전축을 shift해주지 않으면 오른쪽 아래와 같은 영상이 얻어지니 주의하세요!!