I'd like to expand this topic, referring to this SO question and accepted answer given by the user Lars Schillingmann:
Rotate an image without cropping in OpenCV in C++
I already posted my doubts about the math/geometry underlying the adding of two "manual" offsets to the matrix "rot" which will be used as input for the warpAffine(...) function in the comments below the accepted answer (code of the answer reported below):
#include "opencv2/opencv.hpp"
int main()
{
cv::Mat src = cv::imread("im.png", CV_LOAD_IMAGE_UNCHANGED);
double angle = -45;
// get rotation matrix for rotating the image around its center
cv::Point2f center(src.cols/2.0, src.rows/2.0);
cv::Mat rot = cv::getRotationMatrix2D(center, angle, 1.0);
// determine bounding rectangle
cv::Rect bbox = cv::RotatedRect(center,src.size(), angle).boundingRect();
// adjust transformation matrix
rot.at<double>(0,2) += bbox.width/2.0 - center.x;
rot.at<double>(1,2) += bbox.height/2.0 - center.y;
cv::Mat dst;
cv::warpAffine(src, dst, rot, bbox.size());
cv::imwrite("rotated_im.png", dst);
return 0;
}
I have absolutely no doubt about the rotationMatrix obtained from cv::getRotationMatrix2D(...), which is obviously correct.
(start reference for others trying to understand the math "hidden" inside this function, you can skip this)
If you decompose the definition of the resulting matrix reported in opencv docs here you can see that it's the result of the following operations:
1) Translate original image by Tx = -rotationCenter.x and Ty= -rotationCenter.y (image is translated to get the given rotation center into position (0,0) ) (TransformationMatrix M1 - translation only)
2) Rotate the translated image by the given angle (and scale the image by the given factor) (TransformationMatrix M2 - rotation+scaling only)
3) Translate back the rotated image by Tx = +rotationCenter.x and Ty = +rotationCenter.y, thus inverting the translation done applying M1. (TransformationMatrix M3 - translation only)
Multiplying these 3 matrix in the given order ( M1*M2*M3 you obtain the same matrix you get from getRotationMatrix2D.
(end reference for others trying to understand the math "hidden" inside this function)
The accepted answer then finds the boundingRect() of the rotatedRect to get a destination image with the right dimensions for the rotated original image and then manually adds two offsets in the following two lines, which are the operations i am asking clarification about:
rot.at<double>(0,2) += bbox.width/2.0 - center.x;
rot.at<double>(1,2) += bbox.height/2.0 - center.y;
After asking the author of the accepted answer, i followed this link (also referred inside the accepted answer itself) to understand the underlying math. The explanation given in that blog seems ok to me, but the source code (Python example given in that blog post, i need C++ implementation) seems doing something a bit different from the C++ code from the accepted answer.
It first calculates the destination image dimensions and gives getRotationMatrix2d(...) the destination image center as rotation center point, then manually adds two offsets to the resulting rot_mat, but the offsets are calculated in a different way.
I ported that Python function to C++ (code listed below, my function also calculates inverted matrix for future use, ignore that part) and got a coherent result when executed. I also tested the code above and it seems to give a coherent result too.
Mat rotate_about_center(Mat src, float angle, float scale, Mat& invertMat) {
// math behind this transformation is explained here:
// http://john.freml.in/opencv-rotation (with python code example)
int w = src.cols;
int h = src.rows;
float rangle = angle * CV_PI/180.0;
// now calculate new image width and height
int nw = (abs(sin(rangle)*h) + abs(cos(rangle)*w))*scale;
int nh = (abs(cos(rangle)*h) + abs(sin(rangle)*w))*scale;
// ask OpenCV for the rotation matrix
Mat rot_mat = getRotationMatrix2D(Point(nw*0.5, nh*0.5), angle, scale);
cout <<"rot_mat:\n"<< rot_mat <<endl;
// calculate the move from the old center to the new center combined
// with the rotation
Mat m = Mat(Size(3,1), CV_64FC1);
m.at<double>(0,0)=(nw-w)*0.5;
m.at<double>(0,1)=(nh-h)*0.5;
m.at<double>(0,2)=0.0;
transpose(m,m);
cout <<"m:\n"<< m <<endl;
Mat rot_move = rot_mat * m;
// the move only affects the translation, so update the translation
// part of the transform
rot_mat.at<double>(0,2) += rot_move.at<double>(0,0);
rot_mat.at<double>(1,2) += rot_move.at<double>(0,1);
Mat dst;
warpAffine(src, dst , rot_mat, Size(nw, nh));
invertAffineTransform(rot_mat, invertMat);
return dst;
}
Could someone explain me if these two code snippets are equivalent? If they are equivalent, could you please give me an explanation about why are equivalent?