Squared cropping functions

I created multiple functions to help with cropping square images. These can be handy for creating thumbnails, for example.

Another example might be machine learning. Image classification and training sometimes requires images of a fixed size, usually square.

You can download the examples at GitHub using the link below.

https://github.com/krakkus/krakkus_python_examples/tree/main/squared_cropping_functions

Functions

function: squared_crop_from_box_reduce_one

The first one is a rather simple way to get a square crop out of a bounding box and a larger image.

First, determine the longest side of the bounding box, and then recalculate either ymax or xmax of the box coordinates to equal the shortest side.

And because we are reducing the box in size, it can never go past the edge of the full image.

From left to right: full image + bounding box, regular crop, squared crop
import cv2


def squared_crop_from_box_reduce_one(image, box):
    # Extract the box coordinates
    ymin, xmin, ymax, xmax = box

    # Get the dimensions of the source image
    image_h, image_w, _ = image.shape

    # The coordinates must be in correct order
    assert xmax > xmin and ymax > ymin

    # The coordinates must be within the image
    assert xmax <= image_w and ymax <= image_h
    assert xmin >= 0 and ymin >= 0

    # Get the width and height of the box
    box_h = ymax - ymin
    box_w = xmax - xmin

    # If for some reason the box is already squared, get the crop
    if box_h == box_w:
        crop = image[ymin:ymax, xmin:xmax]
    # We need to adjust the box
    else:
        # If the box height is bigger than its width...
        if box_h > box_w:
            # Then we adjust the height to be equal to the width
            ymax = ymin + box_w
        # If the box width is bigger than its height
        else:
            # Then we adjust the width to be equal to the height
            xmax = xmin + box_h

        # We make the crop using the new coordinates
        crop = image[ymin:ymax, xmin:xmax]

    # We should now have a crop
    assert crop is not None

    # And it should be squared
    crop_h, crop_w, _ = crop.shape
    assert crop_h == crop_w

    # Return the final crop
    return crop


def main():
    # Filename of the image we want to test with
    fn_source_image = '..\\images\\india_women_hockey.jpg'

    # Coordinates in tensorflow format e.g. ymin, xmin, ymax, xmax
    box_coordinates = (105, 863, 664, 1130)

    # Load image into a numpy array
    source_image = cv2.imread(fn_source_image)

    # Extract the box coordinates
    ymin, xmin, ymax, xmax = box_coordinates

    # Do a regular crop
    regular_crop_image = source_image[ymin:ymax, xmin:xmax]

    # And do the custom crop
    custom_crop_image = squared_crop_from_box_expand(source_image, box_coordinates)

    # Draw rectangle around the object
    cv2.rectangle(source_image, (xmin, ymin), (xmax, ymax), (0, 255, 0), 3)

    # Show the image, regular crop and squared crop
    cv2.imshow('Source image', source_image)
    cv2.imshow('Regular crop', regular_crop_image)
    cv2.imshow('Custom crop', custom_crop_image)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


if __name__ == '__main__':
    main()

function: squared_crop_from_box_reduce_both

Just like the function above, this one takes an image and box coordinates, and returns a square crop. The difference is that this shortens the long side at both ends, leaving you with a center crop.

From left to right: full image + bounding box, regular crop, squared centered crop
import cv2


