67

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

VishnuVS
  • 1,055
  • 2
  • 14
  • 29

13 Answers13

102

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;
}
Lars Schillingmann
  • 1,528
  • 1
  • 13
  • 15
  • This code still creates a small black edge on flipping 90 degree. – twister Jan 14 '17 at 18:11
  • 1
    I know it is an old question, and the result seems absolutely correct when executed, anyway could you please clarify the math behind the following two lines? rot.at(0,2) += bbox.width/2.0 - center.x; rot.at(1,2) += bbox.height/2.0 - center.y; Here you add another translation to the Tx,Ty computed by cv::getRotationMatrix2D inside the obtained matrix. I can't figure out the geometry/math rule you use to do this. – valleymanbs Feb 01 '17 at 20:09
  • 1
    The roation matrix `rot` maps the target to `center`. The bounding box `bbox` has a different center. The idea is to remove `center` from the translational component of the affine transformation. The new center is the center of the bounding box `bbox` which is added. – Lars Schillingmann Feb 03 '17 at 14:20
  • Thank you for your answer Lars, anyway, sorry, it is still not clear to me: if you decompose the rotation matrix obtained from getRotationMatriX2D you can see it is the result of the following rotation/translation, executed in this order: 1) translate input matrix rotation center (in this case the original image center) from (center_x,center_y) into (0,0); 2) rotate input matrix (and scaling it) with given angle; 3) translate back input matrix inverting point 1). Multiplication of these 3 transformation matrix gives the formula you see in opencv docs for getRotationMatrix2D. – valleymanbs Feb 07 '17 at 13:42
  • OK, sorry, I hadn't seen the content of this link: http://john.freml.in/opencv-rotation; clarifies all the math behind your answer. I leave the above comment, could be useful for others reasoning on geometry behind this operation. Thank you again @LarsSchillingmann – valleymanbs Feb 07 '17 at 13:44
  • Sorry for being so boring @LarsSchillingmann, but could you please have a look at this question i have just posted? I'd like to fully understand what you do in your code, I can see results are equivalent to the Python snippet linked at http://john.freml.in/opencv-rotation, but I still can't get to understand your code fully... http://stackoverflow.com/questions/42095280/opencv-rotate-image-without-cropping-clarification – valleymanbs Feb 07 '17 at 16:57
  • This changes the size of the image (for example if you rotate your image 90 degree you'd see that it creates a bigger image). So this is not perfect. – Milad Apr 24 '18 at 16:14
  • 1
    To correct the black edges boundingRect2f can be used. It also seems that the center for warpAffine needs to be in pixel coordinates. I modified the answer accordingly. – Lars Schillingmann Apr 25 '18 at 01:57
  • I'd like to know what I have to do to transform an existing cv::RotatedRect which matches a location in the original image (src) into the same location in the rotated image (dest) after I've applied your transformation. – Bastian Aug 10 '19 at 11:06
  • 1
    You might want to pass `INTER_NEAREST` to `warpAffine`, it [seems to be less blurry](https://stackoverflow.com/questions/39371507/image-loses-quality-with-cv2-warpperspective) than the default. `cv::warpAffine(src, dst, rot, bbox.size(), cv::INTER_NEAREST);` – Boris Verkhovskiy Dec 02 '19 at 19:15
33

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

Community
  • 1
  • 1
Haris
  • 13,645
  • 12
  • 90
  • 121
  • It works! Thanks! There's no solution more simple? How this powerful library doesn't have a function like Octave? In Octave (or Matlab) is only 'imrotate(I, 45)'!! – Manuel Ignacio López Quintero Feb 26 '14 at 14:48
  • This version it's not perfect because it crops a little. Look at the bottom corners of the first image!! – Manuel Ignacio López Quintero Feb 26 '14 at 15:49
  • That's may be of 1 or 2 pixel error, might happened while rounding or typecasting double to int, you can ignore it or just extend the box 1 or 2 pixel outward before cropping. – Haris Feb 27 '14 at 06:04
  • I'll try it again, I have to update your code with 'cv::Mat targetMat(newWidth, newHeight, src.type(), cv::Scalar(0));'. Before I obtained NaN values. – Manuel Ignacio López Quintero Feb 27 '14 at 08:47
  • I update your code adding bounding cheking for control errors. See my own response below for the details. – Manuel Ignacio López Quintero Mar 24 '14 at 15:23
  • @hegazy Yes it may not work with 4 channel as the input image considered as 3 channel(need to check all above OpenCV functions support 4 channel like warpaffine), or you can proceed like split 4 channel to alpha and BGR, now process both alpha and BGR separately and finally merge channels. – Haris Jan 20 '15 at 07:47
25

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
Remi Cuingnet
  • 943
  • 10
  • 14
15

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
Rose Perrone
  • 61,572
  • 58
  • 208
  • 243
12

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

razz
  • 9,770
  • 7
  • 50
  • 68
  • This code will crash whether either the width or height of 'bounds' is less than those for 'img'. This is the case, for example, when angle is +/-90. The cause is negative values for offsetX or offsetY in the roi. – Matthew M. May 25 '21 at 19:19
11

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.

Community
  • 1
  • 1
Robula
  • 649
  • 1
  • 12
  • 29
1

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

burhan rashid
  • 444
  • 5
  • 11
1

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

orbit
  • 133
  • 7
0

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
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
0

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

Jonathan Muller
  • 7,348
  • 2
  • 23
  • 31
0

For anyone using Emgu.CV or OpenCvSharp wrapper in .NET, there's a C# implement of Lars Schillingmann's answer:

Emgu.CV:

using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Structure;

public static class MatExtension
{
    /// <summary>
    /// <see>https://stackoverflow.com/questions/22041699/rotate-an-image-without-cropping-in-opencv-in-c/75451191#75451191</see>
    /// </summary>
    public static Mat Rotate(this Mat src, float degrees)
    {
        degrees = -degrees; // counter-clockwise to clockwise
        var center = new PointF((src.Width - 1) / 2f, (src.Height - 1) / 2f);
        using var rotationMat = new Mat();
        CvInvoke.GetRotationMatrix2D(center, degrees, 1, rotationMat);
        var boundingRect = new RotatedRect(new(), src.Size, degrees).MinAreaRect();
        rotationMat.Set(0, 2, rotationMat.Get<double>(0, 2) + (boundingRect.Width / 2f) - (src.Width / 2f));
        rotationMat.Set(1, 2, rotationMat.Get<double>(1, 2) + (boundingRect.Height / 2f) - (src.Height / 2f));
        var rotatedSrc = new Mat();
        CvInvoke.WarpAffine(src, rotatedSrc, rotationMat, boundingRect.Size);
        return rotatedSrc;
    }

    /// <summary>
    /// <see>https://stackoverflow.com/questions/32255440/how-can-i-get-and-set-pixel-values-of-an-emgucv-mat-image/69537504#69537504</see>
    /// </summary>
    public static unsafe void Set<T>(this Mat mat, int row, int col, T value) where T : struct =>
        _ = new Span<T>(mat.DataPointer.ToPointer(), mat.Rows * mat.Cols * mat.ElementSize)
        {
            [(row * mat.Cols) + col] = value
        };

    public static unsafe T Get<T>(this Mat mat, int row, int col) where T : struct =>
        new ReadOnlySpan<T>(mat.DataPointer.ToPointer(), mat.Rows * mat.Cols * mat.ElementSize)
            [(row * mat.Cols) + col];
}

OpenCvSharp:

OpenCvSharp already has Mat.Set<> method that functions same as mat.at<> in the original OpenCV, so we don't have to copy these methods from How can I get and set pixel values of an EmguCV Mat image?

using OpenCvSharp;

public static class MatExtension
{
    /// <summary>
    /// <see>https://stackoverflow.com/questions/22041699/rotate-an-image-without-cropping-in-opencv-in-c/75451191#75451191</see>
    /// </summary>
    public static Mat Rotate(this Mat src, float degrees)
    {
        degrees = -degrees; // counter-clockwise to clockwise
        var center = new Point2f((src.Width - 1) / 2f, (src.Height - 1) / 2f);
        using var rotationMat = Cv2.GetRotationMatrix2D(center, degrees, 1);
        var boundingRect = new RotatedRect(new(), new(src.Width, src.Height), degrees).BoundingRect();
        rotationMat.Set(0, 2, rotationMat.Get<double>(0, 2) + (boundingRect.Width / 2f) - (src.Width / 2f));
        rotationMat.Set(1, 2, rotationMat.Get<double>(1, 2) + (boundingRect.Height / 2f) - (src.Height / 2f));
        var rotatedSrc = new Mat();
        Cv2.WarpAffine(src, rotatedSrc, rotationMat, boundingRect.Size);
        return rotatedSrc;
    }
}

Also, you may want to mutate the src param directly instead of returning a new clone of it during rotation, for that you can just set the det param of WrapAffine() as the same with src: c++, opencv: Is it safe to use the same Mat for both source and destination images in filtering operation?

CvInvoke.WarpAffine(src, src, rotationMat, boundingRect.Size);

This is being called as in-place mode: https://answers.opencv.org/question/24/do-all-opencv-functions-support-in-place-mode-for-their-arguments/

Can the OpenCV function cvtColor be used to convert a matrix in place?

n0099
  • 520
  • 1
  • 4
  • 12
-2

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.

Nandan IK
  • 15
  • 1
-4

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
eyllanesc
  • 235,170
  • 19
  • 170
  • 241