效果图

标题

CLAHE简介

HE 直方图增强,大家都不陌生,是一种比较古老的对比度增强算法,它有两种变体:AHE 和 CLAHE;两者都是自适应的增强算法,功能差不多,但是前者有一个很大的缺陷,就是有时候会过度放大图像中相同区域的噪声,为了解决这一问题,出现了 HE 的另一种改进算法,就是 CLAHE;CLAHE 是另外一种直方图均衡算法,CLAHE 和 AHE 的区别在于前者对区域对比度实行了限制,并且利用插值来加快计算。它能有效的增强或改善图像(局部)对比度,从而获取更多图像相关边缘信息有利于分割。还能够有效改善 AHE 中放大噪声的问题。另外,CLAHE 的有一个用途是被用来对图像去雾。

详细理论请参考博客

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类工厂方法封装相关

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

  1. OpenCV自适应直方图均衡CLAHE图像和分块大小不能整除的处理

    一.引言 最近一个月来都在研究OpenCV 中CLAHE算法的一些问题,如: 图像横向和纵向分块大小与图像的宽和高不能整除怎么处理? CLIP的剪裁是怎么实施的? 解决棋盘效应的具体插值处理过程怎样? ...

  2. OpenCV-Python对比度受限的自适应直方图均衡CLAHE知识介绍

    一.引言 在前面的如下几篇文章中: <数字图像处理>第三章学习总结感悟2-1:直方图均衡处理 数字图像处理:局部直方图处理(Local Histogram Processing) 数字图像 ...

  3. [嵌入式er笔记]大端小端详解(含代码及详细注释)

    link 之前文章< 浅谈ARM ABI,Android ABI >中有提到计划专门一篇文章讲下大小端,今天兑现一下. 1>"大端" "小端" ...

  4. 手撕yolo3系列——详解yolo3整体网络代码(详细注释)

    完整代码百度云直达链接(包含预训练权重)(小白注释) https://pan.baidu.com/s/1US6e93OaCYOghmF21v0UIA 提取码:z8at 参考链接 [注]代码是大神的代码 ...

  5. OpenCV限制对比度自适应直方图均衡(CLAHE)的实例(附完整代码)

    OpenCV限制对比度自适应直方图均衡的实例 OpenCV限制对比度自适应直方图均衡的实例 OpenCV限制对比度自适应直方图均衡的实例 #include <iostream> #incl ...

  6. malloc 源码_【C++学习笔记(九)】之 new运算符的使用/ new 与 malloc的异同(附代码,详细注释)...

    new 运算符的使用 一. new的简单介绍: new运算符用于在堆区开辟空间,所有new出来的对象,对返回该类对象的指针. 二. new 与 malloc 的异同点: 1.malloc作用相同, m ...

  7. 慕课Python机器学习应用-代码实现(详细注释+数据集)

    课程链接:Python机器学习应用_北京理工大学_中国大学MOOC(慕课) 学习和实现的算法有Kmeans和DBSCAN,数据集是自己在网上搜集的,在我的资源区提供免费下载. Kmeans算法: 1. ...

  8. 基于OpenCV实现的图像油画效果代码(高效率、低耗时的C++代码-带详细注释)

    油画的特点是其颜色值很有限,并且笔比较粗,所以我们要实现油画效果关键就是利用运算实现这两点. 本文实现的图像油画效果代码原理如下: 一.将原图进行灰度化处理后将灰度值线性压缩到0~level,得到图像 ...

  9. OpenCV自适应直方图均衡CLAHE C++源代码分享

    一.引言 最近收到几个网友提供OpenCV中CLAHE的源代码的请求,在此直接将OpenCV4.54版本CLAHE.CPP的源码分享出来. 二.OpenCV源代码的下载 下载地址:https://so ...

最新文章

  1. python使用random模块生成随机数、实现随机乱序和随机抽样?
  2. HTML开发中的一个问题
  3. Windows Server 2012 R2配置ISCSI磁盘共享盘(4)
  4. 黄聪:C#代码生成工具:文本模板初体验 Hello,World!
  5. 路由器DHCP服务器及PPP封装验证
  6. java验证码的实现
  7. python比较两个数的和_Python中的is和==比较两个对象的两种方法
  8. java中字符串的精确匹配_Java最佳实践–字符串性能和精确字符串匹配
  9. javaScript DOM编程常用的方法与属性
  10. php关闭当前页_php如何直接关闭页面注销SESSION
  11. 洛谷P5715、P5716、P5718、P5722题题解(Java语言描述)
  12. DynamoRIO工作原理
  13. oracle8i+下载,oracle database 8i, 9i,10g, 11g正确下载地址
  14. UltraISO9.3.0.2610中文绿色注册版
  15. 【目标跟踪】基于卡尔曼滤波实现多车辆路径跟踪含Matlab源码
  16. 《我的青春谁做主》经典搞笑台词
  17. 零基础入门网络,静态LACP链路捆绑详解,带宽扩容,链路冗余备份
  18. vs2019运行提示脚本错误
  19. 大一下数据结构编程实验——线性结构及其应用
  20. 外汇交易提醒:美元三连阴创逾一周新低,欧洲央行或准备更大幅度加息

热门文章

  1. 【建议星星】要就来45道Promise面试题一次爽到底(1.1w字用心整理)
  2. IMX6ULL设备上面调试SDIOwifi。
  3. C++判断一个数字是几位数
  4. 硬盘分区删除后的恢复!
  5. 微机原理和计算机组成原理一样吗_计算机组成原理(指令)
  6. HashSet和HashMap
  7. 乔戈里带你0元白嫖阿里云服务器指南
  8. pip的安装,更新,卸载模块以及使用方法 python
  9. 微x模块怎么导入主题_模块
  10. 生产实习(制作温度计)