Introduction
Have you ever tried to rotate an image in OpenCV? It's damn simple, right?
- You include the OpenCV headers:
#include <cmath> #include <iostream> #include <algorithm> #include <opencv2/core.hpp> #include <opencv2/imgproc.hpp> #include <opencv2/highgui.hpp>
- You load the image:
Here's how mycv::Mat image = cv::imread("sria91.png");
image
looks: - You call
cv::rotate
. For example to rotate an image clockwise by 90 degrees:cv::Mat rotated_image = cv::rotate(image, cv::ROTATE_90_CLOCKWISE);
- And then you can display the image:
Here's how thecv::imshow("Output", output_image); cv::waitKey(10000);
output_image
looks:
But what if you wanted to rotate the image by an arbitrary angle say 23 degrees anti-clockwise? How would you go about that?
Naive approach
If you look at the OpenCV's official C++ tutorials you would be out of luck. But, if you dig deeper you might come across OpenCV's official Python tutorials and find this. Adapting the example on that page for rotation to C++ you could write the following:
cv::Mat input_image = cv::imread("sria91.png");
int rows = input_image.rows;
int cols = input_image.cols;
cv::Mat transformation_matrix = cv::getRotationMatrix2D(cv::Point2f((cols-1)/2.0, (rows-1)/2.0), 23, 1);
cv::Mat output_image;
cv::warpAffine(input_image, output_image, transformation_matrix, cv::Size(cols, rows));
cv::imshow("Output", output_image);
cv::waitKey(10000);
The result looks like this:
You would notice that something is not right. The output image is cropped from all around. How would you get an image that is rotated and also not cropped? I've got you covered. Follow along to find out.
The correct approach
This approach is based on the first principles.
- The first thing you have to do is to construct the rotation matrix.
double angle = 23; // in degrees double angle_in_radians = angle * M_PI / 180; cv::Mat rotation_matrix = cv::Mat::eye(3, 3, CV_64F); rotation_matrix.at<double>(0, 0) = cos(angle_in_radians); rotation_matrix.at<double>(0, 1) = -sin(angle_in_radians); rotation_matrix.at<double>(1, 0) = sin(angle_in_radians); rotation_matrix.at<double>(1, 1) = cos(angle_in_radians);
- Then you construct the translation matrix.
cv::Mat translation_matrix = cv::Mat::eye(3, 3, CV_64F); translation_matrix.at<double>(0, 2) = - rows / 2; translation_matrix.at<double>(1, 2) = - cols / 2;
- Collect the coordinates for corners of the
image
.cv::Mat corners = cv::Mat::ones(3, 4, CV_64F); corners.at<double>(0, 0) = 0; corners.at<double>(1, 0) = 0; corners.at<double>(0, 1) = 0; corners.at<double>(1, 1) = cols - 1; corners.at<double>(0, 2) = rows - 1; corners.at<double>(1, 2) = cols - 1; corners.at<double>(0, 3) = rows - 1; corners.at<double>(1, 3) = 0;
- Find the coordinates of the corners of the
image
when its center is translated to the origin and rotated byangle
.cv::Mat new_corners = (rotation_matrix * translation_matrix) * corners;
- Calculate the rows and columns for the output image.
double *p_new_corners; p_new_corners = new_corners.row(0).ptr<double>(); int max_rows = *max_element(p_new_corners, p_new_corners+4); int min_rows = *min_element(p_new_corners, p_new_corners+4); p_new_corners = new_corners.row(1).ptr<double>(); int max_cols = *max_element(p_new_corners, p_new_corners+4); int min_cols = *min_element(p_new_corners, p_new_corners+4); int new_rows = max_rows - min_rows + 1; int new_cols = max_cols - min_cols + 1;
- Construct the
new_translation_matrix
to translate the image's center from the origin.cv::Mat new_translation_matrix = cv::Mat::eye(3, 3, CV_64F); new_translation_matrix.at<double>(0, 2) = new_rows / 2; new_translation_matrix.at<double>(1, 2) = new_cols / 2;
- Compute the
inverse_transformation_matrix
to map the coordinates fromoutput_image
toinput_image
.cv::Mat inverse_transformation_matrix = (new_translation_matrix * rotation_matrix * translation_matrix).inv();
- Now you allocate the memory for the
output_image
.cv::Mat output_image(new_rows, new_cols, input_image.type());
- Finally rotate and display the image.
And the result looks like this:cv::Mat new_point = cv::Mat::ones(3, 1, CV_64F); for (int new_row=0; new_row<new_rows; new_row++) { for (int new_col=0; new_col<new_cols; new_col++) { new_point.at<double>(0, 0) = new_row; new_point.at<double>(1, 0) = new_col; cv::Mat point = inverse_transformation_matrix * new_point; int row = point.at<double>(0, 0); int col = point.at<double>(1, 0); if (row >= 0 && row < rows && col >= 0 && col < cols) { output_image.at<cv::Vec3b>(cv::Point(new_col, new_row)) = input_image.at<cv::Vec3b>(cv::Point(col, row)); } } } cv::imshow("Output", input_image); cv::waitKey(10000);