Hooli
Hooli

Reputation: 731

How to scale the image along X and Y axis and crop to a specific height and width?

I need to the stretch the images in X and Y directions but at the same time keep the target image size 300x300. I am using Open CV. Everytime I scale the image and then resize it, I lose the scaling and it looks the same as the image I fed it to scale. How do I accomplish this?

This is the code for scaling

def scale(infile,outfile,scx,scy):
    img = cv2.imread(infile,0)
    height, width = img.shape[:2]
    #aspectRatio = width / height
    rows,cols = img.shape
    if scx > 1 or scy > 1:
        scimg = cv2.resize(img,None, fx = scx, fy = scy, interpolation = cv2.INTER_LINEAR)
    else:
        scimg = cv2.resize(img,None, fx = scx, fy = scy, interpolation = cv2.INTER_CUBIC)

    #cheight, cwidth = scimg.shape[:2]
    #area = cheight * cwidth
    #nheight = math.sqrt(area / aspectRatio)
    #nwidth = nheight * aspectRatio
    #cv2.resize(scimg,((int)(nwidth), (int)(nheight)), interpolation = cv2.INTER_CUBIC)
    #top = (int) (0.2*rows)
    #bottom = (int) (0.2*rows)
    #left = (int) (0.2*cols)
    #right = (int) (0.2*cols);
    #cv2.copyMakeBorder(scimg,top,left,bottom,right,cv2.BORDER_CONSTANT,value=255)
    cv2.imwrite(outfile,scimg)

and I use the scales array for x and y scale factors : scales = [(1,2),(2,1)] I want that the final image should be 300x300 and should contain the scaled version of the image. Thank you!!

Upvotes: 0

Views: 4983

Answers (1)

alkasm
alkasm

Reputation: 23012

If you want this just for display purposes, the easiest way is to use cv2.imshow() on your cropped image and use cv2.resizeWindow() to set the window size to the original image size.

import cv2
import numpy as np

img = cv2.imread('lena.png')
h, w = img.shape[:2]
scale = (0.6, 0.4) # define your scale
scaled_img = cv2.resize(img, None, fx=scale[0], fy=scale[1]) # scale image

cv2.namedWindow("Scaled image", cv2.WINDOW_NORMAL) # create a resizeable window
cv2.imshow("Scaled image", scaled_img) # display the image in the window
cv2.resizeWindow("Scaled image", w, h) # resize the window
cv2.waitKey(0)

Will display a scaled image inside the window where the window has the original image size. This will work even if you scale the image larger, by cropping the right side and top side of the image off (the image is anchored at the bottom left corner of the window). See the output window:

Scaled Lena display

If, however, you want this to be saved into another matrix, then you'll need to pad the image with whatever color you want outside of the image. The easiest way is to create a matrix with that color, but then create an ROI the size of your scaled image and place the scaled image within the ROI.

import cv2
import numpy as np

img = cv2.imread('lena.png')
scale = (0.6, 0.4) # define your scale
scaled_img = cv2.resize(img, None, fx=scale[0], fy=scale[1]) # scale image

sh, sw = scaled_img.shape[:2] # get h, w of scaled image
padded_scaled = np.zeros(img.shape, dtype=np.uint8)
padded_scaled[0:sh, 0:sw] = scaled_img

cv2.imshow("Scaled image", padded_scaled)
cv2.waitKey(0)

This will put the scaled image at 0,0 in the destination padded_scaled:

Padded and scaled image

If instead you want it displayed at the center, you can do some simple computations to get the center of the image, and then shift by half the width and height of the scaled image for the starting position.

import cv2
import numpy as np

img = cv2.imread('lena.png')
scale = (0.6, 0.4) # define your scale
scaled_img = cv2.resize(img, None, fx=scale[0], fy=scale[1]) # scale image

h, w = img.shape[:2] # get h, w of image
sh, sw = scaled_img.shape[:2] # get h, w of scaled image
center_y = int(h/2 - sh/2)
center_x = int(w/2 - sw/2)
padded_scaled = np.zeros(img.shape, dtype=np.uint8) # using img.shape to obtain #channels
padded_scaled[center_y:center_y+sh, center_x:center_x+sw] = scaled_img

cv2.imshow("Scaled image", padded_scaled)
cv2.waitKey(0)

This will place the scaled image in the center of the padded matrix:

Scaled and centered image


This will only work when images are scaled smaller than the original image, as you're placing a smaller image into a larger blank matrix. However, if you want to allow for larger scaling, then you'll need a crop at that point. You can just crop a region from the scaled image that is the same size as the original image. However, if the scale is larger than 1 in one dimension and less than 1 in another, you'll need to both crop and pad. I separated this into two sections of if statements: first create a result that is at most the size of the image by cropping if necessary, and then pad if it's smaller.

import cv2
import numpy as np

img = cv2.imread('lena.png')
scale_x, scale_y = 13, .3 # define your scale
h, w = img.shape[:2] # get h, w of image

if scale_x > 1 or scale_y > 1:

    scaled_img = cv2.resize(img, None, fx=scale_x, fy=scale_y, interpolation = cv2.INTER_LINEAR) # scale image
    sh, sw = scaled_img.shape[:2] # get h, w of scaled image
    center_y = int(sh/2 - h/2)
    center_x = int(sw/2 - w/2)
    cropped = scaled_img[center_y:center_y+h, center_x:center_x+w]

    result = cropped

elif scale_x > 0 or scale_y > 0:
    scaled_img = cv2.resize(img, None, fx=scale_x, fy=scale_y, interpolation = cv2.INTER_CUBIC) # scale image
    result = scaled_img

else: # scale_x or scale_y is negative
    print("Scales must be greater than 0; returning the original image.")
    result = img

if result.shape < img.shape: # one of the dimensions was scaled smaller, so need to pad
    sh, sw = result.shape[:2] # get h, w of cropped, scaled image
    center_y = int(h/2 - sh/2)
    center_x = int(w/2 - sw/2)
    padded_scaled = np.zeros(img.shape, dtype=np.uint8)
    padded_scaled[center_y:center_y+sh, center_x:center_x+sw] = result
    result = padded_scaled

cv2.imshow("Scaled padded/cropped image", result)
cv2.waitKey(0)

With a large stretch in one direction and a smaller scale in the other, this will result in an image displayed like this:

Scaled, cropped, and padded image

Upvotes: 1

Related Questions