From 3fbe1f8d649ee709858b593bd69a4a85fe3224cb Mon Sep 17 00:00:00 2001 From: vartenkov Date: Thu, 16 Mar 2017 18:50:29 +0300 Subject: [PATCH] Fix multichannel warping with BORDER_CONSTANT Warping a matrix with more than 4 channels using BORDER_CONSTANT and INTER_NEAREST, INTER_CUBIC or INTER_LANCZOS4 interpolation led to undefined behaviour. This commit changes the behavior of these methods to be similar to that of INTER_LINEAR. Changed the scope of some of the variables to more local. Modified some tests to be able to detect the error described. --- modules/imgproc/src/imgwarp.cpp | 92 +++++++++++++-------------- modules/imgproc/test/test_imgwarp.cpp | 21 ++++-- 2 files changed, 60 insertions(+), 53 deletions(-) diff --git a/modules/imgproc/src/imgwarp.cpp b/modules/imgproc/src/imgwarp.cpp index 7bcf16a394..c5d2182c2e 100644 --- a/modules/imgproc/src/imgwarp.cpp +++ b/modules/imgproc/src/imgwarp.cpp @@ -3533,14 +3533,13 @@ static void remapNearest( const Mat& _src, Mat& _dst, const Mat& _xy, int borderType, const Scalar& _borderValue ) { Size ssize = _src.size(), dsize = _dst.size(); - int cn = _src.channels(); + const int cn = _src.channels(); const T* S0 = _src.ptr(); + T cval[CV_CN_MAX]; size_t sstep = _src.step/sizeof(S0[0]); - Scalar_ cval(saturate_cast(_borderValue[0]), - saturate_cast(_borderValue[1]), - saturate_cast(_borderValue[2]), - saturate_cast(_borderValue[3])); - int dx, dy; + + for(int k = 0; k < cn; k++ ) + cval[k] = saturate_cast(_borderValue[k & 3]); unsigned width1 = ssize.width, height1 = ssize.height; @@ -3550,14 +3549,14 @@ static void remapNearest( const Mat& _src, Mat& _dst, const Mat& _xy, dsize.height = 1; } - for( dy = 0; dy < dsize.height; dy++ ) + for(int dy = 0; dy < dsize.height; dy++ ) { T* D = _dst.ptr(dy); const short* XY = _xy.ptr(dy); if( cn == 1 ) { - for( dx = 0; dx < dsize.width; dx++ ) + for(int dx = 0; dx < dsize.width; dx++ ) { int sx = XY[dx*2], sy = XY[dx*2+1]; if( (unsigned)sx < width1 && (unsigned)sy < height1 ) @@ -3583,9 +3582,9 @@ static void remapNearest( const Mat& _src, Mat& _dst, const Mat& _xy, } else { - for( dx = 0; dx < dsize.width; dx++, D += cn ) + for(int dx = 0; dx < dsize.width; dx++, D += cn ) { - int sx = XY[dx*2], sy = XY[dx*2+1], k; + int sx = XY[dx*2], sy = XY[dx*2+1]; const T *S; if( (unsigned)sx < width1 && (unsigned)sy < height1 ) { @@ -3602,7 +3601,7 @@ static void remapNearest( const Mat& _src, Mat& _dst, const Mat& _xy, else { S = S0 + sy*sstep + sx*cn; - for( k = 0; k < cn; k++ ) + for(int k = 0; k < cn; k++ ) D[k] = S[k]; } } @@ -3622,7 +3621,7 @@ static void remapNearest( const Mat& _src, Mat& _dst, const Mat& _xy, sy = borderInterpolate(sy, ssize.height, borderType); S = S0 + sy*sstep + sx*cn; } - for( k = 0; k < cn; k++ ) + for(int k = 0; k < cn; k++ ) D[k] = S[k]; } } @@ -3852,16 +3851,15 @@ static void remapBilinear( const Mat& _src, Mat& _dst, const Mat& _xy, typedef typename CastOp::rtype T; typedef typename CastOp::type1 WT; Size ssize = _src.size(), dsize = _dst.size(); - int k, cn = _src.channels(); + const int cn = _src.channels(); const AT* wtab = (const AT*)_wtab; const T* S0 = _src.ptr(); size_t sstep = _src.step/sizeof(S0[0]); T cval[CV_CN_MAX]; - int dx, dy; CastOp castOp; VecOp vecOp; - for( k = 0; k < cn; k++ ) + for(int k = 0; k < cn; k++ ) cval[k] = saturate_cast(_borderValue[k & 3]); unsigned width1 = std::max(ssize.width-1, 0), height1 = std::max(ssize.height-1, 0); @@ -3871,7 +3869,7 @@ static void remapBilinear( const Mat& _src, Mat& _dst, const Mat& _xy, width1 = std::max(ssize.width-2, 0); #endif - for( dy = 0; dy < dsize.height; dy++ ) + for(int dy = 0; dy < dsize.height; dy++ ) { T* D = _dst.ptr(dy); const short* XY = _xy.ptr(dy); @@ -3879,7 +3877,7 @@ static void remapBilinear( const Mat& _src, Mat& _dst, const Mat& _xy, int X0 = 0; bool prevInlier = false; - for( dx = 0; dx <= dsize.width; dx++ ) + for(int dx = 0; dx <= dsize.width; dx++ ) { bool curInlier = dx < dsize.width ? (unsigned)XY[dx*2] < width1 && @@ -3948,7 +3946,7 @@ static void remapBilinear( const Mat& _src, Mat& _dst, const Mat& _xy, int sx = XY[dx*2], sy = XY[dx*2+1]; const AT* w = wtab + FXY[dx]*4; const T* S = S0 + sy*sstep + sx*cn; - for( k = 0; k < cn; k++ ) + for(int k = 0; k < cn; k++ ) { WT t0 = S[k]*w[0] + S[k+cn]*w[1] + S[sstep+k]*w[2] + S[sstep+k+cn]*w[3]; D[k] = castOp(t0); @@ -4012,7 +4010,7 @@ static void remapBilinear( const Mat& _src, Mat& _dst, const Mat& _xy, (sx >= ssize.width || sx+1 < 0 || sy >= ssize.height || sy+1 < 0) ) { - for( k = 0; k < cn; k++ ) + for(int k = 0; k < cn; k++ ) D[k] = cval[k]; } else @@ -4046,7 +4044,7 @@ static void remapBilinear( const Mat& _src, Mat& _dst, const Mat& _xy, v2 = sx0 >= 0 && sy1 >= 0 ? S0 + sy1*sstep + sx0*cn : &cval[0]; v3 = sx1 >= 0 && sy1 >= 0 ? S0 + sy1*sstep + sx1*cn : &cval[0]; } - for( k = 0; k < cn; k++ ) + for(int k = 0; k < cn; k++ ) D[k] = castOp(WT(v0[k]*w[0] + v1[k]*w[1] + v2[k]*w[2] + v3[k]*w[3])); } } @@ -4064,16 +4062,16 @@ static void remapBicubic( const Mat& _src, Mat& _dst, const Mat& _xy, typedef typename CastOp::rtype T; typedef typename CastOp::type1 WT; Size ssize = _src.size(), dsize = _dst.size(); - int cn = _src.channels(); + const int cn = _src.channels(); const AT* wtab = (const AT*)_wtab; const T* S0 = _src.ptr(); size_t sstep = _src.step/sizeof(S0[0]); - Scalar_ cval(saturate_cast(_borderValue[0]), - saturate_cast(_borderValue[1]), - saturate_cast(_borderValue[2]), - saturate_cast(_borderValue[3])); - int dx, dy; + T cval[CV_CN_MAX]; CastOp castOp; + + for(int k = 0; k < cn; k++ ) + cval[k] = saturate_cast(_borderValue[k & 3]); + int borderType1 = borderType != BORDER_TRANSPARENT ? borderType : BORDER_REFLECT_101; unsigned width1 = std::max(ssize.width-3, 0), height1 = std::max(ssize.height-3, 0); @@ -4084,21 +4082,20 @@ static void remapBicubic( const Mat& _src, Mat& _dst, const Mat& _xy, dsize.height = 1; } - for( dy = 0; dy < dsize.height; dy++ ) + for(int dy = 0; dy < dsize.height; dy++ ) { T* D = _dst.ptr(dy); const short* XY = _xy.ptr(dy); const ushort* FXY = _fxy.ptr(dy); - for( dx = 0; dx < dsize.width; dx++, D += cn ) + for(int dx = 0; dx < dsize.width; dx++, D += cn ) { int sx = XY[dx*2]-1, sy = XY[dx*2+1]-1; const AT* w = wtab + FXY[dx]*16; - int i, k; if( (unsigned)sx < width1 && (unsigned)sy < height1 ) { const T* S = S0 + sy*sstep + sx*cn; - for( k = 0; k < cn; k++ ) + for(int k = 0; k < cn; k++ ) { WT sum = S[0]*w[0] + S[cn]*w[1] + S[cn*2]*w[2] + S[cn*3]*w[3]; S += sstep; @@ -4123,21 +4120,21 @@ static void remapBicubic( const Mat& _src, Mat& _dst, const Mat& _xy, (sx >= ssize.width || sx+4 <= 0 || sy >= ssize.height || sy+4 <= 0)) { - for( k = 0; k < cn; k++ ) + for(int k = 0; k < cn; k++ ) D[k] = cval[k]; continue; } - for( i = 0; i < 4; i++ ) + for(int i = 0; i < 4; i++ ) { x[i] = borderInterpolate(sx + i, ssize.width, borderType1)*cn; y[i] = borderInterpolate(sy + i, ssize.height, borderType1); } - for( k = 0; k < cn; k++, S0++, w -= 16 ) + for(int k = 0; k < cn; k++, S0++, w -= 16 ) { WT cv = cval[k], sum = cv*ONE; - for( i = 0; i < 4; i++, w += 4 ) + for(int i = 0; i < 4; i++, w += 4 ) { int yi = y[i]; const T* S = S0 + yi*sstep; @@ -4169,16 +4166,16 @@ static void remapLanczos4( const Mat& _src, Mat& _dst, const Mat& _xy, typedef typename CastOp::rtype T; typedef typename CastOp::type1 WT; Size ssize = _src.size(), dsize = _dst.size(); - int cn = _src.channels(); + const int cn = _src.channels(); const AT* wtab = (const AT*)_wtab; const T* S0 = _src.ptr(); size_t sstep = _src.step/sizeof(S0[0]); - Scalar_ cval(saturate_cast(_borderValue[0]), - saturate_cast(_borderValue[1]), - saturate_cast(_borderValue[2]), - saturate_cast(_borderValue[3])); - int dx, dy; + T cval[CV_CN_MAX]; CastOp castOp; + + for(int k = 0; k < cn; k++ ) + cval[k] = saturate_cast(_borderValue[k & 3]); + int borderType1 = borderType != BORDER_TRANSPARENT ? borderType : BORDER_REFLECT_101; unsigned width1 = std::max(ssize.width-7, 0), height1 = std::max(ssize.height-7, 0); @@ -4189,21 +4186,20 @@ static void remapLanczos4( const Mat& _src, Mat& _dst, const Mat& _xy, dsize.height = 1; } - for( dy = 0; dy < dsize.height; dy++ ) + for(int dy = 0; dy < dsize.height; dy++ ) { T* D = _dst.ptr(dy); const short* XY = _xy.ptr(dy); const ushort* FXY = _fxy.ptr(dy); - for( dx = 0; dx < dsize.width; dx++, D += cn ) + for(int dx = 0; dx < dsize.width; dx++, D += cn ) { int sx = XY[dx*2]-3, sy = XY[dx*2+1]-3; const AT* w = wtab + FXY[dx]*64; const T* S = S0 + sy*sstep + sx*cn; - int i, k; if( (unsigned)sx < width1 && (unsigned)sy < height1 ) { - for( k = 0; k < cn; k++ ) + for(int k = 0; k < cn; k++ ) { WT sum = 0; for( int r = 0; r < 8; r++, S += sstep, w += 8 ) @@ -4226,21 +4222,21 @@ static void remapLanczos4( const Mat& _src, Mat& _dst, const Mat& _xy, (sx >= ssize.width || sx+8 <= 0 || sy >= ssize.height || sy+8 <= 0)) { - for( k = 0; k < cn; k++ ) + for(int k = 0; k < cn; k++ ) D[k] = cval[k]; continue; } - for( i = 0; i < 8; i++ ) + for(int i = 0; i < 8; i++ ) { x[i] = borderInterpolate(sx + i, ssize.width, borderType1)*cn; y[i] = borderInterpolate(sy + i, ssize.height, borderType1); } - for( k = 0; k < cn; k++, S0++, w -= 64 ) + for(int k = 0; k < cn; k++, S0++, w -= 64 ) { WT cv = cval[k], sum = cv*ONE; - for( i = 0; i < 8; i++, w += 8 ) + for(int i = 0; i < 8; i++, w += 8 ) { int yi = y[i]; const T* S1 = S0 + yi*sstep; diff --git a/modules/imgproc/test/test_imgwarp.cpp b/modules/imgproc/test/test_imgwarp.cpp index 8246754387..8d7b1f86ea 100644 --- a/modules/imgproc/test/test_imgwarp.cpp +++ b/modules/imgproc/test/test_imgwarp.cpp @@ -1686,22 +1686,33 @@ TEST(Resize, Area_half) TEST(Imgproc_Warp, multichannel) { + static const int inter_types[] = {INTER_NEAREST, INTER_AREA, INTER_CUBIC, + INTER_LANCZOS4, INTER_LINEAR}; + static const int inter_n = sizeof(inter_types) / sizeof(int); + + static const int border_types[] = {BORDER_CONSTANT, BORDER_DEFAULT, + BORDER_REFLECT, BORDER_REPLICATE, + BORDER_WRAP, BORDER_WRAP}; + static const int border_n = sizeof(border_types) / sizeof(int); + RNG& rng = theRNG(); - for( int iter = 0; iter < 30; iter++ ) + for( int iter = 0; iter < 100; iter++ ) { + int inter = inter_types[rng.uniform(0, inter_n)]; + int border = border_types[rng.uniform(0, border_n)]; int width = rng.uniform(3, 333); int height = rng.uniform(3, 333); - int cn = rng.uniform(1, 10); + int cn = rng.uniform(1, 15); Mat src(height, width, CV_8UC(cn)), dst; //randu(src, 0, 256); src.setTo(0.); - Mat rot = getRotationMatrix2D(Point2f(0.f, 0.f), 1, 1); - warpAffine(src, dst, rot, src.size()); + Mat rot = getRotationMatrix2D(Point2f(0.f, 0.f), 1.0, 1.0); + warpAffine(src, dst, rot, src.size(), inter, border); ASSERT_EQ(0.0, norm(dst, NORM_INF)); Mat rot2 = Mat::eye(3, 3, rot.type()); rot.copyTo(rot2.rowRange(0, 2)); - warpPerspective(src, dst, rot2, src.size()); + warpPerspective(src, dst, rot2, src.size(), inter, border); ASSERT_EQ(0.0, norm(dst, NORM_INF)); } }