使用PIL在中心裁剪图像

47

我如何在中心裁剪图片?因为我知道这个框是一个4元组,定义了左、上、右和下像素坐标,但我不知道如何获取这些坐标,以便它在中心裁剪。


1
你知道图片的大小和你想要获取的较小图片的大小,对吗?你有什么可以展示的地方吗,展示一下你尝试过的内容? - Thomas Fenzl
9个回答

106
假设您知道要裁剪到的大小(新宽度X新高度):
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))

如果您尝试将小图像裁剪为更大的大小,这将会导致失败,但我假设您不会尝试那样做(或者您可以捕获该情况并不裁剪该图像)。


小修正,crop需要一个数组。im.crop((left, top, right, bottom)) - freakTheMighty
1
吹毛求疵:你的意思是一个序列,而不是一个数组。 :) - Eric O. Lebigot
5
需要注意的是,im.crop() 方法不会就地裁剪图像,而是返回裁剪后的图像。因此,需要使用 im=im.crop((left, top, right, bottom)) 来更新原始图像。 - jeanggi90
1
这段代码如果 (width - new_width)/2 != int 也会出错。如果强制转换为int,输出图像的像素数量可能不一致,在机器学习中这是一个问题,因为期望有特定的数据格式。等等...我有什么遗漏吗? - maxalmond
@maxalmond 四舍五入会同时作用于左、右和上、下,因此四舍五入后的差值始终为新宽度和新高度。 - andre_bauer
不幸的是,四舍五入确实是一个问题。例如,假设您有width, height = 357,450new_width = new_height = 357你最终得到一张357x358像素的图像。您必须做出明确的决定,在哪里裁剪额外的像素,这种情况下可以选择在顶部或底部裁剪。 - tuky

15

提出的解决方案存在一个潜在问题,即所需大小与旧大小之间存在奇数差异。每一边都不能有半个像素。必须选择一侧多加一个像素。

如果水平方向存在奇数差异,则下面的代码将把额外的像素放在右侧;如果垂直方向存在奇数差异,则将额外的像素放在底部。

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

5
四舍五入是不正确的,结果图像不一定具有所需的尺寸。正确的公式是 right = width - floor((width - new_width) / 2) - panda-34

6

我感觉对于大多数应用来说,最简单的解决方案仍然缺失。已接受的答案存在像素不平衡的问题,特别是对于机器学习算法而言,裁剪图像的像素数量至关重要。

在以下示例中,我想将图像从中心裁剪为224/100。我不在意像素是否向左或向右偏移0.5,只要输出图片始终符合定义的尺寸即可。这避免了对数学的依赖。*

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

输出结果是裁剪前的(在代码中未显示): enter image description here 裁剪后: enter image description here 元组显示了尺寸。

2

这是我在寻找的函数:

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

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

cropped_im.show()

以下内容摘自另一个答案


2

我最初使用了被接受的答案:

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

但是我遇到了Dean Pospisil提到的问题

所提出的解决方案可能存在一个潜在的问题,就是在所需大小和旧大小之间存在奇数差异的情况下。你无法在每一侧都有半个像素。必须选择一侧来多放一个像素。

Dean Pospisil的解决方案可行,我也想出了自己的计算方法来解决这个问题:

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

采用接受的答案,一个尺寸为180px x 180px的图片裁剪成180px x 101px会得到一个裁剪后的图片尺寸为180px x 102px

按照我的计算,应该正确地裁剪成180px x 101px


1

可能我来晚了,但至少我到了这里。

我想要中心裁剪图像,将9:16的图像转换成16:9的纵向或横向。

这是我使用的算法:

  1. 将图像分为4个相等的部分
  2. 丢弃第1部分和第4部分
  3. 将左侧设置为0,右侧设置为图像的宽度

代码:

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

后续:

在此输入图片描述 希望这能帮到您


1
裁剪中心和周围:
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))

1
你可以使用Torchvision的CenterCrop转换来实现这个功能。以下是一个示例。
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 适用于 torch.TensorPIL.Image,并保留数据类型,即当输入为 PIL.Image 时,输出也是一个(裁剪后的)PIL.Image。另外一个好处是,如果输入图像尺寸小于所需的裁剪尺寸,则上述转换会自动应用填充。


0
如果你想在四个边上都等比例地裁剪图片,你可以使用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)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接