Merge pull request #29554 from mvanhorn/fix/29279-ipp-warpaffine-nearest-bitexact

imgproc: route IPP warpAffine INTER_NEAREST to native kernel for bit-exactness 🤖🤖🤖
This commit is contained in:
Abhishek Gola
2026-08-06 19:09:35 +05:30
committed by GitHub
2 changed files with 70 additions and 3 deletions

View File

@@ -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)

View File

@@ -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<int>(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<Mat> 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<double>(0, 2) += 37.3;
M.at<double>(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];