97

I'm having a hard time finding examples for rotating an image around a specific point by a specific (often very small) angle in Python using OpenCV.

This is what I have so far, but it produces a very strange resulting image, but it is rotated somewhat:

def rotateImage( image, angle ):
    if image != None:
        dst_image = cv.CloneImage( image )

        rotate_around = (0,0)
        transl = cv.CreateMat(2, 3, cv.CV_32FC1 )

        matrix = cv.GetRotationMatrix2D( rotate_around, angle, 1.0, transl )
        cv.GetQuadrangleSubPix( image, dst_image, transl )
        cv.GetRectSubPix( dst_image, image, rotate_around )

    return dst_image
Mike
  • 967
  • 1
  • 6
  • 8

12 Answers12

164
import numpy as np
import cv2

def rotate_image(image, angle):
  image_center = tuple(np.array(image.shape[1::-1]) / 2)
  rot_mat = cv2.getRotationMatrix2D(image_center, angle, 1.0)
  result = cv2.warpAffine(image, rot_mat, image.shape[1::-1], flags=cv2.INTER_LINEAR)
  return result

Assuming you're using the cv2 version, that code finds the center of the image you want to rotate, calculates the transformation matrix and applies to the image.

Sajjad Aemmi
  • 2,120
  • 3
  • 13
  • 25
Alex Rodrigues
  • 2,567
  • 1
  • 14
  • 9
  • Thanks for the help, however I'm using the "cv" module and you are using "cv2", so its complaining specifically about "image.shape" not existing. I have only been using the "cv" module up until now, so I don't quite get all the changes with "cv2" yet. I know my image is (140,140), so I tried hard coding that in place of image.shape, but it didn't like that at all either. – Mike Jan 28 '12 at 05:06
  • 1
    I think I may have made some progress, but still running into a problem. Here is the latest code: result = cv2.warpAffine(image, rot_mat, cv.GetSize(image), flags=cv2.INTER_LINEAR) Traceback (most recent call last): result = cv2.warpAffine(image, rot_mat, cv.GetSize(image), flags=cv2.INTER_LINEAR) TypeError: is not a numpy array – Mike Jan 28 '12 at 05:26
  • 15
    I have a problem running cv2.getRotationMatrix2D(center=image_center ,angle=angle,scale=1) TypeError: function takes exactly 2 arguments (3 given) – Hani Mar 08 '12 at 18:44
  • 6
    image.shape include the width,height and channel – Treper Nov 30 '12 at 08:09
  • 4
    @Hani try cv2.getRotationMatrix2D((imagecenter[0],imagecenter[1]),angle,1.0) – sreemanth pulagam Jan 20 '14 at 07:22
  • This solution changes the image dimensions. See nicodjimenez's answer below. – Graydyn Young Jul 06 '16 at 19:46
  • 3
    `angle` is in degrees. https://docs.opencv.org/2.4/modules/imgproc/doc/geometric_transformations.html?highlight=getrotationmatrix#getrotationmatrix2d – Eduardo Pignatelli May 15 '19 at 08:48
  • I believe there might be an error in this answer. I think the correct image center to rotate around is off by 0.5 pixel. The correct code would be `image_center = tuple((np.array(image.shape[1::-1])-1) / 2)`. To check this I encourage you to create a small image with a single line in the middle and rotate it with 45 degrees. Like this `im = np.zeros(shape=(3, 3), dtype=np.uint8); im[:, 1] = 1; im_rot = rotate_image(im, 45)`. Using the original code, the line is not correctly rotated, with the adjusted center it is correctly rotated. Please correct me if I am mistaken. – MuadDev Jan 17 '22 at 17:06
79

Or much easier use SciPy

from scipy import ndimage

#rotation angle in degree
rotated = ndimage.rotate(image_to_rotate, 45)

see here for more usage info.

fivef
  • 2,397
  • 21
  • 21
21
def rotate(image, angle, center = None, scale = 1.0):
    (h, w) = image.shape[:2]

    if center is None:
        center = (w / 2, h / 2)

    # Perform the rotation
    M = cv2.getRotationMatrix2D(center, angle, scale)
    rotated = cv2.warpAffine(image, M, (w, h))

    return rotated
