在Python和OpenCV中保持长宽比调整图像大小

18

我想在Python中从任何输入图片中获取一个1000 x 1000像素的图片,以便输入不会失去其长宽比。换句话说,我想调整输入大小,使其较长的一维为1000像素,并且“填充”另一维直到成为1000 x 1000正方形,背景颜色为原始图片的颜色。最终结果必须在中心位置。


这个图像的两个维度都保证小于1000吗?否则你会“丢失信息”。 - Willem Van Onsem
不,输入图像可以是任意尺寸。 - Hendrik
1
如果你只是想将一堆图像处理成这个大小,可以使用ImageMagick或类似的单个bash命令来完成...不需要使用OpenCV。但如果它只是你算法的一部分,你可以简单地使用resize将图像调整为最大尺寸1000,然后用copyMakeBorder填充剩余的位。 - alkasm
你可以使用Python Wand来完成这个任务,它使用Imagemagick。 - fmw42
4个回答

47

使用OpenCV

您可以使用OpenCV中的resize()将图像调整大小以适应所需的尺寸。但是,resize()要求您输入目标大小(在两个维度上)或缩放比例(在两个维度上),因此您不能只输入一个参数为1000并让它计算另一个参数。因此,最可靠的方法是找到纵横比并计算大尺寸被伸展到1000时小尺寸会是多少。然后您就可以进行调整大小操作了。

h, w = img.shape[:2]
aspect = w/h
请注意,如果aspect大于1,则图片会水平方向定位,如果它小于1,则图片会垂直方向定位(如果aspect = 1,则为正方形)。
不同的插值方法在将图像拉伸到更高分辨率或缩放到较低分辨率时看起来效果更好。从resize()文档中可以了解到:
要缩小图像,通常使用CV_INTER_AREA插值最佳,而要扩大图像,通常使用CV_INTER_CUBIC(较慢)或CV_INTER_LINEAR(速度更快,但仍然看起来不错)。
因此,在调整大小后,我们将得到一个1000xNNx1000的图像(其中N<=1000),然后需要在两侧使用任何背景颜色填充它以填充到1000x1000的图像。为此,您可以使用纯OpenCV实现的copyMakeBorder(),或者由于您正在使用Python,您可以使用numpy.pad()。您需要决定如果需要添加奇数个像素以使其1000x1000,则该怎么办,例如,额外的像素是向左还是向右(或向上或向下,取决于您的图像方向)。
这是一个脚本,它定义了一个resizeAndPad()函数,该函数自动计算纵横比例,按比例缩放并根据需要进行填充,然后将其用于水平、垂直和正方形图像:
import cv2
import numpy as np

def resizeAndPad(img, size, padColor=0):

    h, w = img.shape[:2]
    sh, sw = size

    # interpolation method
    if h > sh or w > sw: # shrinking image
        interp = cv2.INTER_AREA
    else: # stretching image
        interp = cv2.INTER_CUBIC

    # aspect ratio of image
    aspect = w/h  # if on Python 2, you might need to cast as a float: float(w)/h

    # compute scaling and pad sizing
    if aspect > 1: # horizontal image
        new_w = sw
        new_h = np.round(new_w/aspect).astype(int)
        pad_vert = (sh-new_h)/2
        pad_top, pad_bot = np.floor(pad_vert).astype(int), np.ceil(pad_vert).astype(int)
        pad_left, pad_right = 0, 0
    elif aspect < 1: # vertical image
        new_h = sh
        new_w = np.round(new_h*aspect).astype(int)
        pad_horz = (sw-new_w)/2
        pad_left, pad_right = np.floor(pad_horz).astype(int), np.ceil(pad_horz).astype(int)
        pad_top, pad_bot = 0, 0
    else: # square image
        new_h, new_w = sh, sw
        pad_left, pad_right, pad_top, pad_bot = 0, 0, 0, 0

    # set pad color
    if len(img.shape) is 3 and not isinstance(padColor, (list, tuple, np.ndarray)): # color image but only one color provided
        padColor = [padColor]*3

    # scale and pad
    scaled_img = cv2.resize(img, (new_w, new_h), interpolation=interp)
    scaled_img = cv2.copyMakeBorder(scaled_img, pad_top, pad_bot, pad_left, pad_right, borderType=cv2.BORDER_CONSTANT, value=padColor)

    return scaled_img

