49

l have a set of images of different sizes (45,50,3), (69,34,3), (34,98,3). l want to add padding to these images as follows:

Take the max width and length of the whole images then put the image in that size

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)

How can l add padding in opencv so that all the images will have the same size? In the example l gave the images will get the shape of (69,98,3)

nathancy
  • 42,661
  • 14
  • 115
  • 137
vincent
  • 1,558
  • 4
  • 21
  • 34
  • 1
    check this link: https://stackoverflow.com/questions/36044061/add-padding-to-object-in-4-channel-image – zindarod Apr 13 '17 at 11:44
  • @Zindarod. l have black and white images pixels are either 0 or 255 only. most of my characters in images are written in black. So l need white pixel padding. However l have also some images where the characters are in white so l need black pixel padding. l'm wandering if there is a trick in open cv to detect that then add white or black pixel padding – vincent Apr 13 '17 at 11:45

11 Answers11

76

You can use:

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

Where src is your source image and top, bottom, left, right are the padding around the image.

You can use max(sizes) - size value of the image in a while loop to add the padding to each image. The bordertype can be one of these:

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

cv2.copyMakeBorder tutorial

KetZoomer
  • 2,701
  • 3
  • 15
  • 43
Azade Farshad
  • 1,022
  • 8
  • 18
  • how can l get top bottom left and right values. l have just width and length that l can get from image.shape – vincent Apr 13 '17 at 11:50
  • @vincent _"You can use max(sizes) - size value of the image"_ – Miki Apr 13 '17 at 12:10
  • you can add values only to left or top, or add half of the width and length values to all directions. like: top = length/2, bottom = length/2, left = width/2, right = width/2 – Azade Farshad Apr 13 '17 at 12:16
  • @Azadef, please see my update. it didn't perfom correclty on dimension – vincent Apr 13 '17 at 13:33
  • 2
    This definitely needs to show the code for actually getting the dims for both images, and then resizing both (because one might be wider while the other is taller) using this approach. – Mike 'Pomax' Kamermans Apr 29 '21 at 16:55
39

Here is another way to do that in Python/OpenCV/Numpy. It uses Numpy slicing to copy the input image into a new image of the desired output size and a given offset. Here I compute the offset to do center padding. I think this is easier to do using width, height, xoffset, yoffset, rather than how much to pad on each side.

Input:

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)

enter image description here

fmw42
  • 46,825
  • 10
  • 62
  • 80
  • 1
    Good answer but naming variables as 2 letters is unreadable and bad practice. – Paul Feakins Feb 12 '21 at 16:15
  • `@Paul Feakins` So what would you suggest in place of what I have? – fmw42 May 09 '21 at 15:49
  • "height", "width" etc. are usually fine. – Paul Feakins May 10 '21 at 21:22
  • 3
    In Python examples, one often sees,`h,w,c = img.shape` So single letters are OK, but not double letters? No one wants to type height and width all the time.! Nevertheless, I do understand the concern for readability and understanding – fmw42 May 10 '21 at 21:37
  • "No one wants to type height and width all the time.!" Haha! You think that's bad you should see some of the code I've seen recently, stuff like "discountPriceBeforeTaxIsApplied" etc. So there is a balance to be had, and I know yours is quite a simple script, but I personally think being a bit more descriptive with the variable names would aid readability. – Paul Feakins May 12 '21 at 08:23
  • 1
    I would generally agree with you. I was just being lazy and most people know what w, h mean. But I prefer to reserve those for the results of the bounding box. Perhaps wd and ht might have been better choices, if not the full width and and height names. Thanks for the comment. I will try to be better about this in the future. – fmw42 May 12 '21 at 15:32
17

try to use this function:

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

Raw image

After resizing with padding

see https://gist.github.com/BIGBALLON/cb6ab73f6aaaa068ab6756611bb324b2

codedbychavez
  • 193
  • 4
  • 6
Wei Li
  • 171
  • 1
  • 2
14

Like this (Padding is called borders on openCV):

BLUE = [255,255,255]
constant= cv2.copyMakeBorder(image.copy(),10,10,10,10,cv2.BORDER_CONSTANT,value=BLUE)

And blue can become white even

source: https://docs.opencv.org/3.4/da/d0c/tutorial_bounding_rects_circles.html

sivi
  • 10,654
  • 2
  • 52
  • 51
9

As I do not see an accepted answer, and also the fact that one has to determine the top, bottom, left, right of the function, I have bellow what worked for me easily. Taken from: 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()
George Sotiropoulos
  • 1,864
  • 1
  • 22
  • 32
  • What if I want the height/width of the resulting image to differ? For example, only pad height but leave width as is. – PlsWork Jun 09 '19 at 13:20
  • @AnnaVopureta Without testing it, I would guess that in the `im = cv2.resize(im, (new_size[1], new_size[0]))` you will have to change one of the new_size with old_size. The first one is for keeping height constant and the second of width – George Sotiropoulos Jun 20 '19 at 13:49
5

Here is a function doing all for you :

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

Ismael EL ATIFI
  • 1,939
  • 20
  • 16
3

Here a single line could do it

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

This will keep my image in 100x100 maintaining its aspect ratio with zero padding

Prajot Kuvalekar
  • 5,128
  • 3
  • 21
  • 32
1

Just use Pillow's crop_pad(). It automatically resize and adds 'zeros' padding where needed (rgb=(0,0,0) / black) with no picture scaling.

from PIL import Image

img = Image.open(your_file_path)
img.crop_pad((width, height))
industArk
  • 189
  • 2
  • 5
1

It's the best what i could do, works only with black and white images

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
seblful
  • 27
  • 5
1

Inspired by @sebl ful. It should work with either black and white image or color image

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
FreeToGo
  • 360
  • 5
  • 8
0

As George said above he wrote the best solution I have found, here I converted it into a function that resizes images while respecting the aspect ratio, it best works with squares, I don't need to change the square aspect as I'm training for deep-learning model.

def resize_me_with_ration(im , desired_size = 200):
    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)
    return new_im