33

谁能帮我弄清楚我的图像自动裁剪脚本中发生了什么?我有一个具有大透明区域/空间的 png 图像。我希望能够自动裁剪该空间并留下必需品。原始图像有一个正方形的画布,最好是矩形的,只封装分子。

这是原始图像: 原始图像

做一些谷歌搜索,我发现了 PIL/python 代码,据报道它可以工作,但是在我手中,运行下面的代码会过度裁剪图像。

import Image
import sys

image=Image.open('L_2d.png')
image.load()

imageSize = image.size
imageBox = image.getbbox()

imageComponents = image.split()

rgbImage = Image.new("RGB", imageSize, (0,0,0))
rgbImage.paste(image, mask=imageComponents[3])
croppedBox = rgbImage.getbbox()
print imageBox
print croppedBox
if imageBox != croppedBox:
    cropped=image.crop(croppedBox)
    print 'L_2d.png:', "Size:", imageSize, "New Size:",croppedBox
    cropped.save('L_2d_cropped.png')

输出是这样的:脚本的输出

任何更熟悉图像处理/PLI 的人都可以帮我解决这个问题吗?

4

8 回答 8

42

安装枕头

pip install Pillow

并用作

from PIL import Image
    
image=Image.open('L_2d.png')

imageBox = image.getbbox()
cropped = image.crop(imageBox)
cropped.save('L_2d_cropped.png')

当您按 搜索边界时mask=imageComponents[3],您仅按蓝色通道搜索。

于 2013-01-08T09:30:26.617 回答
34

您可以使用 numpy,将图像转换为数组,查找所有非空列和行,然后从这些创建图像:

import Image
import numpy as np

image=Image.open('L_2d.png')
image.load()

image_data = np.asarray(image)
image_data_bw = image_data.max(axis=2)
non_empty_columns = np.where(image_data_bw.max(axis=0)>0)[0]
non_empty_rows = np.where(image_data_bw.max(axis=1)>0)[0]
cropBox = (min(non_empty_rows), max(non_empty_rows), min(non_empty_columns), max(non_empty_columns))

image_data_new = image_data[cropBox[0]:cropBox[1]+1, cropBox[2]:cropBox[3]+1 , :]

new_image = Image.fromarray(image_data_new)
new_image.save('L_2d_cropped.png')

结果看起来像 裁剪图像

如果有任何不清楚的地方,请询问。

于 2013-01-08T09:21:59.043 回答
21

我测试了这篇文章中回答的大部分答案,但是,我最终得到了自己的答案。我使用了蟒蛇python3。

from PIL import Image, ImageChops

def trim(im):
    bg = Image.new(im.mode, im.size, im.getpixel((0,0)))
    diff = ImageChops.difference(im, bg)
    diff = ImageChops.add(diff, diff, 2.0, -100)
    #Bounding box given as a 4-tuple defining the left, upper, right, and lower pixel coordinates.
    #If the image is completely empty, this method returns None.
    bbox = diff.getbbox()
    if bbox:
        return im.crop(bbox)

if __name__ == "__main__":
    bg = Image.open("test.jpg") # The image to be cropped
    new_im = trim(bg)
    new_im.show()
于 2018-02-04T07:57:57.217 回答
5

这是另一个使用pyvips的版本。

import sys
import pyvips

image = pyvips.Image.new_from_file(sys.argv[1])
left, top, width, height = image.find_trim(threshold=2, background=[255, 255, 255])
image = image.crop(left, top, width, height)
image.write_to_file(sys.argv[2])

pyvips 修剪器对摄影图像很有用。它执行中值过滤,减去背景,找到超过阈值的像素,并删除该集合之外的第一行和最后一行和列。中值和阈值意味着它不会被 JPEG 压缩之类的东西所抛弃,因为噪声或不可见的压缩伪影会混淆其他修剪器。

如果您不提供background参数,它将使用 (0, 0) 处的像素。threshold默认为 10,这对于 JPEG 来说是正确的。

它在8k x 8k 像素的 NASA 地球图像上运行:

$ time ./trim.py /data/john/pics/city_lights_asia_night_8k.jpg x.jpg
real    0m1.868s
user    0m13.204s
sys     0m0.280s
peak memory: 100mb

前:

地球在收获前的晚上

后:

收割后的地球

这里有一篇博文,里面有更多的讨论

于 2017-06-23T11:00:41.333 回答
2

最近看到这篇文章,注意到 PIL 库发生了变化。我用openCV重新实现了这个:

import cv2

