2

I am working on a problem where I need to resize an image to a specific aspect ratio. I am not able to think of any solution. I am sharing a sample image with a processed image from pixelz. For example, the input image size is 1833 x 2133 and it gets resized to 1623 x 2164, which is a 3:4 ratio. The output image size is not fixed could be any nearest size with a 3:4 ratio.

Maybe we could approach it by finding the nearest number pair that has a 3:4 ratio.

enter image description here

Monark Unadkat
  • 180
  • 1
  • 11
  • 1
    More importantly, could you clarify your question: do you want to crop the image (discarding a bit at the top/bottom or at the left/right) or do you want to blindly resize it without cropping (on your example image, this would make the woman look fatter or thinner than she originally was)? – Stef Nov 10 '21 at 12:33
  • @Stef, it would be better we can do that. let's say I am also segmenting the object or foreground from the image. yeah, I guess we can discard background pixels, kind of cropping, and it would be a better solution. – Monark Unadkat Nov 10 '21 at 13:32
  • Why is 1623x2164 better than other 3:4 ratios such as 1599/2132 or 1602/2136 ? Do you have other constraints that drive this particular choice. – Alain T. Nov 10 '21 at 13:37
  • @AlainT. No No. I was just giving an example of how the wxh should change. Any nearest 3:4 resolution will work. – Monark Unadkat Nov 10 '21 at 14:05
  • 2
    If any 3:4 is ok, then: `h -= h%4` `w = h*3//4` – Alain T. Nov 10 '21 at 14:15
  • Not sure why this was closed as a duplicate - it's not. The other solution talks about cropping, this one talks about resizing to new aspect ratio. Clearly, those closing this didn't know the difference. – Todd Main Feb 23 '23 at 19:10

2 Answers2

4

Cropping

First you need to decide whether the image is too wide or too tall for the wanted ratio.

Then adjust the corresponding dimension.

I don't know which python image processing library you're using, so my answer only contains the arithmetic code to compute the new width and height, not the actual code to transform the image.

def cropped_dims(width, height, ratio):
    if width > height*ratio:
        width = int(height*ratio + 0.5)
    else:
        height = int(width/ratio + 0.5)
    return (width, height)

print(cropped_dims(1833, 2133, 3/4))
# (1600, 2133)

Closest number pair

In the example you give, 1833 x 2133 gets resized to 1623 x 2164, not to 1600 x 2133. Instead of cropping down to the appropriate ratio, you mention looking for the "nearest number pair" with the appropriate ratio.

A number pair can be represented as a point in the plane, using Cartesian coordinates.

Under this representation, the number pairs with the appropriate ratio are exactly the points on the line whose slope is this ratio (with the exception of point (0,0) which doesn't have a ratio).

The original dimensions of the image are also a number pair, and this number pair is represented by a point in the plane; a priori, this point is not on the line.

The solution your are looking for is the closest point on the line. This is a very classical geometry problem, with a simple solution. For an explanation on how to solve this problem, see for instance this question: Point on a line closest to third point.

The line L containing the points of ratio ratio consists in the points of the form (ratio * t, t) for some real number t.

The perpendicular D to that line L passing through (width, height) consists in the points of the form (width+s, height - ratio * s) for some real number s.

The closest point to (width, height) on line L is the point at the intersection of L and D. You can find its coordinates by solving for unknowns (s,t) the system of equations (ratio * t, t) == (width+s, height - ratio * s).

The solution is t = (ratio * width + height) / (1 + ratio**2).

Hence the python code:

def resized_dims(width, height, ratio):
   t = (height + ratio * width) / (ratio**2 + 1)
   new_width = int(t*ratio + 0.5)
   new_height = int(t + 0.5)
   return new_width, new_height

print(resized_dims(1833, 2133, 3/4))
# (1684, 2245)

Comparing the different solutions:

import math

w, h = (1833, 2133)
cropped_w, cropped_h = cropped_dims(1833, 2133, 3/4) # (1600, 2133)
closest_w, closest_h = resized_dims(1833, 2133, 3/4) # (1684, 2245)
your_w, your_h = (1623, 2164)

print('Original ratio:           ', w/h)
print('Wanted ratio:             ', 3/4)
print('Ratio of cropped:         ', cropped_w/cropped_h)
print('Ratio of closest:         ', closest_w/closest_h)
print('Ratio of your solution:   ', your_w/your_h)
print()
print('Distance of cropped:      ', math.dist((w,h), (cropped_w,cropped_h)))
print('Distance of closest:      ', math.dist((w,h), (closest_w,closest_h)))
print('Distance of your solution:', math.dist((w,h), (your_w,your_h)))

# Original ratio:            0.8594
# Wanted ratio:              0.75
# Ratio of cropped:          0.7501
# Ratio of closest:          0.7501
# Ratio of your solution:    0.75
# 
# Distance of cropped:       233.0
# Distance of closest:       186.40
# Distance of your solution: 212.28

Stef
  • 13,242
  • 2
  • 17
  • 28
1

In your specific case, you would need find integral solution to the equation(as you hint)

1833+x/z = 3/4 This will lead to something lie z= (1833 +x) *4/3 One can see that with a value of x=0, and z= 2444 hence the nearest aspect ratio sized image will be 1833/2444

One can also write a python routine that does returns an image size, for a given image and aspect ratio.

def find_nearest (img_x , asp_neu, asp_den):
    delta_x =1
    while True:
       
        y= (img_x*asp_den) % asp_neu
        if y == 0 :
            img_y= (img_x*asp_den)/asp_neu
            return [img_x , img_y]
        if delta_x > 100:  #just for safety
            return [0, 0]
        delta_x +=1
        img_x +=1

print(find_nearest(1833,3,4 ))
ecsridhar
  • 111
  • 5