mirror of
https://github.com/opencv/opencv.git
synced 2026-07-31 08:13:04 +04:00
imgproc: optimize tiled parallel filter with TLS buffers and custom border fill
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user