def crop_im(im, padding=0.1):
    """
    Takes cv2 image, im, and padding % as a float, padding,
    and returns cropped image.
    """
    bw = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
    rows, cols = bw.shape
    non_empty_columns = np.where(bw.min(axis=0)<255)[0]
    non_empty_rows = np.where(bw.min(axis=1)<255)[0]
    cropBox = (int(min(non_empty_rows) * (1 - padding)),
                int(min(max(non_empty_rows) * (1 + padding), rows)),
                int(min(non_empty_columns) * (1 - padding)),
                int(min(max(non_empty_columns) * (1 + padding), cols)))
    cropped = im[cropBox[0]:cropBox[1]+1, cropBox[2]:cropBox[3]+1 , :]

    return cropped

im = cv2.imread('testimage.png')
cropped = crop_im(im)
cv2.imshow('', cropped)
cv2.waitKey(0)
于 2017-02-08T21:08:58.037 回答
2

这是对 snew 回复的改进,它适用于透明背景。mathematical morphology我们可以让它在白色背景(而不是透明)上工作,使用以下代码:

from PIL import Image
from skimage.io import imread
from skimage.morphology import convex_hull_image
im = imread('L_2d.jpg')
plt.imshow(im)
plt.title('input image')
plt.show()
# create a binary image
im1 = 1 - rgb2gray(im)
threshold = 0.5
im1[im1 <= threshold] = 0
im1[im1 > threshold] = 1
chull = convex_hull_image(im1)
plt.imshow(chull)
plt.title('convex hull in the binary image')
plt.show()
imageBox = Image.fromarray((chull*255).astype(np.uint8)).getbbox()
cropped = Image.fromarray(im).crop(imageBox)
cropped.save('L_2d_cropped.jpg')
plt.imshow(cropped)
plt.show()

在此处输入图像描述 在此处输入图像描述 在此处输入图像描述

于 2018-08-06T08:16:30.497 回答
2

pilkit已经包含用于自动裁剪的处理器TrimBorderColor。像这样的东西应该工作:

from pilkit.lib import Image
from pilkit.processors import TrimBorderColor

img = Image.open('/path/to/my/image.png')
processor = TrimBorderColor()
new_img = processor.process(img)

https://github.com/matthewwithanm/pilkit/blob/b24990167aacbaab3db6d8ec9a02f9ad42856898/pilkit/processors/crop.py#L33

于 2019-12-10T19:19:05.180 回答
0

我知道这篇文章很旧,但由于某种原因,没有一个建议的答案对我有用。所以我从现有答案中破解了我自己的版本:

import Image
import numpy as np
import glob
import shutil
import os

grey_tolerance = 0.7 # (0,1) = crop (more,less)

f = 'test_image.png'
file,ext = os.path.splitext(f)

def get_cropped_line(non_empty_elms,tolerance,S):
    if (sum(non_empty_elms) == 0):
        cropBox = ()
    else:
        non_empty_min = non_empty_elms.argmax()
        non_empty_max = S - non_empty_elms[::-1].argmax()+1
        cropBox = (non_empty_min,non_empty_max)
    return cropBox

def get_cropped_area(image_bw,tol):
    max_val = image_bw.max()
    tolerance = max_val*tol
    non_empty_elms = (image_bw<=tolerance).astype(int)
    S = non_empty_elms.shape
    # Traverse rows
    cropBox = [get_cropped_line(non_empty_elms[k,:],tolerance,S[1]) for k in range(0,S[0])]
    cropBox = filter(None, cropBox)
    xmin = [k[0] for k in cropBox]
    xmax = [k[1] for k in cropBox]
    # Traverse cols
    cropBox = [get_cropped_line(non_empty_elms[:,k],tolerance,S[0]) for k in range(0,S[1])]
    cropBox = filter(None, cropBox)
    ymin = [k[0] for k in cropBox]
    ymax = [k[1] for k in cropBox]
    xmin = min(xmin)
    xmax = max(xmax)
    ymin = min(ymin)
    ymax = max(ymax)
    ymax = ymax-1 # Not sure why this is necessary, but it seems to be.
    cropBox = (ymin, ymax-ymin, xmin, xmax-xmin)
    return cropBox

def auto_crop(f,ext):
    image=Image.open(f)
    image.load()
    image_data = np.asarray(image)
    image_data_bw = image_data[:,:,0]+image_data[:,:,1]+image_data[:,:,2]
    cropBox = get_cropped_area(image_data_bw,grey_tolerance)
    image_data_new = image_data[cropBox[0]:cropBox[1]+1, cropBox[2]:cropBox[3]+1 , :]
    new_image = Image.fromarray(image_data_new)
    f_new = f.replace(ext,'')+'_cropped'+ext
    new_image.save(f_new)
于 2017-05-29T20:30:10.660 回答