From db0380a7a0f76c4bd44fd1a21ebca5c1b072b402 Mon Sep 17 00:00:00 2001 From: Pratham Kumar Date: Mon, 9 Nov 2026 12:21:40 +0530 Subject: [PATCH] imgproc: parallelize calcHist using stripe-based parallel_for_ (#30585) * imgproc: parallelize calcHist using stripe-based parallel_for_ * backport calcHist parallelization from 5.x --- modules/imgproc/src/histogram.cpp | 84 +++++++++++++++++++++++- modules/imgproc/test/test_histograms.cpp | 67 +++++++++++++++++++ 2 files changed, 148 insertions(+), 3 deletions(-) diff --git a/modules/imgproc/src/histogram.cpp b/modules/imgproc/src/histogram.cpp index ae72c97edd..5674ffc96d 100644 --- a/modules/imgproc/src/histogram.cpp +++ b/modules/imgproc/src/histogram.cpp @@ -806,6 +806,83 @@ namespace cv } #endif +namespace cv +{ + +template +static void calcHistRunParallel( std::vector& ptrs, const std::vector& deltas, + Size imsize, Mat& hist, int dims, const float** ranges, + const double* uniranges, bool uniform, int esz1, CalcFunc calcFunc ) +{ + const int64 N = (int64)imsize.width * imsize.height; + const size_t M = hist.total(); + + if( N < (int64)256 * 1024 ) + { + calcFunc( ptrs, deltas, imsize, hist, dims, ranges, uniranges, uniform ); + return; + } + + int nstripes = M > 0 ? std::max(1, std::min(8, cvRound((double)N * 1e-3 / (double)M))) : 1; + nstripes = std::min( nstripes, getNumThreads() ); + if( nstripes <= 1 ) + { + calcFunc( ptrs, deltas, imsize, hist, dims, ranges, uniranges, uniform ); + return; + } + + const bool splitByRows = imsize.height > 1; + const int splitDim = splitByRows ? imsize.height : imsize.width; + nstripes = std::min( nstripes, splitDim ); + + std::vector local_hists( nstripes ); + + parallel_for_( Range( 0, nstripes ), [&]( const Range& r ) + { + for( int s = r.start; s < r.end; s++ ) + { + const int start = (int)((int64)s * splitDim / nstripes); + const int end = (int)((int64)(s + 1) * splitDim / nstripes); + if( start >= end ) + continue; + + std::vector local_ptrs = ptrs; + Size local_size; + + if( splitByRows ) + { + for( int i = 0; i < dims; i++ ) + { + size_t row_step = ((size_t)imsize.width * deltas[i*2] + deltas[i*2 + 1]) * esz1; + local_ptrs[i] = (uchar*)((const uchar*)local_ptrs[i] + (size_t)start * row_step); + } + if( local_ptrs[dims] ) + local_ptrs[dims] = (uchar*)((const uchar*)local_ptrs[dims] + (size_t)start * deltas[dims*2 + 1]); + local_size = Size( imsize.width, end - start ); + } + else + { + for( int i = 0; i < dims; i++ ) + local_ptrs[i] = (uchar*)((const uchar*)local_ptrs[i] + (size_t)start * deltas[i*2] * esz1); + if( local_ptrs[dims] ) + local_ptrs[dims] = (uchar*)((const uchar*)local_ptrs[dims] + (size_t)start * deltas[dims*2]); + local_size = Size( end - start, 1 ); + } + + local_hists[s] = Mat::zeros( hist.dims, hist.size.p, hist.type() ); + calcFunc( local_ptrs, deltas, local_size, local_hists[s], dims, ranges, uniranges, uniform ); + } + }); + + for( int s = 0; s < nstripes; s++ ) + { + if( !local_hists[s].empty() ) + hist += local_hists[s]; + } +} + +} // namespace cv + void cv::calcHist( const Mat* images, int nimages, const int* channels, InputArray _mask, OutputArray _hist, int dims, const int* histSize, const float** ranges, bool uniform, bool accumulate ) @@ -857,13 +934,14 @@ void cv::calcHist( const Mat* images, int nimages, const int* channels, const double* _uniranges = uniform ? &uniranges[0] : 0; int depth = images[0].depth(); + int esz1 = (int)images[0].elemSize1(); if( depth == CV_8U ) - calcHist_8u(ptrs, deltas, imsize, ihist, dims, ranges, _uniranges, uniform ); + calcHistRunParallel( ptrs, deltas, imsize, ihist, dims, ranges, _uniranges, uniform, esz1, &calcHist_8u ); else if( depth == CV_16U ) - calcHist_(ptrs, deltas, imsize, ihist, dims, ranges, _uniranges, uniform ); + calcHistRunParallel( ptrs, deltas, imsize, ihist, dims, ranges, _uniranges, uniform, esz1, &calcHist_ ); else if( depth == CV_32F ) - calcHist_(ptrs, deltas, imsize, ihist, dims, ranges, _uniranges, uniform ); + calcHistRunParallel( ptrs, deltas, imsize, ihist, dims, ranges, _uniranges, uniform, esz1, &calcHist_ ); else CV_Error(cv::Error::StsUnsupportedFormat, ""); diff --git a/modules/imgproc/test/test_histograms.cpp b/modules/imgproc/test/test_histograms.cpp index 7b35ddcc46..465d8475be 100644 --- a/modules/imgproc/test/test_histograms.cpp +++ b/modules/imgproc/test/test_histograms.cpp @@ -2032,6 +2032,73 @@ TEST(Imgproc_Hist_Calc, IPP_ranges_with_nonequal_exponent_21595) ASSERT_EQ(histogram_u.at(2), 4.f) << "1 not counts correctly, res: " << histogram_u.at(2); } +typedef testing::TestWithParam< testing::tuple > Imgproc_Hist_Calc_Parallel; + +TEST_P(Imgproc_Hist_Calc_Parallel, consistency) +{ + int type = testing::get<0>(GetParam()); + int dims = testing::get<1>(GetParam()); + Size sz = testing::get<2>(GetParam()); + + int cn = dims; + + Mat buf(Size(sz.width + 1, sz.height), CV_MAKETYPE(type, cn)); + randu(buf, Scalar::all(0), Scalar::all(256)); + Mat src = buf(Rect(0, 0, sz.width, sz.height)); + + Mat mask; + if( dims == 1 ) + { + mask.create(sz, CV_8UC1); + randu(mask, Scalar::all(0), Scalar::all(2)); + } + + std::vector ch(dims), hs(dims); + std::vector > rv(dims); + std::vector rp(dims); + for( int i = 0; i < dims; i++ ) + { + ch[i] = i; + hs[i] = (dims == 1) ? 64 : (dims == 2) ? 8 : 4; + rv[i].assign(2, 0.f); + rv[i][1] = 256.f; + rp[i] = &rv[i][0]; + } + + // Row-by-row: every call stays below the parallel threshold. + Mat baseline; + for( int y = 0; y < src.rows; y++ ) + { + Mat row = src.row(y); + Mat mask_row = (dims == 1) ? mask.row(y) : Mat(); + bool acc = (y > 0); + calcHist(&row, 1, ch.data(), mask_row, baseline, dims, hs.data(), rp.data(), true, acc); + } + + setNumThreads(8); + Mat parallel_result; + calcHist(&src, 1, ch.data(), mask, parallel_result, dims, hs.data(), rp.data(), true, false); + + // Flattened to one row: column-split path. + Mat contiguous = src.clone(); + Mat contiguous_result; + calcHist(&contiguous, 1, ch.data(), mask, contiguous_result, dims, hs.data(), rp.data(), true, false); + setNumThreads(-1); + + EXPECT_EQ(0.0, cv::norm(baseline, parallel_result, NORM_INF)) + << "row-split, type=" << type << " dims=" << dims << " size=" << sz; + EXPECT_EQ(0.0, cv::norm(baseline, contiguous_result, NORM_INF)) + << "column-split, type=" << type << " dims=" << dims << " size=" << sz; +} + +INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgproc_Hist_Calc_Parallel, + testing::Combine( + testing::Values(CV_8U, CV_16U, CV_32F), + testing::Values(1, 2, 3), + testing::Values(Size(640, 480), Size(1920, 1080), Size(7, 13)) + ) +); + ////////////////////////////////////////// equalizeHist() ///////////////////////////////////////// void equalizeHistReference(const Mat& src, Mat& dst)