使用PIL去除空白处

67

在PIL中,有没有一种简单的方法可以修剪图像上的空格?

ImageMagick可以通过以下方式轻松支持它:

convert test.jpeg -fuzz 7% -trim test_trimmed.jpeg

我找到了一个PIL的解决方案:

from PIL import Image, ImageChops

def trim(im, border):
    bg = Image.new(im.mode, im.size, border)
    diff = ImageChops.difference(im, bg)
    bbox = diff.getbbox()
    if bbox:
        return im.crop(bbox)

但是这种解决方案有缺点:

  1. 我需要定义边框颜色,对我来说不是什么大问题,因为我的图像有白色背景。
  2. 最大的缺点是,这种PIL解决方案不支持ImageMagick的“-fuzz”关键字,无法添加模糊裁剪,因为我可能会遇到一些JPEG压缩伪影和不必要的巨大阴影。

也许PIL有一些内置函数可以实现?或者有一些快速的解决方案吗?


1
我知道代码完全相同,但也可以在这里找到 - https://gist.github.com/mattjmorrison/932345 - Sergey M
5个回答

153

我认为PIL中没有任何内置函数可以完成这个任务。但是我修改了您的代码,现在它可以实现了:

  • 它使用getpixel从左上像素获取边框颜色,因此您不需要传递颜色。
  • 从差异图像中减去一个标量,这是一种将所有值低于100, 100, 100(在我的例子中)饱和为零的快速方法。因此,这是一种消除压缩引起的任何“抖动”的简洁方式。

代码:

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)
    bbox = diff.getbbox()
    if bbox:
        return im.crop(bbox)

im = Image.open("bord3.jpg")
im = trim(im)
im.show()

压缩后的jpeg图像:

enter image description here 裁剪后:enter image description here

带噪声的jpeg图像:

enter image description here 裁剪后:enter image description here


7
请注意,您正在执行的操作非常危险:它确实可以补偿边界中的噪声,但是对于背景和图像本身非常相似的图像(例如白色物品放置在白色背景上的照片),您将无法再处理。 - Wichert Akkerman
1
这行代码是做什么的?“diff = ImageChops.add(diff, diff, 2.0, -100)”你只是想避免getbbox时边缘为零吗? - Ezekiel Kruglick
3
@WichertAkkerman 可能我找到了解决方法:将“diff = ImageChops.add(diff,diff,2.0,-100)”替换为“diff = ImageChops.add(diff,diff)”。 - Vladimir Chub
5
我发现这对于模式为“RGBA”的图像无效(ImageChops.difference返回一个完全透明的图像)。相反,将bg = Image.new(im.mode, im.size, im.getpixel((0,0)))更改为bg = Image.new("RGB", im.size, im.getpixel((0,0)))以及diff = ImageChops.difference(im, bg) 更改为 diff = ImageChops.difference(im.convert("RGB"), bg)是可行的。 - Matt Pitkin
1
这个功能非常适合裁剪文字周围的空白空间。谢谢! - undefined
显示剩余2条评论

6

2
请您详细阐述一下答案吗?(附带一个例子) - secavfr
3
我刚试了一下在Wand中使用trim()功能,同时也尝试了已接受的答案,结论是Wand的裁剪效果比较差,这让我很惊讶,因为我的图像很清晰,并且边框完全是实心的。 - ptk

4

fraxel提供的答案是可行的,但正如Matt Pitkin所指出的,有时候图像应该转换为“RGB”,否则边框将无法被检测到:

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)
    bbox = diff.getbbox()
    if bbox:
        return im.crop(bbox)
    else: 
        # Failed to find the borders, convert to "RGB"        
        return trim(im.convert('RGB'))

我遇到了一个情况,递归深度达到了最大值。 - Dee
我遇到了一个案例的最大递归深度。 - Dee
我已经按照这篇答案所示,移除了递归,并将其粘贴到白色背景中。链接如下:https://dev59.com/iFUL5IYBdhLWcg3wDkhT#50898375 - Dee
我将递归删除,并首先将其粘贴到白色背景中,如此答案所示:https://stackoverflow.com/a/50898375/5581893 - undefined

2

使用ufp.image模块中的trim函数。

import ufp.image
import PIL
im = PIL.Image.open('test.jpg', 'r')
trimed = ufp.image.trim(im, fuzz=13.3)
trimed.save('trimed.jpg')

我无法通过pip安装ufp。我收到以下错误消息:ERROR: Command "python setup.py egg_info" failed with error code 1 in /private/var/folders/vd/5ccxv4957f1_prjqt1l_ppsw0000gq/T/pip-install-ya7p01_3/ufp/。此外,没有ufp的github存储库,因此我无法联系开发人员。 - logic1976
6
ufp不兼容Python 3。 - Dustin Oprea

0
如果您的图像是米白色的,那么这将有助于设置一个阈值,并使用它来剪切边框。
输入和输出都是Pillow图像。
import cv2

# Trim Whitespace Section
def trim_whitespace_image(image):
    # Convert the image to grayscale
    gray_image = image.convert('L')

    # Convert the grayscale image to a NumPy array
    img_array = np.array(gray_image)

    # Apply binary thresholding to create a binary image 
    # (change the value here default is 250)    ↓
    _, binary_array = cv2.threshold(img_array, 250, 255, cv2.THRESH_BINARY_INV)

    # Find connected components in the binary image
    num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(binary_array)

    # Find the largest connected component (excluding the background)
    largest_component_label = np.argmax(stats[1:, cv2.CC_STAT_AREA]) + 1
    largest_component_mask = (labels == largest_component_label).astype(np.uint8) * 255

    # Find the bounding box of the largest connected component
    x, y, w, h = cv2.boundingRect(largest_component_mask)

    # Crop the image to the bounding box
    cropped_image = image.crop((x, y, x + w, y + h))

    return cropped_image

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