-
-
Notifications
You must be signed in to change notification settings - Fork 55.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Nearest neighbor interpolation does not give expected results #9096
Comments
How did you come up with expected result? The downsampling factor is 3/5 (in horizontal direction) meaning destination columns 0, 1, 2 are mapped to source columns with indices 0, 1 * 5/3 and 2 * 5/3 (i.e., 0, 1.67 and 3.33). After rounding half up (probably what you expect), the values of the corresponding columns are 0, 2, 3. Either way, it seems OpenCV rounds towards zero instead of rounding half away from zero (not verified). |
You can compute the coordinates as following:
to get the expected result. PIL works accordingly:
gives:
|
Why do you shift by 0.5 before transforming the coordinates? Do you assume the center of the pixel to be at .5 fraction of the pixel coordinate? I don't know much about conventions used by PIL.
|
Exactly, treating pixels as squares with center at .5. |
@sergiud The issue is that opencv's @alalek What is the reason for the |
@gerhardneuhold I agree. |
There is no exact formula in documentation (and probably will not be there for general case - for performance reason and rounding issues). Existed resize tests are passed. So this issue looks like the question about the used resize formula in OpenCV. |
Just copy here from this thread: Results of reading and resizing can be different in cv2 and Pilllow. This creates problems when you want to reuse a model (neural network) trained using cv2 with Pillow. import cv2
from PIL import Image
im = cv2.imread('_in.png', cv2.IMREAD_COLOR)
cv2.resize(im, (3, 3), interpolation=cv2.INTER_NEAREST)
cv2.imwrite('_out.cv2.png', im)
im = Image.open('_in.png')
im = im.resize((3, 3), Image.NEAREST)
im.save('_out.pil.png') Please, look at the sample: This image with the uniform gradient (from 100% white to 100% black) allows us to find out which pixels are used by each library. This is the same image after resizing to (3, 3). Left is CV2, right is Pillow: OpenCV uses the topmost left white pixel from the source image, but the bottommost right pixel on the result is too bright. Here are maps of pixels on the source image: It's clear to me there are some problems with rounding in OpenCV there. |
@sergiud, @alalek I just tried this on my machine (version 3.3.0 on a mac via python bindings) and the behaviour is as described by @gerhardneuhold. He is quite right that your earlier example from July introduces sub-pixel translation, and if this is the logic that is implemented in the code than it needs to be fixed. When resizing the image it's not helpful to think in terms of pixel centers, you have to think in terms of image edges, and pixel spans. Pixel
So if we assume pixel centers are at What current code seems to be doing is this, instead
So you are sampling from a slightly different "physical region", introducing sub-pixel translation. I don't think those differences come from rounding behaviour differences or anything like that, it comes from subpixel translation introduced by resize method. The error is in your coordinate computation math. And yes |
using The convention whether pixels are points or whether pixels are areas is a common debate in computer graphics. It's the same issue in OpenGL, Direct3D, ... import numpy as np
import cv2 as cv
src = np.float32([[0, 1, 2, 3, 4]])
(srows, scols) = src.shape
(dcols, drows) = dsize = (3,1)
# some composition functions
def translate(tvec):
H = np.eye(3)
H[0,2] = tvec[0]
H[1,2] = tvec[1]
return H
def scale(svec):
H = np.eye(3)
H[0,0] = svec[0]
H[1,1] = svec[1]
return H
# don't need rotation, diy
# or use getRotationMatrix2D(center, angle, scale)
def project(H):
# it's a projective space,
# where (x,y) is represented by all (x,y,1)*w coordinates, i.e. a ray through (x,y,1)
# these are homogeneous coordinates
# project onto w=1
M = H / H[2,2]
# check that this isn't a perspective transform, affine only
assert np.allclose(M[2,0:2], 0)
# if you want perspective transforms, return the full 3x3 matrix and use warpPerspective
return M[0:2, 0:3]
# 0.0 1.0 2.0 3.0 4.0
# 5: |-----+-----|-----+-----|-----+-----|-----+-----|-----+-----|
# phy: |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|
# 3: |---------+---------|---------+---------|---------+---------|
# 0' 1' 2'
# define dst->src transformation
# for sampling dest point value in src space
M = project(
scale([scols/dcols, srows/drows])
)
dst1 = cv.warpAffine(src, M, dsize=dsize, flags=cv.WARP_INVERSE_MAP | cv.INTER_LINEAR)
print(dst1)
# => [[0. 1.65625 3.34375]]
dst1 = cv.warpAffine(src, M, dsize=dsize, flags=cv.WARP_INVERSE_MAP | cv.INTER_NEAREST)
print(dst1)
# => [[0. 2. 3.]]
# 0.0 1.0 2.0 3.0 4.0
# 5: |-----+-----|-----+-----|-----+-----|-----+-----|-----+-----|
# phy: |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|
# 3: |---------+---------|---------+---------|---------+---------|
# 0' 1' 2'
# define dst->src transformation
# for sampling dest point value in src space
M = project(
translate([-0.5, -0.5]) @
scale([scols/dcols, srows/drows]) @
translate([+0.5, +0.5])
)
dst2 = cv.warpAffine(src, M, dsize=dsize, flags=cv.WARP_INVERSE_MAP | cv.INTER_LINEAR)
print(dst2)
# => [[0.34375 2. 3.65625]]
dst2 = cv.warpAffine(src, M, dsize=dsize, flags=cv.WARP_INVERSE_MAP | cv.INTER_NEAREST)
print(dst2)
# => [[0. 2. 4.]] |
Possibly fixed by |
Great - just for completeness, the related interpolation mode for this issue here is cv2.INTER_NEAREST_EXACT (instead of INTER_LINEAR_EXACT). |
User error. You aren't passing the INTER value in the correct position. Please direct your usage questions to the forum or Stack Overflow. |
Thanks for your reply! it's really my mistake, i forgot to add fx,fy parms this leading INTER value in wrong position. |
Can this issue be closed? The story about resize compatibility across the libraries is pretty old. However I believe that we have finished with the described problem by this code: import cv2
import numpy as np
a = np.array([[0, 1, 2, 3, 4]], dtype=np.uint8)
print('nearest', cv2.resize(a, dsize=(3, 1), interpolation=cv2.INTER_NEAREST_EXACT))
|
Basically this is not a story about compatibility, this is story of wrong behavior.
What does mean by "bit exact" if neighbor interpolation doesn't isn't interpolation actually and don't operates pixels' values? It should be "Correct nearest neighbor interpolation".
It will not produce the same results as PIL since PIL originally had the same issue and it was fixed in Pillow (python-pillow/Pillow#2022) which is different library. |
Oh, despite #9096 (comment), it worked only for odd dimensions at input: import cv2
import numpy as np
from skimage.transform import resize
for l in range(5, 9):
for t in (3, 4):
a = np.arange(l, dtype=np.uint8).reshape(1, l)
outCV = cv2.resize(a, dsize=(t, 1), interpolation=cv2.INTER_NEAREST_EXACT)
outScikit = resize(a, output_shape=(1, t), order=0, preserve_range=True, anti_aliasing=False)
print(a, outCV, outScikit)
|
Both results are correct. The sampling points are in the exact middle of two pixels, so the "nearest" pixel is ambiguous and different rounding conventions can produce different results. |
@ppwwyyxx, there is a statement that
So the goal of #23634 is to resolve #22204. However, the results are not the same for all the scales. For example:
But I believe that at least the case with x2 nearest downsampling from even sizes should be deterministic. So it's not about correctness but portability. |
Fix even input dimensions for INTER_NEAREST_EXACT #23634 ### Pull Request Readiness Checklist resolves #22204 related: #9096 (comment) /cc @Yosshi999 See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [x] The feature is well documented and sample code can be built with the project CMake
Fix even input dimensions for INTER_NEAREST_EXACT opencv#23634 ### Pull Request Readiness Checklist resolves opencv#22204 related: opencv#9096 (comment) /cc @Yosshi999 See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [x] The feature is well documented and sample code can be built with the project CMake
FYI using project, translate, scale from this comment and INTER_NEAREST (because INTER_NEAREST_EXACT isn't recognized by warpAffine it seems), cv.warpAffine matches skimage for these tests:
However, for a slightly different scale, it does not:
Is that expected? It seems to usually be the same as skimage when the scale divides the shape evenly (i.e.,
|
You used integer image inputs? Then rounding may accidentally make results the same. I would advise, in order of insight, highest to lowest:
The goal should be to behave in sensible ways, regardless of what some other library happens to do. They are not the benchmark. |
Yes. I am using integer images. Since this thread is about making sure NN interpolation works correctly, I was simply commenting on a difference in behavior in an area that I hadn't seen mentioned in this thread. |
Fix even input dimensions for INTER_NEAREST_EXACT opencv#23634 ### Pull Request Readiness Checklist resolves opencv#22204 related: opencv#9096 (comment) /cc @Yosshi999 See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [x] The feature is well documented and sample code can be built with the project CMake
System information (version)
Detailed description
Nearest neighbor interpolation using cv2.resize does not give expected results.
Steps to reproduce
gives
expected
The text was updated successfully, but these errors were encountered: