直方图(Histogram),又称质量分布图,是一种统计报告图,由一系列高度不等的纵向条纹或线段表示数据分布的情况。 一般用横轴表示数据类型,纵轴表示分布情况。
直方图是图像处理中的一个有着广泛应用的工具,直方图本质是概率分布的图形化,同时直方图也可以用来表示向量。
下面以一幅分辨率为8*8、8级灰度的图像来介绍如何建立一幅直方图
以Pi(i = 0、1、2、3…7)表示八级灰度每一级的概率,Ni(i = 0、1、2、3…7)表示88共64个像素点中各级灰度出现的次数,则Pi = Ni/(88),即灰度出现的次数对像素点总数的归一化。
由此绘制出的Pi-灰度的关系图就是灰度直方图。横坐标为灰度值,纵坐标为该灰度值出现的概率。
当然一般是以8位表示一个像素点,灰度一般有256级。
从这张直方图上可以很直观地看到,它的灰度值以0、1、2为主,显然是一张偏暗的图片。
对于图像,不光可以建立灰度直方图,也可以自己设计方法建立任意形式的直方图。如可以对RGB图像三个颜色通道分别建立直方图,得到彩色直方图。也可以针对图像的HSV色彩模型,建立HSC直方图。
两个直方图的比较可以使用欧几里德距离,马氏距离等。Opencv中的compareHist函数提供了四种方法:Correlation、Chi-Square,Intersection,Bhattacharyya距离。
可以想象,如果两个直方图的欧几里德距离为零的话,那么就应该是两个完全一样的直方图。
比较两幅图像的直方图,可以得到两幅图像的相似程度。其本质是对比灰度出现的概率是否相似。
在图像匹配的时候,也可以对其他的特征量建立直方图,来进行匹配。
同样的图像直方图显然相同。反之却不一定成立,这是因为直方图只能记录各级灰度出现的概率,却丢失了空间信息。
如下图就是一个明显的反例:
那么为何要对灰度进行归一化呢,假设我们有一张原图和一张对原图进行放大缩小后的图片,两种图片实际上是同一张图片,只是大小不同,它们的直方图理应是相同的。但是如果不仅归一化,仅仅是以各级灰度出现的次数作为纵坐标,由于放大缩小后的图像与原图像素点存在差异,它呈现出来的纵坐标也是不同的,这就导致了两张相同的图片直方图不同,因此要进行归一化。
在图像二值化的过程中,我们可以通过分析直方图选择一个适当的阈值,把灰度图像转换为二值化图像,通常的目的是分离前景和背景
以一张256级灰度的细胞的图片为例,下面是它的直方图
可以明显地看到,它的直方图呈现双峰性,它的前景分布集中在一个区域,背景分布集中在另一个区域,这时候我们只要选择直方图中的谷值,就能得到较为理想的二值化图像。
通过Opencv编程实现读取一张图片计算并显示RGB三通道直方图。
这里用的Opencv版本是4.40。
当然这里可以用Opencv自带的函数calcHist()函数计算直方图,自己写一个算直方图的函数只是为了能够加深对直方图和OpencvMat类的理解。
#include
#include
using namespace cv;
#define hist_rgb_width 800
#define hist_rgb_height 400
void Calc_histogram(cv::Mat src)
{
/*定义直方图矩阵
*这里以一个256*3的矩阵存放各个通道各个灰度值的个数
*以数组下标标记灰度值,数组内容为该灰度值的个数
*/
float histogram[256][3] = { 0 };
//定义vector作为三个通道的容器
std::vector<cv::Mat> channels;
//分离三个通道像素值
cv::split(src, channels);
cv::Mat B = channels.at(0);
cv::Mat G = channels.at(1);
cv::Mat R = channels.at(2);
//*****遍历*****
//获取向量长度
int height = src.rows;
int width = src.cols;
long int size = height * width;
for (int j = 0; j < height; ++j)
{
for (int i = 0; i < width; ++i)
{
++histogram[B.at<uchar>(j, i)][0];
++histogram[G.at<uchar>(j, i)][1];
++histogram[R.at<uchar>(j, i)][2];
}
}
//*****归一化*****
/* 由于直方图归一化以后的纵坐标小于1,需要放大以后显示才直观
* 而放大多少倍才能使现有的画布容纳所有放大后的线段
* 这里用三个变量记录归一化后最大的值,将最大值放大为画布高度400
* 最大值能容纳,其他的值也自然能容纳了
*/
float BMax = 0;
float GMax = 0;
float RMax = 0;
for (int i = 0; i < 256; ++i)
{
histogram[i][0] = histogram[i][0] / size;
histogram[i][1] = histogram[i][1] / size;
histogram[i][2] = histogram[i][2] / size;
if (histogram[i][0] > BMax)
BMax = histogram[i][0];
if (histogram[i][1] > GMax)
GMax = histogram[i][1];
if (histogram[i][2] > RMax)
RMax = histogram[i][2];
}
cv::Mat dispMat(hist_rgb_height, hist_rgb_width, CV_8UC3, Scalar(0, 0, 0));
cv::Point pt1, pt2;
pt1.y = 400;
pt1.x = 0;
pt2.x = 0;
int Coeficient = 0;
//绘制R通道直方图
Coeficient = (int)(400 / RMax);
for (int i = 0; i < 256; ++i)
{
pt2.y = pt1.y - histogram[i][2] * Coeficient;
cv::line(dispMat, pt1, pt2, Scalar(0, 0, 255), 1, 8, 0);
pt2.x = ++pt1.x;
}
//绘制G通道直方图
Coeficient = (int)(400 / GMax);
for (int i = 0; i < 256; ++i)
{
pt2.y = pt1.y - histogram[i][1] * Coeficient;
cv::line(dispMat, pt1, pt2, Scalar(0, 255, 0), 1, 8, 0);
pt2.x = ++pt1.x;
}
//绘制B通道直方图
Coeficient = (int)(400 / BMax);
for (int i = 0; i < 256; ++i)
{
pt2.y = pt1.y - histogram[i][0] * Coeficient;
cv::line(dispMat, pt1, pt2, Scalar(255, 0, 0), 1, 8, 0);
pt2.x = ++pt1.x;
}
imshow("Histogram", dispMat);
}
int main()
{
/*读入要计算直方图的图片
*注意这里要用“\\”
*/
cv::Mat src = imread("C:\\Users\\STAR ZHANG\\Pictures\\3.jpg");
//调用函数
Calc_histogram(src);
/*WaitKey(n)是Opencv自带的函数
*其中n表示等待按键n毫秒后,关闭显示的窗口
*n为0或者WaitKey()(形参缺省)表示一直等待按键
*/
waitKey(0);
return 0;
}