Rotate an image without cropping in OpenCV in C++

C++Opencv

C++ Problem Overview


I'd like to rotate an image, but I can't obtain the rotated image without cropping

My original image:

enter image description here

Now I use this code:

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

// Compile with g++ code.cpp -lopencv_core -lopencv_highgui -lopencv_imgproc

int main()
{
	cv::Mat src = cv::imread("im.png", CV_LOAD_IMAGE_UNCHANGED);
	cv::Mat dst;

    cv::Point2f pc(src.cols/2., src.rows/2.);
    cv::Mat r = cv::getRotationMatrix2D(pc, -45, 1.0);

    cv::warpAffine(src, dst, r, src.size()); // what size I should use?

    cv::imwrite("rotated_im.png", dst);

	return 0;
}

And obtain the following image:

enter image description here

But I'd like to obtain this:

enter image description here

C++ Solutions


Solution 1 - C++

My answer is inspired by the following posts / blog entries:

Main ideas:

  • Adjusting the rotation matrix by adding a translation to the new image center
  • Using cv::RotatedRect to rely on existing opencv functionality as much as possible

Code tested with opencv 3.4.1:

#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 in pixel coordinates
    cv::Point2f center((src.cols-1)/2.0, (src.rows-1)/2.0);
    cv::Mat rot = cv::getRotationMatrix2D(center, angle, 1.0);
    // determine bounding rectangle, center not relevant
    cv::Rect2f bbox = cv::RotatedRect(cv::Point2f(), src.size(), angle).boundingRect2f();
    // adjust transformation matrix
    rot.at<double>(0,2) += bbox.width/2.0 - src.cols/2.0;
    rot.at<double>(1,2) += bbox.height/2.0 - src.rows/2.0;

    cv::Mat dst;
    cv::warpAffine(src, dst, rot, bbox.size());
    cv::imwrite("rotated_im.png", dst);

    return 0;
}

Solution 2 - C++

Just try the code below, the idea is simple:

  1. You need to create a blank image with the maximum size you're expecting while rotating at any angle. Here you should use Pythagoras as mentioned in the above comments.

  2. Now copy the source image to the newly created image and pass it to warpAffine. Here you should use the centre of newly created image for rotation.

  3. After warpAffine if you need to crop exact image for this translate four corners of source image in enlarged image using rotation matrix as described here

  4. Find minimum x and minimum y for top corner, and maximum x and maximum y for bottom corner from the above result to crop image.

This is the code:

int theta = 0;
Mat src,frame, frameRotated;
src = imread("rotate.png",1);
cout<<endl<<endl<<"Press '+' to rotate anti-clockwise and '-' for clockwise 's' to save" <<endl<<endl;

int diagonal = (int)sqrt(src.cols*src.cols+src.rows*src.rows);
int newWidth = diagonal;
int newHeight =diagonal;

int offsetX = (newWidth - src.cols) / 2;
int offsetY = (newHeight - src.rows) / 2;
Mat targetMat(newWidth, newHeight, src.type());
Point2f src_center(targetMat.cols/2.0F, targetMat.rows/2.0F);


