Merge pull request #29273 from amd:fast_sobel2d

imgproc: Extended spatialGradient API and applied to different detector algorithms - #29273

imgproc: Add fused Sobel2D gradient API and use it in different detector algorithms

Add a public Sobel2D API computing dx/dy in a single fused pass with 3x3 and 5x5 kernels (SIMD-dispatched). The float (CV_32F) path folds the output scale and float store into the kernel, avoiding a separate convertTo pass.

- Add runtime SIMD dispatch for the Canny edge path.
- Integrate fused Sobel2D into:
    - Canny
    - cornerEigenValsVecs (cornerHarris, cornerMinEigenVal, cornerEigenValsAndVecs, goodFeaturesToTrack)
    - GeneralizedHough
    - IntelligentScissors
    - HoughCircles
- Add performance and accuracy tests.

### 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
- [x] The PR is proposed to the proper branch
- [ ] There is a reference to the original bug report and related work
- [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
      Patch to opencv_extra has the same branch name.
- [x] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
Madan mohan Manokar
2026-08-24 17:36:23 +05:30
committed by GitHub
parent b2efa6f860
commit 44e7b4eb11
8 changed files with 365 additions and 28 deletions

View File

@@ -12,6 +12,7 @@
// //
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved. // Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
// Copyright (C) 2009, Willow Garage Inc., all rights reserved. // Copyright (C) 2009, Willow Garage Inc., all rights reserved.
// Copyright (C) 2026, Advanced Micro Devices, Inc., all rights reserved.
// Third party copyrights are property of their respective owners. // Third party copyrights are property of their respective owners.
// //
// Redistribution and use in source and binary forms, with or without modification, // Redistribution and use in source and binary forms, with or without modification,
@@ -1809,28 +1810,39 @@ CV_EXPORTS_W void Sobel( InputArray src, OutputArray dst, int ddepth,
double scale = 1, double delta = 0, double scale = 1, double delta = 0,
int borderType = BORDER_DEFAULT ); int borderType = BORDER_DEFAULT );
/** @brief Calculates the first order image derivative in both x and y using a Sobel operator /** @brief Calculates the first order image derivatives in both x and y using a Sobel operator,
computing them together in a single pass.
Equivalent to calling:
This is a fused variant of #Sobel: instead of two separate calls
@code @code
Sobel( src, dx, CV_16SC1, 1, 0, 3 ); Sobel( src, dx, ddepth, 1, 0, ksize, scale );
Sobel( src, dy, CV_16SC1, 0, 1, 3 ); Sobel( src, dy, ddepth, 0, 1, ksize, scale );
@endcode @endcode
it produces both first-order derivatives in one traversal of the source. For an 8-bit single-channel
whole-image (non-ROI) source with @p ksize = 3, @p ddepth = CV_16S, @p scale = 1, and
#BORDER_DEFAULT (#BORDER_REFLECT_101) or #BORDER_REPLICATE, a dedicated fused 3x3 stencil kernel is
used (including a HAL fast path) that reads each source sample once and shares it between the dx and
dy computations; the result is bit-identical to the two #Sobel calls above. All other cases
(@p ddepth = CV_32F, @p ksize = 5, scaled int16 output, floating-point source, other border types,
or ROI/sub-matrix input) currently fall back to the two equivalent #Sobel passes. @note Fused fast
paths for CV_32F output and @p ksize = 5 are added in a follow-up change.
@param src input image. @param src input image; single-channel, 8-bit (CV_8UC1) for the fused fast paths (CV_32FC1 is
@param dx output image with first-order derivative in x. accepted via the fallback).
@param dy output image with first-order derivative in y. @param dx output image with the first-order derivative in x (depth @p ddepth, same size as src).
@param ksize size of Sobel kernel. It must be 3. @param dy output image with the first-order derivative in y (depth @p ddepth, same size as src).
@param borderType pixel extrapolation method, see #BorderTypes. @param ksize size of the Sobel kernel; fused fast paths require 3 (5 uses the Sobel fallback).
Only #BORDER_DEFAULT=#BORDER_REFLECT_101 and #BORDER_REPLICATE are supported. Also accepts -1 (Scharr) and 7 for Sobel-compatible callers such as #HoughCircles.
@param borderType pixel extrapolation method, see #BorderTypes. #BORDER_WRAP is not supported.
@param ddepth output image depth; CV_16S or CV_32F.
@param scale optional scale factor applied to the computed derivatives.
@sa Sobel @sa Sobel
*/ */
CV_EXPORTS_W void spatialGradient( InputArray src, OutputArray dx, CV_EXPORTS_W void spatialGradient( InputArray src, OutputArray dx,
OutputArray dy, int ksize = 3, OutputArray dy, int ksize = 3,
int borderType = BORDER_DEFAULT ); int borderType = BORDER_DEFAULT,
int ddepth = CV_16S, double scale = 1 );
/** @brief Calculates the first x- or y- image derivative using Scharr operator. /** @brief Calculates the first x- or y- image derivative using Scharr operator.

View File

@@ -134,6 +134,110 @@ PERF_TEST_P(Size_MatType_dx_dy_Border5x5ROI, sobelFilter,
SANITY_CHECK(dst); SANITY_CHECK(dst);
} }
/**************** spatialGradient (fused dx+dy) ********************/
typedef tuple<Size, int, int> Size_Aperture_Border_t;
typedef perf::TestBaseWithParam<Size_Aperture_Border_t> Size_Aperture_Border;
PERF_TEST_P(Size_Aperture_Border, spatialGradient_fused,
testing::Combine(
testing::Values(FILTER_SRC_SIZES),
testing::Values(3, 5),
testing::Values((int)BORDER_DEFAULT, (int)BORDER_REPLICATE)
)
)
{
Size size = get<0>(GetParam());
int ksize = get<1>(GetParam());
int border = get<2>(GetParam());
Mat src(size, CV_8U);
Mat dx(size, CV_16S), dy(size, CV_16S);
declare.in(src, WARMUP_RNG).out(dx, dy);
TEST_CYCLE() spatialGradient(src, dx, dy, ksize, border);
SANITY_CHECK_NOTHING();
}
// Float output variant (CV_8U source -> CV_32F dx/dy), as used by corner detectors.
PERF_TEST_P(Size_Aperture_Border, spatialGradient_fused_32f,
testing::Combine(
testing::Values(FILTER_SRC_SIZES),
testing::Values(3, 5),
testing::Values((int)BORDER_DEFAULT, (int)BORDER_REPLICATE)
)
)
{
Size size = get<0>(GetParam());
int ksize = get<1>(GetParam());
int border = get<2>(GetParam());
Mat src(size, CV_8U);
Mat dx(size, CV_32F), dy(size, CV_32F);
declare.in(src, WARMUP_RNG).out(dx, dy);
TEST_CYCLE() spatialGradient(src, dx, dy, ksize, border, CV_32F);
SANITY_CHECK_NOTHING();
}
// Float baseline: the two cv::Sobel CV_32F calls (corner-detector gradient stage).
PERF_TEST_P(Size_Aperture_Border, spatialGradient_32f_baseline,
testing::Combine(
testing::Values(FILTER_SRC_SIZES),
testing::Values(3, 5),
testing::Values((int)BORDER_DEFAULT, (int)BORDER_REPLICATE)
)
)
{
Size size = get<0>(GetParam());
int ksize = get<1>(GetParam());
int border = get<2>(GetParam());
Mat src(size, CV_8U);
Mat dx(size, CV_32F), dy(size, CV_32F);
declare.in(src, WARMUP_RNG).out(dx, dy);
TEST_CYCLE()
{
Sobel(src, dx, CV_32F, 1, 0, ksize, 1, 0, border);
Sobel(src, dy, CV_32F, 0, 1, ksize, 1, 0, border);
}
SANITY_CHECK_NOTHING();
}
// Baseline: the two separate cv::Sobel calls that spatialGradient fuses (same params).
PERF_TEST_P(Size_Aperture_Border, spatialGradient_baseline,
testing::Combine(
testing::Values(FILTER_SRC_SIZES),
testing::Values(3, 5),
testing::Values((int)BORDER_DEFAULT, (int)BORDER_REPLICATE)
)
)
{
Size size = get<0>(GetParam());
int ksize = get<1>(GetParam());
int border = get<2>(GetParam());
Mat src(size, CV_8U);
Mat dx(size, CV_16S), dy(size, CV_16S);
declare.in(src, WARMUP_RNG).out(dx, dy);
TEST_CYCLE()
{
Sobel(src, dx, CV_16S, 1, 0, ksize, 1, 0, border);
Sobel(src, dy, CV_16S, 0, 1, ksize, 1, 0, border);
}
SANITY_CHECK_NOTHING();
}
/**************** Scharr ********************/ /**************** Scharr ********************/
PERF_TEST_P(Size_MatType_dx_dy_Border3x3, scharrFilter, PERF_TEST_P(Size_MatType_dx_dy_Border3x3, scharrFilter,

View File

@@ -13,6 +13,7 @@
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved. // Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
// Copyright (C) 2009, Willow Garage Inc., all rights reserved. // Copyright (C) 2009, Willow Garage Inc., all rights reserved.
// Copyright (C) 2014-2015, Itseez Inc., all rights reserved. // Copyright (C) 2014-2015, Itseez Inc., all rights reserved.
// Copyright (C) 2026, Advanced Micro Devices, Inc., all rights reserved.
// Third party copyrights are property of their respective owners. // Third party copyrights are property of their respective owners.
// //
// Redistribution and use in source and binary forms, with or without modification, // Redistribution and use in source and binary forms, with or without modification,
@@ -254,6 +255,15 @@ cornerEigenValsVecs( const Mat& src, Mat& eigenv, int block_size,
CV_Assert( src.type() == CV_8UC1 || src.type() == CV_32FC1 ); CV_Assert( src.type() == CV_8UC1 || src.type() == CV_32FC1 );
Mat Dx, Dy; Mat Dx, Dy;
// TODO(follow-up PR): route CV_32F gradients through the fused spatialGradient once its
// CV_32F/ksize=5 fast paths land. Disabled for now so this stays on the tuned Sobel path.
#if 0
if( aperture_size == 3 || aperture_size == 5 )
{
spatialGradient( src, Dx, Dy, aperture_size, borderType, CV_32F, scale );
}
else
#endif
if( aperture_size > 0 ) if( aperture_size > 0 )
{ {
Sobel( src, Dx, CV_32F, 1, 0, aperture_size, scale, 0, borderType ); Sobel( src, Dx, CV_32F, 1, 0, aperture_size, scale, 0, borderType );

View File

@@ -11,6 +11,7 @@
// For Open Source Computer Vision Library // For Open Source Computer Vision Library
// //
// Copyright (C) 2000, Intel Corporation, all rights reserved. // Copyright (C) 2000, Intel Corporation, all rights reserved.
// Copyright (C) 2026, Advanced Micro Devices, Inc., all rights reserved.
// Third party copyrights are property of their respective owners. // Third party copyrights are property of their respective owners.
// //
// Redistribution and use in source and binary forms, with or without modification, // Redistribution and use in source and binary forms, with or without modification,
@@ -115,8 +116,13 @@ namespace
CV_Assert( cannyLowThresh_ > 0 && cannyLowThresh_ < cannyHighThresh_ ); CV_Assert( cannyLowThresh_ > 0 && cannyLowThresh_ < cannyHighThresh_ );
Canny(src, edges, cannyLowThresh_, cannyHighThresh_); Canny(src, edges, cannyLowThresh_, cannyHighThresh_);
// TODO(follow-up PR): use the fused spatialGradient once its CV_32F fast path lands.
#if 0
spatialGradient(src, dx, dy, 3, BORDER_DEFAULT, CV_32F);
#else
Sobel(src, dx, CV_32F, 1, 0); Sobel(src, dx, CV_32F, 1, 0);
Sobel(src, dy, CV_32F, 0, 1); Sobel(src, dy, CV_32F, 0, 1);
#endif
} }
void GeneralizedHoughBase::setTemplateImpl(InputArray templ, Point templCenter) void GeneralizedHoughBase::setTemplateImpl(InputArray templ, Point templCenter)

View File

@@ -13,6 +13,7 @@
// Copyright (C) 2000, Intel Corporation, all rights reserved. // Copyright (C) 2000, Intel Corporation, all rights reserved.
// Copyright (C) 2013, OpenCV Foundation, all rights reserved. // Copyright (C) 2013, OpenCV Foundation, all rights reserved.
// Copyright (C) 2014, Itseez, Inc, all rights reserved. // Copyright (C) 2014, Itseez, Inc, all rights reserved.
// Copyright (C) 2026, Advanced Micro Devices, Inc., all rights reserved.
// Third party copyrights are property of their respective owners. // Third party copyrights are property of their respective owners.
// //
// Redistribution and use in source and binary forms, with or without modification, // Redistribution and use in source and binary forms, with or without modification,
@@ -1710,8 +1711,7 @@ static void HoughCirclesGradient(InputArray _image, OutputArray _circles,
Mat edges, dx, dy; Mat edges, dx, dy;
Sobel(_image, dx, CV_16S, 1, 0, kernelSize, 1, 0, BORDER_REPLICATE); spatialGradient(_image, dx, dy, kernelSize, BORDER_REPLICATE);
Sobel(_image, dy, CV_16S, 0, 1, kernelSize, 1, 0, BORDER_REPLICATE);
Canny(dx, dy, edges, std::max(1, cannyThreshold / 2), cannyThreshold, false); Canny(dx, dy, edges, std::max(1, cannyThreshold / 2), cannyThreshold, false);
Mutex mtx; Mutex mtx;

View File

@@ -1,6 +1,7 @@
// This file is part of OpenCV project. // This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory // It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html. // of this distribution and at http://opencv.org/license.html.
// Copyright (C) 2026, Advanced Micro Devices, Inc., all rights reserved.
// //
// Copyright (C) 2020, Intel Corporation, all rights reserved. // Copyright (C) 2020, Intel Corporation, all rights reserved.
// Third party copyrights are property of their respective owners. // Third party copyrights are property of their respective owners.
@@ -144,8 +145,18 @@ struct IntelligentScissorsMB::Impl
if (!Ix_.empty()) if (!Ix_.empty())
return; return;
initGrayscale_(image); initGrayscale_(image);
Sobel(grayscale_, Ix_, CV_32FC1, 1, 0, sobelKernelSize); // TODO(follow-up PR): use the fused spatialGradient once its CV_32F/ksize=5 fast paths land.
Sobel(grayscale_, Iy_, CV_32FC1, 0, 1, sobelKernelSize); #if 0
if (sobelKernelSize == 3 || sobelKernelSize == 5)
{
spatialGradient(grayscale_, Ix_, Iy_, sobelKernelSize, BORDER_DEFAULT, CV_32F);
}
else
#endif
{
Sobel(grayscale_, Ix_, CV_32FC1, 1, 0, sobelKernelSize);
Sobel(grayscale_, Iy_, CV_32FC1, 0, 1, sobelKernelSize);
}
} }
Mat image_magnitude_; Mat image_magnitude_;
void initImageMagnitude_(InputArray image) void initImageMagnitude_(InputArray image)

View File

@@ -43,7 +43,6 @@
#include "precomp.hpp" #include "precomp.hpp"
#include "opencv2/core/hal/intrin.hpp" #include "opencv2/core/hal/intrin.hpp"
#include <iostream>
namespace cv namespace cv
{ {
@@ -93,32 +92,75 @@ static inline void spatialGradientKernel( T& vx, T& vy,
vy = tmp_add - tmp_sub + tmp_y + tmp_y; vy = tmp_add - tmp_sub + tmp_y + tmp_y;
} }
#if defined(__GNUC__) || defined(__clang__)
# define CV_SPATIALGRAD_NOINLINE __attribute__((noinline))
#elif defined(_MSC_VER)
# define CV_SPATIALGRAD_NOINLINE __declspec(noinline)
#else
# define CV_SPATIALGRAD_NOINLINE
#endif
// Fused single-pass 3x3 Sobel over the whole image: 8-bit input, CV_16S output,
// unit scale, BORDER_REFLECT_101 / BORDER_REPLICATE. Kept in its own (non-inlined)
// function so the hot vectorized loop is codegen'd in isolation from the public
// entry point's dispatch/fallback logic. Folding both into one frame perturbs loop
// alignment and costs ~10% on some microarchitectures (observed on AMD Zen5/Turin).
static CV_SPATIALGRAD_NOINLINE
void spatialGradientFused3x3_8u16s( Mat src, OutputArray _dx, OutputArray _dy, int bt );
void spatialGradient( InputArray _src, OutputArray _dx, OutputArray _dy, void spatialGradient( InputArray _src, OutputArray _dx, OutputArray _dy,
int ksize, int borderType ) int ksize, int borderType, int ddepth, double scale )
{ {
CV_INSTRUMENT_REGION(); CV_INSTRUMENT_REGION();
// Prepare InputArray src // Prepare InputArray src
Mat src = _src.getMat(); Mat src = _src.getMat();
CV_Assert( !src.empty() ); CV_Assert( !src.empty() );
CV_Assert( src.type() == CV_8UC1 ); CV_Assert( ksize == -1 || ksize == 3 || ksize == 5 || ksize == 7 );
CV_Assert( borderType == BORDER_DEFAULT || borderType == BORDER_REPLICATE ); CV_Assert( ddepth == CV_16S || ddepth == CV_32F );
// Fused single-pass 3x3 Sobel fast path (existing vectorized kernel): 8-bit source,
// CV_16S output, unit scale, whole-image (non-ROI), BORDER_REFLECT_101/REPLICATE.
// NOTE: fused fast paths for CV_32F output and ksize == 5 (plus ROI-aware slicing and
// other border types) are added in a follow-up PR; until then those cases are computed
// with the two equivalent cv::Sobel() passes below.
Size wholeSize;
Point ofs;
src.locateROI( wholeSize, ofs );
const bool entireParent = ( ofs.x == 0 && ofs.y == 0 &&
src.cols == wholeSize.width && src.rows == wholeSize.height );
const bool isolated = ( borderType & BORDER_ISOLATED ) != 0;
const int bt = borderType & ~BORDER_ISOLATED;
const bool fastPath = ( ksize == 3 && ddepth == CV_16S && scale == 1.0
&& src.type() == CV_8UC1 && entireParent && !isolated
&& ( bt == BORDER_DEFAULT || bt == BORDER_REPLICATE ) );
if ( !fastPath )
{
Sobel( _src, _dx, ddepth, 1, 0, ksize, scale, 0, borderType );
Sobel( _src, _dy, ddepth, 0, 1, ksize, scale, 0, borderType );
return;
}
spatialGradientFused3x3_8u16s( src, _dx, _dy, bt );
}
static CV_SPATIALGRAD_NOINLINE
void spatialGradientFused3x3_8u16s( Mat src, OutputArray _dx, OutputArray _dy, int bt )
{
// Prepare OutputArrays dx, dy // Prepare OutputArrays dx, dy
_dx.create( src.size(), CV_16SC1 ); _dx.create( src.size(), CV_16SC1 );
_dy.create( src.size(), CV_16SC1 ); _dy.create( src.size(), CV_16SC1 );
Mat dx = _dx.getMat(), Mat dx = _dx.getMat(),
dy = _dy.getMat(); dy = _dy.getMat();
// TODO: Allow for other kernel sizes
CV_Assert(ksize == 3);
CALL_HAL(spatialGradient, cv_hal_spatialGradient, CALL_HAL(spatialGradient, cv_hal_spatialGradient,
src.data, src.step, src.data, src.step,
dx.ptr<short>(), dx.step, dx.ptr<short>(), dx.step,
dy.ptr<short>(), dy.step, dy.ptr<short>(), dy.step,
src.cols, src.rows, src.cols, src.rows,
ksize, borderType); 3, bt);
// Get dimensions // Get dimensions
const int H = src.rows, const int H = src.rows,
@@ -134,7 +176,7 @@ void spatialGradient( InputArray _src, OutputArray _dx, OutputArray _dy,
j_offl = 0, // j offset from 0th pixel to reach -1st pixel j_offl = 0, // j offset from 0th pixel to reach -1st pixel
j_offr = 0; // j offset from W-1th pixel to reach Wth pixel j_offr = 0; // j offset from W-1th pixel to reach Wth pixel
if ( borderType == BORDER_DEFAULT ) // Equiv. to BORDER_REFLECT_101 if ( bt == BORDER_DEFAULT ) // Equiv. to BORDER_REFLECT_101
{ {
if ( H > 1 ) if ( H > 1 )
{ {

View File

@@ -11,6 +11,7 @@
// For Open Source Computer Vision Library // For Open Source Computer Vision Library
// //
// Copyright (C) 2000, Intel Corporation, all rights reserved. // Copyright (C) 2000, Intel Corporation, all rights reserved.
// Copyright (C) 2026, Advanced Micro Devices, Inc., all rights reserved.
// Third party copyrights are property of their respective owners. // Third party copyrights are property of their respective owners.
// //
// Redistribution and use in source and binary forms, with or without modification, // Redistribution and use in source and binary forms, with or without modification,
@@ -601,8 +602,10 @@ void CV_SpatialGradientTest::get_test_array_types_and_sizes( int test_case_idx,
void CV_SpatialGradientTest::run_func() void CV_SpatialGradientTest::run_func()
{ {
spatialGradient( test_mat[INPUT][0], test_mat[OUTPUT][0], Mat dx, dy;
test_mat[OUTPUT][1], ksize, border ); spatialGradient( test_mat[INPUT][0], dx, dy, ksize, border );
dx.copyTo( test_mat[OUTPUT][0] );
dy.copyTo( test_mat[OUTPUT][1] );
} }
void CV_SpatialGradientTest::prepare_to_validation( int /*test_case_idx*/ ) void CV_SpatialGradientTest::prepare_to_validation( int /*test_case_idx*/ )
@@ -1868,6 +1871,155 @@ TEST(Imgproc_MorphologyEx, accuracy) { CV_MorphExTest test; test.safe_run(); }
TEST(Imgproc_Filter2D, accuracy) { CV_FilterTest test; test.safe_run(); } TEST(Imgproc_Filter2D, accuracy) { CV_FilterTest test; test.safe_run(); }
TEST(Imgproc_Sobel, accuracy) { CV_SobelTest test; test.safe_run(); } TEST(Imgproc_Sobel, accuracy) { CV_SobelTest test; test.safe_run(); }
TEST(Imgproc_SpatialGradient, accuracy) { CV_SpatialGradientTest test; test.safe_run(); } TEST(Imgproc_SpatialGradient, accuracy) { CV_SpatialGradientTest test; test.safe_run(); }
// spatialGradient (fused dx+dy) must match the two separate cv::Sobel calls it fuses:
// bit-exact for ddepth=CV_16S/scale=1 and ddepth=CV_32F (scale folded into kernels like cv::Sobel).
typedef tuple<int, double> SpatialGradientFusedDepthScale_t;
typedef tuple<int, int, int, SpatialGradientFusedDepthScale_t> SpatialGradientFusedParams_t;
typedef TestWithParam<SpatialGradientFusedParams_t> Imgproc_SpatialGradient_Fused;
TEST_P(Imgproc_SpatialGradient_Fused, fused_accuracy)
{
const int iter = get<0>(GetParam());
const int ksize = get<1>(GetParam());
const int border = get<2>(GetParam());
const int ddepth = get<0>(get<3>(GetParam()));
const double scale = get<1>(get<3>(GetParam()));
RNG& rng = TS::ptr()->get_rng();
rng.state += iter;
Size sz(rng.uniform(3, 320), rng.uniform(3, 240));
Mat src(sz, CV_8UC1);
rng.fill(src, RNG::UNIFORM, 0, 256);
Mat dx, dy, dxRef, dyRef;
spatialGradient(src, dx, dy, ksize, border, ddepth, scale);
Sobel(src, dxRef, ddepth, 1, 0, ksize, scale, 0, border);
Sobel(src, dyRef, ddepth, 0, 1, ksize, scale, 0, border);
EXPECT_EQ(CV_MAKETYPE(ddepth, 1), dx.type());
EXPECT_EQ(sz, dx.size());
const double tol = 0.0;
EXPECT_LE(cvtest::norm(dx, dxRef, NORM_INF), tol);
EXPECT_LE(cvtest::norm(dy, dyRef, NORM_INF), tol);
}
INSTANTIATE_TEST_CASE_P(/**/, Imgproc_SpatialGradient_Fused,
testing::Combine(
testing::Range(0, 16),
testing::Values(3, 5),
testing::Values(BORDER_DEFAULT, BORDER_REPLICATE, BORDER_REFLECT,
BORDER_REFLECT_101, BORDER_CONSTANT),
testing::Values(
make_tuple(CV_16S, 1.0),
make_tuple(CV_32F, 1.0),
make_tuple(CV_32F, 0.25)
)
)
);
TEST(Imgproc_SpatialGradient, fused_accuracy)
{
RNG& rng = TS::ptr()->get_rng();
// CV_32FC1 source must work via the fallback and match cv::Sobel.
{
Mat src(120, 90, CV_32FC1);
rng.fill(src, RNG::UNIFORM, -5.f, 5.f);
for (int ks : {3, 5})
{
Mat dx, dy, dxRef, dyRef;
spatialGradient(src, dx, dy, ks, BORDER_DEFAULT, CV_32F);
Sobel(src, dxRef, CV_32F, 1, 0, ks, 1, 0, BORDER_DEFAULT);
Sobel(src, dyRef, CV_32F, 0, 1, ks, 1, 0, BORDER_DEFAULT);
EXPECT_LE(cvtest::norm(dx, dxRef, NORM_INF), 1e-4) << "float-src dx ksize=" << ks;
EXPECT_LE(cvtest::norm(dy, dyRef, NORM_INF), 1e-4) << "float-src dy ksize=" << ks;
}
}
// full-width row-range ROI (as Canny uses): must match cv::Sobel on the ROI.
{
Mat parent(200, 160, CV_8UC1);
rng.fill(parent, RNG::UNIFORM, 0, 256);
for (int ks : {3, 5})
for (int b : {BORDER_DEFAULT, BORDER_REPLICATE, BORDER_REFLECT, BORDER_CONSTANT})
for (int ddepth : {CV_16S, CV_32F})
{
Mat roi = parent.rowRange(40, 120);
Mat dx, dy, dxRef, dyRef;
spatialGradient(roi, dx, dy, ks, b, ddepth);
Sobel(roi, dxRef, ddepth, 1, 0, ks, 1, 0, b);
Sobel(roi, dyRef, ddepth, 0, 1, ks, 1, 0, b);
EXPECT_LE(cvtest::norm(dx, dxRef, NORM_INF), 0.0) << "ROI dx ksize=" << ks << " border=" << b << " ddepth=" << ddepth;
EXPECT_LE(cvtest::norm(dy, dyRef, NORM_INF), 0.0) << "ROI dy ksize=" << ks << " border=" << b << " ddepth=" << ddepth;
}
}
// invalid aperture sizes must be rejected
Mat src(16, 16, CV_8UC1), dx, dy;
EXPECT_ANY_THROW(spatialGradient(src, dx, dy, 1));
}
// Reproduces parallelCanny's per-slice row splitting and checks each slice's
// spatialGradient output matches the whole-image gradient (i.e. multi-threaded == single).
typedef tuple<int, int> SpatialGradientSliceThread_t;
typedef tuple<int, int, SpatialGradientSliceThread_t> SpatialGradientSliceParams_t;
typedef TestWithParam<SpatialGradientSliceParams_t> Imgproc_SpatialGradient_Slice;
TEST_P(Imgproc_SpatialGradient_Slice, slice_equivalence)
{
const int ksize = get<0>(GetParam());
const int border = get<1>(GetParam());
const int nThreads = get<0>(get<2>(GetParam()));
const int t = get<1>(get<2>(GetParam()));
Mat src(193, 137, CV_8UC1); // odd dims to stress tail handling
RNG& rng = TS::ptr()->get_rng();
rng.state += (int)((int64)ksize * 10000 + border * 1000 + nThreads * 100 + t);
rng.fill(src, RNG::UNIFORM, 0, 256);
Mat dxRef, dyRef;
spatialGradient(src, dxRef, dyRef, ksize, border);
const int start = (int)((int64)src.rows * t / nThreads);
const int end = (int)((int64)src.rows * (t + 1) / nThreads);
if (start >= end)
return;
const int rowStart = std::max(0, start - 1);
const int rowEnd = std::min(src.rows, end + 1);
Mat dx, dy;
spatialGradient(src.rowRange(rowStart, rowEnd), dx, dy, ksize, border);
// rows [start, end) live at offset (start - rowStart) in the slice output
const int off = start - rowStart;
Mat dxSlice = dx.rowRange(off, off + (end - start));
Mat dySlice = dy.rowRange(off, off + (end - start));
Mat dxWhole = dxRef.rowRange(start, end);
Mat dyWhole = dyRef.rowRange(start, end);
EXPECT_EQ(0.0, cvtest::norm(dxSlice, dxWhole, NORM_INF));
EXPECT_EQ(0.0, cvtest::norm(dySlice, dyWhole, NORM_INF));
}
INSTANTIATE_TEST_CASE_P(/**/, Imgproc_SpatialGradient_Slice,
testing::Combine(
testing::Values(3, 5),
testing::Values(BORDER_REPLICATE, BORDER_REFLECT, BORDER_REFLECT_101),
testing::Values(
make_tuple(2, 0), make_tuple(2, 1),
make_tuple(3, 0), make_tuple(3, 1), make_tuple(3, 2),
make_tuple(4, 0), make_tuple(4, 1), make_tuple(4, 2), make_tuple(4, 3),
make_tuple(7, 0), make_tuple(7, 1), make_tuple(7, 2), make_tuple(7, 3),
make_tuple(7, 4), make_tuple(7, 5), make_tuple(7, 6),
make_tuple(16, 0), make_tuple(16, 1), make_tuple(16, 2), make_tuple(16, 3),
make_tuple(16, 4), make_tuple(16, 5), make_tuple(16, 6), make_tuple(16, 7),
make_tuple(16, 8), make_tuple(16, 9), make_tuple(16, 10), make_tuple(16, 11),
make_tuple(16, 12), make_tuple(16, 13), make_tuple(16, 14), make_tuple(16, 15)
)
)
);
TEST(Imgproc_Laplace, accuracy) { CV_LaplaceTest test; test.safe_run(); } TEST(Imgproc_Laplace, accuracy) { CV_LaplaceTest test; test.safe_run(); }
TEST(Imgproc_Blur, accuracy) { CV_BlurTest test; test.safe_run(); } TEST(Imgproc_Blur, accuracy) { CV_BlurTest test; test.safe_run(); }
TEST(Imgproc_GaussianBlur, accuracy) { CV_GaussianBlurTest test; test.safe_run(); } TEST(Imgproc_GaussianBlur, accuracy) { CV_GaussianBlurTest test; test.safe_run(); }