使用 python/PIL 自动裁剪图像
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/14211340/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Automatically cropping an image with python/PIL
提问by dimka
Can anyone help me figure out what's happening in my image auto-cropping script? I have a png image with a large transparent area/space. I would like to be able to automatically crop that space out and leave the essentials. Original image has a squared canvas, optimally it would be rectangular, encapsulating just the molecule.
谁能帮我弄清楚我的图像自动裁剪脚本中发生了什么?我有一个带有大透明区域/空间的 png 图像。我希望能够自动裁剪该空间并留下必需品。原始图像有一个正方形的画布,最好是矩形的,只包含分子。
here's the original image:

这是原始图像:

Doing some googling i came across PIL/python code that was reported to work, however in my hands, running the code below over-crops the image.
做一些谷歌搜索时,我遇到了据报告可以工作的 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')
the output is this:
输出是这样的:
Can anyone more familiar with image-processing/PLI can help me figure out the issue?
任何更熟悉图像处理/PLI 的人都可以帮助我找出问题所在吗?
采纳答案by Thorsten Kranz
You can use numpy, convert the image to array, find all non-empty columns and rows and then create an image from these:
您可以使用 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')
If anything is unclear, just ask.
如果有什么不清楚的,尽管问。
回答by sneawo
For me it works as:
对我来说,它的工作原理是:
import Image
image=Image.open('L_2d.png')
imageBox = image.getbbox()
cropped=image.crop(imageBox)
cropped.save('L_2d_cropped.png')
When you search for boundaries by mask=imageComponents[3], you search only by blue channel.
当您通过 搜索边界时mask=imageComponents[3],您只能通过蓝色通道进行搜索。
回答by wordsforthewise
Came across this post recently and noticed the PIL library has changed. I re-implemented this with openCV:
最近看到这篇文章,注意到 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 = (min(non_empty_rows) * (1 - padding),
min(max(non_empty_rows) * (1 + padding), rows),
min(non_empty_columns) * (1 - padding),
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)
回答by Charles
I know that this post is old but, for some reason, none of the suggested answers worked for me. So I hacked my own version from existing answers:
我知道这篇文章很旧,但由于某种原因,没有一个建议的答案对我有用。所以我从现有的答案中破解了我自己的版本:
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)
回答by jcupitt
Here's another version using pyvips.
这是使用pyvips的另一个版本。
This one is a little fancier: it looks at the pixel at (0, 0), assumes that to be the background colour, then does a median filter and finds the first and last row and column containing a pixel which differs from that by more than a threshold. This extra processing means it also works on photographic or compressed images, where a simple trim can be thrown off by noise or compression artifacts.
这个有点花哨:它查看 (0, 0) 处的像素,假设它是背景颜色,然后进行中值滤波器并找到包含与像素相差更多的像素的第一行和最后一行比一个门槛。这种额外的处理意味着它也适用于摄影或压缩图像,在这些图像中,噪声或压缩伪影可能会破坏简单的修剪。
import sys
import pyvips
# An equivalent of ImageMagick's -trim in libvips ... automatically remove
# "boring" image edges.
# We use .project to sum the rows and columns of a 0/255 mask image, the first
# non-zero row or column is the object edge. We make the mask image with an
# amount-differnt-from-background image plus a threshold.
im = pyvips.Image.new_from_file(sys.argv[1])
# find the value of the pixel at (0, 0) ... we will search for all pixels
# significantly different from this
background = im(0, 0)
# we need to smooth the image, subtract the background from every pixel, take
# the absolute value of the difference, then threshold
mask = (im.median(3) - background).abs() > 10
# sum mask rows and columns, then search for the first non-zero sum in each
# direction
columns, rows = mask.project()
# .profile() returns a pair (v-profile, h-profile)
left = columns.profile()[1].min()
right = columns.width - columns.fliphor().profile()[1].min()
top = rows.profile()[0].min()
bottom = rows.height - rows.flipver().profile()[0].min()
# and now crop the original image
im = im.crop(left, top, right - left, bottom - top)
im.write_to_file(sys.argv[2])
Here it is running on an 8k x 8k pixel NASA earth image:
$ 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
Before:
前:
After:
后:
There's a blog post with some more discussion here.
有一篇博客文章,这里有更多讨论。
回答by neouyghur
I tested most of the answers replied in this post, however, I was ended up my own answer. I used anaconda python3.
我测试了这篇文章中回答的大部分答案,但是,我最终得到了自己的答案。我使用了anaconda 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()
回答by Sandipan Dey
This is an improvement over snew's reply, which works for transparent background. With mathematical morphologywe can make it work on white background (instead of transparent), with the following code:
这是对 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()
回答by igo
pilkitalready contains processor for automatic cropping TrimBorderColor. SOmething like this should work:
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)

