3.1 灰度直方图
灰度直方图描述了一幅图像的灰度级统计信息,主要应用于图像分割和图像灰度变换等处理过程中。
3.1.1 理论基础
从数学上来说,图像直方图描述的是图像的各个灰度级的统计特性,它是图像灰度值的函数,统计一幅图像中各个灰度级出现的次数或概率。有一种特殊的直方图叫作归一化直方图,可以直接反映不同灰度级出现的比率。
从图形上来说,灰度直方图是一个二维图,横坐标为图像中各个像素点的灰度级别,纵坐标表示具有各个灰度级别的像素在图像中出现的次数或概率。
提示
如不特别说明,本书中的直方图的纵坐标都对应着该灰度级别在图像中出现的次数,而归一化直方图的纵坐标则对应着该灰度级别在图像中出现的概率。
灰度直方图的计算是根据其统计定义进行的。图像的灰度直方图是一个离散函数,它表示图像每一灰度级与该灰度级出现频率的对应关系。假设一幅图像的像素总数为N,灰度级总数为L,其中灰度级为g的像素总数为Ng,则这幅数字图像的灰度直方图横坐标即为灰度g(0≤g≤L-1),纵坐标则为灰度值出现的次数Ng。实际上,用像素总数N去除以各个灰度值出现的次数Ng即得到各个灰度级出现的概率Pg=Ng/N=Ng/ΣNg,从而得到归一化的灰度直方图,其纵坐标为概率Pg。
3.1.2 MATLAB实现
MATLAB中的imhist函数可以进行图像的灰度直方图运算,调用语法如下。
imhist(I) imhist(I, n) [counts, x] = imhist(...)
参数说明:
• I为需要计算灰度直方图的图像;
• n为指定的灰度级数目,如果指定参数n,则会将所有的灰度级均匀分布在n个小区间内,而不是将所有的灰度级全部分开。
返回值:
• counts为直方图数据向量,counts(i)表示第i个灰度区间中的像素数目;
• x是保存了对应的灰度小区间的向量。
若调用时不接收这个函数的返回值,则直接显示直方图;在得到这些返回数据之后,也可以使用stem(x, counts)来手工绘制直方图。
1.一般直方图
下面使用了MATLAB中的一张内置示例图片演示灰度直方图的生成与显示,程序如下。
I = imread('pout.tif'); % 读取图像pout.tif figure; % 打开一个新窗口
图3.1 灰度直方图的生成
图3.1(b)中未经归一化的灰度直方图的纵轴表示图像中所有像素取到某一特定灰度值的次数;横轴对应从0到255的所有灰度值,覆盖了uint8存储格式的灰度图像中的所有可能取值。
因为相近的灰度值所具有的含义往往是相似的,所以常常没有必要在每个灰度级上都进行统计。下面的命令将0~255总共256个灰度级平均划分为64个长度为4的灰度区间,此时纵轴分别统计每个灰度区间中的像素在图像中的出现次数。其编程代码如下。
imshow(I); title('Source'); % 显示原图像 figure; % 打开一个新窗口 imhist(I); title('Graph'); % 显示直方图
上述程序的运行结果如图3.1所示。
imhist(I, 64); % 生成有64个小区间的灰度直方图
上述命令执行后效果如图3.2所示。
图3.2 分为64段小区间的灰度直方图
由于要统计落入每个灰度区间内的像素数目,灰度区间常常被形象地称为“收集箱”。在图3.2所示的直方图中,由于减少了收集箱的数目,使得落入每个收集箱的像素数目有所增加,从而使直方图更具统计特性。收集箱的数目一般设为2的整数次幂,以保证可以无需圆整。
2.归一化直方图
在imhist函数的返回值中,counts保存了落入每个区间的像素的个数,通过计算counts与图像中像素总数的商可以得到归一化的直方图。
绘制有32个灰度区间的归一化直方图的MATLAB程序如下。
I = imread('pout.tif'); % 读入原图像 figure; % 打开新窗口 [M, N] = size(I); % 计算图像大小 [counts, x] = imhist(I, 32); % 计算有32个小区间的灰度直方图 counts = counts / M / N; % 计算归一化灰度直方图各区间的值 stem(x, counts); % 绘制归一化直方图
上述程序的运行结果如图3.3所示。
图3.3 归一化直方图
分析图像的灰度直方图往往可以得到很多有效的信息。例如,从图3.4的一系列灰度直方图上,可以很直观地看出图像的亮度和对比度特征。实际上,直方图的峰值位置说明了图像总体上的亮暗:如果图像较亮,则直方图的峰值出现在直方图的较右部分;如果图像较暗,则直方图的峰值出现在直方图的较左部分,从而造成暗部细节难以分辨。如果直方图中只有中间某一小段非零值,则这张图像的对比度较低;反之,如果直方图的非零值分布很宽而且比较均匀,则图像的对比度较高。
图3.4 图像的灰度直方图与其亮度、对比度的关系
上面列举的情况,均可以通过以直方图为依据的图像增强方法进行处理,这些方法的具体介绍详见3.7节。
3.1.3 Visual C++实现
可以利用循环遍历图像中的每一个像素,并按灰度值分类,以此为依据累加灰度值计数器。为了得到归一化的直方图,在最后将直方图各个级别上的数值除以图像的总面积即可。
GenHist()方法的实现代码如下。
/************************************************** BOOL CImgProcess::GenHist(double * pdHist, int n) 功能: 生成图像的灰度直方图 参数: double * pdHist:输出的灰度直方图数组 BYTE n:灰度直方图的灰度级数(段数) 返回值: BOOL类型,true为成功,false为失败 ***************************************************/ BOOL CImgProcess::GenHist(double * pdHist, int n) { // 首先检查图像的类型 if (m_pBMIH->biBitCount! =8) return false; // 检查n范围 if ((n<=0)||(n>256)) return false; // 计算分段因子 double dDivider; memset(pdHist, 0, n * sizeof(double)); dDivider = 256.0 / (double)n; BYTE bGray; // 临时变量,存储当前光标像素的灰度值 for (int i=0; i<m_pBMIH->biHeight; i++) { for (int j=0; j<m_pBMIH->biWidth; j++) { bGray = GetGray(j, i); pdHist[(int)(bGray / dDivider)]++; // 指定的灰度区间自加 } } UINT square = m_pBMIH->biWidth * m_pBMIH->biHeight; for (int k=0; k<n; k++) { pdHist[k]=pdHist[k]/square; } return true; }
利用GenHist()函数生成灰度直方图的完整示例被封装在DIPDemo工程的视图类函数void CDIPDemoView::OnViewIntensity()中,由于这是本书中的第一个利用我们的程序包进行编程的例子,所以下面给出了OnViewIntensity()的完整实现。
void CDIPDemoView::OnViewIntensity() { // 查看当前图像灰度直方图 // 获取文档 CDIPDemoDoc* pDoc = GetDocument(); // 输入对象 CImgProcess imgInput = pDoc->m_Image; // 直方图数组 double hist[256]; // 设置忙状态 BeginWaitCursor(); // 求取直方图数组 imgInput.GenHist(hist); CDlgHist dlg; dlg.m_pdHist = hist; if (dlg.DoModal() ! = IDOK) { // 返回 return; } // 更新视图 pDoc->UpdateAllViews(NULL); // 恢复光标 EndWaitCursor(); }
读者可以通过光盘示例程序DIPDemo中的菜单命令“点运算→灰度直方图”来观察处理效果,如3.5所示。
图3.5 灰度直方图变换效果