def squared_crop_from_box_reduce_one(image, box):
    # Extract the box coordinates
    ymin, xmin, ymax, xmax = box

    # Get the dimensions of the source image
    image_h, image_w, _ = image.shape

    # The coordinates must be in correct order
    assert xmax > xmin and ymax > ymin

    # The coordinates must be within the image
    assert xmax <= image_w and ymax <= image_h
    assert xmin >= 0 and ymin >= 0

    # Get the width and height of the box
    box_h = ymax - ymin
    box_w = xmax - xmin

    # If for some reason the box is already squared, get the crop
    if box_h == box_w:
        crop = image[ymin:ymax, xmin:xmax]
    # We need to adjust the box
    else:
        # If the box height is bigger than its width...
        if box_h > box_w:
            # Determine how much the height of the box needs shortened and take half of that
            half = int((box_h-box_w) / 2)
            # Use this to increase ymin so the crop becomes centered
            ymin += half
            # Then we adjust the height to be equal to the width
            ymax = ymin + box_w
        # If the box width is bigger than its height
        else:
            # Determine how much the width of the box needs shortened and take half of that
            half = int((box_w - box_h) / 2)
            # Use this to increase xmin so the crop becomes centered
            xmin += half
            # Then we adjust the width to be equal to the height
            xmax = xmin + box_h

        # We make the crop using the new coordinates
        crop = image[ymin:ymax, xmin:xmax]

    # We should now have a crop
    assert crop is not None

    # And it should be squared
    crop_h, crop_w, _ = crop.shape
    assert crop_h == crop_w

    # Return the final crop
    return crop


def main():
    # Filename of the image we want to test with
    fn_source_image = '..\\images\\india_women_hockey.jpg'

    # Coordinates in tensorflow format e.g. ymin, xmin, ymax, xmax
    box_coordinates = (105, 863, 664, 1130)

    # Load image into a numpy array
    source_image = cv2.imread(fn_source_image)

    # Extract the box coordinates
    ymin, xmin, ymax, xmax = box_coordinates

    # Do a regular crop
    regular_crop_image = source_image[ymin:ymax, xmin:xmax]

    # And do the custom crop
    custom_crop_image = squared_crop_from_box_expand(source_image, box_coordinates)

    # Draw rectangle around the object
    cv2.rectangle(source_image, (xmin, ymin), (xmax, ymax), (0, 255, 0), 3)

    # Show the image, regular crop and squared crop
    cv2.imshow('Source image', source_image)
    cv2.imshow('Regular crop', regular_crop_image)
    cv2.imshow('Custom crop', custom_crop_image)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


if __name__ == '__main__':
    main()

function: squared_crop_from_box_expand

Instead of cutting off part of the crop to make it square, this function expands the bounding box equally. When the bounding goes beyond the edges of the image, we shift it back within the limits. This might cause the target to not be in the center of the crop. But it is complete and square.

Also, if the squared crop area can’t fit within the bounds of the image, it will raise an exception. This could be solved by falling back to another cropping method, but I will leave that up to you.

From left to right: full image + bounding box, regular crop, squared and expanded crop
import cv2


def squared_crop_from_box_expand(image, box):
    # Extract the box coordinates
    ymin, xmin, ymax, xmax = box

    # Get the dimensions of the source image
    image_h, image_w, _ = image.shape

    # The coordinates must be in correct order
    assert xmax > xmin and ymax > ymin

    # The coordinates must be within the image
    assert xmax <= image_w and ymax <= image_h
    assert xmin >= 0 and ymin >= 0

    # Get the width and height of the box
    box_h = ymax - ymin
    box_w = xmax - xmin

    # If square box can't never fit, raise exception
    if max(box_h, box_w) > min(image_h, image_w):
        raise Exception('Squared box cant fit within image limits')

    # If for some reason the box is already squared, get the crop
    if box_h == box_w:
        crop = image[ymin:ymax, xmin:xmax]
    # We need to adjust the box
    else:
        # If the box height is bigger than its width...
        if box_h < box_w:
            # Determine how much the height of the box needs expanded and take half of that
            half = int((box_h-box_w) / 2)
            # Use this to decrease ymin so the crop becomes centered
            ymin -= half
            # Then we adjust the height to be equal to the width
            ymax = ymin + box_w
        # If the box width is bigger than its height
        else:
            # Determine how much the width of the box needs expanded and take half of that
            half = int((box_w - box_h) / 2)
            # Use this to decrease xmin so the crop becomes centered
            xmin -= half
            # Then we adjust the width to be equal to the height
            xmax = xmin + box_h

        # Because we have expanded the box, there is a change that box is partially outside the image ...
        # and we need to shift it back withing the images limits
        if ymin < 0:
            diff = 0 - ymin
            ymin += diff
            ymax += diff
        if xmin < 0:
            diff = 0 - xmin
            xmin += diff
            xmax += diff
        if ymax > image_h:
            diff = ymax - image_h
            ymin -= diff
            ymax -= diff
        if xmax > image_w:
            diff = xmax - image_w
            xmin -= diff
            xmax -= diff

        # We make the crop using the new coordinates
        crop = image[ymin:ymax, xmin:xmax]

    # We should now have a crop
    assert crop is not None

    # And it should be squared
    crop_h, crop_w, _ = crop.shape
    assert crop_h == crop_w

    # Return the final crop
    return crop


