Crop an image in the centre using PIL
Asked Answered
B

9

48

How can I crop an image in the center? Because I know that the box is a 4-tuple defining the left, upper, right, and lower pixel coordinate but I don't know how to get these coordinates so it crops in the center.

Bose answered 20/5, 2013 at 9:14 Comment(1)
you do know the size of the image and the size of the smaller image you want to get, I assume? Have you anything to show where you tried?Gehenna
G
108

Assuming you know the size you would like to crop to (new_width X new_height):

import Image
im = Image.open(<your image>)
width, height = im.size   # Get dimensions

left = (width - new_width)/2
top = (height - new_height)/2
right = (width + new_width)/2
bottom = (height + new_height)/2

# Crop the center of the image
im = im.crop((left, top, right, bottom))

This will break if you attempt to crop a small image larger, but I'm going to assume you won't be trying that (Or that you can catch that case and not crop the image).

Granville answered 20/5, 2013 at 11:9 Comment(6)
Small correction, crop takes an array. im.crop((left, top, right, bottom))Instruction
Nitpick: You mean a sequence, not an array. :)Corin
A catch is that im.crop() does not crop the image in-place but return the cropped image. Hence, im=im.crop((left, top, right, bottom)) is required.Glioma
This code also breaks if (width - new_width)/2 != int if int is forced by casting, the pixel-count of the output image may not be consistent, which is a problem in ML where a certain data format is expected. wait ... am I missing something?Booma
@Booma rounding happens and will be the same for left,right and for top, bottom, so the diff after rounding will always be exactly new_width and new_heightKarafuto
Unfortunately, the rounding is a problem indeed. e.g. assume you have width, height = 357,450, new_width = new_height = 357 you end up with a 357×358 image. You have to make the explicit decision, where to crop that extra pixel, either top or bottom in this case.Marinamarinade
B
15

One potential problem with the proposed solution is in the case there is an odd difference between the desired size, and old size. You can't have a half pixel on each side. One has to choose a side to put an extra pixel on.

If there is an odd difference for the horizontal the code below will put the extra pixel to the right, and if there is and odd difference on the vertical the extra pixel goes to the bottom.

import numpy as np

def center_crop(img, new_width=None, new_height=None):        

    width = img.shape[1]
    height = img.shape[0]

    if new_width is None:
        new_width = min(width, height)

    if new_height is None:
        new_height = min(width, height)

    left = int(np.ceil((width - new_width) / 2))
    right = width - int(np.floor((width - new_width) / 2))

    top = int(np.ceil((height - new_height) / 2))
    bottom = height - int(np.floor((height - new_height) / 2))

    if len(img.shape) == 2:
        center_cropped_img = img[top:bottom, left:right]
    else:
        center_cropped_img = img[top:bottom, left:right, ...]

    return center_cropped_img
Brunn answered 3/9, 2015 at 21:38 Comment(1)
rounding is incorrect, the result image will not always have the required dimensions. Correct formula is right = width - floor((width - new_width) / 2)Willwilla
B
6

I feel like the simplest solution that is most suitable for most applications is still missing. The accepted answer has an issue with uneven pixels and especially for ML algorithms, the pixel count of the cropped image is paramount.

In the following example, I would like to crop an image to 224/100, from the center. I do not care if the pixels are shifted to the left or right by 0.5, as long as the output picture will always be of the defined dimensions. It avoids the reliance on math.*.

from PIL import Image
import matplotlib.pyplot as plt


im = Image.open("test.jpg")
left = int(im.size[0]/2-224/2)
upper = int(im.size[1]/2-100/2)
right = left +224
lower = upper + 100

im_cropped = im.crop((left, upper,right,lower))
print(im_cropped.size)
plt.imshow(np.asarray(im_cropped))

The output is before cropping (not shown in code):

enter image description here

after:

enter image description here

The touples show the dimensions.

Booma answered 27/3, 2020 at 9:19 Comment(0)
C
3

I originally used the accepted answer:

