3.3直方图处理
- 什么是直方图(histogram)
在统计学中,直方图(Histogram)是一种对数据分布情况的图形表示,是一种二维统计图表,它的两个坐标分别是统计样本和该样本对应的某个属性的度量。一般来说用横轴表示数据类型,纵轴表示分布情况。
直方图是数值数据分布的精确图形表示。 这是一个连续变量(定量变量)的概率分布的估计,并且被卡尔·皮尔逊(Karl Pearson)首先引入。它是一种条形图。 为了构建直方图,第一步是将值的范围分段,即将整个值的范围分成一系列间隔,然后计算每个间隔中有多少值。 这些值通常被指定为连续的,不重叠的变量间隔。 间隔必须相邻,并且通常是(但不是必须的)相等的大小。
直方图也可以被归一化以显示“相对”频率。 然后,它显示了属于几个类别中的每个案例的比例,其高度等于1。 - 在图像处理中,直方图是非常重要,也非常有用的一个处理要素。
一般来说图像直方图的横轴表示亮度,从左到右表示亮度从低到高。直方图的纵轴表示像素数量,从下到上表示像素从少到多。直方图在某个亮度区间的凸起越高,就表示在这个亮度区间内的像素越多。比如这个直方图的凸起就主要集中在左侧,也就是说这张照片的亮度整体偏低。 - 灰度级【0,L-1】范围的数字图像的直方图为离散函数 h(rk) = nk,其中rk是第k级灰度,nk是图像中灰度级为rk的像素个数。
归一化的直方图由 P(rk) = nk/n 得出,其中n为像素总数目;P(rk)给出了灰度级为rk发生的概率估计值 - 暗色图像 —直方图组成成分集中于灰度级低的一侧
亮色图像—直方图组成部分集中在灰度级高的一侧
低对比度图像—直方图窄而集中于灰度级中部
高对比度图像—直方图覆盖了灰度级很宽的范围,像素分布较为均匀 - 结论:若一副图像占全部可能的灰度级范围并且均匀分布,则这样的图像有高对比度和多变的灰度色调
3.3.1直方图计算并绘制
1.实现步骤
1、先计算直方图:cv::calcHist(&ImageGray, 1, channels, cv::Mat(), hist, 1, histSize, ranges);
/*images: 输入的图像或数组,它们的深度必须为CV_8U, CV_16U或CV_32F中的一类,尺寸必须相同。
. nimages: 输入数组个数,也就是第一个参数中存放了几张图像,有几个原数组。
. channels: 需要统计的通道dim,第一个数组通道从0到image[0].channels()-1,第二个数组从image[0].channels()到images[0].channels()+images[1].channels()-1,以后的数组以此类推
. mask: 可选的操作掩码。如果此掩码不为空,那么它必须为8位并且尺寸要和输入图像images[i]一致。非零掩码用于标记出统计直方图的数组元素数据。
. hist: 输出的目标直方图,一个二维数组
. dims: 需要计算直方图的维度,必须是正数且并不大于CV_MAX_DIMS(在opencv中等于32)
. histSize: 每个维度的直方图尺寸的数组
. ranges: 每个维度中bin的取值范围
. uniform: 直方图是否均匀的标识符,有默认值true
. accumulate: 累积标识符,有默认值false,若为true,直方图再分配阶段不会清零。此功能主要是允许从多个阵列中计算单个直方图或者用于再特定的时间更新直方图.
dims: 需要统计的特征的数据,上面的例子中,dims=1因为我们仅仅统计了灰度值(灰度图像)
b. bins:每个特征空间子区段的数据,上面的例子中bins=16
c. range: 每个特征空间的取值范围,在上面的例子中range=[0,255]
*/
2、再绘制画布://初始化画布参数
int hist_w = 500;
int hist_h = 500;
int nHistSize = 255; //小方块的高
//区间
int bin_w = cvRound( (double) hist_w / nHistSize ); //cvRound():返回跟参数最接近的整数值,即四舍五入;用一种舍入的方法将输入浮点数转换成整数
cv::Mat histImage( hist_w, hist_h, CV_8UC3, cv::Scalar(0,0,0)); //histimage的矩阵
3、归一化:
normalize(hist, hist, 0, histImage.rows, cv::NORM_MINMAX, -1, cv::Mat());
/src: 输入数组
dst: 输出数组,与src有相同的尺寸
.alpha : 将数组归一化范围的最大值,有默认值1
.beta : 归一化的最小值,有默认值0
.norm_type : 归一化方式,可以查看NormTypes()函数查看详细信息,有默认值NORM_L2
.dtype : 当该值取负数时,输出数组与src有相同类型,否则,与src有相同的通道并且深度为CV_MAT_DEPTH(dtype)
.mask : 可选的掩膜版/
4、绘制直方图:line
2–代码实现
```cpp
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
#include <vector>
using namespace std;
using namespace cv;
int main()
{
//图像源获取及判断
cv::Mat Image, ImageGray;
Image = imread("c:/users/征途/desktop/vs-cpp/project3/04.jpg");
if (Image.empty())
return -1;
cv::imshow("Image", Image);
//转换为灰度图像
cv::cvtColor(Image, ImageGray, CV_BayerRG2BGR);
//定义直方图参数
const int channels[1] = { 0 };
const int histSize[1] = { 256 };
float pranges[2] = { 0,255 };
const float*ranges[1] = { pranges };
cv::MatND hist;
//计算直方图
cv::calcHist(&ImageGray, 1, channels, cv::Mat(), hist, 1, histSize, ranges);
/*images: 输入的图像或数组,它们的深度必须为CV_8U, CV_16U或CV_32F中的一类,尺寸必须相同。
. nimages: 输入数组个数,也就是第一个参数中存放了几张图像,有几个原数组。
. channels: 需要统计的通道dim,第一个数组通道从0到image[0].channels()-1,第二个数组从image[0].channels()到images[0].channels()+images[1].channels()-1,以后的数组以此类推
. mask: 可选的操作掩码。如果此掩码不为空,那么它必须为8位并且尺寸要和输入图像images[i]一致。非零掩码用于标记出统计直方图的数组元素数据。
. hist: 输出的目标直方图,一个二维数组
. dims: 需要计算直方图的维度,必须是正数且并不大于CV_MAX_DIMS(在opencv中等于32)
. histSize: 每个维度的直方图尺寸的数组
. ranges: 每个维度中bin的取值范围
. uniform: 直方图是否均匀的标识符,有默认值true
. accumulate: 累积标识符,有默认值false,若为true,直方图再分配阶段不会清零。此功能主要是允许从多个阵列中计算单个直方图或者用于再特定的时间更新直方图.
dims: 需要统计的特征的数据,上面的例子中,dims=1因为我们仅仅统计了灰度值(灰度图像)
b. bins:每个特征空间子区段的数据,上面的例子中bins=16
c. range: 每个特征空间的取值范围,在上面的例子中range=[0,255]
*/
//初始化画布参数
int hist_w = 500;
int hist_h = 500;
int nHistSize = 255; //小方块的高
//区间
int bin_w = cvRound( (double) hist_w / nHistSize ); //cvRound():返回跟参数最接近的整数值,即四舍五入;用一种舍入的方法将输入浮点数转换成整数
cv::Mat histImage( hist_w, hist_h, CV_8UC3, cv::Scalar(0,0,0)); //histimage的矩阵
//将直方图归一化到[0,hisImage.rows]
normalize(hist, hist, 0, histImage.rows, cv::NORM_MINMAX, -1, cv::Mat());
/*src: 输入数组
dst: 输出数组,与src有相同的尺寸
.alpha : 将数组归一化范围的最大值,有默认值1
.beta : 归一化的最小值,有默认值0
.norm_type : 归一化方式,可以查看NormTypes()函数查看详细信息,有默认值NORM_L2
.dtype : 当该值取负数时,输出数组与src有相同类型,否则,与src有相同的通道并且深度为CV_MAT_DEPTH(dtype)
.mask : 可选的掩膜版*/
//在直方图画布上画出直方图
for (int i = 1; i < nHistSize; i++)
{
line(histImage, cv::Point(bin_w*(i - 1), hist_h - cvRound(hist.at<float>(i - 1))),
cv::Point(bin_w*(i), hist_h - cvRound(hist.at<float>(i))),
cv::Scalar(0, 0, 255), 2, 8, 0); // cv::Scalar的构造函数是cv::Scalar(v1, v2, v3, v4),前面的三个参数是依次设置BGR的,和RGB相反,第四个参数设置图片的透明度。
/*void line(Mat& img, Point pt1, Point pt2, const Scalar& color, int thickness=1, int lineType=8, int shift=0)
参数:
img: 要绘制线段的图像。
pt1: 线段的起点。
pt2: 线段的终点。
color: 线段的颜色,通过一个Scalar对象定义。
thickness: 线条的宽度。
lineType: 线段的类型。可以取值8, 4, 和CV_AA, 分别代表8邻接连接线,4邻接连接线和反锯齿连接线。默认值为8邻接。为了获得更好地效果可以选用CV_AA(采用了高斯滤波)。
shift: 坐标点小数点位数。*/
}
//显示直方图
cv::imshow("histImage", histImage);
waitKey(0);
return 0;
}
3-结果
3.3.2直方图均衡化
1. 直方图均衡化通常用来增加许多图像的局部对比度,尤其是当图像的有用数据的对比度相当接近的时候。
通过这种方法,亮度可以更好地在直方图上分布。这样就可以用于增强局部的对比度而不影响整体的对比度,直方图均衡化通过有效地扩展常用的亮度来实现这种功能。
直方图的均衡化的是将一幅图像的直方图变平,使各个灰度级的趋于均匀分布,这样能够很好的增强图像对比度。直方图均衡化是一种自动化的变换,仅需要输入图像,就能够确定图像的变换函数。但是直方图的均衡化操作也有一定的确定,在均衡化的过程中对图像中的数据不加选择,这样有可能会增强图像的背景;变换后图像的灰度级减少,有可能造成某些细节的消失;会压缩图像直方图中的高峰,造成处理后图像对比度的不自然等。
2. 直方图均衡化的目的----寻找变换函数,该函数仅仅依靠输入图像直方图的信息就可以产生均匀直方图的输出图像,即得到一副灰度级丰 富且动态范围大的图像
3. 数字图像的直方图处理
对于离散值,我们处理概率与和,而不是概率密度函数与积分‘
L-1:最大灰度值 M:图像行数 N:图像列数 nj:灰度值为j的像素的个数 Sk:均衡化后的灰度值
4.实现步骤
首先定义一个存储灰度级的容器,然后将每个灰度级对应的像素个数存入容器,然后可得到公式中的灰度级对应的像素总数
5. 代码实现
自己编写函数
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
#include <vector>
using namespace std;
using namespace cv;
int main()
{
const int grayMax = 255;
vector<vector<int>>graylevel(grayMax+1); //,vector是一个容器,vector 是向量类型,它可以容纳许多类型的数据,如若干个整数,所以称其为容器
//上面定义了256个整型元素的向量; vector<int> a(10); 定义了10个整型元素的向量,但没有给出初值,其值是不确定的
Mat image = imread("C:/Users/征途/Desktop/vs-cpp/Project1/03.jpg")
Mat img; mat支持常见的矩阵运算,cv::Mat是一个类(Class),Mat类型:矩阵类型,
Mat src;
image.copyTo(img);//作用是把image的内容粘贴到img;
if(!image.data) //,!表示非。解释起来就是如果image.data等于0
{
return -1;
}
for(int i = 0; i < image.rows-1; i++)
{
uchar*ptr = image.ptr<uchar>(i);//指针data指向图像第i行的第一个数据
for(int j = 0; j < image.cols-1; j++)
{
int x = ptr[j];
graylevel[x].push_back(0);//这个地方写的不好,引入二维数组只是为了记录每一个灰度值的像素个数
//vector中push_back函数的意思是在vector的末尾插入一个元素。
}
}
for(int i = 0; i < img.rows-1; i++)
{
uchar* imgptr=img.ptr<uchar>(i);//指针ptr指向图像第i行的第一个数据
uchar* imageptr=image.ptr<uchar>(i);
for(int j = 0; j < img.cols-1; j++)
{
int sumpiexl = 0;
for (int k = 0; k < imageptr[j]; k++)
{
sumpiexl = graylevel[k].size()+sumpiexl; //vector 的size函数返回vector大小,返回值类型为size_type
//上面一行代表了每一个灰度值为k的像素的个数之和
}
imgptr[j]=(grayMax*sumpiexl/(image.rows*image.cols));//(L-1)nj/ MN
}
}
imshow("自己实现",img);
waitKey(0);
return 0;
}
使用opencv自带在直方图均衡化函数
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
#include <vector>
using namespace std;
using namespace cv;
int main()
{
const int grayMax = 255;
vertor<vertor<int>>graylevel(grayMax+1); //,vector是一个容器,vector 是向量类型,它可以容纳许多类型的数据,如若干个整数,所以称其为容器
//上面定义了256个整型元素的向量; vector<int> a(10); 定义了10个整型元素的向量,但没有给出初值,其值是不确定的
Mat image = imread("C:/Users/征途/Desktop/vs-cpp/Project1/03.jpg")
Mat img;
Mat src;
image.copyTo(img);//作用是把image的内容粘贴到img;
equalizeHist(image,src);
imshow("原图",image);
imshow("opencv自带",src);
waitKey(0);
return 0;
}
结果
3.3.3直方图匹配(直方图规定化)
1.概念
直方图规定化可以简要的理解为已有模板图像A,按照A图像的直方图对目标图像B的直方图修改,并应用到B图像上。希望处理后的图像具有规定的直方图形状。
直方图规定化,也称为直方图匹配,经过规定化处理将原图像的直方图变换为特定形状的直方图(上面中的示例,就是将图像的直方图变换为另一幅图像的直方图)。它可以按照预先设定的它可以按照预先设定的某个形状来调整图像的直方图,运用均衡化原理的基础上,通过建立原始图像和期望图像
2.数学原理
将原始图像的灰度直方图进行均衡化,得到一个变换函数
s = T®
其中s是均衡化后的像素,r是原始像素
对规定的直方图进行均衡化,得到一个变换函数
v = G(z)
其中v是均衡化后的像素,z是规定化的像素
上面都是对同一图像的均衡化,其结果应该是相等的,
规定化操作的目的就是找到原始图像的像素sk, sk 到规定化后图像像素的zk之间的一个映射。有了上一步的等式后,可以得到sk=G(zk),因此要想找到sk想对应的zk只需要在z进行迭代,找到使式子G(zm)−sk的绝对值最小即可
对图像A和B进行均衡化操作
得到A、B的直方图,并分别计算累积直方图
得到B到A的累计直方图的映射关系,即计算|V2-V1|的最小值,从第4、6行得到第7行的结果,因此映射关系如第8行所示。
3.实现步骤
见《数字图像处理》P78
计算原图像的累积直方图
计算规定直方图的累积直方图
计算两累积直方图的差值的绝对值
根据累积直方图差值建立灰度级的映射
4.实现代码
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
#include <vector>
#include <opencv2/imgproc/imgproc.hpp>
#include<cmath>
using namespace std;
using namespace cv;
///先建立规定化的函数
// image1为模板图像,image2为待规定化处理的图像
Mat MyHistMatch(const Mat & image1, const Mat & image2) {
Mat src_image = image1.clone();
Mat dst_image = image2.clone();
// 首先把图像转化成灰度图像
cv::cvtColor(src_image, src_image, CV_BGR2GRAY);
cv::cvtColor(dst_image, dst_image, CV_BGR2GRAY);
// 先对图像进行均衡化处理
cv::equalizeHist(src_image, src_image);
cv::equalizeHist(dst_image, dst_image);
//新建目标图像同样大小和类型的图像
Mat result(dst_image.cols, dst_image.rows, CV_32FC1); //Mat特指2维矩阵
cv::MatND src_hist, dst_hist; //MatND是多维矩阵(>=3维
int dims = 1;
float hranges[] = { 0,255 };
const float *ranges[] = { hranges };
int size = 256;
int channels = 0;
// 计算模板图像和目标图像的直方图
cv::calcHist(&src_image, 1, &channels, Mat(), src_hist, dims, &size, ranges);//该函数
cv::calcHist(&dst_image, 1, &channels, Mat(), dst_hist, dims, &size, ranges);
/*calcHist(&image,// 要计算图像的
1, // 只计算一幅图像的直方图
channels, // 通道数量
Mat(), // 不使用掩码
hist, // 存放直方图
1, // 1D直方图
histSize, // 统计的灰度的个数
ranges); // 灰度值的范围
return hist;
}*/
// 得到模板图像和目标图像的累积直方图
float src_cdf[256] = { 0 };
float dst_cdf[256] = { 0 };
for (int i = 0; i < 256; i++) {
if (i == 0) {
src_cdf[i] = src_hist.at<float>(i);
dst_cdf[i] = dst_hist.at<float>(i);
}
else {
src_cdf[i] = src_cdf[i - 1] + src_hist.at<float>(i); //at<float>(i)获取某点的值
dst_cdf[i] = dst_cdf[i - 1] + dst_hist.at<float>(i); //进行累积
}
// 对目标图像进行规定化处理
// 1.计算累积概率的差值
float diff_cdf[256][256];
for (int i = 0; i < 256; i++)
{
for (int j = 0; j < 256; j++)
{
diff_cdf[i][j] = fabs(src_cdf[i] - dst_cdf[j]);//fabs求浮点数x的绝对值,头文件求浮点数x的绝对值
}
}
//2.构建灰度级映射表
Mat lut(1, 256, CV_8U);
for (int i = 0; i < 256; i++)
{
//查找源灰度级为i的映射灰度和i的累积概率差最小的规定化灰度
float min = diff_cdf[i][0]; //初始值为(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) = static_cast<uchar>(index); //static_cast < type-id > ( expression )该运算符把expression转换为type - id类型
//上面一句是什么意思????
}
// 应用查找表得到均衡化后的图像
cv::LUT(dst_image, lut, result); //该函数表示查找表
return result;
}
//主函数调用
int main1()
{
image1 = imread("C:/Users/征途/Desktop/vs-cpp/Project3/03.jpg");
image2 = imread("C:/Users/征途/Desktop/vs - cpp/Project3/03.jpg");
MyHistMatch(image1, image2);
imshow("待匹配img", image1);
imshow("匹配的模板img", image2);
waitKey(0);
return 0;
}
3.3.4局部直方图均衡化
1.概念
解决方法即使在图像中每一个像素的领域中根据灰度级分布设变换函数。
2.原理
局部直方图增强过程定义为一个矩形的领域,并把该领域的中心从一个像素移到另一个像素,在每个位置的领域中该点的直方图都要被计算,并且得到的不是直方图均衡化就是规定变换函数,这个函数最终用来映射领域中心像素的灰度。然后,领域的中心被移至一个相邻像素位置,重复该过程。当邻域进行逐像素平移时,由于只有邻域中的一行或一列改变,所以可以在移动一步中,以新数据更新前一个位置得到的直方图。
3.实现步骤步骤:
1)求第一个邻域内的直方图
2)根据直方图均衡化将该邻域中心点的像素更新
3)将中心点移向下一个邻域,比如,此时中心点为(3,3)(第一个数为行,第二个值为列)先向下移动一个像素,中心点变为(4,3),假设Size=7,则此时得到的邻域与前一个邻域相比只有一行像素不同,即(0,0)(0,1)…(0,6),与(7,0)(7,1)…(7,6)可能不同,此时比较第0行和第7行相对应的元素是否相同来更新直方图,如果直方图有变化,则更新当前中心点的像素值
4)对所有的像素点执行第三歩
4.c++实现代码
待补充。。。
3.3.5直方图统计