mirror of
https://github.com/opencv/opencv.git
synced 2026-07-29 23:33:05 +04:00
Merge pull request #28873 from mvanhorn:osc/28429-fix-inter-nearest-exact-fp-5x
imgproc: fix INTER_NEAREST_EXACT to match Pillow (5.x retarget of #28661) #28873 ## Summary Fixes `INTER_NEAREST_EXACT` producing different results from Pillow for images with dimensions that are multiples of 64 (e.g., 128, 192). Retargets #28661 to 5.x per core team request. ## Root cause The old 16-bit fixed-point arithmetic (`ifx`, `ifx0`, `>> 16`) rounds differently from Pillow's pixel-center coordinate mapping. At exact pixel boundaries (e.g., y=7 with 128 to 160: `0.4 + 7*0.8 = 5.999999999999999`), Pillow's truncation drops to 5 while the old opencv formula rounds to 6. ## Changes - `resize.cpp`: Replace `ifx/ifx0/ify/ify0` 16-bit fixed-point with an int64 arithmetic formula equivalent to `floor((i + 0.5) * src / dst)`, computed as `((int64_t)(i * 2 + 1) * src) / (dst * 2)`. This matches Pillow's pixel-center mapping exactly, is deterministic across platforms, and avoids FP rounding divergence entirely. - `test_resize_bitexact.cpp`: Add `NearestExact_PillowCompat` covering 4 dimension pairs including the reproducer from #28429. ## Testing - All 3 `Resize_Bitexact` tests pass (Linear8U, Nearest8U, NearestExact_PillowCompat) on this branch built against 5.x - Verified bit-exact match to PIL output (Python 3.14, Pillow) on 49 dimension combinations including random pairs - Built on macOS ARM64 with HAL (carotene/KleidiCV) active Fixes #28429
This commit is contained in:
@@ -4,6 +4,10 @@ from __future__ import print_function
|
||||
|
||||
import numpy as np
|
||||
import cv2 as cv
|
||||
try:
|
||||
from PIL import Image
|
||||
except ImportError:
|
||||
Image = None
|
||||
|
||||
from tests_common import NewOpenCVTests
|
||||
|
||||
@@ -28,3 +32,17 @@ class Imgproc_Tests(NewOpenCVTests):
|
||||
img_blur0 = cv.filter2D(img, cv.CV_32F, kernel*(1./9))
|
||||
img_blur1 = cv.filter2Dp(img, kernel, ddepth=cv.CV_32F, scale=1./9)
|
||||
self.assertLess(cv.norm(img_blur0 - img_blur1, cv.NORM_INF), eps)
|
||||
|
||||
def test_resize_pillow(self):
|
||||
if Image is None:
|
||||
self.skipTest("Pillow is not available")
|
||||
|
||||
r = np.random.randint(0, 255, size=(128, 147, 3), dtype="uint8")
|
||||
target_size=[(128,128), (129,129), (160,160)]
|
||||
for ts in target_size:
|
||||
|
||||
pil_result = np.array(Image.fromarray(r).resize(ts, Image.NEAREST))
|
||||
ocv_result = cv.resize(r, dsize=ts, interpolation=cv.INTER_NEAREST_EXACT)
|
||||
status = np.all(pil_result == ocv_result)
|
||||
print(ts, status)
|
||||
self.assertTrue(status, "resize result differs from Pillow for target size %s" % (ts,))
|
||||
|
||||
Reference in New Issue
Block a user