HumanSignal / label-studio

Label Studio is a multi-type data labeling and annotation tool with standardized output format
https://labelstud.io
Apache License 2.0
19.37k stars 2.41k forks source link

Bounding boxes displaced in the image. #5923

Open luforestal opened 5 months ago

luforestal commented 5 months ago

Hi, I have exported my annotations in JSON format. But when I try to view them in python, they appear offset almost 100 pixels. Here is an example of how they look in LabelStudio and how I see them in my code.

The code that I'm using:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from PIL import Image

def get_rotated_bounding_box_corners_and_extremes(x_center_pct, y_center_pct, width_pct, height_pct, rotation_deg, image_width, image_height):
    """
    Calculate the four corner coordinates of a rotated bounding box and its extreme values.

    Args:
    x_center_pct (float): x-coordinate of the center of the box in percentage.
    y_center_pct (float): y-coordinate of the center of the box in percentage.
    width_pct (float): Width of the box in percentage.
    height_pct (float): Height of the box in percentage.
    rotation_deg (float): Rotation of the box in degrees.
    image_width (int): Width of the image in pixels.
    image_height (int): Height of the image in pixels.

    Returns:
    corners (list of tuples): List with the (x, y) coordinates of the four corners of the rotated bounding box.
    extremes (tuple): Tuple with (xmin, xmax, ymin, ymax) of the rotated bounding box.
    """

    x_center = x_center_pct / 100 * image_width
    y_center = y_center_pct / 100 * image_height
    width = width_pct / 100 * image_width
    height = height_pct / 100 * image_height

    rotation_rad = np.deg2rad(rotation_deg)

    corners = np.array([
        [x_center - width / 2, y_center - height / 2],
        [x_center + width / 2, y_center - height / 2],
        [x_center + width / 2, y_center + height / 2],
        [x_center - width / 2, y_center + height / 2]
    ])

    rotation_matrix = np.array([
        [np.cos(rotation_rad), -np.sin(rotation_rad)],
        [np.sin(rotation_rad), np.cos(rotation_rad)]
    ])

    rotated_corners = np.dot(corners - np.array([x_center, y_center]), rotation_matrix.T) + np.array([x_center, y_center])

    rotated_corners = rotated_corners.astype(int)

    # Obtener extremos
    x_coords = rotated_corners[:, 0]
    y_coords = rotated_corners[:, 1]
    xmin = x_coords.min()
    xmax = x_coords.max()
    ymin = y_coords.min()
    ymax = y_coords.max()

    return rotated_corners.tolist(), (xmin, xmax, ymin, ymax)

def plot_rotated_bounding_box(image_path, corners):
    """
    Plot the rotated bounding box on the original image.

    Args:
    image_path (str): Path to the original image.
    corners (list of tuples): List with the (x, y) coordinates of the four corners of the rotated bounding box.
    """

    img = Image.open(image_path)
    img_width, img_height = img.size
    print('width', img_width, 'h', img_height)

    fig, ax = plt.subplots(1)
    ax.imshow(img)

    for i in range(len(corners)):
        next_i = (i + 1) % len(corners)
        x_values = [corners[i][0], corners[next_i][0]]
        y_values = [corners[i][1], corners[next_i][1]]
        ax.plot(x_values, y_values, 'r-')

    plt.gca().set_aspect('equal', adjustable='box')
    plt.show()

# Example
image_path = 'save_frame_test.jpg'  
image_width = 640
image_height = 352
x_center_pct =  72.26388590829315
y_center_pct = 87.85889758341253
width_pct = 14.273870065050815  
height_pct = 9.12891897312828
rotation_deg = -25.40638952513592

Screenshots IMAGE AND LABELS IN LABELSTUDIO image

IMAGE AND ONE LABEL IN PYTHON My labels in python (at least one example) image

Environment (please complete the following information):

sajarin commented 5 months ago

hey @luforestal thanks for the bug report. As a sane first step, can we try upgrading Label Studio to the latest? We just want to make sure that this bug is present on the latest version.

mgcrea commented 5 months ago

Hey, chiming in to say that I am encountering a similar issue with rotated bounding boxes when trying to work from the JSON export.

I get inconsistencies with the rotation that are visible for angles > 5°.

My example:

LabelStudio

Screenshot 2024-06-16 at 23 02 33

Script output result (using nodejs/sharp to do the drawing and compositing with svg).

Screenshot 2024-06-16 at 23 02 46

It looks like the angle value used in labelstudio does not have the center of the rectangle (cx, cy) for origin (as expected) but the top-left point of the rectangle instead, which is quite strange as the UI actually rotates around the center!

So for me the fix looks like to patch all rects with:

const patchLabelStudioRect = <T extends Rect>(rect: T): T => {
  const { x, y } = rotatePoint({ x: rect.x, y: rect.y }, rect.rotation, getRectCenter(rect));
  rect.x = x;
  rect.y = y;
  return rect;
};

Interesting to dig this up a bit more, do you know where I can find the code responsible for drawing the boxes?

I'm using label-studio 1.2.0.