1. 直方图均衡化(Histogram Equalization)
通常情况下,直方图比较均匀的图像看上去更符合人眼的视觉效果。如果需要将图中灰度偏暗、偏亮或偏集中的直方图调整为灰度均匀分布的直方图,就需要用到直方图均衡化算法。
直方图均衡化本质上就是将图像中的灰度级数目进行均衡化的分布,即为灰度级数目不集中在部分的灰度级之上,当图像直方图完全的均匀分布后,此时的图像的熵是最大的(即每个灰度级的概率分布都是均衡的),图像的对比度也是最大的,进而增强了图像的可观察度。
直方图均衡化作为一种增强图像对比度的方法,其主要思想是将一副图像的直方图分布通过累积分布函数变成近似均匀分布,从而增强图像的对比度。理想效果如下:
实际情况处理效果如下:
从上图可以看到直方图均衡化后,直方图的分布不那么集中了,图像的对比度也提高了。
直方图均衡化实现步骤
(1)统计图像中灰度级的数目
(2)计算灰度级的累加分布概率
(3)通过变换函数,获变换后的灰度级的概率密度
(4)遍布原图,修改原图像
代码实现如下:
# include <iostream>
#include <opencv.hpp>
using namespace cv;
using namespace std;
void GetHistogram(const Mat& srcImage, int* histogram);
int main() {
Mat srcImage; //存放读取的原图
Mat dstImage; //直方均衡的图像
srcImage = imread("d://1.jpg", 0); //1RGB 0灰色
if (!srcImage.data)
cout << "图片加载失败,请检查文件是否存在!" << endl;
else
cout << "图片加载成功!" << endl;
CV_Assert(srcImage.type() == CV_8UC1); //判断图像是否为灰度图片,非灰色报错
dstImage.create(srcImage.size(), srcImage.type()); //设置目标图与原图大小和类型相同
// 计算直方图,即为每个灰度级在图像中的数目
int histogram[256]; //存放为每一个灰度级在图像中的数目
GetHistogram(srcImage, histogram);
//计算累积分布函数,变换函数
int pixelSrcImage = srcImage.cols * srcImage.rows;
int Lut[256]; //分布函数
Lut[0] = (1.0 * histogram[0] / pixelSrcImage) * 255;
int sum = histogram[0];
for (int i = 1; i < 256; ++i) {
sum += histogram[i];
Lut[i] = (1.0 * sum / pixelSrcImage) * 255;
}
//灰度变换
uchar* srcImageDate = srcImage.data;
uchar* dstImageDate = dstImage.data;
for (int i = 0; i < pixelSrcImage; ++i) {
dstImageDate[i] = Lut[srcImageDate[i]];
}
imshow("原始图像", srcImage);
imshow("直方图均衡后图像", dstImage);
//保持等待状态
waitKey(0);//括号里可以填任意正整数,意味着,图像显示的毫秒时间
return 0;
}
void GetHistogram(const Mat& srcImage, int* histogram) {
memset(histogram, 0, 256 * sizeof(int)); //设置histogram数组初始全为0
//建立直方图, 统计图像中每一灰度级数
int pixelCount = srcImage.cols * srcImage.rows;
uchar *srcImageDate = srcImage.data;
for (int i = 0; i < pixelCount; ++i) {
int grayDate = srcImageDate[i];
histogram[grayDate]++;
}
}
HE是对图像全局进行调整的方法,不能有效地提高局部对比度,对于某些图像而言,使用直方图均衡化后也无法显示其中的某些细节,进而引出了(自适应)局部直方图均衡化。
2. 自适应直方图均衡
其原理和直方图均衡化一样,只是在映射时选取了图像局部区域的累计分布函数。另外,考虑到如果有噪声的话,噪声会被放大。为了避免这种情况的出现要使用对比度限制。对于每个小块来说,如果直方图中的 bin超过对比度的上限的话,就把其中的像素点均匀分散到其他bins 中,然后在进行直方图均衡化。
对比度受限的自适应直方图均衡(CLAHE,Contrast Limited Adaptive Histogram Equalization)算法,尽管最初它仅仅是被当作一种图像增强算法被提出,但是现今在图像去雾、低照度图像增强,水下图像效果调节、以及数码照片改善等方面都有应用。
在OpenCV中通过createCLAHE函数进行实现,其中C++调用的代码如下(注意:输入图像应该为8bit图像):
Mat adaptiveEqualizeHist(Mat src)
{
src.convertTo(src, CV_8UC1);
Mat temp, clahe_img;
temp = src.clone();
cv::Ptr<cv::CLAHE> clahe = createCLAHE();
clahe->setClipLimit(4.); // (int)(4.*(8*8)/256)
clahe->setTilesGridSize(Size(8, 8)); // 将图像分为8*8块
clahe->apply(temp, clahe_img);
temp.release();
clahe_img.copyTo(temp);
return temp;
}
python调用如下:
import cv2 as cv
import matplotlib.pyplot as plt
import numpy as np
src = cv.imread("pic.png", 0) # 直接以灰度图方式读入
img = src.copy()
# 创建一个自适应均衡化的对象,并应用于图像
clahe = cv.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8))
dst = clahe.apply(img)
# 显示图像
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(10, 8), dpi=100)
axes[0].imshow(img, cmap=plt.cm.gray)
axes[0].set_title("原图")
axes[1].imshow(dst, cmap=plt.cm.gray)
axes[1].set_title("自适应均衡化后的图像")
plt.show()
3. 直方图匹配
通过上述介绍,我们已经知道了直方图均衡化实质上就是通过累计分布函数映射的一个变换函数,通过查找表LUT即可实现。
直方图均衡化是在整个灰度阶范围内对图像进行拉升;但是有的时候,这种整个范围内的拉升也许并不是最好的,我们可能会需要其按照某个灰度分布去进行拉升,也就需要用到直方图匹配(规定化)。
直方图匹配也叫做直方图规定化,是将某个图像的灰度分布按照某个特定的模式去变换的操作。
其原理是在直方图均衡化的基础上去实现的。其实现步骤如下:
代码实现步骤
- 计算源图像的累计直方图;
- 计算规定图像的累计直方图;
- 计算源图像累计直方图各个灰度阶到规定图像的累计直方图各个灰度阶的差的绝对值;
- 求出步骤3中各阶中绝对值对应的最小值,最小值对应的灰度阶即为映射后的值。
#include<iostream>
#include<opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat image1, image1_gray, hist1, image2, image2_gray, hist2, image_enhanced; //定义修改图像,灰度修改图像, 修改直方图,目标图像,灰度目标图像,目标直方图, 规定化增强图像
image1 = imread("lena.png"); //读取图像;
if (image1.empty())
{
cout << "读取错误" << endl;
return -1;
}
cvtColor(image1, image1_gray, COLOR_BGR2GRAY); //灰度化
imshow(" image1_gray", image1_gray); //显示灰度图像
image2 = imread("1043.jpg"); //读取图像;
if (image2.empty())
{
cout << "读取错误" << endl;
return -1;
}
cvtColor(image2, image2_gray, COLOR_BGR2GRAY); //灰度化
imshow(" image2_gray", image2_gray); //显示灰度图像
//均衡化处理
equalizeHist(image1_gray, image1_gray);
equalizeHist(image2_gray, image2_gray);
//获取两个均衡化图像的直方图
int histsize = 256;
float ranges[] = { 0,256 };
const float* histRanges = { ranges };
calcHist(&image1_gray, 1, 0, Mat(), hist1, 1, &histsize, &histRanges, true, false);
calcHist(&image2_gray, 1, 0, Mat(), hist2, 1, &histsize, &histRanges, true, false);
//计算两个均衡化图像直方图的累积概率
float hist1_cdf[256] = { hist1.at<float>(0) };
float hist2_cdf[256] = { hist2.at<float>(0) };
for (int i = 1; i < 256; i++)
{
hist1_cdf[i] = (hist1_cdf[i - 1] + hist1.at<float>(i));
hist2_cdf[i] = (hist2_cdf[i - 1] + hist2.at<float>(i));
}
for (int i = 0; i < 256; i++)
{
hist1_cdf[i] = hist1_cdf[i] / (image1_gray.rows * image1_gray.cols);
hist2_cdf[i] = hist2_cdf[i] / (image2_gray.rows * image2_gray.cols);
}
// 两个累计概率之间的差值,用于找到最接近的点
float diff_cdf[256][256];
for (int i = 0; i < 256; i++) {
for (int j = 0; j < 256; j++)
{
diff_cdf[i][j] = fabs(hist1_cdf[i] - hist2_cdf[j]);
}
}
Mat lut(1, 256, CV_8U);
for (int i = 0; i < 256; i++)
{
//查找源灰度级为i的映射灰度和i的累积概率差最小(灰度接近)的规定化灰度
float min = diff_cdf[i][0];
int index = 0;
for (int j = 0; j < 256; j++) {
if (min > diff_cdf[i][j]) {
min = diff_cdf[i][j];
index = j;
}
}
lut.at<uchar>(i) = index;
}
LUT(image1_gray, lut, image_enhanced); //图像中进行映射
imshow("image_enhanced", image_enhanced);
waitKey(0); //暂停,保持图像显示,等待按键结束
return 0;
}
参考文献
[1] 直方图均衡化的原理及实现
[2] 图像的空间域增强——直方图均衡化算法的原理和实现
[3] https://www.cnblogs.com/yanshw/p/15429887.html
[4] https://blog.csdn.net/mmmmmk_/article/details/82927411
[5] https://zhuanlan.zhihu.com/p/618170011