将矩形图像调整为正方形,保持比例并用黑色填充背景。

48

我正在尝试调整一批灰度图像的大小,这些图像的尺寸为256 x N像素(N不同,但始终≤256)。

我的意图是对图像进行缩小。

调整大小后,需要输出一个正方形(1:1)图像,满足以下要求:

  • 调整大小后的图像在垂直方向上居中显示
  • 保持宽高比
  • 其余像素以黑色呈现

从视觉上看,应该得到如下所示的结果:

enter image description here

我已经尝试创建一个numpy零矩阵,其目标大小为(例如200 x 200),但无法将调整大小后的图像粘贴到其垂直中心。

欢迎使用cv2、PIL或numpy提供任何建议。

6个回答

62

你可以使用Pillow来实现这个:

代码:

from PIL import Image

def make_square(im, min_size=256, fill_color=(0, 0, 0, 0)):
    x, y = im.size
    size = max(min_size, x, y)
    new_im = Image.new('RGBA', (size, size), fill_color)
    new_im.paste(im, (int((size - x) / 2), int((size - y) / 2)))
    return new_im

测试代码:

test_image = Image.open('hLarp.png')
new_image = make_square(test_image)
new_image.show()

对于白色背景,您可以执行以下操作:

new_image = make_square(test_image, fill_color=(255, 255, 255, 0))

结果:

在这里输入图片描述


3
看起来很不错,谢谢 —— 那么 .paste 中的原点将会在中心位置吗? - pepe
居中对齐,除非图像的像素数为奇数。 - Stephen Rauch
2
当然 - 我抓住了它,因为我刚刚使用了它。 - user2647513
这段代码只是将背景调整为min_size大小,并将原始图像居中放置在其中。如何将原始图像调整为min_size大小?谢谢。 - sliawatimena
1
我尝试在使用Python 3的Jupyter笔记本上运行它,但是我不得不将*(size - x) / 2(size - y) / 2转换为整数,使用int((size - x) / 2)int((size - x) / 2)*。此外,我还必须将RGBA更改为RGB以获得黑色背景。 - Afsan Abdulali Gujarati
显示剩余3条评论

9
这里有一段代码,使用OPENCV模块(同时使用NUMPY模块)解决了您的问题。
#Importing modules opencv + numpy
import cv2
import numpy as np

#Reading an image (you can use PNG or JPG)
img = cv2.imread("image.png")

#Getting the bigger side of the image
s = max(img.shape[0:2])

#Creating a dark square with NUMPY  
f = np.zeros((s,s,3),np.uint8)

#Getting the centering position
ax,ay = (s - img.shape[1])//2,(s - img.shape[0])//2

#Pasting the 'image' in a centering position
f[ay:img.shape[0]+ay,ax:ax+img.shape[1]] = img

#Showing results (just in case) 
cv2.imshow("IMG",f)
#A pause, waiting for any press in keyboard
cv2.waitKey(0)

#Saving the image
cv2.imwrite("img2square.png",f)
cv2.destroyAllWindows()

通过使用.zeros方法,能否创建一个具有透明背景的正方形? - Nurzhan Nogerbek

7

PIL.ImageOps.pad:

from PIL import Image, ImageOps

with Image.open('hLARP.png') as im:
    im = ImageOps.pad(im, (200, 200), color='black')
im.save('result.png')

3
PIL有缩略图方法,可以保持宽高比例缩放。然后,您只需要将其居中粘贴到黑色背景矩形上即可。
from PIL import Image

def black_background_thumbnail(path_to_image, thumbnail_size=(200,200)):
    background = Image.new('RGBA', thumbnail_size, "black")    
    source_image = Image.open(path_to_image).convert("RGBA")
    source_image.thumbnail(thumbnail_size)
    (w, h) = source_image.size
    background.paste(source_image, ((thumbnail_size[0] - w) / 2, (thumbnail_size[1] - h) / 2 ))
    return background

if __name__ == '__main__':
    img = black_background_thumbnail('hLARP.png')
    img.save('tmp.jpg')
    img.show()

2

看这里!这是@Stepeh Rauch答案的一个过度工程版本,包含一个交互元素并考虑了奇数像素的填充。

用法