import Image
im = Image.open(<your image>)
width, height = im.size   # Get dimensions

left = (width - new_width)/2
top = (height - new_height)/2
right = (width + new_width)/2
bottom = (height + new_height)/2

# Crop the center of the image
im = im.crop((left, top, right, bottom))

But I came into the problem mentioned by Dean Pospisil

One potential problem with the proposed solution is in the case there is an odd difference between the desired size, and old size. You can't have a half pixel on each side. One has to choose a side to put an extra pixel on.

Dean Pospisil's solution works, I also came up with my own calculation to fix this:

import Image
im = Image.open(<your image>)
width, height = im.size   # Get dimensions

left = round((width - new_width)/2)
top = round((height - new_height)/2)
x_right = round(width - new_width) - left
x_bottom = round(height - new_height) - top
right = width - x_right
bottom = height - x_bottom

# Crop the center of the image
im = im.crop((left, top, right, bottom))

With the accepted answer, an image of 180px x 180px to be cropped to 180px x 101px will result in a cropped image to 180px x 102px.

With my calculation, it will be correctly cropped to 180px x 101px

Codeclination answered 22/5, 2020 at 5:55 Comment(0)
T
2

This is the function I was looking for:

from PIL import Image
im = Image.open("test.jpg")

crop_rectangle = (50, 50, 200, 200)
cropped_im = im.crop(crop_rectangle)

cropped_im.show()

Taken from another answer

Theology answered 12/8, 2017 at 8:1 Comment(0)
E
2

You could use Torchvision's CenterCrop transformation for this. Here's an example

from PIL import Image
from torchvision.transforms import functional as F

crop_size = 256  # can be either an integer or a tuple of ints for (height, width) separately
img = Image.open(<path_to_your_image>)
cropped_img = F.center_crop(img, crop_size)

F.center_crop works with torch.Tensors or PIL.Images and retains the data type i.e. when input is a PIL.Image then output is also a (cropped) PIL.Image. An added bonus is that the above transformation would automatically apply padding in case the input image size is smaller than the requested crop size.

Embed answered 14/2, 2023 at 14:19 Comment(0)
H
1

Crop center and around:

def im_crop_around(img, xc, yc, w, h):
    img_width, img_height = img.size  # Get dimensions
    left, right = xc - w / 2, xc + w / 2
    top, bottom = yc - h / 2, yc + h / 2
    left, top = round(max(0, left)), round(max(0, top))
    right, bottom = round(min(img_width - 0, right)), round(min(img_height - 0, bottom))
    return img.crop((left, top, right, bottom))


def im_crop_center(img, w, h):
    img_width, img_height = img.size
    left, right = (img_width - w) / 2, (img_width + w) / 2
    top, bottom = (img_height - h) / 2, (img_height + h) / 2
    left, top = round(max(0, left)), round(max(0, top))
    right, bottom = round(min(img_width - 0, right)), round(min(img_height - 0, bottom))
    return img.crop((left, top, right, bottom))
Hemistich answered 17/12, 2019 at 21:24 Comment(0)
C
1

May be i am late to this party but at least i am here I want to center crop the image convert 9:16 image to 16:9 portrait to landscape

This is the algo i used :

  1. divide image in 4 equal parts
  2. discard part 1 and part four
  3. Set left to 0, right to width of image

code :

from PIL import Image

im = Image.open('main.jpg')
width, height = im.size

if height > width:
    h2 = height/2
    h4 = h2/2

    border = (0, h4, width, h4*3)

    cropped_img = im.crop(border)
    cropped_img.save("test.jpg")

before :

enter image description here

after:

enter image description here I hope this helps

Cordate answered 28/6, 2020 at 23:39 Comment(0)
E
0

If you want to crop equally on all four sides you can use ImageOps.crop

from PIL import Image, ImageOps

pixels_to_remove = 32

img = Image.open(img_path)
img = ImageOps.crop(input_img, border=pixels_to_remove)
Epiclesis answered 1/12, 2023 at 22:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.