Academic
Blog
Analytics

Image rotation using OpenCV

8 mins read time ❤️ Views


Summary of the problem

  • We have an image
  • We draw some rectangles over as object detection.
  • We rotate the image by given angle.
  • We need to calculate the new coordinates for each rectangle after the image rotation.

This calculation of the coordinates can be made using an affine transformation.

Affine transformation

The image transformation can be expressed in the form of a matrix multiplication using an affine transformation. This matrix can be used to express a rotation, translation and scale operations. The usual way to represent a affine transformation is using a 2 x 3 matrix.

Considering that we want to transform a 2D vector

The transformed vector can be obtained by

Transformation matrix

The transformation matrix can be obtained using the rotation angle and the centre coordinates. It can be expressed also as the following structure:

Where:

  • and is the rotation angle.

For this case, we use

Adjust coordinates to the new reference point

For this case of rotation image, where the image size changes after rotation and also the reference point, the transformation matrix has to be modified. The figure represents how the new dimension is calculated:


Image rotation schema

Where the new with and height can be calculated using the following relations:

Since the image size changes, the coordinates of the rotation point (centre of the image) change too. Then it has to be taken into account in the transformation matrix. This is added to the last column of the transformation matrix as follows:

Implementation using OpenCV

I have implemented the solution in python, using OpenCV. I use a sample image of a 🐈, because everybody loves cats. I create the bounding box of the face and the eyes using Sloth. The result is a json like this:

[
    {
        "annotations": [
            {
                "class": "polygon",
                "xn": "776.9486798180992;881.0203274611703;884.6090049661037;780.5373573230327",
                "yn": "383.9884930278827;380.39981552294927;498.8261731857542;498.8261731857542"
            },
            {
                "class": "polygon",
                "xn": "897.1693762333709;999.4466851239752;1003.0353626289086;900.7580537383044",
                "yn": "534.712948235089;538.3016257400226;653.139305897894;653.139305897894"
            },
            {
                "class": "polygon",
                "xn": "1279.3635305087869;1293.718240528521;624.4298858584261;628.0185633633596",
                "yn": "111.24900265293799;805.6580998575671;787.7147123328997;113.04334140540473"
            }
        ],
        "class": "image",
        "filename": "cat.jpg"
    }
]

I load the image original_image.jpg and rotate it using a function detailled in https://www.pyimagesearch.com/2017/01/02/rotate-images-correctly-with-opencv-and-python/. It is faster than scipy fonctions.

# Original image
img_orig = cv2.imread('images/original_image.jpg')
# Rotated image
rotated_img = rotate_bound(img_orig, theta)

I use a for loop to transform each (x,y) coordinates.

new_bb = {}
for i in bb:
    new_bb[i] = rotate_box(bb[i], cx, cy, heigth, width)

I create the transformation matrix and modify the third column to take into account the new image size.

def rotate_box(bb, cx, cy, h, w):
    new_bb = list(bb)
    for i,coord in enumerate(bb):
        # opencv calculates standard transformation matrix
        M = cv2.getRotationMatrix2D((cx, cy), theta, 1.0)
        # Grab  the rotation components of the matrix)
        cos = np.abs(M[0, 0])
        sin = np.abs(M[0, 1])
        # compute the new bounding dimensions of the image
        nW = int((h * sin) + (w * cos))
        nH = int((h * cos) + (w * sin))
        # adjust the rotation matrix to take into account translation
        M[0, 2] += (nW / 2) - cx
        M[1, 2] += (nH / 2) - cy
        # Prepare the vector to be transformed
        v = [coord[0],coord[1],1]
        # Perform the actual rotation and return the image
        calculated = np.dot(M,v)
        new_bb[i] = (calculated[0],calculated[1])
    return new_bb
  • The image centre before rotation is: (2880, 1920)

  • The image centre after rotation is: (2907, 1961)

Results

Code

import matplotlib as mpl
mpl.use('Agg')
import matplotlib.pyplot as plt
import cv2
import matplotlib.patches as mpatches
import numpy as np
import json

theta = 45

with open('images/example1_labels.json') as json_data:
    d = json.load(json_data)
    print(d)

bb1 = {}
for i,j in enumerate(d[0]['annotations']):
    xs = j['xn'].split(';')
    ys = j['yn'].split(';')
    bb1[i] = [(float(xs[0]),float(ys[0])), (float(xs[1]),float(ys[1])),(float(xs[2]),float(ys[2])),(float(xs[3]),float(ys[3]))]

print(bb1)

