mirror of
https://github.com/opencv/opencv.git
synced 2026-09-11 04:43:22 -05:00
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:
committed by
GitHub
parent
b2efa6f860
commit
44e7b4eb11
@@ -12,6 +12,7 @@
|
||||
//
|
||||
// Copyright (C) 2000-2008, Intel Corporation, 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.
|
||||
//
|
||||
// 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,
|
||||
int borderType = BORDER_DEFAULT );
|
||||
|
||||
/** @brief Calculates the first order image derivative in both x and y using a Sobel operator
|
||||
|
||||
Equivalent to calling:
|
||||
/** @brief Calculates the first order image derivatives in both x and y using a Sobel operator,
|
||||
computing them together in a single pass.
|
||||
|
||||
This is a fused variant of #Sobel: instead of two separate calls
|
||||
@code
|
||||
Sobel( src, dx, CV_16SC1, 1, 0, 3 );
|
||||
Sobel( src, dy, CV_16SC1, 0, 1, 3 );
|
||||
Sobel( src, dx, ddepth, 1, 0, ksize, scale );
|
||||
Sobel( src, dy, ddepth, 0, 1, ksize, scale );
|
||||
@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 dx output image with first-order derivative in x.
|
||||
@param dy output image with first-order derivative in y.
|
||||
@param ksize size of Sobel kernel. It must be 3.
|
||||
@param borderType pixel extrapolation method, see #BorderTypes.
|
||||
Only #BORDER_DEFAULT=#BORDER_REFLECT_101 and #BORDER_REPLICATE are supported.
|
||||
@param src input image; single-channel, 8-bit (CV_8UC1) for the fused fast paths (CV_32FC1 is
|
||||
accepted via the fallback).
|
||||
@param dx output image with the first-order derivative in x (depth @p ddepth, same size as src).
|
||||
@param dy output image with the first-order derivative in y (depth @p ddepth, same size as src).
|
||||
@param ksize size of the Sobel kernel; fused fast paths require 3 (5 uses the Sobel fallback).
|
||||
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
|
||||
*/
|
||||
|
||||
CV_EXPORTS_W void spatialGradient( InputArray src, OutputArray dx,
|
||||
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.
|
||||
|
||||
|
||||
@@ -134,6 +134,110 @@ PERF_TEST_P(Size_MatType_dx_dy_Border5x5ROI, sobelFilter,
|
||||
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 ********************/
|
||||
|
||||
PERF_TEST_P(Size_MatType_dx_dy_Border3x3, scharrFilter,
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2009, Willow Garage 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.
|
||||
//
|
||||
// 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 );
|
||||
|
||||
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 )
|
||||
{
|
||||
Sobel( src, Dx, CV_32F, 1, 0, aperture_size, scale, 0, borderType );
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
@@ -115,8 +116,13 @@ namespace
|
||||
CV_Assert( cannyLowThresh_ > 0 && 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, dy, CV_32F, 0, 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
void GeneralizedHoughBase::setTemplateImpl(InputArray templ, Point templCenter)
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
// Copyright (C) 2000, Intel Corporation, all rights reserved.
|
||||
// Copyright (C) 2013, OpenCV Foundation, 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.
|
||||
//
|
||||
// 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;
|
||||
|
||||
Sobel(_image, dx, CV_16S, 1, 0, kernelSize, 1, 0, BORDER_REPLICATE);
|
||||
Sobel(_image, dy, CV_16S, 0, 1, kernelSize, 1, 0, BORDER_REPLICATE);
|
||||
spatialGradient(_image, dx, dy, kernelSize, BORDER_REPLICATE);
|
||||
Canny(dx, dy, edges, std::max(1, cannyThreshold / 2), cannyThreshold, false);
|
||||
|
||||
Mutex mtx;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// This file is part of OpenCV project.
|
||||
// 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.
|
||||
// Copyright (C) 2026, Advanced Micro Devices, Inc., all rights reserved.
|
||||
//
|
||||
// Copyright (C) 2020, Intel Corporation, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
@@ -144,8 +145,18 @@ struct IntelligentScissorsMB::Impl
|
||||
if (!Ix_.empty())
|
||||
return;
|
||||
initGrayscale_(image);
|
||||
Sobel(grayscale_, Ix_, CV_32FC1, 1, 0, sobelKernelSize);
|
||||
Sobel(grayscale_, Iy_, CV_32FC1, 0, 1, sobelKernelSize);
|
||||
// TODO(follow-up PR): use the fused spatialGradient once its CV_32F/ksize=5 fast paths land.
|
||||
#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_;
|
||||
void initImageMagnitude_(InputArray image)
|
||||
|
||||
@@ -43,7 +43,6 @@
|
||||
#include "precomp.hpp"
|
||||
#include "opencv2/core/hal/intrin.hpp"
|
||||
|
||||
#include <iostream>
|
||||
namespace cv
|
||||
{
|
||||
|
||||
@@ -93,32 +92,75 @@ static inline void spatialGradientKernel( T& vx, T& vy,
|
||||
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,
|
||||
int ksize, int borderType )
|
||||
int ksize, int borderType, int ddepth, double scale )
|
||||
{
|
||||
CV_INSTRUMENT_REGION();
|
||||
|
||||
// Prepare InputArray src
|
||||
Mat src = _src.getMat();
|
||||
CV_Assert( !src.empty() );
|
||||
CV_Assert( src.type() == CV_8UC1 );
|
||||
CV_Assert( borderType == BORDER_DEFAULT || borderType == BORDER_REPLICATE );
|
||||
CV_Assert( ksize == -1 || ksize == 3 || ksize == 5 || ksize == 7 );
|
||||
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
|
||||
_dx.create( src.size(), CV_16SC1 );
|
||||
_dy.create( src.size(), CV_16SC1 );
|
||||
Mat dx = _dx.getMat(),
|
||||
dy = _dy.getMat();
|
||||
|
||||
// TODO: Allow for other kernel sizes
|
||||
CV_Assert(ksize == 3);
|
||||
|
||||
CALL_HAL(spatialGradient, cv_hal_spatialGradient,
|
||||
src.data, src.step,
|
||||
dx.ptr<short>(), dx.step,
|
||||
dy.ptr<short>(), dy.step,
|
||||
src.cols, src.rows,
|
||||
ksize, borderType);
|
||||
3, bt);
|
||||
|
||||
// Get dimensions
|
||||
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_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 )
|
||||
{
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
// For Open Source Computer Vision Library
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// 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()
|
||||
{
|
||||
spatialGradient( test_mat[INPUT][0], test_mat[OUTPUT][0],
|
||||
test_mat[OUTPUT][1], ksize, border );
|
||||
Mat dx, dy;
|
||||
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*/ )
|
||||
@@ -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_Sobel, accuracy) { CV_SobelTest 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_Blur, accuracy) { CV_BlurTest test; test.safe_run(); }
|
||||
TEST(Imgproc_GaussianBlur, accuracy) { CV_GaussianBlurTest test; test.safe_run(); }
|
||||
|
||||
Reference in New Issue
Block a user