mirror of
https://github.com/opencv/opencv.git
synced 2026-07-21 19:33:03 +04:00
imgproc: optimize tiled parallel filter with TLS buffers and custom border fill
This commit is contained in:
@@ -97,4 +97,46 @@ PERF_TEST_P( Image_KernelSize, GaborFilter2d,
|
||||
SANITY_CHECK(filteredImage, 1e-6, ERROR_RELATIVE);
|
||||
}
|
||||
|
||||
// Performance test for the tiled parallel FilterEngine path (images >= 1MP).
|
||||
// Exercises filter2D and sepFilter2D separately across common types and border modes.
|
||||
typedef TestBaseWithParam< tuple<Size, int, BorderMode, bool> > ImgProc_ParallelFilter_Perf;
|
||||
|
||||
PERF_TEST_P( ImgProc_ParallelFilter_Perf, filter2D_parallel,
|
||||
Combine(
|
||||
Values( Size(1280, 1024), sz1080p ),
|
||||
Values( CV_8UC1, CV_8UC3, CV_32FC1 ),
|
||||
Values( BORDER_DEFAULT, BORDER_CONSTANT ),
|
||||
Values( false, true ) // false = filter2D, true = sepFilter2D
|
||||
)
|
||||
)
|
||||
{
|
||||
const Size sz = get<0>(GetParam());
|
||||
const int type = get<1>(GetParam());
|
||||
const int borderMode = get<2>(GetParam());
|
||||
const bool isSep = get<3>(GetParam());
|
||||
|
||||
Mat src(sz, type);
|
||||
Mat dst(sz, type);
|
||||
declare.in(src, WARMUP_RNG).out(dst);
|
||||
|
||||
if (isSep)
|
||||
{
|
||||
Mat kx = (Mat_<float>(1, 3) << 0.25f, 0.5f, 0.25f);
|
||||
Mat ky = (Mat_<float>(3, 1) << 0.25f, 0.5f, 0.25f);
|
||||
TEST_CYCLE() cv::sepFilter2D(src, dst, -1, kx, ky,
|
||||
Point(-1, -1), 0, borderMode);
|
||||
}
|
||||
else
|
||||
{
|
||||
Mat kernel = (Mat_<float>(3, 3) <<
|
||||
1/16.f, 2/16.f, 1/16.f,
|
||||
2/16.f, 4/16.f, 2/16.f,
|
||||
1/16.f, 2/16.f, 1/16.f);
|
||||
TEST_CYCLE() cv::filter2D(src, dst, -1, kernel,
|
||||
Point(-1, -1), 0, borderMode);
|
||||
}
|
||||
|
||||
SANITY_CHECK_NOTHING();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -69,13 +69,14 @@ template<typename T, typename ST>
|
||||
struct RowSum :
|
||||
public BaseRowFilter
|
||||
{
|
||||
RowSum( int _ksize, int _anchor ) :
|
||||
BaseRowFilter()
|
||||
RowSum( int _ksize, int _anchor )
|
||||
{
|
||||
ksize = _ksize;
|
||||
anchor = _anchor;
|
||||
}
|
||||
|
||||
bool isStateless() const CV_OVERRIDE { return true; }
|
||||
|
||||
virtual void operator()(const uchar* src, uchar* dst, int width, int cn) CV_OVERRIDE
|
||||
{
|
||||
CV_INSTRUMENT_REGION();
|
||||
@@ -180,8 +181,7 @@ template<typename ST, typename T>
|
||||
struct ColumnSum :
|
||||
public BaseColumnFilter
|
||||
{
|
||||
ColumnSum( int _ksize, int _anchor, double _scale ) :
|
||||
BaseColumnFilter()
|
||||
ColumnSum( int _ksize, int _anchor, double _scale )
|
||||
{
|
||||
ksize = _ksize;
|
||||
anchor = _anchor;
|
||||
@@ -280,8 +280,7 @@ template<>
|
||||
struct ColumnSum<int, uchar> :
|
||||
public BaseColumnFilter
|
||||
{
|
||||
ColumnSum( int _ksize, int _anchor, double _scale ) :
|
||||
BaseColumnFilter()
|
||||
ColumnSum( int _ksize, int _anchor, double _scale )
|
||||
{
|
||||
ksize = _ksize;
|
||||
anchor = _anchor;
|
||||
@@ -436,8 +435,7 @@ public BaseColumnFilter
|
||||
{
|
||||
enum { SHIFT = 23 };
|
||||
|
||||
ColumnSum( int _ksize, int _anchor, double _scale ) :
|
||||
BaseColumnFilter()
|
||||
ColumnSum( int _ksize, int _anchor, double _scale )
|
||||
{
|
||||
ksize = _ksize;
|
||||
anchor = _anchor;
|
||||
@@ -613,8 +611,7 @@ template<>
|
||||
struct ColumnSum<int, short> :
|
||||
public BaseColumnFilter
|
||||
{
|
||||
ColumnSum( int _ksize, int _anchor, double _scale ) :
|
||||
BaseColumnFilter()
|
||||
ColumnSum( int _ksize, int _anchor, double _scale )
|
||||
{
|
||||
ksize = _ksize;
|
||||
anchor = _anchor;
|
||||
@@ -763,8 +760,7 @@ template<>
|
||||
struct ColumnSum<int, ushort> :
|
||||
public BaseColumnFilter
|
||||
{
|
||||
ColumnSum( int _ksize, int _anchor, double _scale ) :
|
||||
BaseColumnFilter()
|
||||
ColumnSum( int _ksize, int _anchor, double _scale )
|
||||
{
|
||||
ksize = _ksize;
|
||||
anchor = _anchor;
|
||||
@@ -910,8 +906,7 @@ template<>
|
||||
struct ColumnSum<int, int> :
|
||||
public BaseColumnFilter
|
||||
{
|
||||
ColumnSum( int _ksize, int _anchor, double _scale ) :
|
||||
BaseColumnFilter()
|
||||
ColumnSum( int _ksize, int _anchor, double _scale )
|
||||
{
|
||||
ksize = _ksize;
|
||||
anchor = _anchor;
|
||||
@@ -1044,8 +1039,7 @@ template<>
|
||||
struct ColumnSum<int, float> :
|
||||
public BaseColumnFilter
|
||||
{
|
||||
ColumnSum( int _ksize, int _anchor, double _scale ) :
|
||||
BaseColumnFilter()
|
||||
ColumnSum( int _ksize, int _anchor, double _scale )
|
||||
{
|
||||
ksize = _ksize;
|
||||
anchor = _anchor;
|
||||
@@ -1700,13 +1694,14 @@ template<typename T, typename ST>
|
||||
struct SqrRowSum :
|
||||
public BaseRowFilter
|
||||
{
|
||||
SqrRowSum( int _ksize, int _anchor ) :
|
||||
BaseRowFilter()
|
||||
SqrRowSum( int _ksize, int _anchor )
|
||||
{
|
||||
ksize = _ksize;
|
||||
anchor = _anchor;
|
||||
}
|
||||
|
||||
bool isStateless() const CV_OVERRIDE { return true; }
|
||||
|
||||
virtual void operator()(const uchar* src, uchar* dst, int width, int cn) CV_OVERRIDE
|
||||
{
|
||||
CV_INSTRUMENT_REGION();
|
||||
|
||||
@@ -63,14 +63,14 @@
|
||||
|
||||
namespace cv {
|
||||
|
||||
BaseRowFilter::BaseRowFilter() { ksize = anchor = -1; }
|
||||
BaseRowFilter::BaseRowFilter() : ksize(-1), anchor(-1) {}
|
||||
BaseRowFilter::~BaseRowFilter() {}
|
||||
|
||||
BaseColumnFilter::BaseColumnFilter() { ksize = anchor = -1; }
|
||||
BaseColumnFilter::BaseColumnFilter() : ksize(-1), anchor(-1) {}
|
||||
BaseColumnFilter::~BaseColumnFilter() {}
|
||||
void BaseColumnFilter::reset() {}
|
||||
|
||||
BaseFilter::BaseFilter() { ksize = Size(-1,-1); anchor = Point(-1,-1); }
|
||||
BaseFilter::BaseFilter() : ksize(-1, -1), anchor(-1, -1) {}
|
||||
BaseFilter::~BaseFilter() {}
|
||||
void BaseFilter::reset() {}
|
||||
|
||||
@@ -207,6 +207,14 @@ int FilterEngine::proceed(const uchar* src, int srcstep, int count,
|
||||
CV_CPU_DISPATCH_MODES_ALL);
|
||||
}
|
||||
|
||||
bool FilterEngine::isStateless() const
|
||||
{
|
||||
bool s2d = !filter2D || filter2D->isStateless();
|
||||
bool sr = !rowFilter || rowFilter->isStateless();
|
||||
bool sc = !columnFilter || columnFilter->isStateless();
|
||||
return s2d && sr && sc;
|
||||
}
|
||||
|
||||
void FilterEngine::apply(const Mat& src, Mat& dst, const Size& wsz, const Point& ofs)
|
||||
{
|
||||
CV_INSTRUMENT_REGION();
|
||||
@@ -455,16 +463,18 @@ template<typename ST, class CastOp, class VecOp> struct Filter2D : public BaseFi
|
||||
vecOp = _vecOp;
|
||||
CV_Assert( _kernel.type() == DataType<KT>::type );
|
||||
preprocess2DKernel( _kernel, coords, coeffs );
|
||||
ptrs.resize( coords.size() );
|
||||
}
|
||||
|
||||
bool isStateless() const CV_OVERRIDE { return true; }
|
||||
|
||||
void operator()(const uchar** src, uchar* dst, int dststep, int count, int width, int cn) CV_OVERRIDE
|
||||
{
|
||||
KT _delta = delta;
|
||||
const Point* pt = &coords[0];
|
||||
const KT* kf = (const KT*)&coeffs[0];
|
||||
const ST** kp = (const ST**)&ptrs[0];
|
||||
int i, k, nz = (int)coords.size();
|
||||
AutoBuffer<const ST*> _kp(nz);
|
||||
const ST** kp = _kp.data();
|
||||
CastOp castOp = castOp0;
|
||||
|
||||
width *= cn;
|
||||
@@ -507,7 +517,6 @@ template<typename ST, class CastOp, class VecOp> struct Filter2D : public BaseFi
|
||||
|
||||
std::vector<Point> coords;
|
||||
std::vector<uchar> coeffs;
|
||||
std::vector<uchar*> ptrs;
|
||||
KT delta;
|
||||
CastOp castOp0;
|
||||
VecOp vecOp;
|
||||
|
||||
@@ -48,6 +48,8 @@
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
|
||||
#ifdef HAVE_OPENCL
|
||||
bool ocl_sepFilter2D(
|
||||
InputArray _src, OutputArray _dst, int ddepth,
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
#include "precomp.hpp"
|
||||
#include "opencv2/core/hal/intrin.hpp"
|
||||
#include "filter.hpp"
|
||||
#include "opencv2/core/utils/tls.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
@@ -296,12 +297,264 @@ int FilterEngine__proceed(FilterEngine& this_, const uchar* src, int srcstep, in
|
||||
return dy;
|
||||
}
|
||||
|
||||
// Lightweight tile border fill, templated on element size (bytes) so the
|
||||
// compiler can inline and optimize memcpy for common fixed sizes (1, 2, 4 …).
|
||||
// Avoids the full cv::copyMakeBorder() overhead: no OpenCL/IPP dispatch,
|
||||
// no Mat header re-allocation, and a single AutoBuffer for the border table.
|
||||
template<int esz>
|
||||
static void fillTileBorder(
|
||||
const uchar* src, size_t srcstep, int src_w, int src_h,
|
||||
uchar* dst, size_t dststep,
|
||||
int pad_top, int pad_bottom, int pad_left, int pad_right,
|
||||
int borderType, const uchar* constVal)
|
||||
{
|
||||
const int dst_w = src_w + pad_left + pad_right;
|
||||
|
||||
// ── Left/right column offset table (byte offsets into a source row) ────────
|
||||
AutoBuffer<int> _tab(pad_left + pad_right);
|
||||
int* tab = _tab.data();
|
||||
for (int i = 0; i < pad_left; i++)
|
||||
tab[i] = borderInterpolate(i - pad_left, src_w, borderType) * esz;
|
||||
for (int i = 0; i < pad_right; i++)
|
||||
tab[pad_left + i] = borderInterpolate(src_w + i, src_w, borderType) * esz;
|
||||
|
||||
// For BORDER_CONSTANT top/bottom rows, pre-build a full-width fill row.
|
||||
AutoBuffer<uchar> _constRow;
|
||||
uchar* constRow = nullptr;
|
||||
if (borderType == BORDER_CONSTANT && (pad_top > 0 || pad_bottom > 0))
|
||||
{
|
||||
_constRow.allocate(dst_w * esz);
|
||||
constRow = _constRow.data();
|
||||
for (int x = 0; x < dst_w; x++)
|
||||
memcpy(constRow + x * esz, constVal, esz);
|
||||
}
|
||||
|
||||
// ── Interior rows ─────────────────────────────────────────────────────────
|
||||
uchar* dstRow = dst + pad_top * dststep;
|
||||
for (int r = 0; r < src_h; r++, dstRow += dststep, src += srcstep)
|
||||
{
|
||||
// Copy source pixels into the interior portion of the row.
|
||||
memcpy(dstRow + pad_left * esz, src, src_w * esz);
|
||||
|
||||
if (borderType == BORDER_CONSTANT)
|
||||
{
|
||||
for (int i = 0; i < pad_left; i++)
|
||||
memcpy(dstRow + i * esz, constVal, esz);
|
||||
for (int i = 0; i < pad_right; i++)
|
||||
memcpy(dstRow + (pad_left + src_w + i) * esz, constVal, esz);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < pad_left; i++)
|
||||
memcpy(dstRow + i * esz, src + tab[i], esz);
|
||||
for (int i = 0; i < pad_right; i++)
|
||||
memcpy(dstRow + (pad_left + src_w + i) * esz, src + tab[pad_left + i], esz);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Top rows ──────────────────────────────────────────────────────────────
|
||||
for (int r = 0; r < pad_top; r++)
|
||||
{
|
||||
if (borderType == BORDER_CONSTANT)
|
||||
memcpy(dst + r * dststep, constRow, dst_w * esz);
|
||||
else
|
||||
{
|
||||
int j = borderInterpolate(r - pad_top, src_h, borderType);
|
||||
memcpy(dst + r * dststep, dst + (pad_top + j) * dststep, dst_w * esz);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Bottom rows ───────────────────────────────────────────────────────────
|
||||
uchar* dstBot = dst + (pad_top + src_h) * dststep;
|
||||
for (int r = 0; r < pad_bottom; r++)
|
||||
{
|
||||
if (borderType == BORDER_CONSTANT)
|
||||
memcpy(dstBot + r * dststep, constRow, dst_w * esz);
|
||||
else
|
||||
{
|
||||
int j = borderInterpolate(src_h + r, src_h, borderType);
|
||||
memcpy(dstBot + r * dststep, dst + (pad_top + j) * dststep, dst_w * esz);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TiledFilterInvoker : public ParallelLoopBody
|
||||
{
|
||||
struct TiledFilterBuffers {
|
||||
Mat padded_tile;
|
||||
Mat hbuf;
|
||||
};
|
||||
|
||||
public:
|
||||
TiledFilterInvoker(FilterEngine& _fe, const Mat& _src, Mat& _dst, int _tileSize = 128)
|
||||
: fe(_fe), src(_src), dst(_dst), tileSize(_tileSize)
|
||||
{
|
||||
tilesX = (dst.cols + tileSize - 1) / tileSize;
|
||||
}
|
||||
|
||||
virtual void operator() (const Range& range) const CV_OVERRIDE
|
||||
{
|
||||
int ax = fe.anchor.x, kwidth = fe.ksize.width;
|
||||
int ay = fe.anchor.y, kheight = fe.ksize.height;
|
||||
int dx1 = ax, dx2 = kwidth - ax - 1;
|
||||
int dy1 = ay, dy2 = kheight - ay - 1;
|
||||
|
||||
int borderType = fe.rowBorderType;
|
||||
int cn = CV_MAT_CN(fe.srcType);
|
||||
bool isSep = fe.isSeparable();
|
||||
|
||||
TiledFilterBuffers& tls = tlsData.getRef();
|
||||
|
||||
for (int i = range.start; i < range.end; i++)
|
||||
{
|
||||
int ty = i / tilesX;
|
||||
int tx = i % tilesX;
|
||||
|
||||
int dst_x = tx * tileSize;
|
||||
int dst_y = ty * tileSize;
|
||||
int w = std::min(tileSize, dst.cols - dst_x);
|
||||
int h = std::min(tileSize, dst.rows - dst_y);
|
||||
|
||||
int src_x1 = dst_x - dx1, src_y1 = dst_y - dy1;
|
||||
int src_x2 = dst_x + w + dx2, src_y2 = dst_y + h + dy2;
|
||||
|
||||
int pad_top = std::max(0, -src_y1);
|
||||
int pad_bottom = std::max(0, src_y2 - src.rows);
|
||||
int pad_left = std::max(0, -src_x1);
|
||||
int pad_right = std::max(0, src_x2 - src.cols);
|
||||
|
||||
int clamped_x1 = std::max(0, src_x1);
|
||||
int clamped_y1 = std::max(0, src_y1);
|
||||
int clamped_x2 = std::min(src.cols, src_x2);
|
||||
int clamped_y2 = std::min(src.rows, src_y2);
|
||||
|
||||
Mat src_region = src(Rect(clamped_x1, clamped_y1,
|
||||
clamped_x2 - clamped_x1,
|
||||
clamped_y2 - clamped_y1));
|
||||
|
||||
Mat tile_mat;
|
||||
if (pad_top == 0 && pad_bottom == 0 && pad_left == 0 && pad_right == 0)
|
||||
{
|
||||
tile_mat = src_region;
|
||||
}
|
||||
else
|
||||
{
|
||||
int padded_w = w + dx1 + dx2;
|
||||
int padded_h = h + dy1 + dy2;
|
||||
if (tls.padded_tile.cols < padded_w || tls.padded_tile.rows < padded_h || tls.padded_tile.type() != fe.srcType)
|
||||
tls.padded_tile.create(padded_h, padded_w, fe.srcType);
|
||||
|
||||
tile_mat = tls.padded_tile(Rect(0, 0, padded_w, padded_h));
|
||||
|
||||
const int esz = (int)CV_ELEM_SIZE(fe.srcType);
|
||||
const uchar* cval = fe.constBorderValue.empty() ? nullptr : &fe.constBorderValue[0];
|
||||
|
||||
#define FILL_BORDER(E) fillTileBorder<E>(src_region.ptr(), (size_t)src_region.step, \
|
||||
src_region.cols, src_region.rows, tile_mat.ptr(), (size_t)tile_mat.step, \
|
||||
pad_top, pad_bottom, pad_left, pad_right, borderType, cval)
|
||||
switch (esz)
|
||||
{
|
||||
case 1: FILL_BORDER(1); break;
|
||||
case 2: FILL_BORDER(2); break;
|
||||
case 3: FILL_BORDER(3); break;
|
||||
case 4: FILL_BORDER(4); break;
|
||||
case 6: FILL_BORDER(6); break;
|
||||
case 8: FILL_BORDER(8); break;
|
||||
case 12: FILL_BORDER(12); break;
|
||||
case 16: FILL_BORDER(16); break;
|
||||
default:
|
||||
{
|
||||
// Generic fallback for exotic element sizes.
|
||||
Scalar bv = Scalar::all(0);
|
||||
if (!fe.constBorderValue.empty())
|
||||
{
|
||||
const uchar* bptr = &fe.constBorderValue[0];
|
||||
int depth = CV_MAT_DEPTH(fe.srcType);
|
||||
for (int k = 0; k < cn; k++)
|
||||
{
|
||||
switch(depth)
|
||||
{
|
||||
case CV_8U: bv[k] = bptr[k]; break;
|
||||
case CV_8S: bv[k] = ((const schar*)bptr)[k]; break;
|
||||
case CV_16U: bv[k] = ((const ushort*)bptr)[k]; break;
|
||||
case CV_16S: bv[k] = ((const short*)bptr)[k]; break;
|
||||
case CV_32S: bv[k] = ((const int*)bptr)[k]; break;
|
||||
case CV_32F: bv[k] = ((const float*)bptr)[k]; break;
|
||||
case CV_64F: bv[k] = ((const double*)bptr)[k]; break;
|
||||
default: bv[k] = bptr[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
copyMakeBorder(src_region, tile_mat, pad_top, pad_bottom, pad_left, pad_right, borderType, bv);
|
||||
}
|
||||
}
|
||||
#undef FILL_BORDER
|
||||
}
|
||||
|
||||
uchar* dst_ptr = dst.ptr(dst_y) + dst_x * dst.elemSize();
|
||||
if (isSep)
|
||||
{
|
||||
int hstep = (int)alignSize(w * CV_ELEM_SIZE(fe.bufType), VEC_ALIGN);
|
||||
if (tls.hbuf.rows < tile_mat.rows || tls.hbuf.cols < hstep)
|
||||
tls.hbuf.create(tile_mat.rows, hstep, CV_8U);
|
||||
|
||||
uchar* hbuf = tls.hbuf.ptr();
|
||||
for (int r = 0; r < tile_mat.rows; r++)
|
||||
(*fe.rowFilter)(tile_mat.ptr(r), hbuf + r * hstep, w, cn);
|
||||
|
||||
AutoBuffer<const uchar*> _brows(h + kheight - 1);
|
||||
const uchar** brows = _brows.data();
|
||||
for (int m = 0; m < h + kheight - 1; m++)
|
||||
brows[m] = hbuf + m * hstep;
|
||||
|
||||
(*fe.columnFilter)(brows, dst_ptr, (int)dst.step, h, w * cn);
|
||||
}
|
||||
else
|
||||
{
|
||||
AutoBuffer<const uchar*> _brows(h + kheight - 1);
|
||||
const uchar** brows = _brows.data();
|
||||
for (int k = 0; k < h + kheight - 1; k++)
|
||||
brows[k] = tile_mat.ptr(k);
|
||||
|
||||
(*fe.filter2D)(brows, dst_ptr, (int)dst.step, h, w, cn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
FilterEngine& fe;
|
||||
const Mat& src;
|
||||
Mat& dst;
|
||||
int tileSize;
|
||||
int tilesX;
|
||||
mutable TLSData<TiledFilterBuffers> tlsData;
|
||||
};
|
||||
|
||||
void FilterEngine__apply(FilterEngine& this_, const Mat& src, Mat& dst, const Size& wsz, const Point& ofs)
|
||||
{
|
||||
CV_INSTRUMENT_REGION();
|
||||
|
||||
CV_DbgAssert(src.type() == this_.srcType && dst.type() == this_.dstType);
|
||||
|
||||
// Tiled Fast Path for stateless parallel filters on large images.
|
||||
int nthreads = cv::getNumThreads();
|
||||
if (this_.isStateless() && nthreads > 1 &&
|
||||
(size_t)src.total() >= std::max((size_t)1024 * 1024, (size_t)nthreads * 64 * 1024) &&
|
||||
this_.rowBorderType == this_.columnBorderType)
|
||||
{
|
||||
// For in-place operations (e.g. morphologyEx MORPH_OPEN), clone src so that
|
||||
// concurrent tiles read from an immutable snapshot rather than racing on writes.
|
||||
Mat src_copy = (src.data == dst.data) ? src.clone() : src;
|
||||
|
||||
// Heuristic: Balance L2 cache locality (128) vs parallel load balancing (64).
|
||||
int tileSize = (src.total() < (size_t)nthreads * 128 * 128 * 4) ? 64 : 128;
|
||||
int totalTiles = ((dst.cols + tileSize - 1) / tileSize) * ((dst.rows + tileSize - 1) / tileSize);
|
||||
|
||||
TiledFilterInvoker invoker(this_, src_copy, dst, tileSize);
|
||||
parallel_for_(Range(0, totalTiles), invoker);
|
||||
return;
|
||||
}
|
||||
|
||||
FilterEngine__start(this_, wsz, src.size(), ofs);
|
||||
int y = this_.startY - ofs.y;
|
||||
FilterEngine__proceed(this_,
|
||||
@@ -2398,6 +2651,8 @@ template<typename ST, typename DT, class VecOp> struct RowFilter : public BaseRo
|
||||
vecOp = _vecOp;
|
||||
}
|
||||
|
||||
bool isStateless() const CV_OVERRIDE { return true; }
|
||||
|
||||
void operator()(const uchar* src, uchar* dst, int width, int cn) CV_OVERRIDE
|
||||
{
|
||||
CV_INSTRUMENT_REGION();
|
||||
@@ -2599,6 +2854,8 @@ template<class CastOp, class VecOp> struct ColumnFilter : public BaseColumnFilte
|
||||
(kernel.rows == 1 || kernel.cols == 1));
|
||||
}
|
||||
|
||||
bool isStateless() const CV_OVERRIDE { return true; }
|
||||
|
||||
void operator()(const uchar** src, uchar* dst, int dststep, int count, int width) CV_OVERRIDE
|
||||
{
|
||||
CV_INSTRUMENT_REGION();
|
||||
@@ -3116,16 +3373,18 @@ template<typename ST, class CastOp, class VecOp> struct Filter2D : public BaseFi
|
||||
vecOp = _vecOp;
|
||||
CV_Assert( _kernel.type() == DataType<KT>::type );
|
||||
preprocess2DKernel( _kernel, coords, coeffs );
|
||||
ptrs.resize( coords.size() );
|
||||
}
|
||||
|
||||
bool isStateless() const CV_OVERRIDE { return true; }
|
||||
|
||||
void operator()(const uchar** src, uchar* dst, int dststep, int count, int width, int cn) CV_OVERRIDE
|
||||
{
|
||||
KT _delta = delta;
|
||||
const Point* pt = &coords[0];
|
||||
const KT* kf = (const KT*)&coeffs[0];
|
||||
const ST** kp = (const ST**)&ptrs[0];
|
||||
int i, k, nz = (int)coords.size();
|
||||
AutoBuffer<const ST*> _kp(nz);
|
||||
const ST** kp = _kp.data();
|
||||
CastOp castOp = castOp0;
|
||||
|
||||
width *= cn;
|
||||
@@ -3168,7 +3427,6 @@ template<typename ST, class CastOp, class VecOp> struct Filter2D : public BaseFi
|
||||
|
||||
std::vector<Point> coords;
|
||||
std::vector<uchar> coeffs;
|
||||
std::vector<uchar*> ptrs;
|
||||
KT delta;
|
||||
CastOp castOp0;
|
||||
VecOp vecOp;
|
||||
|
||||
@@ -74,6 +74,8 @@ public:
|
||||
virtual ~BaseRowFilter();
|
||||
//! the filtering operator. Must be overridden in the derived classes. The horizontal border interpolation is done outside of the class.
|
||||
virtual void operator()(const uchar* src, uchar* dst, int width, int cn) = 0;
|
||||
//! returns true if the filter holds no mutable state between calls (safe for concurrent tile-parallel invocation)
|
||||
virtual bool isStateless() const { return false; }
|
||||
|
||||
int ksize;
|
||||
int anchor;
|
||||
@@ -104,6 +106,8 @@ public:
|
||||
virtual void operator()(const uchar** src, uchar* dst, int dststep, int dstcount, int width) = 0;
|
||||
//! resets the internal buffers, if any
|
||||
virtual void reset();
|
||||
//! returns true if the filter holds no mutable state between calls (safe for concurrent tile-parallel invocation)
|
||||
virtual bool isStateless() const { return false; }
|
||||
|
||||
int ksize;
|
||||
int anchor;
|
||||
@@ -132,6 +136,8 @@ public:
|
||||
virtual void operator()(const uchar** src, uchar* dst, int dststep, int dstcount, int width, int cn) = 0;
|
||||
//! resets the internal buffers, if any
|
||||
virtual void reset();
|
||||
//! returns true if the filter holds no mutable state between calls (safe for concurrent tile-parallel invocation)
|
||||
virtual bool isStateless() const { return false; }
|
||||
|
||||
Size ksize;
|
||||
Point anchor;
|
||||
@@ -251,6 +257,9 @@ public:
|
||||
int remainingInputRows() const;
|
||||
int remainingOutputRows() const;
|
||||
|
||||
//! returns true if the engine's filters are entirely stateless and thus safe for 2D parallel block chunking
|
||||
bool isStateless() const;
|
||||
|
||||
int srcType;
|
||||
int dstType;
|
||||
int bufType;
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
#include "precomp.hpp"
|
||||
#include <limits.h>
|
||||
#include "opencv2/core/hal/intrin.hpp"
|
||||
#include "filter.hpp"
|
||||
|
||||
/****************************************************************************************\
|
||||
Basic Morphological Operations: Erosion & Dilation
|
||||
@@ -495,6 +496,8 @@ template<class Op, class VecOp> struct MorphRowFilter : public BaseRowFilter
|
||||
anchor = _anchor;
|
||||
}
|
||||
|
||||
bool isStateless() const CV_OVERRIDE { return true; }
|
||||
|
||||
void operator()(const uchar* src, uchar* dst, int width, int cn) CV_OVERRIDE
|
||||
{
|
||||
CV_INSTRUMENT_REGION();
|
||||
@@ -551,6 +554,8 @@ template<class Op, class VecOp> struct MorphColumnFilter : public BaseColumnFilt
|
||||
anchor = _anchor;
|
||||
}
|
||||
|
||||
bool isStateless() const CV_OVERRIDE { return true; }
|
||||
|
||||
void operator()(const uchar** _src, uchar* dst, int dststep, int count, int width) CV_OVERRIDE
|
||||
{
|
||||
CV_INSTRUMENT_REGION();
|
||||
@@ -638,7 +643,7 @@ template<class Op, class VecOp> struct MorphColumnFilter : public BaseColumnFilt
|
||||
};
|
||||
|
||||
|
||||
template<class Op, class VecOp> struct MorphFilter : BaseFilter
|
||||
template<class Op, class VecOp> struct MorphFilter : public BaseFilter
|
||||
{
|
||||
typedef typename Op::rtype T;
|
||||
|
||||
@@ -651,16 +656,18 @@ template<class Op, class VecOp> struct MorphFilter : BaseFilter
|
||||
std::vector<uchar> coeffs; // we do not really the values of non-zero
|
||||
// kernel elements, just their locations
|
||||
preprocess2DKernel( _kernel, coords, coeffs );
|
||||
ptrs.resize( coords.size() );
|
||||
}
|
||||
|
||||
bool isStateless() const CV_OVERRIDE { return true; }
|
||||
|
||||
void operator()(const uchar** src, uchar* dst, int dststep, int count, int width, int cn) CV_OVERRIDE
|
||||
{
|
||||
CV_INSTRUMENT_REGION();
|
||||
|
||||
const Point* pt = &coords[0];
|
||||
const T** kp = (const T**)&ptrs[0];
|
||||
int i, k, nz = (int)coords.size();
|
||||
AutoBuffer<const T*> _kp(nz);
|
||||
const T** kp = _kp.data();
|
||||
Op op;
|
||||
|
||||
width *= cn;
|
||||
@@ -671,7 +678,7 @@ template<class Op, class VecOp> struct MorphFilter : BaseFilter
|
||||
for( k = 0; k < nz; k++ )
|
||||
kp[k] = (const T*)src[pt[k].y] + pt[k].x*cn;
|
||||
|
||||
i = vecOp(&ptrs[0], nz, dst, width);
|
||||
i = vecOp((uchar**)kp, nz, dst, width);
|
||||
#if CV_ENABLE_UNROLLED
|
||||
for( ; i <= width - 4; i += 4 )
|
||||
{
|
||||
@@ -700,7 +707,6 @@ template<class Op, class VecOp> struct MorphFilter : BaseFilter
|
||||
}
|
||||
|
||||
std::vector<Point> coords;
|
||||
std::vector<uchar*> ptrs;
|
||||
VecOp vecOp;
|
||||
};
|
||||
|
||||
|
||||
@@ -2537,4 +2537,112 @@ INSTANTIATE_TEST_CASE_P(/**/, Imgproc_sepFilter2D_types,
|
||||
testing::Values(CV_16S, CV_32F, CV_64F),
|
||||
);
|
||||
|
||||
// Verify that the tiled parallel FilterEngine path produces bit-exact results
|
||||
// compared to the sequential (single-threaded) path for large images.
|
||||
|
||||
typedef tuple<Size, int, int, int> ParallelFilterParams;
|
||||
typedef TestWithParam<ParallelFilterParams> ImgProc_ParallelFilter;
|
||||
|
||||
static void runFilter(const Mat& src, Mat& dst, int borderType, bool isSep)
|
||||
{
|
||||
if (isSep)
|
||||
{
|
||||
Mat kx = (Mat_<float>(1, 3) << 0.25f, 0.5f, 0.25f);
|
||||
Mat ky = (Mat_<float>(3, 1) << 0.25f, 0.5f, 0.25f);
|
||||
cv::sepFilter2D(src, dst, -1, kx, ky, Point(-1, -1), 0, borderType);
|
||||
}
|
||||
else
|
||||
{
|
||||
Mat kernel = (Mat_<float>(3, 3) <<
|
||||
1/16.f, 2/16.f, 1/16.f,
|
||||
2/16.f, 4/16.f, 2/16.f,
|
||||
1/16.f, 2/16.f, 1/16.f);
|
||||
cv::filter2D(src, dst, -1, kernel, Point(-1, -1), 0, borderType);
|
||||
}
|
||||
}
|
||||
|
||||
class ScopedThreadsGuard
|
||||
{
|
||||
public:
|
||||
ScopedThreadsGuard() : old_threads(getNumThreads()) {}
|
||||
~ScopedThreadsGuard() { setNumThreads(old_threads); }
|
||||
void set(int n) { setNumThreads(n); }
|
||||
private:
|
||||
int old_threads;
|
||||
};
|
||||
|
||||
TEST_P(ImgProc_ParallelFilter, accuracy)
|
||||
{
|
||||
const Size sz = get<0>(GetParam());
|
||||
const int type = get<1>(GetParam());
|
||||
const int borderType = get<2>(GetParam());
|
||||
const bool isSep = get<3>(GetParam()) != 0;
|
||||
|
||||
Mat src(sz, type);
|
||||
randu(src, 0, 256);
|
||||
|
||||
ScopedThreadsGuard threadsGuard;
|
||||
const int prev_threads = getNumThreads();
|
||||
|
||||
// Parallel run — use at least 2 threads to exercise the tiled path.
|
||||
threadsGuard.set(std::max(2, prev_threads));
|
||||
Mat dst_par;
|
||||
runFilter(src, dst_par, borderType, isSep);
|
||||
|
||||
// Sequential reference.
|
||||
threadsGuard.set(1);
|
||||
Mat dst_seq;
|
||||
runFilter(src, dst_seq, borderType, isSep);
|
||||
|
||||
Mat diff;
|
||||
double max_err = 0;
|
||||
absdiff(dst_par, dst_seq, diff);
|
||||
minMaxLoc(diff.reshape(1), nullptr, &max_err);
|
||||
EXPECT_EQ(0.0, max_err) << "Parallel and sequential filter results differ";
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(FullImage, ImgProc_ParallelFilter,
|
||||
Combine(
|
||||
Values(Size(1200, 1200), Size(2000, 1000)),
|
||||
Values(CV_8UC1, CV_8UC3, CV_32FC1),
|
||||
Values(BORDER_DEFAULT, BORDER_CONSTANT),
|
||||
Values(0, 1) // 0 = filter2D, 1 = sepFilter2D
|
||||
)
|
||||
);
|
||||
|
||||
// Verify compound morphological operations (in-place second pass) are bitexact.
|
||||
TEST(ImgProc_ParallelFilter, morphology_compound)
|
||||
{
|
||||
const Size sz(1920, 1080);
|
||||
const int types[] = { CV_8UC1, CV_8UC3 };
|
||||
const int morphOps[] = { MORPH_OPEN, MORPH_CLOSE, MORPH_TOPHAT, MORPH_BLACKHAT };
|
||||
|
||||
for (int ti = 0; ti < 2; ti++)
|
||||
{
|
||||
Mat src(sz, types[ti]);
|
||||
randu(src, 0, 256);
|
||||
|
||||
for (int oi = 0; oi < 4; oi++)
|
||||
{
|
||||
ScopedThreadsGuard threadsGuard;
|
||||
threadsGuard.set(std::max(2, getNumThreads()));
|
||||
Mat dst_par;
|
||||
cv::morphologyEx(src, dst_par, morphOps[oi], Mat());
|
||||
|
||||
threadsGuard.set(1);
|
||||
Mat dst_seq;
|
||||
cv::morphologyEx(src, dst_seq, morphOps[oi], Mat());
|
||||
|
||||
Mat diff;
|
||||
double max_err = 0;
|
||||
absdiff(dst_par, dst_seq, diff);
|
||||
minMaxLoc(diff.reshape(1), nullptr, &max_err);
|
||||
EXPECT_EQ(0.0, max_err)
|
||||
<< "morphOp=" << morphOps[oi]
|
||||
<< " type=" << types[ti]
|
||||
<< ": parallel vs sequential results differ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace
|
||||
|
||||
@@ -14,4 +14,15 @@
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
<filteredResources>
|
||||
<filter>
|
||||
<id>1777629542150</id>
|
||||
<name></name>
|
||||
<type>30</type>
|
||||
<matcher>
|
||||
<id>org.eclipse.core.resources.regexFilterMatcher</id>
|
||||
<arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
|
||||
</matcher>
|
||||
</filter>
|
||||
</filteredResources>
|
||||
</projectDescription>
|
||||
|
||||
Reference in New Issue
Block a user