# Note: PySide2 can also be replaced by PyQt5, PyQt6, PySide6
# Also note! Any of the above are >100MB
pip install utilitys pyside2 pillow
$ python <file.py> --help
usage: <file>.py [-h] [--folder FOLDER] [--ext EXT]

optional arguments:
  -h, --help       show this help message and exit
  --folder FOLDER  Folder of images allowed for viewing. Must have at least one image (default: .)
  --ext EXT        Image extension to look for (default: png)

$ python <file>.py --folder "./path/to/folder/of/your/image(s).png" --ext "jpg"

file.py 内容

import argparse
from pathlib import Path
from typing import Tuple, Union, Any

import numpy as np
import pyqtgraph as pg
from PIL import Image
from utilitys import fns, widgets, RunOpts


def pad_to_size(
    image: Image.Image,
    size_wh: Union[int, Tuple[int, int]] = None,
    fill_color: Any = 0,
    **resize_kwargs,
) -> Image.Image:
    """
    Keeps an image's aspect ratio by resizing until the largest side is constrained
    by the specified output size. Then, the deficient dimension is padded until
    the image is the specified size.
    """
    if size_wh is None:
        size_wh = max(image.size)

    if isinstance(size_wh, int):
        size_wh = (size_wh, size_wh)

    im_size_wh = np.array(image.size)
    ratios = im_size_wh / size_wh

    # Resize until the largest side is constrained by the specified output size
    im_size_wh = np.ceil(im_size_wh / ratios.max()).astype(int)
    # Prefer 1-pixel difference in aspect ratio vs. odd padding
    pad_amt = np.array(size_wh) - im_size_wh
    use_ratio_idx = np.argmax(ratios)
    unused_ratio_idx = 1 - use_ratio_idx

    # Sanity check for floating point accuracy: At least one side must match
    # user-requested dimension
    if np.all(pad_amt != 0):
        # Adjust dimension that is supposed to match
        im_size_wh[use_ratio_idx] += pad_amt[use_ratio_idx]
    # Prefer skewing aspect ratio by 1 pixel instead of odd padding
    # If odd, 1 will be added. Otherwise, the dimension remains unchanged
    im_size_wh[unused_ratio_idx] += pad_amt[unused_ratio_idx] % 2
    image = image.resize(tuple(im_size_wh), **resize_kwargs)

    new_im = Image.new("RGB", size_wh, fill_color)
    width, height = image.size
    new_im.paste(image, (int((size_wh[0] - width) / 2), int((size_wh[1] - height) / 2)))
    return new_im


def main(folder=".", ext="png"):
    """
    Parameters
    ----------
    folder: str, Path
        Folder of images allowed for viewing. Must have at least one image
    ext: str, Path
        Image extension to look for

    """
    folder = Path(folder)
    files = fns.naturalSorted(folder.glob(f"*.{ext}"))
    err_msg = f"{folder} must have at least one image file with extension `{ext}`"
    assert len(files), err_msg

    pg.mkQApp()
    viewer = widgets.ImageViewer()

    def readim(file_index=0, try_pad=False, output_w=512, output_h=512):
        if 0 > file_index > len(files):
            return
        image = Image.open(files[file_index])
        if try_pad:
            image = pad_to_size(image, (output_w, output_h), fill_color=(255, 255, 255))
        viewer.setImage(np.array(image))

    viewer.toolsEditor.registerFunc(readim, runOpts=RunOpts.ON_CHANGED)
    wc = viewer.widgetContainer()
    readim()
    wc.show()
    pg.exec()


if __name__ == "__main__":
    # Print defaults in help signature
    fmt = dict(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    cli = fns.makeCli(main, parserKwargs=fmt)
    args = cli.parse_args()
    main(**vars(args))

2
from PIL import Image

def reshape(image):
    '''
    Reshapes the non-square image by pasting
    it to the centre of a black canvas of size
    n*n where n is the biggest dimension of
    the non-square image. 
    '''
    old_size = image.size
    max_dimension, min_dimension = max(old_size), min(old_size)
    desired_size = (max_dimension, max_dimension)
    position = int(max_dimension/2) - int(min_dimension/2) 
    blank_image = Image.new("RGB", desired_size, color='black')
    if image.height<image.width:
        blank_image.paste(image, (0, position))
    else:
        blank_image.paste(image, (position, 0))
    return blank_image

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