while(1){
src.copyTo(frame);
double radians = theta * M_PI / 180.0;
double sin = abs(std::sin(radians));
double cos = abs(std::cos(radians));

frame.copyTo(targetMat.rowRange(offsetY, offsetY + frame.rows).colRange(offsetX, offsetX + frame.cols));
Mat rot_mat = getRotationMatrix2D(src_center, theta, 1.0);
warpAffine(targetMat, frameRotated, rot_mat, targetMat.size());
 //Calculate bounding rect and for exact image
 //Reference:- https://stackoverflow.com/questions/19830477/find-the-bounding-rectangle-of-rotated-rectangle/19830964?noredirect=1#19830964
    Rect bound_Rect(frame.cols,frame.rows,0,0);

    int x1 = offsetX;
    int x2 = offsetX+frame.cols;
    int x3 = offsetX;
    int x4 = offsetX+frame.cols;

    int y1 = offsetY;
    int y2 = offsetY;
    int y3 = offsetY+frame.rows;
    int y4 = offsetY+frame.rows;

    Mat co_Ordinate = (Mat_<double>(3,4) << x1, x2, x3, x4,
                                            y1, y2, y3, y4,
                                            1,  1,  1,  1 );
    Mat RotCo_Ordinate = rot_mat * co_Ordinate;

    for(int i=0;i<4;i++){
       if(RotCo_Ordinate.at<double>(0,i)<bound_Rect.x)
         bound_Rect.x=(int)RotCo_Ordinate.at<double>(0,i); //access smallest 
       if(RotCo_Ordinate.at<double>(1,i)<bound_Rect.y)
        bound_Rect.y=RotCo_Ordinate.at<double>(1,i); //access smallest y
     }

     for(int i=0;i<4;i++){
       if(RotCo_Ordinate.at<double>(0,i)>bound_Rect.width)
         bound_Rect.width=(int)RotCo_Ordinate.at<double>(0,i); //access largest x
       if(RotCo_Ordinate.at<double>(1,i)>bound_Rect.height)
        bound_Rect.height=RotCo_Ordinate.at<double>(1,i); //access largest y
     }

    bound_Rect.width=bound_Rect.width-bound_Rect.x;
    bound_Rect.height=bound_Rect.height-bound_Rect.y;

    Mat cropedResult;
    Mat ROI = frameRotated(bound_Rect);
    ROI.copyTo(cropedResult);

    imshow("Result", cropedResult);
    imshow("frame", frame);
    imshow("rotated frame", frameRotated);
    char k=waitKey();
    if(k=='+') theta+=10;
    if(k=='-') theta-=10;
    if(k=='s') imwrite("rotated.jpg",cropedResult);
    if(k==27) break;

}

enter image description here

Cropped Image

enter image description here enter image description here

Solution 3 - C++

Thanks Robula! Actually, you do not need to compute sine and cosine twice.

import cv2

def rotate_image(mat, angle):
  # angle in degrees

  height, width = mat.shape[:2]
  image_center = (width/2, height/2)

  rotation_mat = cv2.getRotationMatrix2D(image_center, angle, 1.)

  abs_cos = abs(rotation_mat[0,0])
  abs_sin = abs(rotation_mat[0,1])

  bound_w = int(height * abs_sin + width * abs_cos)
  bound_h = int(height * abs_cos + width * abs_sin)

  rotation_mat[0, 2] += bound_w/2 - image_center[0]
  rotation_mat[1, 2] += bound_h/2 - image_center[1]

  rotated_mat = cv2.warpAffine(mat, rotation_mat, (bound_w, bound_h))
  return rotated_mat

Solution 4 - C++

Thanks @Haris! Here's the Python version:

def rotate_image(image, angle):
  '''Rotate image "angle" degrees.

  How it works:
    - Creates a blank image that fits any rotation of the image. To achieve
      this, set the height and width to be the image's diagonal.
    - Copy the original image to the center of this blank image
    - Rotate using warpAffine, using the newly created image's center
      (the enlarged blank image center)
    - Translate the four corners of the source image in the enlarged image
      using homogenous multiplication of the rotation matrix.
    - Crop the image according to these transformed corners
  '''

  diagonal = int(math.sqrt(pow(image.shape[0], 2) + pow(image.shape[1], 2)))
  offset_x = (diagonal - image.shape[0])/2
  offset_y = (diagonal - image.shape[1])/2
  dst_image = np.zeros((diagonal, diagonal, 3), dtype='uint8')
  image_center = (diagonal/2, diagonal/2)

  R = cv2.getRotationMatrix2D(image_center, angle, 1.0)
  dst_image[offset_x:(offset_x + image.shape[0]), \
            offset_y:(offset_y + image.shape[1]), \
            :] = image
  dst_image = cv2.warpAffine(dst_image, R, (diagonal, diagonal), flags=cv2.INTER_LINEAR)

  # Calculate the rotated bounding rect
  x0 = offset_x
  x1 = offset_x + image.shape[0]
  x2 = offset_x
  x3 = offset_x + image.shape[0]

  y0 = offset_y
  y1 = offset_y
  y2 = offset_y + image.shape[1]
  y3 = offset_y + image.shape[1]

  corners = np.zeros((3,4))
  corners[0,0] = x0
  corners[0,1] = x1
  corners[0,2] = x2
  corners[0,3] = x3
  corners[1,0] = y0
  corners[1,1] = y1
  corners[1,2] = y2
  corners[1,3] = y3
  corners[2:] = 1

  c = np.dot(R, corners)

  x = int(c[0,0])
  y = int(c[1,0])
  left = x
  right = x
  up = y
  down = y

  for i in range(4):
    x = int(c[0,i])
    y = int(c[1,i])
    if (x < left): left = x
    if (x > right): right = x
    if (y < up): up = y
    if (y > down): down = y
  h = down - up
  w = right - left

  cropped = np.zeros((w, h, 3), dtype='uint8')
  cropped[:, :, :] = dst_image[left:(left+w), up:(up+h), :]
  return cropped

