1
0
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:
Ismail
2026-04-24 09:22:35 +02:00
parent a5295015b2
commit d16f2bfef2
9 changed files with 472 additions and 32 deletions
+108
View File
@@ -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