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:
out
might still be slightly larger thanimg
due 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
out
is equal or greater thanimg
. Usingceil()
should handle this. (See if you can find any image size and zoom factor for whichout
is still smaller thanimg
.) - Check whether
out
is actually greater thanimg
before 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