Search code examples
pythonimageopencvimage-processingimage-rotation

OpenCV python3 cannot rotate image back correctly


I want to use python3 with OpenCV packages to rotate an image in a degree 15 and rotate it back. However, the code below seem does not work but I think there no logic error in the codes. In the "NEAREST" window, the image is rotated correctly but in the "NEAREST-OK-RESTORE" window the image is not rotate back to its original position ( showed in "original" window ).

enter image description here

#! /usr/bin/env python3
#! -*- coding:utf-8 -*-

import cv2
import numpy as np

imgmat = cv2.imread('./lena.jpg',255)
print(hex(id(imgmat)))

cv2.imshow('original',imgmat)

rows,cols = imgmat.shape

M = cv2.getRotationMatrix2D((cols/2,rows/2),-15,1)
dst = cv2.warpAffine(src=imgmat,M=M,dsize=(cols,rows),flags=cv2.INTER_NEAREST)
print(hex(id(dst)))
cv2.imshow('NEAREST',dst)


OKMM = cv2.invertAffineTransform(M)
dst = cv2.warpAffine(dst,OKMM,(cols,rows),flags = cv2.INTER_NEAREST)
print(hex(id(dst)))
cv2.imshow('NEAREST-OK-RESTORE',dst)

cv2.waitKey(0)

The test image is here: enter image description here


Solution

  • The reason why this is happening is because when you are performing the rotation, this is doing it within the span of the original image dimensions, so this results in image clipping if you are rotating the image and the resulting corner points extend beyond the original image dimensions.

    What you have to do is zero pad the image so that the rotated image is fully contained within it, rotate this image then when you rotate back, you will have to crop the result.

    To figure out how much to zero pad your image by, note that the largest possible dimensions of your image will be when the image is at a 45 degree rotation. This means that the diagonal of the image would now be the number of rows in the image. Therefore, zero pad your image so that we can at least store the image when subject to a 45 degree rotation, rotate this image and when you rotate back you'll have to crop the result.

    You can use numpy.pad to do this for you and when you're done you can simply crop the result:

    I've made the following changes to your code to do this:

    import cv2
    import numpy as np
    
    imgmat = cv2.imread('./lena.jpg',255)
    print(hex(id(imgmat)))
    
    cv2.imshow('original',imgmat)
    
    rows,cols = imgmat.shape
    
    # NEW - Determine the diagonal length of the image
    diagonal = int(np.ceil(np.sqrt(rows**2.0 + cols**2.0)))
    
    # NEW - Determine how many pixels we need to pad to the top/bottom and left/right
    pp_r, pp_c = (diagonal - rows) // 2, (diagonal - cols) // 2
    
    # NEW - Pad the image
    imgmat_copy = np.pad(imgmat, ((pp_r, pp_r), (pp_c, pp_c)), 'constant', constant_values=(0,0))
    
    ### Your code as before - note we are rotating the zero padded image
    rows,cols = imgmat_copy.shape
    M = cv2.getRotationMatrix2D((cols/2,rows/2),-15,1)
    dst = cv2.warpAffine(src=imgmat_copy,M=M,dsize=imgmat_copy.shape,flags=cv2.INTER_NEAREST)
    print(hex(id(dst)))
    cv2.imshow('NEAREST',dst)
    
    
    OKMM = cv2.invertAffineTransform(M)
    dst = cv2.warpAffine(dst,OKMM,(cols,rows),flags = cv2.INTER_NEAREST)
    print(hex(id(dst)))
    
    # NEW - Crop the image
    dst = dst[pp_r:-pp_r+1,pp_c:-pp_c+1]
    cv2.imshow('NEAREST-OK-RESTORE',dst)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    

    I now get:

    enter image description here