简体   繁体   English

使用 python/PIL 自动裁剪图像

[英]Automatically cropping an image with python/PIL

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.我有一个带有大透明区域/空间的 png 图像。 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.原始图像有一个正方形的画布,最好是矩形的,只包含分子。

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 的人都可以帮助我找出问题所在吗?

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]搜索边界时,您只能通过蓝色通道进行搜索。

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

The result looks like结果看起来像裁剪图像

If anything is unclear, just ask.如果有什么不清楚的,尽管问。

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

Here's another version using pyvips .这是使用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])

The pyvips trimmer is useful for photographic images. pyvips 修剪器对摄影图像很有用。 It does a median filter, subtracts the background, finds pixels over the threshold, and removes up to the first and last row and column outside this set.它执行中值滤波器,减去背景,找到超过阈值的像素,并删除该集合之外的第一行和最后一行。 The median and threshold mean it is not thrown off by things like JPEG compression, where noise or invisible compression artefacts can confuse other trimmers.中位数和阈值意味着它不会被 JPEG 压缩之类的东西抛弃,其中噪声或不可见的压缩伪影可能会混淆其他修剪器。

If you don't supply the background argument, it uses the pixel at (0, 0).如果您不提供background参数,它将使用 (0, 0) 处的像素。 threshold defaults to 10, which is about right for JPEG. threshold默认为 10,这对于 JPEG 来说是正确的。

Here it is running on an 8k x 8k pixel NASA earth image :这是在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

Before:前:

收割前晚上的地球

After:后:

收获后的地球

There's a blog post with some more discussion here .有一篇博客文章,这里有更多讨论

This is an improvement over snew's reply, which works for transparent background.这是对 snew 回复的改进,它适用于透明背景。 With mathematical morphology we can make it work on white background (instead of transparent), with the following code:使用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()

在此处输入图片说明 在此处输入图片说明 在此处输入图片说明

Came across this post recently and noticed the PIL library has changed.最近看到这篇文章,注意到 PIL 库发生了变化。 I re-implemented this with openCV:我用 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)

pilkit already contains processor for automatic cropping TrimBorderColor . pilkit已经包含用于自动裁剪TrimBorderColor处理器。 SOmething like this should work:这样的事情应该工作:

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 https://github.com/matthewwithanm/pilkit/blob/b24990167aacbaab3db6d8ec9a02f9ad42856898/pilkit/processors/crop.py#L33

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)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM