Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to rotate and translate an image with opencv without losing off-screen data

I'm trying to use opencv to perform subsequent image transformations. I have an image that I want to both rotate and translate while keeping the overall image size constant. I've been using the warpAffine function with rotation and translation matrixes to perform the transformations, but the problem is that after performing one transformation some image data is lost and not carried over to the next transformation.

Original Original image of dogs

Rotated Rotated image of dogs

Translated and rotated. Note how the corners of the original image have been clipped off. Erroneously translated image of dogs

Desired output enter image description here

What I would like is to have an image that's very similar to the translated image here, but without the corners clipped off. I understand that this occurs because after performing the first Affine transform the corner data is removed. However, I'm not really sure how I can preserve this data while still maintaining the image's original size with respect to its center. I am very new to computer vision and do not have a strong background in linear algebra or matrix math. Most of the code I've managed to make work has been learned from online tutorials, so any and all accessible help fixing this would be greatly appreciated!

Here is the code used to generate the above images:

import numpy as np
import cv2

def rotate_image(image, angle):
    w, h = (image.shape[1], image.shape[0])
    cx, cy = (w//2,h//2)

    M = cv2.getRotationMatrix2D((cx,cy), -1*angle, 1.0)
    rotated = cv2.warpAffine(image, M, (w,h))
    return rotated

def translate_image(image, d_x, d_y):
    M = np.float32([
        [1,0,d_x],
        [0,1,d_y]
    ])
    
    return cv2.warpAffine(image, M, (image.shape[1], image.shape[0]))

path = "dog.jpg"
image = cv2.imread(path)
angle = 30.0
d_x = 200
d_y = 300
rotated = rotate_image(image, angle)
translated = translate_image(rotated, d_x, d_y)
like image 384
harke Avatar asked Oct 30 '25 12:10

harke


1 Answers

Chaining the rotation and translation transformations is what you are looking for.

Instead of applying the rotation and translation one after the other, we may apply cv2.warpAffine with the equivalent chained transformation matrix.
Using cv2.warpAffine only once, prevents the corner cutting that resulted by the intermediate image (intermediate rotated only image with cutted corners).

Chaining two transformation matrices is done by multiplying the two matrices.
In OpenCV convention, the first transformation is multiplied from the left side.


The last row of 2D affine transformation matrix is always [0, 0, 1].
OpenCV conversion is omitting the last row, so M is 2x3 matrix.

Chaining OpenCV transformations M0 and M1 applies the following stages:

  • Insert the "omitting last row" [0, 0, 1] to M0 and to M1.

     T0 = np.vstack((M0, np.array([0, 0, 1])))
     T1 = np.vstack((M1, np.array([0, 0, 1])))
    
  • Chain transformations by matrix multiplication:

     T = T1 @ T0
    
  • Remove the last row (equals [0, 0, 1]) for matching OpenCV 2x3 convention:

     M = T[0:2, :]
    

The higher level solution applies the following stages:

  • Compute rotation transformation matrix.
  • Compute translation transformation matrix.
  • Chain the rotation and translation transformations.
  • Apply affine transformation with the chained transformations matrix.

Code sample:

import numpy as np
import cv2

def get_rotation_mat(image, angle):
    w, h = (image.shape[1], image.shape[0])
    cx, cy = (w//2,h//2)

    M = cv2.getRotationMatrix2D((cx, cy), -1*angle, 1.0)
    #rotated = cv2.warpAffine(image, M, (w,h))
    return M

def get_translation_mat(d_x, d_y):
    M = np.float64([
        [1, 0, d_x],
        [0, 1, d_y]
    ])
    
    #return cv2.warpAffine(image, M, (image.shape[1], image.shape[0]))
    return M

def chain_affine_transformation_mats(M0, M1):
    """ 
    Chaining affine transformations given by M0 and M1 matrices.
    M0 - 2x3 matrix applying the first affine transformation (e.g rotation).
    M1 - 2x3 matrix applying the second affine transformation (e.g translation).
    The method returns M - 2x3 matrix that chains the two transformations M0 and M1 (e.g rotation then translation in a single matrix).
    """
    T0 = np.vstack((M0, np.array([0, 0, 1])))  # Add row [0, 0, 1] to the bottom of M0 ([0, 0, 1] applies last row of eye matrix), T0 is 3x3 matrix.
    T1 = np.vstack((M1, np.array([0, 0, 1])))  # Add row [0, 0, 1] to the bottom of M1.
    T = T1 @ T0  # Chain transformations T0 and T1 using matrix multiplication.
    M = T[0:2, :]  # Remove the last row from T (the last row of affine transformations is always [0, 0, 1] and OpenCV conversion is omitting the last row).
    return M

path = "dog.jpg"
image = cv2.imread(path)
angle = 30.0
d_x = 200
d_y = 300
#rotated = rotate_image(image, angle)
#translated = translate_image(rotated, d_x, d_y)
rotationM = get_rotation_mat(image, angle)  # Compute rotation transformation matrix
translationM = get_translation_mat(d_x, d_y)  # Compute translation transformation matrix

M = chain_affine_transformation_mats(rotationM, translationM)  # Chain rotation and translation transformations (translation after rotation)

transformed_image = cv2.warpAffine(image, M, (image.shape[1], image.shape[0]))  # Apply affine transformation with the chained (unified) matrix M.

cv2.imwrite("transformed_dog.jpg", transformed_image)  # Store output for testing

Output:
enter image description here

like image 69
Rotem Avatar answered Nov 02 '25 01:11

Rotem



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!