mirror of
https://github.com/opencv/opencv.git
synced 2026-11-09 18:04:29 -06:00
Merge pull request #30022 from Lurie97:carotene-resize-left-edge-clamp
carotene: clamp left-edge weight in downsample_bilinear_8uc1 - #30022 For the leftmost pixel the sample coordinate c = 0.5*wr - 0.5 can be negative, so the left-column weight overflows full-scale and interpolation extrapolates beyond the source, corrupting the first output column. Clamp the weight to full-scale when c < 0, matching the scalar/OpenCL edge handling. Affects both the 16- and 8-wide column loops. ### Pull Request Readiness Checklist 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 - [ ] The PR is proposed to the proper branch - [ ] There is a reference to the original bug report and related work - [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [ ] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
@@ -1607,6 +1607,15 @@ void downsample_bilinear_8uc1(const Size2D &ssize, const Size2D &dsize,
|
||||
|
||||
cwi[k] = (short)floorf((col2[k] - c) * (1 << SHIFT_BITS) + 0.5f);
|
||||
|
||||
// When the sample point falls left of the first source column
|
||||
// (c < 0, happens for the leftmost destination pixel when
|
||||
// upscaling horizontally, wr < 1), the left-column weight cwi
|
||||
// overflows past the full-scale value (1<<SHIFT_BITS) and the
|
||||
// interpolation extrapolates beyond the source range. Clamp the
|
||||
// weight so the leftmost pixel is taken as-is, matching the
|
||||
// OpenCV scalar / OpenCL behaviour (if (x<0) x=0, u=0).
|
||||
if(c < 0.f) cwi[k] = (1 << SHIFT_BITS);
|
||||
|
||||
if(col1[k] < 0) col1[k] = 0;
|
||||
if(col2[k] >= (ptrdiff_t)ssize.width) col2[k] = ssize.width-1;
|
||||
}
|
||||
@@ -1770,6 +1779,11 @@ downsample_bilinear_8uc1_col_loop8:
|
||||
|
||||
cwi[k] = (s16)floorf((col2[k] - c) * (1 << SHIFT_BITS) + 0.5f);
|
||||
|
||||
// See the 16-wide loop above: clamp the left-column weight when the
|
||||
// sample point is left of the first source column (horizontal
|
||||
// upscaling, wr < 1) to avoid extrapolation on the leftmost pixel.
|
||||
if(c < 0.f) cwi[k] = (1 << SHIFT_BITS);
|
||||
|
||||
if(col1[k] < 0) col1[k] = 0;
|
||||
if(col2[k] >= (ptrdiff_t)ssize.width) col2[k] = (ptrdiff_t)ssize.width-1;
|
||||
}
|
||||
|
||||
@@ -1545,6 +1545,62 @@ TEST(Resize, nearest_regression_15075)
|
||||
EXPECT_EQ(C, cvtest::norm(dst, NORM_L1)) << src.size;
|
||||
}
|
||||
|
||||
// Horizontal upscaling of 8UC1 (fx > 1) exercises the carotene NEON path
|
||||
// (downsample_bilinear_8uc1, wr < 1) on ARM. The leftmost output column used
|
||||
// to extrapolate because the negative sample coordinate produced an
|
||||
// out-of-range interpolation weight. Compare against a double-precision
|
||||
// bilinear reference; the first column must match like any other.
|
||||
// Uses sizes where sw*fx is integral so only the horizontal-upscale path is
|
||||
// under test (no dsize-rounding effects). On non-ARM builds this simply
|
||||
// verifies the scalar path stays correct.
|
||||
TEST(Resize, horizontal_upscale_left_edge_regression)
|
||||
{
|
||||
RNG& rng = theRNG();
|
||||
const struct { int sw, sh; double fx, fy; } cases[] = {
|
||||
{ 100, 100, 1.5, 0.5 },
|
||||
{ 200, 100, 1.5, 0.5 },
|
||||
{ 64, 64, 1.5, 1.0 },
|
||||
{ 128, 96, 2.0, 0.5 },
|
||||
};
|
||||
|
||||
for (const auto& c : cases)
|
||||
{
|
||||
SCOPED_TRACE(cv::format("%dx%d fx=%.1f fy=%.1f", c.sw, c.sh, c.fx, c.fy));
|
||||
Mat src(c.sh, c.sw, CV_8UC1);
|
||||
rng.fill(src, RNG::UNIFORM, 0, 256);
|
||||
|
||||
Mat dst;
|
||||
cv::resize(src, dst, Size(), c.fx, c.fy, INTER_LINEAR);
|
||||
|
||||
const double rx = (double)dst.cols / c.sw, ry = (double)dst.rows / c.sh;
|
||||
double maxdiff = 0;
|
||||
for (int dy = 0; dy < dst.rows; dy++)
|
||||
{
|
||||
double sy = (dy + 0.5) / ry - 0.5;
|
||||
int y0 = cvFloor(sy); double v = sy - y0;
|
||||
if (y0 < 0) { y0 = 0; v = 0; }
|
||||
if (y0 >= c.sh) { y0 = c.sh - 1; v = 0; }
|
||||
int y1 = std::min(y0 + 1, c.sh - 1);
|
||||
for (int dx = 0; dx < dst.cols; dx++)
|
||||
{
|
||||
double sx = (dx + 0.5) / rx - 0.5;
|
||||
int x0 = cvFloor(sx); double u = sx - x0;
|
||||
if (x0 < 0) { x0 = 0; u = 0; }
|
||||
if (x0 >= c.sw) { x0 = c.sw - 1; u = 0; }
|
||||
int x1 = std::min(x0 + 1, c.sw - 1);
|
||||
double ref = (1 - u) * (1 - v) * src.at<uchar>(y0, x0)
|
||||
+ u * (1 - v) * src.at<uchar>(y0, x1)
|
||||
+ (1 - u) * v * src.at<uchar>(y1, x0)
|
||||
+ u * v * src.at<uchar>(y1, x1);
|
||||
maxdiff = std::max(maxdiff, std::abs(ref - dst.at<uchar>(dy, dx)));
|
||||
}
|
||||
}
|
||||
// Allow small fixed-point rounding differences; the pre-fix carotene
|
||||
// bug produced first-column errors of ~100+.
|
||||
EXPECT_LE(maxdiff, 3.0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Imgproc_Warp, multichannel)
|
||||
{
|
||||
static const int inter_types[] = {INTER_NEAREST, INTER_AREA, INTER_CUBIC,
|
||||
|
||||
Reference in New Issue
Block a user