def rotate_box(bb, cx, cy, h, w):
    new_bb = list(bb)
    for i,coord in enumerate(bb):
        # opencv calculates standard transformation matrix
        M = cv2.getRotationMatrix2D((cx, cy), theta, 1.0)
        # Grab  the rotation components of the matrix)
        cos = np.abs(M[0, 0])
        sin = np.abs(M[0, 1])
        # compute the new bounding dimensions of the image
        nW = int((h * sin) + (w * cos))
        nH = int((h * cos) + (w * sin))
        # adjust the rotation matrix to take into account translation
        M[0, 2] += (nW / 2) - cx
        M[1, 2] += (nH / 2) - cy
        # Prepare the vector to be transformed
        v = [coord[0],coord[1],1]
        # Perform the actual rotation and return the image
        calculated = np.dot(M,v)
        new_bb[i] = (calculated[0],calculated[1])
    return new_bb

def rotate_bound(image, angle):
    # grab the dimensions of the image and then determine the
    # centre
    (h, w) = image.shape[:2]
    (cX, cY) = (w // 2, h // 2)

    # grab the rotation matrix (applying the negative of the
    # angle to rotate clockwise), then grab the sine and cosine
    # (i.e., the rotation components of the matrix)
    M = cv2.getRotationMatrix2D((cX, cY), angle, 1.0)
    cos = np.abs(M[0, 0])
    sin = np.abs(M[0, 1])

    # compute the new bounding dimensions of the image
    nW = int((h * sin) + (w * cos))
    nH = int((h * cos) + (w * sin))

    # adjust the rotation matrix to take into account translation
    M[0, 2] += (nW / 2) - cX
    M[1, 2] += (nH / 2) - cY

    # perform the actual rotation and return the image
    return cv2.warpAffine(image, M, (nW, nH))

# Original image
img_orig = cv2.imread('images/cat.jpg')
# Rotated image
rotated_img = rotate_bound(img_orig, theta)

# Plot original image with bounding boxes
fig, [ax1, ax2] = plt.subplots(nrows=2,ncols=1,figsize=(12, 18))
plt.tight_layout()
ax1.imshow(img_orig[...,::-1], aspect='auto')
ax1.axis('off')
ax1.add_patch(mpatches.Polygon(bb1[0], lw=3.0, fill=False, color='red'))
ax1.add_patch(mpatches.Polygon(bb1[1], lw=3.0, fill=False, color='red'))
ax1.add_patch(mpatches.Polygon(bb1[2], lw=3.0, fill=False, color='green'))

# Calculate the shape of rotated images
(heigth, width) = img_orig.shape[:2]
(cx, cy) = (width // 2, heigth // 2)
(new_height, new_width) = rotated_img.shape[:2]
(new_cx, new_cy) = (new_width // 2, new_height // 2)
print(cx,cy,new_cx,new_cy)

## Calculate the new bounding box coordinates
new_bb = {}
for i in bb1:
    new_bb[i] = rotate_box(bb1[i], cx, cy, heigth, width)

## Plot rotated image and bounding boxes
ax2.imshow(rotated_img[...,::-1], aspect='auto')
ax2.axis('off')
ax2.add_patch(mpatches.Polygon(new_bb[0],lw=3.0, fill=False, color='red'))
ax2.add_patch(mpatches.Polygon(new_bb[1],lw=3.0, fill=False, color='red'))
ax2.add_patch(mpatches.Polygon(new_bb[2],lw=3.0, fill=False, color='green'))
ax2.text(0.,0.,'Rotation by: ' + str(theta), transform=ax1.transAxes,
           horizontalalignment='left', verticalalignment='bottom', fontsize=30)
name='Output.png'
plt.savefig(name)
plt.cla()

In relation with 🏷️ opencv, python:

TheFifthDriver: Machine learning driving assistance on FPGA

FPGA implementation of a highly efficient real-time machine learning driving assistance application using a camera circuit.

📅 Dec 7, 2020  Hackster.io   fpga   python   opencv
Intelligent Video Analytics using SSD mobilenet on NVIDIA's Jetson Nano

This project implements a deep learning model on a Jetson Nano to count and track people passing in front of a video camera.

Real time motion detection in Raspberry Pi

In this article I show how to use a Raspberry Pi with motion detection algorithms and schedule task to detect objects using SSD Mobilenet and Yolo models.

Object detection using a Raspberry Pi with Yolo and SSD Mobilenet

This post how how to implement a light object detection algorithm

Subscribe to my newsletter 📰

and share it with your friends: