自适应直方图均衡(CLAHE) 代码及详细注释【OpenCV】

来源:互联网 发布:淘宝steam充值卡黑卡 编辑:程序博客网 时间:2024/04/29 01:08


理论请参考博客


OpenCV源码的本地路径: %OPENCV%\opencv\sources\modules\imgproc\src\clahe.cpp

clahe.cpp

// ----------------------------------------------------------------------// CLAHEnamespace{class CLAHE_CalcLut_Body : public cv::ParallelLoopBody{public:CLAHE_CalcLut_Body(const cv::Mat& src, cv::Mat& lut, cv::Size tileSize, int tilesX, int clipLimit, float lutScale) :src_(src), lut_(lut), tileSize_(tileSize), tilesX_(tilesX), 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 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;// size = tilesX_*tilesY_ * lut_step// Range(0, tilesX_ * tilesY_),全图被分为tilesX_*tiles_Y个块for (int k = range.start; k < range.end; ++k, tileLut += lut_step){// (tx, ty)表示当前所在是哪一块// (0, 0) (1, 0)...(tilesX_-1, 0) // (0, 1) (1, 1)...(tilesX_-1, 1) // ...// (0, tilesY_-1)... (tilesX_-1, tilesY_-1)const int ty = k / tilesX_;const int tx = k % tilesX_;// retrieve tile submatrix// 注意:tileSize.width表示分块的宽度,tileSize.height表示分块高度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 histogramint tileHist[histSize] = { 0, };// 统计 ROI 的直方图int height = tileROI.height;const size_t sstep = tile.step;for (const uchar* ptr = tile.ptr<uchar>(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 histogramif (clipLimit_ > 0){// how many pixels were clippedint clipped = 0;for (int i = 0; i < histSize; ++i){// 超过裁剪阈值if (tileHist[i] > clipLimit_){clipped += tileHist[i] - clipLimit_;tileHist[i] = clipLimit_;}}// redistribute clipped pixelsint 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 Lutint sum = 0;for (int i = 0; i < histSize; ++i){// 累加直方图sum += tileHist[i];tileLut[i] = cv::saturate_cast<uchar>(sum * lutScale_);// static_cast<float>(histSize - 1) / tileSizeTotal}}}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_;};// 根据相邻4块的直方图插值void CLAHE_Interpolation_Body::operator ()(const cv::Range& range) const{const size_t lut_step = lut_.step;// Range(0, src.rows)for (int y = range.start; y < range.end; ++y){const uchar* srcRow = src_.ptr<uchar>(y);uchar* dstRow = dst_.ptr<uchar>(y);const float tyf = (static_cast<float>(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<float>(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);// src_.ptr<uchar>(y)[x]const int srcVal = srcRow[x];// 索引 LUTconst size_t ind1 = tx1 * lut_step + srcVal;const size_t ind2 = tx2 * lut_step + srcVal;  // 向右一块的直方图float res = 0;// 根据直方图的值进行插值// lut_.ptr(ty1 * tilesX_)[tx1 * lut_step + srcVa] => lut_[ty1][tx1][srcVal]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<uchar>(res);}}}class CLAHE_Impl : public cv::CLAHE{public:CLAHE_Impl(double clipLimit = 40.0, int tilesX = 8, int tilesY = 8);cv::AlgorithmInfo* info() const; // Algorithm类工厂方法封装相关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){}        // Algorithm类工厂方法封装相关 //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,tilesX_*tilesY_个块,每个块都有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<float>(histSize - 1) / tileSizeTotal;// △// 计算实际的clipLimitint clipLimit = 0;if (clipLimit_ > 0.0){clipLimit = static_cast<int>(clipLimit_ * tileSizeTotal / histSize);clipLimit = std::max(clipLimit, 1);}// 分块并行计算: LUTCLAHE_CalcLut_Body calcLutBody(srcForLut, lut_, tileSize, tilesX_, 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::CLAHE> cv::createCLAHE(double clipLimit, cv::Size tileGridSize){return new CLAHE_Impl(clipLimit, tileGridSize.width, tileGridSize.height);}

main.cpp

int main(int argc, char** argv){cv::Mat inp_img = cv::imread("D:/Pictures/beard.jpg");if (!inp_img.data) {cout << "Something Wrong";return -1;}namedWindow("Input Image", CV_WINDOW_AUTOSIZE);cv::imshow("Input Image", inp_img);cv::Mat clahe_img;cv::cvtColor(inp_img, clahe_img, CV_BGR2Lab);std::vector<cv::Mat> channels(3);cv::split(clahe_img, channels);cv::Ptr<cv::CLAHE> clahe = cv::createCLAHE();// 直方图的柱子高度大于计算后的ClipLimit的部分被裁剪掉,然后将其平均分配给整张直方图// 从而提升整个图像clahe->setClipLimit(4.);// (int)(4.*(8*8)/256)//clahe->setTilesGridSize(Size(8, 8)); // 将图像分为8*8块cv::Mat dst;clahe->apply(channels[0], dst);dst.copyTo(channels[0]);cv::merge(channels, clahe_img);cv::Mat image_clahe;cv::cvtColor(clahe_img, image_clahe, CV_Lab2BGR);//cout << cvFloor(-1.5) << endl;namedWindow("CLAHE Image", CV_WINDOW_AUTOSIZE);cv::imshow("CLAHE Image", image_clahe);imwrite("out.jpg", image_clahe);cv::waitKey(0);destroyAllWindows();return 0;}

效果如图:






注意:cv::ParallelLoopBody 位于 %OpenCV%\opencv\sources\modules\core\src\parallel.cpp

延伸阅读:Algorithm类工厂方法封装相关


0 0
原创粉丝点击