v_img = cv2.imread('v.jpg') # vertical image
scaled_v_img = resizeAndPad(v_img, (200,200), 127)

h_img = cv2.imread('h.jpg') # horizontal image
scaled_h_img = resizeAndPad(h_img, (200,200), 127)

sq_img = cv2.imread('sq.jpg') # square image
scaled_sq_img = resizeAndPad(sq_img, (200,200), 127)

这将会显示以下图片:

等比例垂直缩放的图片 等比例水平缩放的图片 等比例正方形缩放的图片

使用 ImageMagick

ImageMagick 是一个简单但功能强大的命令行界面,可用于基本的图像处理。只需一条命令即可轻松完成所需操作。有关调整大小命令的描述,请参见此处

$ convert v.jpg -resize 200x200 -background skyblue -gravity center -extent 200x200 scaled-v-im.jpg
$ convert h.jpg -resize 200x200 -background skyblue -gravity center -extent 200x200 scaled-h-im.jpg
$ convert sq.jpg -resize 200x200 -background skyblue -gravity center -extent 200x200 scaled-sq-im.jpg

生成图像:

缩放竖向图像 缩放横向图像 缩放正方形图像


我在调整一个18x36的图片大小时出现了错误。将aspect = w/h替换为aspect = float(w)/h就解决了。 - Marph
1
@Marph 很好的发现。你需要这样做的原因是因为你使用的是Python 2。在Python 2中,4/2 = 2,而float(4)/2 = 2.0。在Python 3中,4/2 = 2.0,因此不需要强制类型转换。也就是说,在Python 2中,除法是整数除法(所以5/3 = 1),而在Python 3中,它总是浮点数(所以5/3 = 1.666666...7)。 - alkasm
如果您正在使用Python 2,请在脚本开头添加以下行:from __future__ import division - leoll2

7

在Alexander-Reynolds的答案基础上,以下是处理所有可能尺寸和情境的代码。

def resizeAndPad(img, size, padColor=255):

    h, w = img.shape[:2]
    sh, sw = size

    # interpolation method
    if h > sh or w > sw: # shrinking image
        interp = cv2.INTER_AREA

    else: # stretching image
        interp = cv2.INTER_CUBIC

    # aspect ratio of image
    aspect = float(w)/h 
    saspect = float(sw)/sh

    if (saspect > aspect) or ((saspect == 1) and (aspect <= 1)):  # new horizontal image
        new_h = sh
        new_w = np.round(new_h * aspect).astype(int)
        pad_horz = float(sw - new_w) / 2
        pad_left, pad_right = np.floor(pad_horz).astype(int), np.ceil(pad_horz).astype(int)
        pad_top, pad_bot = 0, 0

    elif (saspect < aspect) or ((saspect == 1) and (aspect >= 1)):  # new vertical image
        new_w = sw
        new_h = np.round(float(new_w) / aspect).astype(int)
        pad_vert = float(sh - new_h) / 2
        pad_top, pad_bot = np.floor(pad_vert).astype(int), np.ceil(pad_vert).astype(int)
        pad_left, pad_right = 0, 0

    # set pad color
    if len(img.shape) is 3 and not isinstance(padColor, (list, tuple, np.ndarray)): # color image but only one color provided
        padColor = [padColor]*3

    # scale and pad
    scaled_img = cv2.resize(img, (new_w, new_h), interpolation=interp)
    scaled_img = cv2.copyMakeBorder(scaled_img, pad_top, pad_bot, pad_left, pad_right, borderType=cv2.BORDER_CONSTANT, value=padColor)

    return scaled_img