def main():
    # Filename of the image we want to test with
    fn_source_image = '..\\images\\india_women_hockey.jpg'

    # Coordinates in tensorflow format e.g. ymin, xmin, ymax, xmax
    box_coordinates = (105, 863, 664, 1130)

    # Load image into a numpy array
    source_image = cv2.imread(fn_source_image)

    # Extract the box coordinates
    ymin, xmin, ymax, xmax = box_coordinates

    # Do a regular crop
    regular_crop_image = source_image[ymin:ymax, xmin:xmax]

    # And do the custom crop
    custom_crop_image = squared_crop_from_box_expand(source_image, box_coordinates)

    # Draw rectangle around the object
    cv2.rectangle(source_image, (xmin, ymin), (xmax, ymax), (0, 255, 0), 3)

    # Show the image, regular crop and squared crop
    cv2.imshow('Source image', source_image)
    cv2.imshow('Regular crop', regular_crop_image)
    cv2.imshow('Custom crop', custom_crop_image)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


if __name__ == '__main__':
    main()

function: squared_crop_from_image_reduce_one

Instead of box coordinates, this function takes an image only and returns it squared by cutting of the excess part at the far end of the image.

Left: the original image, right: the squared cropped image
import cv2


def squared_crop_from_image_reduce_one(image):
    # Get the dimensions of the source image
    image_h, image_w, _ = image.shape

    # If for some reason the image is already squared, return the image
    if image_h == image_w:
        crop = image
    # We need to adjust the image
    else:
        # If the image height is bigger than its width...
        if image_h > image_w:
            # Then we crop with the height to be equal to the width
            crop = image[0:image_w, 0:image_w]
        # If the image width is bigger than its height
        else:
            # Then we crop with the width to be equal to the height
            crop = image[0:image_h, 0:image_h]

    # We should now have a crop
    assert crop is not None

    # And it should be squared
    crop_h, crop_w, _ = crop.shape
    assert crop_h == crop_w

    # Return the final crop
    return crop


def main():
    # Filename of the image we want to test with
    fn_source_image = '..\\images\\german_men_hockey.jpg'

    # Load image into a numpy array
    source_image = cv2.imread(fn_source_image)

    # And do the custom crop
    custom_crop_image = squared_crop_from_image_reduce_one(source_image)

    # Show the image, regular crop and squared crop
    cv2.imshow('Source image', source_image)
    cv2.imshow('Custom crop', custom_crop_image)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


if __name__ == '__main__':
    main()

function: squared_crop_from_image_reduce_both

This function variant takes an image and cuts off an equal amount of both ends of the long side of the image to make it square.

Left: original image, right: a square crop taken from the center.
import cv2


def squared_crop_from_image_reduce_one(image):
    # Get the dimensions of the source image
    image_h, image_w, _ = image.shape

    # If for some reason the image is already squared, return the image
    if image_h == image_w:
        crop = image
    # We need to adjust the image
    else:
        # If the image height is bigger than its width...
        if image_h > image_w:
            # Determine how much the height of the image needs shortened and take half of that
            half = int((image_h-image_w) / 2)
            # Then we crop with the height to be equal to the width
            crop = image[0+half:image_w+half, 0:image_w]
        # If the image width is bigger than its height
        else:
            # Determine how much the width of the image needs shortened and take half of that
            half = int((image_w-image_h) / 2)
            # Then we crop with the width to be equal to the height
            crop = image[0:image_h, 0+half:image_h+half]

    # We should now have a crop
    assert crop is not None

    # And it should be squared
    crop_h, crop_w, _ = crop.shape
    assert crop_h == crop_w

    # Return the final crop
    return crop


