From 5810a73d30dfdda9455769a81346a84c52c39423 Mon Sep 17 00:00:00 2001 From: Vladislav Vinogradov Date: Mon, 25 Mar 2013 15:38:43 +0400 Subject: [PATCH] CPU implementation of CLAHE --- .../include/opencv2/imgproc/imgproc.hpp | 15 + modules/imgproc/perf/perf_histogram.cpp | 22 ++ modules/imgproc/src/histogram.cpp | 309 +++++++++++++++++- 3 files changed, 338 insertions(+), 8 deletions(-) diff --git a/modules/imgproc/include/opencv2/imgproc/imgproc.hpp b/modules/imgproc/include/opencv2/imgproc/imgproc.hpp index 5d6633087..223ee3277 100644 --- a/modules/imgproc/include/opencv2/imgproc/imgproc.hpp +++ b/modules/imgproc/include/opencv2/imgproc/imgproc.hpp @@ -759,6 +759,21 @@ CV_EXPORTS double compareHist( const SparseMat& H1, const SparseMat& H2, int met //! normalizes the grayscale image brightness and contrast by normalizing its histogram CV_EXPORTS_W void equalizeHist( InputArray src, OutputArray dst ); +class CV_EXPORTS CLAHE : public Algorithm +{ +public: + virtual void apply(InputArray src, OutputArray dst) = 0; + + virtual void setClipLimit(double clipLimit) = 0; + virtual double getClipLimit() const = 0; + + virtual void setTilesGridSize(Size tileGridSize) = 0; + virtual Size getTilesGridSize() const = 0; + + virtual void collectGarbage() = 0; +}; +CV_EXPORTS Ptr createCLAHE(double clipLimit = 40.0, Size tileGridSize = Size(8, 8)); + CV_EXPORTS float EMD( InputArray signature1, InputArray signature2, int distType, InputArray cost=noArray(), float* lowerBound=0, OutputArray flow=noArray() ); diff --git a/modules/imgproc/perf/perf_histogram.cpp b/modules/imgproc/perf/perf_histogram.cpp index 3b320633a..92db3be34 100644 --- a/modules/imgproc/perf/perf_histogram.cpp +++ b/modules/imgproc/perf/perf_histogram.cpp @@ -115,3 +115,25 @@ PERF_TEST_P(MatSize, equalizeHist, SANITY_CHECK(destination); } + +typedef tr1::tuple Sz_ClipLimit_t; +typedef TestBaseWithParam Sz_ClipLimit; + +PERF_TEST_P(Sz_ClipLimit, CLAHE, + testing::Combine(testing::Values(::perf::szVGA, ::perf::sz720p, ::perf::sz1080p), + testing::Values(0.0, 40.0)) + ) +{ + const Size size = get<0>(GetParam()); + const double clipLimit = get<1>(GetParam()); + + Mat src(size, CV_8UC1); + declare.in(src, WARMUP_RNG); + + Ptr clahe = createCLAHE(clipLimit); + Mat dst; + + TEST_CYCLE() clahe->apply(src, dst); + + SANITY_CHECK(dst); +} diff --git a/modules/imgproc/src/histogram.cpp b/modules/imgproc/src/histogram.cpp index 61509d344..22dd9beb1 100644 --- a/modules/imgproc/src/histogram.cpp +++ b/modules/imgproc/src/histogram.cpp @@ -2604,7 +2604,7 @@ cvCopyHist( const CvHistogram* src, CvHistogram** _dst ) int size1[CV_MAX_DIM]; bool is_sparse = CV_IS_SPARSE_MAT(src->bins); int dims1 = cvGetDims( src->bins, size1 ); - + if( dst && (is_sparse == CV_IS_SPARSE_MAT(dst->bins))) { int size2[CV_MAX_DIM]; @@ -2613,14 +2613,14 @@ cvCopyHist( const CvHistogram* src, CvHistogram** _dst ) if( dims1 == dims2 ) { int i; - + for( i = 0; i < dims1; i++ ) { if( size1[i] != size2[i] ) break; } - - eq = (i == dims1); + + eq = (i == dims1); } } @@ -2635,19 +2635,19 @@ cvCopyHist( const CvHistogram* src, CvHistogram** _dst ) { float* ranges[CV_MAX_DIM]; float** thresh = 0; - + if( CV_IS_UNIFORM_HIST( src )) { for( int i = 0; i < dims1; i++ ) ranges[i] = (float*)src->thresh[i]; - + thresh = ranges; } else { thresh = src->thresh2; } - + cvSetHistBinRanges( dst, thresh, CV_IS_UNIFORM_HIST(src)); } @@ -3188,6 +3188,300 @@ void cv::equalizeHist( InputArray _src, OutputArray _dst ) lutBody(heightRange); } +// ---------------------------------------------------------------------- +// CLAHE + +namespace +{ + class CLAHE_CalcLut_Body : public cv::ParallelLoopBody + { + public: + CLAHE_CalcLut_Body(const cv::Mat& src, cv::Mat& lut, cv::Size tileSize, int tilesX, int tilesY, int clipLimit, float lutScale) : + src_(src), lut_(lut), tileSize_(tileSize), tilesX_(tilesX), tilesY_(tilesY), clipLimit_(clipLimit), lutScale_(lutScale) + { + } + + void operator ()(const cv::Range& range) const; + + private: + cv::Mat src_; + mutable cv::Mat lut_; + + cv::Size tileSize_; + int tilesX_; + int tilesY_; + int clipLimit_; + float lutScale_; + }; + + void CLAHE_CalcLut_Body::operator ()(const cv::Range& range) const + { + const int histSize = 256; + + uchar* tileLut = lut_.ptr(range.start); + const size_t lut_step = lut_.step; + + for (int k = range.start; k < range.end; ++k, tileLut += lut_step) + { + const int ty = k / tilesX_; + const int tx = k % tilesX_; + + // retrieve tile submatrix + + cv::Rect tileROI; + tileROI.x = tx * tileSize_.width; + tileROI.y = ty * tileSize_.height; + tileROI.width = tileSize_.width; + tileROI.height = tileSize_.height; + + const cv::Mat tile = src_(tileROI); + + // calc histogram + + int tileHist[histSize] = {0, }; + + int height = tileROI.height; + const size_t sstep = tile.step; + for (const uchar* ptr = tile.ptr(0); height--; ptr += sstep) + { + int x = 0; + for (; x <= tileROI.width - 4; x += 4) + { + int t0 = ptr[x], t1 = ptr[x+1]; + tileHist[t0]++; tileHist[t1]++; + t0 = ptr[x+2]; t1 = ptr[x+3]; + tileHist[t0]++; tileHist[t1]++; + } + + for (; x < tileROI.width; ++x) + tileHist[ptr[x]]++; + } + + // clip histogram + + if (clipLimit_ > 0) + { + // how many pixels were clipped + int clipped = 0; + for (int i = 0; i < histSize; ++i) + { + if (tileHist[i] > clipLimit_) + { + clipped += tileHist[i] - clipLimit_; + tileHist[i] = clipLimit_; + } + } + + // redistribute clipped pixels + int redistBatch = clipped / histSize; + int residual = clipped - redistBatch * histSize; + + for (int i = 0; i < histSize; ++i) + tileHist[i] += redistBatch; + + for (int i = 0; i < residual; ++i) + tileHist[i]++; + } + + // calc Lut + + int sum = 0; + for (int i = 0; i < histSize; ++i) + { + sum += tileHist[i]; + tileLut[i] = cv::saturate_cast(sum * lutScale_); + } + } + } + + class CLAHE_Interpolation_Body : public cv::ParallelLoopBody + { + public: + CLAHE_Interpolation_Body(const cv::Mat& src, cv::Mat& dst, const cv::Mat& lut, cv::Size tileSize, int tilesX, int tilesY) : + src_(src), dst_(dst), lut_(lut), tileSize_(tileSize), tilesX_(tilesX), tilesY_(tilesY) + { + } + + void operator ()(const cv::Range& range) const; + + private: + cv::Mat src_; + mutable cv::Mat dst_; + cv::Mat lut_; + + cv::Size tileSize_; + int tilesX_; + int tilesY_; + }; + + void CLAHE_Interpolation_Body::operator ()(const cv::Range& range) const + { + const size_t lut_step = lut_.step; + + for (int y = range.start; y < range.end; ++y) + { + const uchar* srcRow = src_.ptr(y); + uchar* dstRow = dst_.ptr(y); + + const float tyf = (static_cast(y) / tileSize_.height) - 0.5f; + + int ty1 = cvFloor(tyf); + int ty2 = ty1 + 1; + + const float ya = tyf - ty1; + + ty1 = std::max(ty1, 0); + ty2 = std::min(ty2, tilesY_ - 1); + + const uchar* lutPlane1 = lut_.ptr(ty1 * tilesX_); + const uchar* lutPlane2 = lut_.ptr(ty2 * tilesX_); + + for (int x = 0; x < src_.cols; ++x) + { + const float txf = (static_cast(x) / tileSize_.width) - 0.5f; + + int tx1 = cvFloor(txf); + int tx2 = tx1 + 1; + + const float xa = txf - tx1; + + tx1 = std::max(tx1, 0); + tx2 = std::min(tx2, tilesX_ - 1); + + const int srcVal = srcRow[x]; + + const size_t ind1 = tx1 * lut_step + srcVal; + const size_t ind2 = tx2 * lut_step + srcVal; + + float res = 0; + + res += lutPlane1[ind1] * ((1.0f - xa) * (1.0f - ya)); + res += lutPlane1[ind2] * ((xa) * (1.0f - ya)); + res += lutPlane2[ind1] * ((1.0f - xa) * (ya)); + res += lutPlane2[ind2] * ((xa) * (ya)); + + dstRow[x] = cv::saturate_cast(res); + } + } + } + + class CLAHE_Impl : public cv::CLAHE + { + public: + CLAHE_Impl(double clipLimit = 40.0, int tilesX = 8, int tilesY = 8); + + cv::AlgorithmInfo* info() const; + + void apply(cv::InputArray src, cv::OutputArray dst); + + void setClipLimit(double clipLimit); + double getClipLimit() const; + + void setTilesGridSize(cv::Size tileGridSize); + cv::Size getTilesGridSize() const; + + void collectGarbage(); + + private: + double clipLimit_; + int tilesX_; + int tilesY_; + + cv::Mat srcExt_; + cv::Mat lut_; + }; + + CLAHE_Impl::CLAHE_Impl(double clipLimit, int tilesX, int tilesY) : + clipLimit_(clipLimit), tilesX_(tilesX), tilesY_(tilesY) + { + } + + CV_INIT_ALGORITHM(CLAHE_Impl, "CLAHE", + obj.info()->addParam(obj, "clipLimit", obj.clipLimit_); + obj.info()->addParam(obj, "tilesX", obj.tilesX_); + obj.info()->addParam(obj, "tilesY", obj.tilesY_)) + + void CLAHE_Impl::apply(cv::InputArray _src, cv::OutputArray _dst) + { + cv::Mat src = _src.getMat(); + + CV_Assert( src.type() == CV_8UC1 ); + + _dst.create( src.size(), src.type() ); + cv::Mat dst = _dst.getMat(); + + const int histSize = 256; + + lut_.create(tilesX_ * tilesY_, histSize, CV_8UC1); + + cv::Size tileSize; + cv::Mat srcForLut; + + if (src.cols % tilesX_ == 0 && src.rows % tilesY_ == 0) + { + tileSize = cv::Size(src.cols / tilesX_, src.rows / tilesY_); + srcForLut = src; + } + else + { + cv::copyMakeBorder(src, srcExt_, 0, tilesY_ - (src.rows % tilesY_), 0, tilesX_ - (src.cols % tilesX_), cv::BORDER_REFLECT_101); + + tileSize = cv::Size(srcExt_.cols / tilesX_, srcExt_.rows / tilesY_); + srcForLut = srcExt_; + } + + const int tileSizeTotal = tileSize.area(); + const float lutScale = static_cast(histSize - 1) / tileSizeTotal; + + int clipLimit = 0; + if (clipLimit_ > 0.0) + { + clipLimit = static_cast(clipLimit_ * tileSizeTotal / histSize); + clipLimit = std::max(clipLimit, 1); + } + + CLAHE_CalcLut_Body calcLutBody(srcForLut, lut_, tileSize, tilesX_, tilesY_, clipLimit, lutScale); + cv::parallel_for_(cv::Range(0, tilesX_ * tilesY_), calcLutBody); + + CLAHE_Interpolation_Body interpolationBody(src, dst, lut_, tileSize, tilesX_, tilesY_); + cv::parallel_for_(cv::Range(0, src.rows), interpolationBody); + } + + void CLAHE_Impl::setClipLimit(double clipLimit) + { + clipLimit_ = clipLimit; + } + + double CLAHE_Impl::getClipLimit() const + { + return clipLimit_; + } + + void CLAHE_Impl::setTilesGridSize(cv::Size tileGridSize) + { + tilesX_ = tileGridSize.width; + tilesY_ = tileGridSize.height; + } + + cv::Size CLAHE_Impl::getTilesGridSize() const + { + return cv::Size(tilesX_, tilesY_); + } + + void CLAHE_Impl::collectGarbage() + { + srcExt_.release(); + lut_.release(); + } +} + +cv::Ptr cv::createCLAHE(double clipLimit, cv::Size tileGridSize) +{ + return new CLAHE_Impl(clipLimit, tileGridSize.width, tileGridSize.height); +} + +// ---------------------------------------------------------------------- + /* Implementation of RTTI and Generic Functions for CvHistogram */ #define CV_TYPE_NAME_HIST "opencv-hist" @@ -3339,4 +3633,3 @@ CvType hist_type( CV_TYPE_NAME_HIST, icvIsHist, (CvReleaseFunc)cvReleaseHist, icvReadHist, icvWriteHist, (CvCloneFunc)icvCloneHist ); /* End of file. */ -