How to rotate a rectangle/bounding box together with an image

Question:

I’m working on a data augmentation and im trying to generate synthetic version of every image in my dataset. So i need to rotate images and together with bounding boxes as well in the images.

im only going to rotate images by 90, 180, 270 degrees.

I’m using pascal-voc annotation format as shown here. As a result i have following info.

x_min, y_min, x_max, y_max. Origin of image(i can get it from image size)

i’ve searched a lot on it. But i couldnt find any solution for rotating bounding boxes( or rectangles)

i’ve tried something like this;
i’ve got this solution from here and tried to adapt it but didnt work.

def rotateRect(bndbox, img_size, angle):
    angle = angle * math.pi/180 # conversion from degree to radian
    y_min, y_max, x_min, x_max = bndbox
    ox, oy = img_size[0]/2, img_size[1]/2 # coordinate of origin of image
    rect = [[x_min, y_min], [x_min, y_max],[x_max, y_min],[x_max, y_max]] # coordinates of points of corners of bounding box rectangle.
    nrp = [[0, 0], [0,0 ],[0,0],[0, 0]] #new rectangle position

    for i, pt in enumerate(rect):
        newPx = int(ox + math.cos(angle) * (pt[0] - ox) - math.sin(angle) * (pt[1] - oy)) # new coordinate of point x
        newPy = int(oy + math.sin(angle) * (pt[0] - ox) + math.cos(angle) * (pt[1] - oy))  # new coordinate of point y
        nrp[i] = newPx,newPy
        nx_min, ny_min, nx_max, ny_max = nrp[0][0], nrp[0][1], nrp[2][0], nrp[2][1] # new bounding boxes values. 
     return [ny_min, ny_max, nx_min, nx_max]

thanks.

EDIT:

I need to get this rotation together with image and bounding box.
First picture is original one, second one is rotated as 90 degree(counter-clockwise) and 3rd picture is rotated as -90 degree (counter-wise).
i tried to rotate manually on paint to be precise. So i got these results.

   original of img size:(640x480)
   rotation orj, 90, -90
            --------------
    x_min = 98,  345, 17
    y_min = 345, 218, 98
    x_max = 420, 462, 420
    y_max = 462, 540, 134

Image Rotations; Original, 90 rotation, -90 rotation

Asked By: livan3li

||

Answers:

OK, maybe this can help. Assuming your rectangle is stored as a set of 4 points marking the corners, this will do arbitrary rotation around another point. If you store the points in circular order, then plot will even look like rectangles. I’m not forcing the aspect ratio on the plot, so the rotated rectangle looks like it is skewed, but it’s not.

import math
import matplotlib.pyplot as plt

def rotatebox( rect, center, degrees ):
    rads = math.radians(degrees)

    newpts = []
    for pts in rect:
        diag_x = center[0] - pts[0]
        diag_y = center[1] - pts[1]

        # Rotate the diagonal from center to top left

        newdx = diag_x * math.cos(rads) - diag_y * math.sin(rads)
        newdy = diag_x * math.sin(rads) + diag_y * math.cos(rads)
        newpts.append( (center[0] + newdx, center[1] + newdy) )

    return newpts

# Return a set of X and Y for plotting.

def corners(rect):
    return [k[0] for k in rect]+[rect[0][0]],[k[1] for k in rect]+[rect[0][1]]

rect = [[50,50],[50,120],[150,120],[150,50]]
plt.plot( *corners(rect) )
rect = rotatebox( rect, (100,100), 135 )
plt.plot( *corners(rect) )
plt.show()

The code can be made simpler for the 90/180/270 cases, because no trigonometry is needed. It’s just addition, subtraction, and swapping points. Here, the rectangle is just stored [minx,miny,maxx,maxy].

import matplotlib.pyplot as plt

def rotaterectcw( rect, center ):
    x0 = rect[0] - center[0]
    y0 = rect[1] - center[1]
    x1 = rect[2] - center[0]
    y1 = rect[3] - center[1]
    return center[0]+y0, center[1]-x0, center[0]+y1, center[1]-x1

def corners(rect):
    x0, y0, x1, y1 = rect
    return [x0,x0,x1,x1,x0],[y0,y1,y1,y0,y0]

rect = (50,50,150,120)
plt.plot( *corners(rect) )
rect = rotaterectcw( rect, (60,100) )
plt.plot( *corners(rect) )
rect = rotaterectcw( rect, (60,100) )
plt.plot( *corners(rect) )
rect = rotaterectcw( rect, (60,100) )
plt.plot( *corners(rect) )
plt.show()
Answered By: Tim Roberts

i’ve found simpler way.
enter image description here

Base on this aproach. We can do this calculation without using trigonometric calculations like this:

def rotate90Deg(bndbox, img_width): # just passing width of image is enough for 90 degree rotation.
   x_min,y_min,x_max,y_max = bndbox
   new_xmin = y_min
   new_ymin = img_width-x_max
   new_xmax = y_max
   new_ymax = img_width-x_min
   return [new_xmin, new_ymin,new_xmax,new_ymax]


rotate90Deg([98,345,420,462],640)

this can be used over and over again. And returns new bounding boxes values in Pascal-voc format.

Answered By: livan3li

I tried the implementations mentioned in the other answers but none of them worked for me. I had to rotate the image and the bounding box clockwise by 90 degrees so I made this method,

def rotate90Deg( bndbox , image_width ):
    """
    image_width: Width of the image after clockwise rotation of 90 degrees
    """
    x_min,y_min,x_max,y_max = bndbox
    new_xmin = image_width - y_max # Reflection about center X-line
    new_ymin = x_min
    new_xmax = image_width - y_min # Reflection about center X-line
    new_ymax = x_max
    return [new_xmin, new_ymin,new_xmax,new_ymax]

Usage

image = Image.open( "..." )
image = image.rotate( -90 )
new_bbox = rotate90Deg( bbox , image.width )
Answered By: Shubham Panchal