Omnipresent
  • 29,434
  • 47
  • 142
  • 186
16

I had issues with some of the above solutions, with getting the correct "bounding_box" or new size of the image. Therefore here is my version

def rotation(image, angleInDegrees):
    h, w = image.shape[:2]
    img_c = (w / 2, h / 2)

    rot = cv2.getRotationMatrix2D(img_c, angleInDegrees, 1)

    rad = math.radians(angleInDegrees)
    sin = math.sin(rad)
    cos = math.cos(rad)
    b_w = int((h * abs(sin)) + (w * abs(cos)))
    b_h = int((h * abs(cos)) + (w * abs(sin)))

    rot[0, 2] += ((b_w / 2) - img_c[0])
    rot[1, 2] += ((b_h / 2) - img_c[1])

    outImg = cv2.warpAffine(image, rot, (b_w, b_h), flags=cv2.INTER_LINEAR)
    return outImg
JTIM
  • 2,774
  • 1
  • 34
  • 74
  • was this for face detection? I want to rotate video frames by 90 degrees and run MTCNN because it won't detect frontal faces lying sideways (person lying on the ground) – mLstudent33 Sep 12 '19 at 08:56
  • @mLstudent33 No I used it for a different purpose, but this is just rotating an image. So if you have the angle then it should be fine? – JTIM Sep 12 '19 at 10:19
  • 1
    I think so. I can rotate, run detection, then draw `cv2.rectangle()` and then rotate it back. Thanks for replying. – mLstudent33 Sep 12 '19 at 10:21
12

The cv2.warpAffine function takes the shape parameter in reverse order: (col,row) which the answers above do not mention. Here is what worked for me:

import numpy as np

def rotateImage(image, angle):
    row,col = image.shape
    center=tuple(np.array([row,col])/2)
    rot_mat = cv2.getRotationMatrix2D(center,angle,1.0)
    new_image = cv2.warpAffine(image, rot_mat, (col,row))
    return new_image
nicodjimenez
  • 1,180
  • 17
  • 15
  • getRotationMatrix2D seems to require (col,row), too. `center` should use (col,row) as well, as is done in @Omnipresent's answer. – erwaman Oct 06 '19 at 17:39
  • I agree that it wasn't clear. @alex-rodrigues ' answer does some slicing to the image.shape to get the width and height in the right order for warpAffine: image.shape[1::-1] does this. What this does is takes a slice starting at the 1st element, with a step value of -1, so proceeding left, so you end up with a slice with [1][0], which is the width (columns), followed by height (rows). – Keithel Aug 11 '21 at 17:14
6
import imutils

vs = VideoStream(src=0).start()
...

while (1):
   frame = vs.read()
   ...

   frame = imutils.rotate(frame, 45)

More: https://github.com/jrosebr1/imutils

Greg Wojdyga
  • 101
  • 1
  • 5
6

You can simply use the imutils package to do the rotation. it has two methods

  1. rotate: rotate the image at specified angle. however the drawback is image might get cropped if it is not a square image.
  2. rotate_bound: it overcomes the problem happened with rotate. It adjusts the size of the image accordingly while rotating the image.

more info you can get on this blog: https://www.pyimagesearch.com/2017/01/02/rotate-images-correctly-with-opencv-and-python/

Dmitriy Work
  • 773
  • 10
  • 17
Vaibhav K
  • 2,762
  • 3
  • 21
  • 22
4

Quick tweak to @alex-rodrigues answer... deals with shape including the number of channels.

import cv2
import numpy as np

def rotateImage(image, angle):
    center=tuple(np.array(image.shape[0:2])/2)
    rot_mat = cv2.getRotationMatrix2D(center,angle,1.0)
    return cv2.warpAffine(image, rot_mat, image.shape[0:2],flags=cv2.INTER_LINEAR)
alcoholiday
  • 719
  • 5
  • 10
1

You need a homogenous matrix of size 2x3. First 2x2 is the rotation matrix and last column is a translation vector.

enter image description here

Here's how to build your transformation matrix:

# Exemple with img center point:
# angle = np.pi/6
# specific_point = np.array(img.shape[:2][::-1])/2