Solution 5 - C++

After searching around for a clean and easy to understand solution and reading through the answers above trying to understand them, I eventually came up with a solution using trigonometry.

I hope this helps somebody :)

import cv2
import math

def rotate_image(mat, angle):
    height, width = mat.shape[:2]
    image_center = (width / 2, height / 2)

    rotation_mat = cv2.getRotationMatrix2D(image_center, angle, 1)

    radians = math.radians(angle)
    sin = math.sin(radians)
    cos = math.cos(radians)
    bound_w = int((height * abs(sin)) + (width * abs(cos)))
    bound_h = int((height * abs(cos)) + (width * abs(sin)))

    rotation_mat[0, 2] += ((bound_w / 2) - image_center[0])
    rotation_mat[1, 2] += ((bound_h / 2) - image_center[1])

    rotated_mat = cv2.warpAffine(mat, rotation_mat, (bound_w, bound_h))
    return rotated_mat

EDIT: Please refer to @Remi Cuingnet's answer below.

Solution 6 - C++

Increase the image canvas (equally from the center without changing the image size) so that it can fit the image after rotation, then apply warpAffine:

Mat img = imread ("/path/to/image", 1);
double offsetX, offsetY;
double angle = -45;
double width = img.size().width;
double height = img.size().height;
Point2d center = Point2d (width / 2, height / 2);
Rect bounds = RotatedRect (center, img.size(), angle).boundingRect();
Mat resized = Mat::zeros (bounds.size(), img.type());
offsetX = (bounds.width - width) / 2;
offsetY = (bounds.height - height) / 2;
Rect roi = Rect (offsetX, offsetY, width, height);
img.copyTo (resized (roi));
center += Point2d (offsetX, offsetY);
Mat M = getRotationMatrix2D (center, angle, 1.0);
warpAffine (resized, resized, M, resized.size());

enter image description here

Solution 7 - C++

A python version of rotating an image and take control of the padded black coloured region you can use the scipy.ndimage.rotate. Here is an example:

from skimage import io
from scipy import ndimage

