diff --git a/hal/ipp/src/warp_ipp.cpp b/hal/ipp/src/warp_ipp.cpp index 1eea3228ff..c437fb94ba 100644 --- a/hal/ipp/src/warp_ipp.cpp +++ b/hal/ipp/src/warp_ipp.cpp @@ -99,14 +99,17 @@ int ipp_hal_warpAffine(int src_type, const uchar *src_data, size_t src_step, int {{1, 1, 0}, {0, 0, 0}, {1, 1, 0}, {1, 1, 0}}, //32F {{1, 1, 0}, {0, 0, 0}, {1, 1, 0}, {1, 1, 0}}}; //64F #else // IPP_CALLS_ENFORCED is not defined, results are strictly aligned to OpenCV implementation + // NEAREST column kept 0 for all types: IPP's iwiWarpAffine rounds source coords at the + // half-pixel boundary unlike the native kernel (warpAffineBlocklineNN), so its NN output + // is not bit-exact. Forces fallback to native. See https://github.com/opencv/opencv/issues/29279 /* C1 C2 C3 C4 */ char impl[CV_DEPTH_MAX][4][3]={{{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, //8U {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, //8S - {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {1, 0, 0}}, //16U - {{1, 0, 0}, {0, 0, 0}, {1, 0, 0}, {1, 0, 0}}, //16S + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, //16U + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, //16S {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, //32S {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, //32F - {{1, 0, 0}, {0, 0, 0}, {1, 0, 0}, {1, 0, 0}}}; //64F + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}}; //64F #endif if(impl[CV_TYPE(src_type)][CV_MAT_CN(src_type)-1][interpolation] == 0) diff --git a/modules/imgproc/test/test_imgwarp.cpp b/modules/imgproc/test/test_imgwarp.cpp index 382b74de4d..9e5e2b3448 100644 --- a/modules/imgproc/test/test_imgwarp.cpp +++ b/modules/imgproc/test/test_imgwarp.cpp @@ -1588,6 +1588,70 @@ TEST(Imgproc_Warp, regression_28554) } +TEST(Imgproc_Warp, regression_29279) +{ + // IPP's iwiWarpAffine rounds source coords at the half-pixel boundary unlike the native + // kernel (warpAffineBlocklineNN), so its NN output is not bit-exact. + // See https://github.com/opencv/opencv/issues/29279 + const Size srcSize(800, 600); + const Size dstSize(900, 900); + + // Gradient values in [1, ~30700]: representable exactly in CV_16S, CV_16U, CV_32F and + // CV_64F, so any change in the selected source pixel changes the resulting value. + Mat base(srcSize, CV_32SC1); + for (int y = 0; y < base.rows; y++) + for (int x = 0; x < base.cols; x++) + base.at(y, x) = 1 + ((x * 7 + y * 13) % 30000); + + const int channels[] = { 1, 3, 4 }; + const int nearestDepth[] = { CV_16S, CV_16U, CV_64F }; // formerly IPP-routed for NEAREST + const double angles[] = { 0.0, 13.7, 45.0, 90.0 }; // 0 and 90 are no-ops, guard regressions + + for (size_t ci = 0; ci < sizeof(channels) / sizeof(channels[0]); ci++) + { + const int cn = channels[ci]; + + std::vector planes(cn); + for (int c = 0; c < cn; c++) + planes[c] = base + c * 101; // distinct per-channel values, still in range + Mat srcInt; + merge(planes, srcInt); // CV_32SC(cn) + + Mat srcRef; + srcInt.convertTo(srcRef, CV_MAKETYPE(CV_32F, cn)); + + for (size_t ai = 0; ai < sizeof(angles) / sizeof(angles[0]); ai++) + { + const double angle = angles[ai]; + + // Same convention as the issue reproducer: rotation about (400, 300) plus a + // fractional translation. + Mat M = getRotationMatrix2D(Point2f(400.f, 300.f), angle, 1.0); + M.at(0, 2) += 37.3; + M.at(1, 2) += -12.8; + + Mat dstRef; + warpAffine(srcRef, dstRef, M, dstSize, INTER_NEAREST, BORDER_CONSTANT, Scalar::all(0)); + + for (size_t di = 0; di < sizeof(nearestDepth) / sizeof(nearestDepth[0]); di++) + { + const int type = CV_MAKETYPE(nearestDepth[di], cn); + Mat src; + srcInt.convertTo(src, type); + Mat dst; + warpAffine(src, dst, M, dstSize, INTER_NEAREST, BORDER_CONSTANT, Scalar::all(0)); + + Mat dstF; + dst.convertTo(dstF, CV_MAKETYPE(CV_32F, cn)); + EXPECT_EQ(0.0, cvtest::norm(dstRef, dstF, NORM_INF)) + << "INTER_NEAREST warp not bit-exact with native: depth=" << nearestDepth[di] + << " cn=" << cn << " angle=" << angle; + } + } + } +} + + TEST(Imgproc_GetAffineTransform, singularity) { Point2f A_sample[3];