The code from answer to this question produces an error for some values of zoom factor.
As mentioned in comments by @kg_sYy, "The rounding in int(np.round(h * zoom_factor)) seems to sometimes cause the resulting image to be 1 pixel smaller than target. The calculation then gets -1 as diff and you get image pixel size 1 for output. Changing to np.ceil() instead of np.round() seems to fix it."
However, changing to np.ceil() does not fix the error.
As an example, zoom_factor = 1.1317 and image shape (331, 331, 3) result in zoomed_img being one pixel in size.
What other rounding function could fix the problem and why does it occur?
CodePudding user response:
Why it becomes a single pixel
The code you have linked in question makes the assumption that:
outmight still be slightly larger thanimgdue to rounding, so trim off any extra pixels at the edges
And then proceeds to do trimming without checking anything. The zoomed image becomes 1 pixel whenever out is actually smaller than img (due to bad rounding, as the comment mentions). Then these following calculations fail:
trim_top = ((out.shape[0] - h) // 2)
trim_left = ((out.shape[1] - w) // 2)
Since the new image is actually smaller than (h, w), the trim_top and trim_left are actually negative numbers (-1, empirically).
So the final trim is actually:
out = out[-1: h - 1, -1: w - 1]
This is nothing but the bottom right pixel at [-1, -1].
How to fix
- Ensure that
outis equal or greater thanimg. Usingceil()should handle this. (See if you can find any image size and zoom factor for whichoutis still smaller thanimg.) - Check whether
outis actually greater thanimgbefore trying to trim it. - Handle a possible round off when doing the trim.
def clipped_zoom(img, zoom_factor, **kwargs):
h, w = img.shape[:2]
zoom_tuple = (zoom_factor,) * 2 (1,) * (img.ndim - 2)
# Zooming out
if zoom_factor < 1:
# Bounding box of the zoomed-out image within the output array
zh = int(np.round(h * zoom_factor))
zw = int(np.round(w * zoom_factor))
top = (h - zh) // 2
left = (w - zw) // 2
# Zero-padding
out = np.zeros_like(img)
out[top:top zh, left:left zw] = zoom(img, zoom_tuple, **kwargs)
# Zooming in
elif zoom_factor > 1:
# Bounding box of the zoomed-in region within the input array
zh = int(np.ceil(h / zoom_factor))
zw = int(np.ceil(w / zoom_factor))
top = (h - zh) // 2
left = (w - zw) // 2
out = zoom(img[top: top zh, left: left zw], zoom_tuple, **kwargs)
# >>> Check out against img before trying to trim
# >>> trim safely, accounting for rounding
if out.shape[0] > h:
h_diff = out.shape[0] - h
trim_top = h_diff // 2
trim_bottom = h_diff - trim_top
out = out[trim_top: out.shape[0] - trim_bottom, :]
if out.shape[1] > w:
w_diff = out.shape[1] - w
trim_left = w_diff // 2
trim_right = w_diff - trim_left
out = out[:, trim_left: out.shape[1] - trim_right]
# If zoom_factor == 1, just return the input array
else:
out = img
return out