image = io.imread('https://www.pyimagesearch.com/wp- 
content/uploads/2019/12/tensorflow2_install_ubuntu_header.jpg')
io.imshow(image)
plt.show()

original image

rotated = ndimage.rotate(image, angle=234, mode='nearest')
rotated = cv2.resize(rotated, (image.shape[:2]))
# rotated = cv2.cvtColor(rotated, cv2.COLOR_BGR2RGB)
# cv2.imwrite('rotated.jpg', rotated)
io.imshow(rotated)
plt.show()

rotated image

Solution 8 - C++

Thanks to everyone for this post, it has been super useful. However, I have found some black lines left and up (using Rose's python version) when rotating 90º. The problem seemed to be some int() roundings. In addition to that, I have changed the sign of the angle to make it grow clockwise.

def rotate_image(image, angle):
    '''Rotate image "angle" degrees.
    
    How it works:
    - Creates a blank image that fits any rotation of the image. To achieve
      this, set the height and width to be the image's diagonal.
    - Copy the original image to the center of this blank image
    - Rotate using warpAffine, using the newly created image's center
      (the enlarged blank image center)
    - Translate the four corners of the source image in the enlarged image
      using homogenous multiplication of the rotation matrix.
    - Crop the image according to these transformed corners
    '''
    
    diagonal = int(math.ceil(math.sqrt(pow(image.shape[0], 2) + pow(image.shape[1], 2))))
    offset_x = (diagonal - image.shape[0])/2
    offset_y = (diagonal - image.shape[1])/2
    dst_image = np.zeros((diagonal, diagonal, 3), dtype='uint8')
    image_center = (float(diagonal-1)/2, float(diagonal-1)/2)

    R = cv2.getRotationMatrix2D(image_center, -angle, 1.0)
    dst_image[offset_x:(offset_x + image.shape[0]), offset_y:(offset_y + image.shape[1]), :] = image
    dst_image = cv2.warpAffine(dst_image, R, (diagonal, diagonal), flags=cv2.INTER_LINEAR)

    # Calculate the rotated bounding rect
    x0 = offset_x
    x1 = offset_x + image.shape[0]
    x2 = offset_x + image.shape[0]
    x3 = offset_x

    y0 = offset_y
    y1 = offset_y
    y2 = offset_y + image.shape[1]
    y3 = offset_y + image.shape[1]

    corners = np.zeros((3,4))
    corners[0,0] = x0
    corners[0,1] = x1
    corners[0,2] = x2
    corners[0,3] = x3
    corners[1,0] = y0
    corners[1,1] = y1
    corners[1,2] = y2
    corners[1,3] = y3
    corners[2:] = 1

    c = np.dot(R, corners)

    x = int(round(c[0,0]))
    y = int(round(c[1,0]))
    left = x
    right = x
    up = y
    down = y

    for i in range(4):
        x = c[0,i]
        y = c[1,i]
        if (x < left): left = x
        if (x > right): right = x
        if (y < up): up = y
        if (y > down): down = y
    h = int(round(down - up))
    w = int(round(right - left))
    left = int(round(left))
    up = int(round(up))

    cropped = np.zeros((w, h, 3), dtype='uint8')
    cropped[:, :, :] = dst_image[left:(left+w), up:(up+h), :]
    return cropped

Solution 9 - C++

Go version (using gocv) of @robula and @remi-cuingnet


func rotateImage(mat *gocv.Mat, angle float64) *gocv.Mat {
        height := mat.Rows()
        width := mat.Cols()

        imgCenter := image.Point{X: width/2, Y: height/2}

        rotationMat := gocv.GetRotationMatrix2D(imgCenter, -angle, 1.0)

        absCos := math.Abs(rotationMat.GetDoubleAt(0, 0))
        absSin := math.Abs(rotationMat.GetDoubleAt(0, 1))

        boundW := float64(height) * absSin + float64(width) * absCos
        boundH := float64(height) * absCos + float64(width) * absSin

        rotationMat.SetDoubleAt(0, 2, rotationMat.GetDoubleAt(0, 2) + (boundW / 2) - float64(imgCenter.X))
        rotationMat.SetDoubleAt(1, 2, rotationMat.GetDoubleAt(1, 2) + (boundH / 2) - float64(imgCenter.Y))

        gocv.WarpAffine(*mat, mat, rotationMat, image.Point{ X: int(boundW), Y: int(boundH) })

        return mat
}

I rotate in the same matrice in-memory, make a new matrice if you don't want to alter it

Solution 10 - C++

If you have a rotation and a scaling of the image:

#include "opencv2/opencv.hpp"
#include <functional>
#include <vector>

bool compareCoords(cv::Point2f p1, cv::Point2f p2, char coord)
{
    assert(coord == 'x' || coord == 'y');

    if (coord == 'x')
        return p1.x < p2.x;

    return p1.y < p2.y;
}


int main(int argc, char** argv)
{

    cv::Mat image = cv::imread("lenna.png");

    float angle = 45.0;  // degrees
    float scale = 0.5;
    cv::Mat_<float> rot_mat = cv::getRotationMatrix2D( cv::Point2f( 0.0f, 0.0f ), angle, scale );
    
    // Image corners
    cv::Point2f pA = cv::Point2f(0.0f, 0.0f);
    cv::Point2f pB = cv::Point2f(image.cols, 0.0f);
    cv::Point2f pC = cv::Point2f(image.cols, image.rows);
    cv::Point2f pD = cv::Point2f(0.0f, image.rows);

    std::vector<cv::Point2f> pts = { pA, pB, pC, pD };
    std::vector<cv::Point2f> ptsTransf;
    cv::transform(pts, ptsTransf, rot_mat );

    using namespace std::placeholders;
    float minX = std::min_element(ptsTransf.begin(), ptsTransf.end(), std::bind(compareCoords, _1, _2, 'x'))->x;
    float maxX = std::max_element(ptsTransf.begin(), ptsTransf.end(), std::bind(compareCoords, _1, _2, 'x'))->x;
    float minY = std::min_element(ptsTransf.begin(), ptsTransf.end(), std::bind(compareCoords, _1, _2, 'y'))->y;
    float maxY = std::max_element(ptsTransf.begin(), ptsTransf.end(), std::bind(compareCoords, _1, _2, 'y'))->y;

    float newW = maxX - minX;
    float newH = maxY - minY;

    cv::Mat_<float> trans_mat = (cv::Mat_<float>(2,3) << 0, 0, -minX, 0, 0, -minY);
    cv::Mat_<float> M = rot_mat + trans_mat;

    cv::Mat warpedImage;
    cv::warpAffine( image, warpedImage, M, cv::Size(newW, newH) );

    cv::imshow("lenna", image);
    cv::imshow("Warped lenna", warpedImage);

    cv::waitKey();
    cv::destroyAllWindows();
    return 0;
}

enter image description here enter image description here

Solution 11 - C++

If it is just to rotate 90 degrees, maybe this code could be useful.

    Mat img = imread("images.jpg");
	Mat rt(img.rows, img.rows, CV_8U);
    Point2f pc(img.cols / 2.0, img.rows / 2.0);
 	Mat r = getRotationMatrix2D(pc, 90, 1);
    warpAffine(img, rt, r, rt.size());
    imshow("rotated", rt);

Hope it's useful.

Solution 12 - C++

By the way, for 90º rotations only, here is a more efficient + accurate function:

def rotate_image_90(image, angle):
    angle = -angle
    rotated_image = image
    if angle == 0:
        pass
    elif angle == 90:
        rotated_image = np.rot90(rotated_image)
    elif angle == 180 or angle == -180:
        rotated_image = np.rot90(rotated_image)
        rotated_image = np.rot90(rotated_image)
    elif angle == -90:
        rotated_image = np.rot90(rotated_image)
        rotated_image = np.rot90(rotated_image)
        rotated_image = np.rot90(rotated_image)
    return rotated_image

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionManuel Ignacio L&#243;pez QuinteroView Question on Stackoverflow
Solution 1 - C++Lars SchillingmannView Answer on Stackoverflow
Solution 2 - C++HarisView Answer on Stackoverflow
Solution 3 - C++Remi CuingnetView Answer on Stackoverflow
Solution 4 - C++Rose PerroneView Answer on Stackoverflow
Solution 5 - C++RobulaView Answer on Stackoverflow
Solution 6 - C++razzView Answer on Stackoverflow
Solution 7 - C++burhan rashidView Answer on Stackoverflow
Solution 8 - C++globalcaosView Answer on Stackoverflow
Solution 9 - C++Jonathan MullerView Answer on Stackoverflow
Solution 10 - C++orbitView Answer on Stackoverflow
Solution 11 - C++Nandan IKView Answer on Stackoverflow
Solution 12 - C++globalcaosView Answer on Stackoverflow