开头一叙: 最近把一部电视剧《天才基本法》看完了,学了一句话:“一以贯之的努力,不得懈怠的人生”,今天学直方图均衡化的时候,书上只介绍直方图均衡化后的效果,并没有讲解直方图。面向 之后,也知道为什么书上不介绍了,理解有点困难。不过我还是花费一段时间啃啃了,哈哈~
直方图均衡化
说明:从如下的图和直方图可以看出,图像的亮度主要集中在中间,两边比较淡,导致直方图中间的点集中比较多。
1、直方图均衡化的概念和特点
定义:直方图均衡化是通过拉伸像素强度分布范围来增强图像对比度的一种方法。
均衡化处理后的图像只能是近似均匀分布。均衡化图像的动态范围扩大了,但其本质是扩大了量化间隔,而量化级别反而减少了,所以原来灰度不同的像素经过处理后可能变得接近相同,形成一片相同的灰度区域,各区域之间有明显的边界,从而出现轮廓。
原理: 在原始图像对比度很高的情况下,如果再均衡化则灰度调和,对比度会降低。在泛白缓和的图像中,均衡化会合并一些像素灰度,从而增大对比度。若是对已经均衡化的图片再对其均衡化,图像不会发生太大变化。如下,经过均衡化的图像,其频谱更加舒展。
2、实现直方图均衡化:equalizeHist()函数
void equalizeHist(InputArray src,OutputArray dst)
- 第一个参数:输入图像
- 第二个参数:输出图像,和源图片有一样的尺寸和类型
采用如下步骤对输入图像进行直方图均衡化
1)计算输出图像的直方图 H
2)进行直方图归一化,直方图的组距的和为255
3)计算直方图积分:
4)以H’作为查询表进行图像变换
dst(x,y)=H′(src(x,y))
总之:如下表格例子:
说明:先把每个灰度级进行归一化处理,求每种灰度的累计分布,得到一个映射的灰度映射表,然后根据相应的灰度值来修正原图中的每个像素。
4、示例程序
#include<opencv2/opencv.hpp> #include<opencv2/imgproc/imgproc.hpp> #include<opencv2/highgui/highgui.hpp> #include<iostream> using namespace std; using namespace cv; int main() { Mat srcImage, dstImage; srcImage = imread("E:\\Pec\\cat.jpg"); //转化为灰度图 cvtColor(srcImage, srcImage, COLOR_BGR2GRAY); imshow("【原始图】", srcImage); //进行直方图均衡化 equalizeHist(srcImage, dstImage); //显示结果 imshow("【经过直方图均衡化后的图】", dstImage); waitKey(0); }
接下来难点来了
直方图的计算以及绘制
1 相关的概念以及函数:
- dims:需要统计特征的数目,灰度图–dims=1,RGB值------dims=3
- bins:每个特征空间子区域段的数目,也可称为组距(简单理解为直方图分成几个柱子)
-
- range:每个特征空间的取值范围,灰度值[0,255]
- 计算直方图函数:calcHist()
2 计算图像直方图:calcHist()函数
void calcHist(const Mat* images, int nimages, const int* channels, InputArray mask, OutputArray hist, int dims, const int* histSize, const float** ranges, bool uniform=true, bool accumulate=false)
const Mat* images:输入图像
int nimages:输入图像的个数
const int* channels:需要统计直方图的第几通道
InputArray mask:掩膜,,计算掩膜内的直方图 …Mat()统整幅图像的直方图就把它设为None,但如果想统整幅图像某一部分的直方图就制作一个掩膜图像并使用它。
OutputArray hist:输出的直方图数组(Mat类型)
int dims:需要统计直方图通道的个数
const int* histSize:指的是直方图分成多少个区间,,,就是 bin的个数
const float** ranges: 统计像素值得区间,[0,255]
bool uniform=true:是否对得到的直方图数组进行归一化处理
bool accumulate=false:在多个图像时,是否累计计算像素值得个数
下面使用OpenCV函数 calcHist 计算直方图的例子:
calcHist(&rgb_planes[0], 1, 0, Mat(), r_hist, 1, &histSize, &histRange, uniform, accumulate);
参数说明:
&rgb_planes[0]: 输入数组(或数组集)
1: 输入数组的个数 (这里我们使用了一个单通道图像,我们也可以输入数组集 )
0: 需要统计的通道 (dim)索引 ,这里我们只是统计了灰度 (且每个数组都是单通道)所以只要写 0 就行了。
Mat(): 掩码( 0 表示忽略该像素), 如果未定义,则不使用掩码
r_hist: 储存直方图的矩阵
1: 直方图维数
histSize: 每个维度的bin数目
histRange: 每个维度的取值范围
uniform 和 accumulate: bin大小相同,清楚直方图痕迹
3 一维灰度直方图
#include<opencv2/opencv.hpp> #include<opencv2/imgproc/imgproc.hpp> #include<opencv2/highgui/highgui.hpp> #include<iostream> using namespace std; using namespace cv; int main() { Mat img = imread("E:\\Pec\\car.jpg",0);//以灰度方式打开 imshow("src", img); Mat dstHist;//定义存储直方图变量 int dims = 1;//统计特征数目 float hranges[] = { 0,256 }; const float* ranges = { hranges }; int bins = 256; int channels = 0; //计算直方图 //calcHist(&img, 1, 0, Mat(), dstHist, dims, &size, ranges, true, false); calcHist(&img, 1, &channels, Mat(), dstHist, dims, &bins, &ranges, true, accumulate); //上述计算得到的结果都存入dstHist中 int scale = 1; //创建一个高和宽的图像,初始像素是0 Mat dstImage(bins * scale, bins * 3, CV_8UC3, Scalar(0)); double minValue = 0; double maxValue = 0; //统计最大值、最小值以及返回位置 minMaxLoc(dstHist, &minValue, &maxValue, 0, 0); //saturate_cast函数在OpenCV中的作用是防数据溢出,我们在直接操作像素点的时候, //如果数值结果是赋值或者超过了255的话,在图片中是没办法显示的,这就是防数据溢出的作用,那么什么时候会有数据溢出的风险呢,这种情况在图像卷积操作的时候比较常见。 int pht = saturate_cast<int>(0.9*bins);//设置最大值防止溢出 int j = 0; for (int i = 0; i < 256; i=i+2) { //src.at<uchar>(i,j); //取灰度图像中第i行第j列的点 //src.at<Vec3b>(i,j)[K] 取彩色图像中第i行第j列第K通道的点 float binValue = dstHist.at<float>(i); int realValue = saturate_cast<int>(binValue*pht / maxValue);//归一化,等变量变小了 cout << "i= " << i << "---value= " << realValue << endl; //画图的地方,起点,终点,线的颜色,线宽,线型 //line(dstImage, Point(i*scale, bins - 1), Point(i*scale, bins - realValue), Scalar(0, 255, 0), 1, 8); //花矩形的方式 rectangle(dstImage, Point(j*scale, bins - 1), Point((j + 2)*scale - 1, bins - realValue), Scalar(0, 255, 0),-1); j = j + 3; } imshow("Histogram", dstImage); waitKey(0); }
4 不均匀灰度直方图
#include<opencv2/opencv.hpp> #include<opencv2/imgproc/imgproc.hpp> #include<opencv2/highgui/highgui.hpp> #include<iostream> using namespace std; using namespace cv; int main() { Mat img = imread("E:\\Pec\\car.jpg",0);//以灰度方式打开 imshow("src", img); Mat dstHist;//定义存储直方图变量 int dims = 1;//统计特征数目 int histSize[1] = { 5 }; float hranges[6] = {0, 50,100,150,200,256 }; const float* ranges[1] = { hranges }; int bins = 256; int channels = 0; int size = 256; //计算直方图 calcHist(&img, 1, &channels, Mat(), dstHist,1, histSize, ranges, false); //上述计算得到的结果都存入dstHist中 int scale = 1; //创建一个高和宽的图像,初始像素是0 Mat dstImage(bins * scale, bins, CV_8UC3, Scalar(0)); double minValue = 0; double maxValue = 0; //统计最大值、最小值以及返回位置 minMaxLoc(dstHist, &minValue, &maxValue, 0, 0); //saturate_cast函数在OpenCV中的作用是防数据溢出,我们在直接操作像素点的时候, //如果数值结果是赋值或者超过了255的话,在图片中是没办法显示的,这就是防数据溢出的作用,那么什么时候会有数据溢出的风险呢,这种情况在图像卷积操作的时候比较常见。 int pht = saturate_cast<int>(0.9*bins);//设置最大值防止溢出 int j = 0; for (int i = 0; i < 5; i=i+1) { //src.at<uchar>(i,j); //取灰度图像中第i行第j列的点 //src.at<Vec3b>(i,j)[K] 取彩色图像中第i行第j列第K通道的点 float binValue = dstHist.at<float>(i); int realValue = saturate_cast<int>(binValue*pht / maxValue);//归一化,等变量变小了 cout << "i= " << i << "---value= " << realValue << endl; //画图的地方,起点,终点,线的颜色,线宽,线型 //line(dstImage, Point(i*scale, bins - 1), Point(i*scale, bins - realValue), Scalar(0, 255, 0), 1, 8); //花矩形的方式 rectangle(dstImage, Point(j*scale, bins - 1), Point((j + 20)*scale - 1, bins - realValue), Scalar(0, 255, 0),-1); j = j + 35; } imshow("Histogram", dstImage); waitKey(0); }
原图的直方图与均衡化的直方图对比
#include<opencv2/opencv.hpp> #include<opencv2/imgproc/imgproc.hpp> #include<opencv2/highgui/highgui.hpp> #include<iostream> using namespace std; using namespace cv; Mat equalize(Mat src) { Mat img ; src.copyTo(img); Mat dstHist;//定义存储直方图变量 int dims = 1;//统计特征数目 float hranges[] = { 0,256 }; const float* ranges = { hranges }; int bins = 256; int channels = 0; //计算直方图 calcHist(&img, 1, &channels, Mat(), dstHist, dims, &bins, &ranges, true, accumulate); //上述计算得到的结果都存入dstHist中 int scale = 1; //创建一个高和宽的图像,初始像素是0 Mat dstImage(bins * scale, bins * 3, CV_8UC3, Scalar(0)); double minValue = 0; double maxValue = 0; //统计最大值、最小值以及返回位置 minMaxLoc(dstHist, &minValue, &maxValue, 0, 0); int pht = saturate_cast<int>(0.9*bins);//设置最大值防止溢出 int j = 0; for (int i = 0; i < 256; i = i + 1) { float binValue = dstHist.at<float>(i); int realValue = saturate_cast<int>(binValue*pht / maxValue);//归一化,等变量变小了 //画图的地方,起点,终点,线的颜色,线宽,线型 line(dstImage, Point(i*scale, bins - 1), Point(i*scale, bins - realValue), Scalar(rand() % 255, rand() % 255, rand() % 255), 1, 8); j = j + 3; } return dstImage; } int main() { Mat src, dst; src = imread("E:\\Pec\\cat.jpg",0); imshow("【原始图】", src); //进行直方图均衡化 equalizeHist(src, dst); //显示结果 imshow("【经过直方图均衡化后的图】", dst); Mat dstImage1=equalize(src); imshow("【未均衡化的直方图】", dstImage1); Mat dstImage2 = equalize(dst); imshow("【均衡化的直方图】", dstImage2); waitKey(0); }