# 图像处理——直方图均衡   直方图均衡是一种将图像中的灰度分布转换成均匀分布,从而增强图像的对比度。直方图均衡可以将原本偏白或者偏黑的图像转换成对比度符合人眼视觉的图像。 ## 1 原理 **连续空间**   连续空间内的图像灰度$r\in[0,L-1],L表示灰度级$,期望的均衡化转换函数为 $$ s=T(r),0\le r\le L-1 $$   其中$T$为灰度变换函数,$s$是经过变换后的灰度值,该变化函数满足两个条件: - $T(r)$在区间$[0, L-1]$内是严格单调递增函数。保证转换是一一对应的避免出现二义性; - 当$0 \le r\le L - 1$时,$0 \le s\le L - 1$。保证输入输出的灰度范围相同。   我们期望的是将输入的灰度概率分布$p(r)$转换成输出的灰度概率分布$p(s)=\frac{1}{L - 1}$。这里选择下面的变换函数来证明这个函数是可行的: $$ s=T(r)=(L-1)\int_{0}^{r}p_r(w)dw,w为积分的假变量 $$   首先,$\int_{0}^{r}p_r(w)dw$的值域为$[0,1]$因此$s$的值域为$[0,L-1]$;另外该函数也是严格单调递增的。基本条件满足。下面我们证明$p(s)=\frac{1}{L-1}$。因为 $$ \begin{equation} \begin{aligned} p(s)&=p_r(r)|\frac{dr}{ds}|\\ &=p_r(r)\frac{1}{\frac{ds}{dr}}\\ &=p_r(r)\frac{1}{(L-1)\frac{d}{dr}[\int_{0}^{r}p_r(w)dw]}\\ &=p_r(r)\frac{1}{(L-1)p_r(r)}\\ &=\frac{1}{L-1} \end{aligned} \end{equation} $$   因此$s=T(r)=(L-1)\int_{0}^{r}p_r(w)dw$可以作为均衡化变换函数。 **离散空间**   而对于图像处理始终是离散空间,离散空间的灰度$r_k,k为灰度级,k\in [0,L-1]$的概率近似为 $$ p_r(r_k)=\frac{n_k}{MN} $$   其中$k为灰度级,k\in [0,L-1]$;$M$和$N$分别为图像的宽高,$n_k$为当前灰度级像素点的数量。则利用上面的变换函数得到的$s_k$为 $$ s_k=T(r_k)=(L-1)\sum_{j=0}^{k}p_r(r_j)=\frac{L-1}{MN}\sum_{j=0}^{k}{n_j} $$   另外利用上面的变化函数计算出来的灰度值可能是小数,因此需要最近取整。以及对于RGB图像分别对R、G、B三个通道的图像进行变换的话会出现色相问题,因此需要将图像转换成其他颜色空间比如YUV,只对Y做灰度变换。 ## 2 实现   实现比较简单,首先逐像素计算每个灰度级的数量,根据改数量计算出每个灰度级的概率,最后利用上面的公式计算出最终的颜色映射关系,最终将根据映射关系将原图中颜色进行映射。 ```cpp static vector countGrayProp(const Mat &img) { assert(img.channels() == 1); double pixelProp = 1.0 / (img.rows * img.cols); vector ret(GRAPH_GRAY_LAYER_NUM, 0.0); for (int i = 0; i < img.rows; i++) { for (int j = 0; j < img.cols; j++) { ret[static_cast(img.at(i, j))] += pixelProp; } } return ret; } /* * @brief 均衡化灰度图 */ static Mat avgGrayHistogram(const Mat &img) { assert(img.channels() == 1); vector props = countGrayProp(img); vector propSum(GRAPH_GRAY_LAYER_NUM, 0.0); for (int i = 0; i < GRAPH_GRAY_LAYER_NUM;i ++) { if (i == 0) { propSum[i] = props[i]; } else { propSum[i] = (props[i] + propSum[i - 1]); } } Mat ret(img.rows, img.cols, CV_8UC1); for (int i = 0; i < img.rows; i++) { for (int j = 0; j < img.cols; j++) { int value = static_cast(img.at(i, j)); ret.at(i, j) = int((GRAPH_GRAY_LAYER_NUM - 1) * propSum[value]); } } return ret; } Mat avgHistogram(const Mat &img) { if (img.channels() == 1) { return avgGrayHistogram(img); } else if (img.channels() == 3) { Mat yuvImg; cvtColor(img, yuvImg, COLOR_BGR2YUV); std::vector yuvImgs; split(yuvImg, yuvImgs); yuvImgs[0] = avgGrayHistogram(yuvImgs[0]); Mat proYUV, ret; merge(yuvImgs, proYUV); cv::cvtColor(proYUV, ret, COLOR_YUV2BGR); return ret; } return img; } ```   对于单通道的灰度图效果如下,能够看到均衡化后的灰度直方图中灰度更加均匀: ![](https://cdn.jsdelivr.net/gh/grayondream/MyImageBlob@main/imgs/leno_black_avg.jpg) ![](https://cdn.jsdelivr.net/gh/grayondream/MyImageBlob@main/imgs/leno_white_avg.jpg)   如果直接对rgb三个通道做均衡化会出现下面的情况 ![](https://cdn.jsdelivr.net/gh/grayondream/MyImageBlob@main/imgs/leno_avg_rgb.jpg)   首先将图像转换到其他颜色空间比如YUV,只对Y分量进行均衡化就不会有明显的的色差和奇异点。 ![](https://cdn.jsdelivr.net/gh/grayondream/MyImageBlob@main/imgs/leno_rgb_avg.jpg) ## 3 优缺点 - 优点:易于实现; - 缺点: - 是将原有的灰度级在全局内进行了平均,效果不易控制,且并不是总是那么有效; - 增大了灰度级之间的间隔,导致细节丢失; - 对于直方图有明显高峰的图像,容易导致对比度过高的问题; - 自动生成,无法控制生成的灰度图的直方图。