def main():
    # Filename of the image we want to test with
    fn_source_image = '..\\images\\german_men_hockey.jpg'

    # Load image into a numpy array
    source_image = cv2.imread(fn_source_image)

    # And do the custom crop
    custom_crop_image = squared_crop_from_image_reduce_one(source_image)

    # Show the image, regular crop and squared crop
    cv2.imshow('Source image', source_image)
    cv2.imshow('Custom crop', custom_crop_image)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


if __name__ == '__main__':
    main()

function: squared_crop_from_image_expand_one

This function takes and image and add black to whatever side of short of making the image square.

Left: original image, right, the expanded square image (border is difficult to see)
import cv2

import numpy as np


def squared_crop_from_image_expand_one(image):
    # Get the dimensions of the source image
    image_h, image_w, _ = image.shape

    # If for some reason the image is already squared, return the image
    if image_h == image_w:
        crop = image
    # We need to adjust the image
    else:
        # Create a new empty square image which will fit the image
        size = max(image_h, image_w)
        crop = np.zeros((size, size, 3), np.uint8)

        # Copy the image to the top left in the new square image
        crop[0:image_h, 0:image_w] = image

    # We should now have a crop
    assert crop is not None

    # And it should be squared
    crop_h, crop_w, _ = crop.shape
    assert crop_h == crop_w

    # Return the final crop
    return crop


def main():
    # Filename of the image we want to test with
    fn_source_image = '..\\images\\german_men_hockey.jpg'

    # Load image into a numpy array
    source_image = cv2.imread(fn_source_image)

    # And do the custom crop
    custom_crop_image = squared_crop_from_image_expand_one(source_image)

    # Show the image, regular crop and squared crop
    cv2.imshow('Source image', source_image)
    cv2.imshow('Custom crop', custom_crop_image)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


if __name__ == '__main__':
    main()

function: squared_crop_from_image_expand_both

This one adds black to both short ends of the none squared image, so it becomes square.

Left: original image, right: image with added black space to make it square.
import cv2

import numpy as np


def squared_crop_from_image_expand_both(image):
    # Get the dimensions of the source image
    image_h, image_w, _ = image.shape

    # If for some reason the image is already squared, return the image
    if image_h == image_w:
        crop = image
    # We need to adjust the image
    else:
        # Create a new empty square image which will fit the image
        size = max(image_h, image_w)
        crop = np.zeros((size, size, 3), np.uint8)

        # If the image height is bigger than its width...
        if image_h > image_w:
            # Determine how much the width of the image needs extending and take half of that
            half = int((image_h-image_w) / 2)
            # Then we copy the original image into the center of the black square image
            crop[0:image_h, 0+half:image_w+half] = image
        # If the image width is bigger than its height
        else:
            # Determine how much the height of the image needs extending and take half of that
            half = int((image_w-image_h) / 2)
            # Then we copy the original image into the center of the black square image
            crop[0 + half:image_h + half, 0:image_w] = image

    # We should now have a crop
    assert crop is not None

    # And it should be squared
    crop_h, crop_w, _ = crop.shape
    assert crop_h == crop_w

    # Return the final crop
    return crop


def main():
    # Filename of the image we want to test with
    fn_source_image = '..\\images\\german_men_hockey.jpg'

    # Load image into a numpy array
    source_image = cv2.imread(fn_source_image)

    # And do the custom crop
    custom_crop_image = squared_crop_from_image_expand_both(source_image)

    # Show the image, regular crop and squared crop
    cv2.imshow('Source image', source_image)
    cv2.imshow('Custom crop', custom_crop_image)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


if __name__ == '__main__':
    main()

Leave a Reply

Your email address will not be published. Required fields are marked *