opencv: Nearest neighbor interpolation does not give expected results
System information (version)
- OpenCV => 2.4.9
- Operating System / Platform => Ubuntu 16.04
- Compiler => gcc 5.4.0
Detailed description
Nearest neighbor interpolation using cv2.resize does not give expected results.
Steps to reproduce
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)
gives
nearest [[0 1 3]]
expected
nearest [[0 2 4]]
About this issue
- Original URL
- State: closed
- Created 7 years ago
- Reactions: 6
- Comments: 22 (10 by maintainers)
Commits related to this issue
- Merge pull request #23634 from dkurt:fix_nearest_exact Fix even input dimensions for INTER_NEAREST_EXACT #23634 ### Pull Request Readiness Checklist resolves https://github.com/opencv/opencv/is... — committed to opencv/opencv by dkurt a year ago
- Merge pull request #23634 from dkurt:fix_nearest_exact Fix even input dimensions for INTER_NEAREST_EXACT #23634 ### Pull Request Readiness Checklist resolves https://github.com/opencv/opencv/is... — committed to thewoz/opencv by dkurt a year ago
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.
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
0
of the destination image spans from the left edge of the source image pixel0
and into pixel1
, middle pixel of the destination image spans spans from pixel1
all the way into3
, see diagram below:So if we assume pixel centers are at
0,0
, then expected coords should be1/3, 2, 3+2/3
. Remember that source and destination images should span the same “physical region”.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
resize
should return0,2,4
in the example above and not0,1,3
.User error. You aren’t passing the INTER value in the correct position. Please direct your usage questions to the forum or Stack Overflow.
@ppwwyyxx, there is a statement that
So the goal of https://github.com/opencv/opencv/pull/23634 is to resolve https://github.com/opencv/opencv/issues/22204. However, the results are not the same for all the scales. For example:
[0 1 2 3 4 5 6 7 8 9]
[1 4 8]
[1 4 8]
[1 5 8]
[1 5 8]
[0 1 2 3 4 5]
[0 1 2 4 5]
[0 1 2 4 5]
[0 1 3 4 5]
[0 1 3 4 5]
[0 1 2 3 4 5 6 7]
[0 1 3 4 5 7]
[0 1 3 4 5 7]
[0 2 3 4 6 7]
[0 2 3 4 5 7]
[0 1 2 3 4 5]
[0 2 4]
[1 3 5]
[1 3 5]
[1 3 5]
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.
Great - just for completeness, the related interpolation mode for this issue here is cv2.INTER_NEAREST_EXACT (instead of INTER_LINEAR_EXACT).
Possibly fixed by
cv::InterpolationFlags::INTER_LINEAR_EXACT
cv::InterpolationFlags::INTER_NEAREST_EXACT
in #18053 .using
warpAffine
with custom matrix respecting pixel coordinate flavors would be a workaround. notice that I use INTER_LINEAR so you can see roughly where sampling is done. INTER_LINEAR appears to quantize a little; the values should be exact multiples of thirds.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, …
@gerhardneuhold I agree.
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 (https://github.com/python-pillow/Pillow/pull/2022) which is different library.
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.
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).