给图像加填充以使它们具有相同的形状

49

我有一组不同尺寸的图片 (45,50,3), (69,34,3), (34,98,3),我想按照以下方式为这些图片添加填充:

找出所有图片中宽度和长度的最大值,并将图片调整到该尺寸。

import os
import glob
import cv2

input_path="/home/images"
os.chdir(indput_path)
images=glob.glob("*.png")
Length=[]
Width=[]
for img in images:
    img=cv2.imread(img)
    width,length=img.shape[0:2]
    Length.append(length)
    Width.append(width)
W=max(Width)
L=max(Length)

如何在OpenCV中添加填充以使所有图像具有相同的大小? 在我给出的例子中,图像将获得形状(69,98,3)

如何在OpenCV中添加填充以使所有图像具有相同的大小? 在我给出的例子中,图像将获得形状(69,98,3)


1
请检查此链接:https://stackoverflow.com/questions/36044061/add-padding-to-object-in-4-channel-image - zindarod
@Zindarod。我有黑白图像,像素只有0或255。我的大部分字符都是用黑色书写的,所以我需要白色像素填充。然而,我也有一些字符是用白色书写的,所以我需要黑色像素填充。我想知道在OpenCV中是否有一个技巧来检测并添加白色或黑色像素填充。 - vincent
12个回答

76

你可以使用:

image = cv2.copyMakeBorder(src, top, bottom, left, right, borderType)

其中src是您的源图像,topbottomleftright是图像周围的填充。

您可以在while循环中使用最大尺寸(max(sizes))减去图像大小(size)的值来为每个图像添加填充。 边框类型可以是以下之一:

  • cv2.BORDER_CONSTANT
  • cv2.BORDER_REFLECT
  • cv2.BORDER_REFLECT_101
  • cv2.BORDER_DEFAULT
  • cv2.BORDER_REPLICATE
  • cv2.BORDER_WRAP

cv2.copyMakeBorder教程


我怎么能获取顶部、底部、左侧和右侧的值?我只有图像.shape中得到的宽度和长度。 - vincent
@vincent "你可以使用max(sizes) - 图像的size值" - Miki
你只能将值添加到左侧或顶部,或者将宽度和长度值的一半添加到所有方向。例如:顶部=长度/2,底部=长度/2,左侧=宽度/2,右侧=宽度/2。 - Azade Farshad
@Azadef,请查看我的更新。它在维度上没有正确执行。 - vincent
2
这绝对需要展示获取两个图像的尺寸并使用此方法调整大小(因为一个可能更宽,而另一个可能更高)的代码。 - Mike 'Pomax' Kamermans

40

以下是在Python/OpenCV/Numpy中另一种实现方法。它使用Numpy切片将输入图像复制到所需的输出大小和给定偏移量的新图像中。这里我计算偏移量以进行中心填充。我认为这种方法更容易使用宽度、高度、x偏移量和y偏移量,而不是在每侧填充多少。

输入:

enter image description here

import cv2
import numpy as np

# read image
img = cv2.imread('lena.jpg')
old_image_height, old_image_width, channels = img.shape

# create new image of desired size and color (blue) for padding
new_image_width = 300
new_image_height = 300
color = (255,0,0)
result = np.full((new_image_height,new_image_width, channels), color, dtype=np.uint8)

# compute center offset
x_center = (new_image_width - old_image_width) // 2
y_center = (new_image_height - old_image_height) // 2

# copy img image into center of result image
result[y_center:y_center+old_image_height, 
       x_center:x_center+old_image_width] = img

# view result
cv2.imshow("result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()

# save result
cv2.imwrite("lena_centered.jpg", result)

在此输入图片描述


1
好的回答,但是用两个字母来命名变量是不可读的,也是不好的做法。 - Paul Feakins
@Paul Feakins,你认为我应该用什么替代现在的方案? - fmw42
“height”,“width”等通常都没问题。 - Paul Feakins
3
在Python示例中,经常会看到h,w,c = img.shape。因此,单个字母是可以的,但不是双字母?没有人想一直输入高度和宽度!尽管如此,我确实理解可读性和理解方面的关注。 - fmw42
没有人想一直输入高度和宽度。哈哈!你认为这很糟糕,你应该看看我最近看到的一些代码,像“discountPriceBeforeTaxIsApplied”之类的东西。因此,需要保持平衡,我知道你的脚本相当简单,但我个人认为使用更具描述性的变量名称会有助于提高可读性。 - Paul Feakins
1
我基本上同意你的观点。我只是有点懒,而大多数人知道w、h的含义。但我更喜欢将其保留用于边界框的结果。也许如果不使用完整的宽度和高度名称,wd和ht可能是更好的选择。感谢您的评论。我将来会尽力做得更好。 - fmw42

18

尝试使用这个函数:

from PIL import Image, ImageOps


def padding(img, expected_size):
    desired_size = expected_size
    delta_width = desired_size[0] - img.size[0]
    delta_height = desired_size[1] - img.size[1]
    pad_width = delta_width // 2
    pad_height = delta_height // 2
    padding = (pad_width, pad_height, delta_width - pad_width, delta_height - pad_height)
    return ImageOps.expand(img, padding)


def resize_with_padding(img, expected_size):
    img.thumbnail((expected_size[0], expected_size[1]))
    # print(img.size)
    delta_width = expected_size[0] - img.size[0]
    delta_height = expected_size[1] - img.size[1]
    pad_width = delta_width // 2
    pad_height = delta_height // 2
    padding = (pad_width, pad_height, delta_width - pad_width, delta_height - pad_height)
    return ImageOps.expand(img, padding)


if __name__ == "__main__":
    img = Image.open("./demo.jpg")
    print(img)
    img = resize_with_padding(img, (500, 400))
    print(img.size)
    img.show()
    img.save("resized_img.jpg")

原始图像

使用填充调整大小后

请查看https://gist.github.com/BIGBALLON/cb6ab73f6aaaa068ab6756611bb324b2


14

9

由于我没有看到被接受的答案,而且还需要确定函数的顶部、底部、左侧和右侧,因此我在下面提供了我容易理解的方法。参考自:https://jdhao.github.io/2017/11/06/resize-image-to-square-with-padding/

import cv2

desired_size = 368
im_pth = "/home/jdhao/test.jpg"

im = cv2.imread(im_pth)
old_size = im.shape[:2] # old_size is in (height, width) format

ratio = float(desired_size)/max(old_size)
new_size = tuple([int(x*ratio) for x in old_size])

# new_size should be in (width, height) format

im = cv2.resize(im, (new_size[1], new_size[0]))

delta_w = desired_size - new_size[1]
delta_h = desired_size - new_size[0]
top, bottom = delta_h//2, delta_h-(delta_h//2)
left, right = delta_w//2, delta_w-(delta_w//2)

color = [0, 0, 0]
new_im = cv2.copyMakeBorder(im, top, bottom, left, right, cv2.BORDER_CONSTANT,
    value=color)

cv2.imshow("image", new_im)
cv2.waitKey(0)
cv2.destroyAllWindows()

如果我想要生成的图像的高度/宽度不同怎么办?例如,只填充高度但保留宽度。 - PlsWork
@AnnaVopureta 在没有测试的情况下,我猜测在 im = cv2.resize(im, (new_size[1], new_size[0])) 中,你需要将其中一个 new_size 改为 old_size。第一个是为了保持高度不变,第二个是为了保持宽度不变。 - George Sotiropoulos

5
这里有一个可以为您完成所有操作的函数:
import cv2


def pad_images_to_same_size(images):
    """
    :param images: sequence of images
    :return: list of images padded so that all images have same width and height (max width and height are used)
    """
    width_max = 0
    height_max = 0
    for img in images:
        h, w = img.shape[:2]
        width_max = max(width_max, w)
        height_max = max(height_max, h)

    images_padded = []
    for img in images:
        h, w = img.shape[:2]
        diff_vert = height_max - h
        pad_top = diff_vert//2
        pad_bottom = diff_vert - pad_top
        diff_hori = width_max - w
        pad_left = diff_hori//2
        pad_right = diff_hori - pad_left
        img_padded = cv2.copyMakeBorder(img, pad_top, pad_bottom, pad_left, pad_right, cv2.BORDER_CONSTANT, value=0)
        assert img_padded.shape[:2] == (height_max, width_max)
        images_padded.append(img_padded)

    return images_padded


1
适用于灰度和 RGB。谢谢! - kym

3

这里只需要一行代码就可以完成

from PIL import Image
from PIL import ImageOps
image = Image.open("demo.jpg").convert("RGB")
ImageOps.pad(image,(100,100)).save('imaged-with-border.png')

这将保持我的图像在100x100的大小,同时保持其宽高比并添加填充。


1

受@sebl ful的启发。它应该适用于黑白图像或彩色图像

def resize_with_padding(image, shape_out, DO_PADDING=True, TINY_FLOAT=1e-5):
    """
    Resizes an image to the specified size,
    adding padding to preserve the aspect ratio.
    """
    if image.ndim == 3 and len(shape_out) == 2:
        shape_out = [*shape_out, 3]
    hw_out, hw_image = [np.array(x[:2]) for x in (shape_out, image.shape)]
    resize_ratio = np.min(hw_out / hw_image)
    hw_wk = (hw_image * resize_ratio + TINY_FLOAT).astype(int)

    # Resize the image
    resized_image = cv2.resize(
        image, tuple(hw_wk[::-1]), interpolation=cv2.INTER_NEAREST
    )
    if not DO_PADDING or np.all(hw_out == hw_wk):
        return resized_image

    # Create a black image with the target size
    padded_image = np.zeros(shape_out, dtype=np.uint8)
    
    # Calculate the number of rows/columns to add as padding
    dh, dw = (hw_out - hw_wk) // 2
    # Add the resized image to the padded image, with padding on the left and right sides
    padded_image[dh : hw_wk[0] + dh, dw : hw_wk[1] + dw] = resized_image

    return padded_image

1
只需使用Pillow的crop_pad()函数即可。它可以自动调整大小,并在需要时添加“零”填充(rgb=(0,0,0)/黑色),而不会进行任何图片缩放。
from PIL import Image

img = Image.open(your_file_path)
img.crop_pad((width, height))

1

这是我所能做到的最好,仅适用于黑白图像

def resize_with_padding(image, size=(224,224)):
    '''
    Resizes a black and white image to the specified size, 
    adding padding to preserve the aspect ratio.
    '''
    # Get the height and width of the image
    height, width = image.shape
    
    # Calculate the aspect ratio of the image
    aspect_ratio = height / width
    
    # Calculate the new height and width after resizing to (224,224)
    new_height, new_width = size
    if aspect_ratio > 1:
        new_width = int(new_height / aspect_ratio)
    else:
        new_height = int(new_width * aspect_ratio)
        
    # Resize the image
    resized_image = cv2.resize(image, (new_width, new_height), interpolation = cv2.INTER_NEAREST)
    
    # Create a black image with the target size
    padded_image = np.zeros((224,224), dtype=np.uint8)
    
    # Calculate the number of rows/columns to add as padding
    padding_rows = (224 - new_height) // 2
    padding_cols = (224 - new_width) // 2
    
    # Add the resized image to the padded image, with padding on the left and right sides
    padded_image[padding_rows:padding_rows+new_height, padding_cols:padding_cols+new_width] = resized_image
    
    return padded_image

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