目前,正方形的背景颜色是白色。如何使正方形框具有透明背景? - Nurzhan Nogerbek

1

伦敦小伙儿的C++版本

int resizeAndPad(cv::Mat &src, cv::Mat& dst /*output*/, Size size, int padColor = 0)
{    
    int h = src.rows;
    int w = src.cols;
    int sh = size.height;
    int sw = size.width;
    int interp;
    int new_h;
    int new_w;
    float pad_horz, pad_left, pad_right, pad_top, pad_bot, pad_vert;
    cv::Scalar bColor;
    //interpolation method
    if (h > sh || w > sw)//shrinking image
    {
         interp = cv::INTER_AREA;
    }
    else //stretching image
    {
         interp = cv::INTER_CUBIC;
    }
    //aspect ratio of image
    float aspect = float(w) / h;
    float saspect = float(sw) / sh;
    //new horizontal image
    if ( (saspect >= aspect) || ((saspect == 1) && (aspect <= 1)) )
    {
        new_h = sh;
        new_w = int(new_h * aspect);
        pad_horz = float(sw - new_w) / 2;
        pad_left = int(pad_horz);
        pad_right = int(pad_horz);
        pad_top = 0;
        pad_bot = 0;
    }    
    //new vertical image
    else if ( (saspect < aspect) || ((saspect == 1) && (aspect >= 1)))
    {
        new_w = sw;
        new_h = int(float(new_w) / aspect);
        pad_vert = float(sh - new_h) / 2;
        pad_top = int(pad_vert);
        pad_bot = int(pad_vert);
        pad_left = 0;
        pad_right = 0;
    }
    if (src.channels() == 3)
    {
        bColor = cv::Scalar(padColor, padColor, padColor);
    }
    else
    {
        bColor = cv::Scalar(padColor);
    }
    cv::resize(src, dst, Size(new_w, new_h),None,None,interp);
    cv::copyMakeBorder(dst, dst, pad_top, pad_bot, pad_left, pad_right, cv::BORDER_CONSTANT, bColor);    
    return 0;
}

0
我发现了一个小的拼写错误。 “saspect > aspect” 应该是 “saspect >= aspect”
鸣谢伦敦的朋友。
def resizeAndPad(img, size, padColor=255):   
    h, w = img.shape[:2]
    sh, sw = size
    
    # interpolation method
    if h > sh or w > sw: # shrinking image
        interp = cv2.INTER_AREA
    
    else: # stretching image
        interp = cv2.INTER_CUBIC
    
    # aspect ratio of image
    aspect = float(w)/h 
    saspect = float(sw)/sh
    
    if (saspect >= aspect) or ((saspect == 1) and (aspect <= 1)):  # new horizontal image
        new_h = sh
        new_w = np.round(new_h * aspect).astype(int)
        pad_horz = float(sw - new_w) / 2
        pad_left, pad_right = np.floor(pad_horz).astype(int), np.ceil(pad_horz).astype(int)
        pad_top, pad_bot = 0, 0
    
    elif (saspect < aspect) or ((saspect == 1) and (aspect >= 1)):  # new vertical image
        new_w = sw
        new_h = np.round(float(new_w) / aspect).astype(int)
        pad_vert = float(sh - new_h) / 2
        pad_top, pad_bot = np.floor(pad_vert).astype(int), np.ceil(pad_vert).astype(int)
        pad_left, pad_right = 0, 0
    
    # set pad color
    if len(img.shape) is 3 and not isinstance(padColor, (list, tuple, np.ndarray)): # color image but only one color provided
        padColor = [padColor]*3
    
    # scale and pad
    scaled_img = cv2.resize(img, (new_w, new_h), interpolation=interp)
    scaled_img = cv2.copyMakeBorder(scaled_img, pad_top, pad_bot, pad_left, pad_right, borderType=cv2.BORDER_CONSTANT, value=padColor)
    
    return scaled_img

2
这应该是对其他答案的评论和/或编辑。 - Cris Luengo

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