44

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.

user2401069
  • 451
  • 1
  • 4
  • 4
  • 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? – Thomas Fenzl May 20 '13 at 10:21

8 Answers8

101

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).

Julien
  • 13
  • 5
Chris Clarke
  • 2,103
  • 2
  • 14
  • 19
  • Small correction, crop takes an array. im.crop((left, top, right, bottom)) – freakTheMighty Aug 20 '13 at 21:37
  • 1
    Nitpick: You mean a sequence, not an array. :) – Eric O. Lebigot Nov 11 '13 at 10:07
  • 4
    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. – jeanggi90 Apr 30 '19 at 16:06
  • 1
    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? – maxalmond Apr 02 '20 at 09:47
  • @maxalmond 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_height – andre_bauer Apr 22 '20 at 14:50
  • 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. – tuky Mar 01 '21 at 13:25
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
Wok
  • 4,956
  • 7
  • 42
  • 64
Dean Pospisil
  • 159
  • 1
  • 3
  • 5
    rounding is incorrect, the result image will not always have the required dimensions. Correct formula is `right = width - floor((width - new_width) / 2)` – panda-34 May 03 '16 at 15:33
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.

maxalmond
  • 375
  • 3
  • 9
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

penduDev
  • 4,743
  • 35
  • 37
2

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

Andrew T
  • 103
  • 7
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))
Mendi Barel
  • 3,350
  • 1
  • 23
  • 24
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

rohan parab
  • 1,589
  • 1
  • 14
  • 18
0

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.

mjkvaak
  • 399
  • 3
  • 6