Skip to content

Commit b2c0ce0

Browse files
authored
Merge pull request #3731 from chacha21:cuda_separable_filter_single
supports empty kernels in cuda::SeparableLinearFilters #3731 [#25408](opencv/opencv#25408) When only 1D convolution is needed (row or column filter only), `cuda::LinearFilter` might be slower than `cuda::SeparableLinearFilter` Using `cuda::SeparableLinearFilter` for 1D convolution can be done by using a `(1)` kernel for the ignored dimension. By supporting empty kernels in `cuda::SeparableLinearFilter`, there is no need for that `(1)` kernel any more. Additionaly, the inner `_buf ` used to store the intermediate convolution result can be saved when a single convolution is needed. In "legacy" usage (row+col kernels), there is no regression in `cuda::SeparableLinearFilter` performance. As soon as an empty kernel is used, the performance is largely increased. Devil in the details : the "in-place" processing is supported and might need intermediate buf, but still no regression. - [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 - [X] 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
1 parent 25070d6 commit b2c0ce0

File tree

3 files changed

+138
-16
lines changed

3 files changed

+138
-16
lines changed

modules/cudafilters/include/opencv2/cudafilters.hpp

+3-1
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,14 @@ CV_EXPORTS_W Ptr<Filter> createLaplacianFilter(int srcType, int dstType, int ksi
142142
////////////////////////////////////////////////////////////////////////////////////////////////////
143143
// Separable Linear Filter
144144

145-
/** @brief Creates a separable linear filter.
145+
/** @brief Creates a separable linear filter. In-place processing is supported.
146146
147147
@param srcType Source array type.
148148
@param dstType Destination array type.
149149
@param rowKernel Horizontal filter coefficients. Support kernels with size \<= 32 .
150+
noArray() is supported to ignore the row filtering.
150151
@param columnKernel Vertical filter coefficients. Support kernels with size \<= 32 .
152+
noArray() is supported to ignore the column filtering.
151153
@param anchor Anchor position within the kernel. Negative values mean that anchor is positioned at
152154
the aperture center.
153155
@param rowBorderMode Pixel extrapolation method in the vertical direction For details, see

modules/cudafilters/src/filtering.cpp

+55-15
Original file line numberDiff line numberDiff line change
@@ -386,28 +386,38 @@ namespace
386386
const int cn = CV_MAT_CN(srcType);
387387
const int ddepth = CV_MAT_DEPTH(dstType);
388388

389-
Mat rowKernel = _rowKernel.getMat();
390-
Mat columnKernel = _columnKernel.getMat();
389+
CV_Assert( _rowKernel.empty() || _rowKernel.isMat() );
390+
CV_Assert( _columnKernel.empty() || _columnKernel.isMat() );
391+
Mat rowKernel = _rowKernel.empty() ? cv::Mat() : _rowKernel.getMat();
392+
Mat columnKernel = _columnKernel.empty() ? cv::Mat() : _columnKernel.getMat();
391393

392394
CV_Assert( sdepth <= CV_64F && cn <= 4 );
393-
CV_Assert( rowKernel.channels() == 1 );
394-
CV_Assert( columnKernel.channels() == 1 );
395+
CV_Assert( rowKernel.empty() || rowKernel.channels() == 1 );
396+
CV_Assert( columnKernel.empty() || columnKernel.channels() == 1 );
395397
CV_Assert( rowBorderMode == BORDER_REFLECT101 || rowBorderMode == BORDER_REPLICATE || rowBorderMode == BORDER_CONSTANT || rowBorderMode == BORDER_REFLECT || rowBorderMode == BORDER_WRAP );
396398
CV_Assert( columnBorderMode == BORDER_REFLECT101 || columnBorderMode == BORDER_REPLICATE || columnBorderMode == BORDER_CONSTANT || columnBorderMode == BORDER_REFLECT || columnBorderMode == BORDER_WRAP );
397399

398400
Mat kernel32F;
399401

400-
rowKernel.convertTo(kernel32F, CV_32F);
401-
rowKernel_.upload(kernel32F.reshape(1, 1));
402+
if (!rowKernel.empty())
403+
{
404+
rowKernel.convertTo(kernel32F, CV_32F);
405+
rowKernel_.upload(kernel32F.reshape(1, 1));
406+
}
402407

403-
columnKernel.convertTo(kernel32F, CV_32F);
404-
columnKernel_.upload(kernel32F.reshape(1, 1));
408+
if (!columnKernel.empty())
409+
{
410+
columnKernel.convertTo(kernel32F, CV_32F);
411+
columnKernel_.upload(kernel32F.reshape(1, 1));
412+
}
405413

406-
CV_Assert( rowKernel_.cols > 0 && rowKernel_.cols <= 32 );
407-
CV_Assert( columnKernel_.cols > 0 && columnKernel_.cols <= 32 );
414+
CV_Assert( rowKernel_.empty() || (rowKernel_.cols > 0 && rowKernel_.cols <= 32 ));
415+
CV_Assert( columnKernel_.empty() || (columnKernel_.cols > 0 && columnKernel_.cols <= 32 ));
408416

409-
normalizeAnchor(anchor_.x, rowKernel_.cols);
410-
normalizeAnchor(anchor_.y, columnKernel_.cols);
417+
if (!rowKernel_.empty())
418+
normalizeAnchor(anchor_.x, rowKernel_.cols);
419+
if (!columnKernel_.empty())
420+
normalizeAnchor(anchor_.y, columnKernel_.cols);
411421

412422
bufType_ = CV_MAKE_TYPE(CV_32F, cn);
413423

@@ -426,15 +436,45 @@ namespace
426436
_dst.create(src.size(), dstType_);
427437
GpuMat dst = _dst.getGpuMat();
428438

429-
ensureSizeIsEnough(src.size(), bufType_, buf_);
439+
const bool isInPlace = (src.data == dst.data);
440+
const bool hasRowKernel = !rowKernel_.empty();
441+
const bool hasColKernel = !columnKernel_.empty();
442+
const bool hasSingleKernel = (hasRowKernel ^ hasColKernel);
443+
const bool needsSrcAdaptation = !hasRowKernel && hasColKernel && (srcType_ != bufType_);
444+
const bool needsDstAdaptation = hasRowKernel && !hasColKernel && (dstType_ != bufType_);
445+
const bool needsBufForIntermediateStorage = (hasRowKernel && hasColKernel) || (hasSingleKernel && isInPlace);
446+
const bool needsBuf = needsSrcAdaptation || needsDstAdaptation || needsBufForIntermediateStorage;
447+
if (needsBuf)
448+
ensureSizeIsEnough(src.size(), bufType_, buf_);
449+
450+
if (needsSrcAdaptation)
451+
src.convertTo(buf_, bufType_, _stream);
452+
GpuMat& srcAdapted = needsSrcAdaptation ? buf_ : src;
430453

431454
DeviceInfo devInfo;
432455
const int cc = devInfo.majorVersion() * 10 + devInfo.minorVersion();
433456

434457
cudaStream_t stream = StreamAccessor::getStream(_stream);
435458

436-
rowFilter_(src, buf_, rowKernel_.ptr<float>(), rowKernel_.cols, anchor_.x, rowBorderMode_, cc, stream);
437-
columnFilter_(buf_, dst, columnKernel_.ptr<float>(), columnKernel_.cols, anchor_.y, columnBorderMode_, cc, stream);
459+
if (!hasRowKernel && !hasColKernel && !isInPlace)
460+
srcAdapted.convertTo(dst, dstType_, _stream);
461+
else if (hasRowKernel || hasColKernel)
462+
{
463+
GpuMat& rowFilterSrc = srcAdapted;
464+
GpuMat& rowFilterDst = !hasRowKernel ? srcAdapted : needsBuf ? buf_ : dst;
465+
GpuMat& colFilterSrc = hasColKernel && needsBuf ? buf_ : srcAdapted;
466+
GpuMat& colFilterTo = dst;
467+
468+
if (hasRowKernel)
469+
rowFilter_(rowFilterSrc, rowFilterDst, rowKernel_.ptr<float>(), rowKernel_.cols, anchor_.x, rowBorderMode_, cc, stream);
470+
else if (hasColKernel && (needsBufForIntermediateStorage && !needsSrcAdaptation))
471+
rowFilterSrc.convertTo(buf_, bufType_, _stream);
472+
473+
if (hasColKernel)
474+
columnFilter_(colFilterSrc, colFilterTo, columnKernel_.ptr<float>(), columnKernel_.cols, anchor_.y, columnBorderMode_, cc, stream);
475+
else if (needsBuf)
476+
buf_.convertTo(dst, dstType_, _stream);
477+
}
438478
}
439479
}
440480

modules/cudafilters/test/test_filters.cpp

+80
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,86 @@ INSTANTIATE_TEST_CASE_P(CUDA_Filters, SeparableLinearFilter, testing::Combine(
281281
BorderType(cv::BORDER_REFLECT)),
282282
WHOLE_SUBMAT));
283283

284+
PARAM_TEST_CASE(SeparableLinearFilterWithEmptyKernels, cv::cuda::DeviceInfo, MatDepth, Channels, MatDepth, bool, bool, bool)
285+
{
286+
cv::cuda::DeviceInfo devInfo;
287+
bool inPlace;
288+
bool useRowKernel;
289+
bool useColKernel;
290+
291+
cv::Size size;
292+
int srcDepth;
293+
int cn;
294+
int dstDepth;
295+
cv::Size ksize;
296+
cv::Point anchor;
297+
int borderType;
298+
int srcType;
299+
int dstType;
300+
301+
virtual void SetUp()
302+
{
303+
devInfo = GET_PARAM(0);
304+
srcDepth = GET_PARAM(1);
305+
cn = GET_PARAM(2);
306+
dstDepth = GET_PARAM(3);
307+
inPlace = GET_PARAM(4);
308+
useRowKernel = GET_PARAM(5);
309+
useColKernel = GET_PARAM(6);
310+
311+
size = cv::Size(640, 480);
312+
ksize = cv::Size(3, 1);
313+
anchor = cv::Point(-1, -1);
314+
borderType = cv::BORDER_REPLICATE;
315+
316+
cv::cuda::setDevice(devInfo.deviceID());
317+
318+
srcType = CV_MAKE_TYPE(srcDepth, cn);
319+
dstType = CV_MAKE_TYPE(dstDepth, cn);
320+
}
321+
};
322+
323+
CUDA_TEST_P(SeparableLinearFilterWithEmptyKernels, Accuracy)
324+
{
325+
cv::Mat src = randomMat(size, srcType);
326+
cv::Mat rowKernel = (cv::Mat_<float>(ksize) << -1, 0, 1);
327+
cv::Mat colKernel = rowKernel.t();
328+
cv::Mat oneKernel = cv::Mat::ones(cv::Size(1, 1), CV_32FC1);
329+
cv::Mat noKernel = cv::Mat();
330+
331+
cv::Ptr<cv::cuda::Filter> sepFilterDummyKernels =
332+
cv::cuda::createSeparableLinearFilter(srcType, dstType,
333+
useRowKernel ? rowKernel : oneKernel,
334+
useColKernel ? colKernel : oneKernel,
335+
cv::Point(-1, -1), cv::BORDER_REPLICATE, cv::BORDER_REPLICATE);
336+
337+
cv::Ptr<cv::cuda::Filter> sepFilterEmptyKernels =
338+
cv::cuda::createSeparableLinearFilter(srcType, dstType,
339+
useRowKernel ? rowKernel : noKernel,
340+
useColKernel ? colKernel : noKernel,
341+
cv::Point(-1, -1), cv::BORDER_REPLICATE, cv::BORDER_REPLICATE);
342+
343+
cv::cuda::GpuMat src_sep_dummyK = loadMat(src);
344+
cv::cuda::GpuMat dst_sep_dummyK = inPlace ? src_sep_dummyK : cv::cuda::GpuMat();
345+
cv::cuda::GpuMat src_sep_emptyK = loadMat(src);
346+
cv::cuda::GpuMat dst_sep_emptyK = inPlace ? src_sep_emptyK : cv::cuda::GpuMat();
347+
348+
sepFilterDummyKernels->apply(src_sep_dummyK, dst_sep_dummyK);
349+
sepFilterEmptyKernels->apply(src_sep_emptyK, dst_sep_emptyK);
350+
351+
EXPECT_MAT_NEAR(dst_sep_dummyK, dst_sep_emptyK, src.depth() < CV_32F ? 1.0 : 1e-2);
352+
}
353+
354+
INSTANTIATE_TEST_CASE_P(CUDA_Filters, SeparableLinearFilterWithEmptyKernels, testing::Combine(
355+
ALL_DEVICES,
356+
testing::Values(MatDepth(CV_8U), MatDepth(CV_16U), MatDepth(CV_16S), MatDepth(CV_32F)),
357+
IMAGE_CHANNELS,
358+
testing::Values(MatDepth(CV_8U), MatDepth(CV_16U), MatDepth(CV_16S), MatDepth(CV_32F)),
359+
testing::Values(false, true),//in-place
360+
testing::Values(false, true),//use row kernel
361+
testing::Values(false, true)//use col kernel
362+
));
363+
284364
/////////////////////////////////////////////////////////////////////////////////////////////////
285365
// Sobel
286366

0 commit comments

Comments
 (0)