def rotate(img: np.ndarray, angle: float, specific_point: np.ndarray) -> np.ndarray:
    warp_mat = np.zeros((2,3))
    cos, sin = np.cos(angle), np.sin(angle)
    warp_mat[:2,:2] = [[cos, -sin],[sin, cos]]
    warp_mat[:2,2] = specific_point - np.matmul(warp_mat[:2,:2], specific_point)
    return cv2.warpAffine(img, warp_mat, img.shape[:2][::-1])
Jean-Christophe
  • 485
  • 4
  • 7
0

You can easily rotate the images using opencv python-

def funcRotate(degree=0):
    degree = cv2.getTrackbarPos('degree','Frame')
    rotation_matrix = cv2.getRotationMatrix2D((width / 2, height / 2), degree, 1)
    rotated_image = cv2.warpAffine(original, rotation_matrix, (width, height))
    cv2.imshow('Rotate', rotated_image)

If you are thinking of creating a trackbar, then simply create a trackbar using cv2.createTrackbar() and the call the funcRotate()fucntion from your main script. Then you can easily rotate it to any degree you want. Full details about the implementation can be found here as well- Rotate images at any degree using Trackbars in opencv

0

Here's an example for rotating about an arbitrary point (x,y) using only openCV

def rotate_about_point(x, y, degree, image):
    rot_mtx = cv.getRotationMatrix2D((x, y), angle, 1)
    abs_cos = abs(rot_mtx[0, 0])
    abs_sin = abs(rot_mtx[0, 1])
    rot_wdt = int(frm_hgt * abs_sin + frm_wdt * abs_cos)
    rot_hgt = int(frm_hgt * abs_cos + frm_wdt * abs_sin)
    rot_mtx += np.asarray([[0, 0, -lftmost_x],
                           [0, 0, -topmost_y]])
    rot_img = cv.warpAffine(image, rot_mtx, (rot_wdt, rot_hgt),
                            borderMode=cv.BORDER_CONSTANT)
    return rot_img
lorenzo
  • 481
  • 4
  • 4
0

you can use the following code:

import numpy as np
from PIL import Image
import math
def shear(angle,x,y):

tangent=math.tan(angle/2)
new_x=round(x-y*tangent)
new_y=y

#shear 2
new_y=round(new_x*math.sin(angle)+new_y)     
#since there is no change in new_x according to the shear matrix

#shear 3
new_x=round(new_x-new_y*tangent)            
#since there is no change in new_y according to the shear matrix

return new_y,new_x




image = np.array(Image.open("test.png"))            
# Load the image
angle=-int(input("Enter the angle :- "))               
# Ask the user to enter the angle of rotation

# Define the most occuring variables
angle=math.radians(angle)                             
#converting degrees to radians
cosine=math.cos(angle)
sine=math.sin(angle)

height=image.shape[0]                                
#define the height of the image
width=image.shape[1]                                    
#define the width of the image

# Define the height and width of the new image that is to be formed
new_height  = round(abs(image.shape[0]*cosine)+abs(image.shape[1]*sine))+1
new_width  = round(abs(image.shape[1]*cosine)+abs(image.shape[0]*sine))+1


output=np.zeros((new_height,new_width,image.shape[2]))
image_copy=output.copy()


# Find the centre of the image about which we have to rotate the image
original_centre_height   = round(((image.shape[0]+1)/2)-1)    
#with respect to the original image
original_centre_width = round(((image.shape[1]+1)/2)-1)   
#with respect to   the original image

# Find the centre of the new image that will be obtained
new_centre_height= round(((new_height+1)/2)-1)        
#with respect to the new image
new_centre_width= round(((new_width+1)/2)-1)          
#with respect to the new image


for i in range(height):
 for j in range(width):
    #co-ordinates of pixel with respect to the centre of original image
    y=image.shape[0]-1-i-original_centre_height                   
    x=image.shape[1]-1-j-original_centre_width 

    #Applying shear Transformation                     
    new_y,new_x=shear(angle,x,y)

   
    new_y=new_centre_height-new_y
    new_x=new_centre_width-new_x
    
    output[new_y,new_x,:]=image[i,j,:]                        

    pil_img=Image.fromarray((output).astype(np.uint8))                       
    pil_img.save("rotated_image.png")