目录
1.1 灰度直方图
1.1.1 什么是灰度直方图
1.1.2 灰度直方图的Python实现
1.2 线性变换
1.2.1 线性变换原理
1.2.2 线性变换的Python实现
1.3 直方图正规化
1.3.1 原理详解
1.3.2 Python实现
1.3.3 正规化函数normalize
参考文献
灰度直方图是图像灰度级的函数,用来描述每个灰度级在图像矩阵中的个数或者占有率。
e.g.若有图像矩阵:
图像矩阵中的数字代表每一个像素点的灰度值,我们对每一个灰度值计数,然后将每个数值按照直方图的可视化方式表示。用占有率(或称归一化直方图、概率直方图)表示就是灰度值 0 在 I 中 的占有率为1/16.
做出灰度直方图如下:
原图:
代码如下:
import sys
import numpy as np
import cv2
import matplotlib.pyplot as plt
"""
方法一:使用imread直接将原图读取为灰度图(flags=0)
image=cv2.imread("pic2.jpg",0)
img2=image
"""
"""
方法二:使用转换函数将BGR图转换为灰度图
"""
img=cv2.imread("pic2.jpg")
img2=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
cv2.imshow("gray",img2)
cv2.waitKey(0)
plt.hist(img2.ravel(),256)
plt.show()
cv2.waitKey(0)
实现结果:
灰度图:
灰度直方图:
注:这里使用plt.hist函数绘制直方图时,使用img2.ravel()对二维图像矩阵进行降维。
在中学中我们学习过一次函数:y=k*x+b,这个过程可以看成是对x的线性变换过程。那么同样的,假设输入图像为,宽为,高为,输出图像记为,图像的线性变换可以利用以下公式定义:
我们可以通过调整参数a、b的大小来实现对图像灰度级范围的变换,从而调整图像的对比度。从处理图像的效果上可以更直观地理解线性变换的作用。下面介绍线性变换的Python实现方法。
图像矩阵的线性变换,无非就是一个常数乘以一个矩阵,在Numpy中通过乘法运算符" * "可以实现。
在实现代码中需要注意这个常数的数据类型会影响输出矩阵的数据类型。对8位图进行线性变换时,计算出的输出值可能会大于255,最后结果会被截断为255,图像效果就是该像素点变成白色。
所以对图像进行线性变换的时候,不能简单使用*运算。需要对超出255的部分进行取余。具体代码如下:
import cv2
import numpy as np
import sys
#主函数
I = cv2.imread("pic2.jpg")
#线性变换
a = 2
O = float(a)*I
#进行数据截断,大于 255 的值要截断为 255,注意截断的写法
O[O>255] = 255
#数据类型转换
O = np.round(O)
O = O.astype(np.uint8)
#显示原图和线性变换后的效果
cv2.imshow("I",I)
cv2.imshow("O",O)
cv2.waitKey(0)
cv2.destroyAllWindows()
上述代码中的O[O>255]=255的作用是将数组O中大于255的值均设置为255,成员函数astype的作用是改变ndarray的数据类型。注意,这里实现的是彩色图的线性变换并且是直接对像素值进行变换(强度变换),这样做的好处是计算两个比较小,也可以使用cv2.split()将像素分裂成三通道分别进行线性变换,再使用cv2.merge()将三通道合并。如果需要对灰度图进行线性变换仅需要先将彩色图转为灰度图。
变换结果如图:
原图 变换后图像
显然经过线性对比度增强后,图像更加清晰了。
以上线性变换是对整个灰度级范围使用了相同的参数,有时也需要针对不同的灰度级范围进行不同的线性变换,这就是常用的分段线性变换,经常用于降低较亮或较暗区域的对比度来增加灰度级处于中间范围的对比度 ;或者压低中间灰度级处的对比度来增强较亮或较暗区域的对比度。
分段线性变换主要用于图像灰度值较为集中的图像,若图像灰度级集中在[100,150]之间,可以通过以下线性变换:
下面总结了a、b参数的设置对图像的影响:
1. a = 1 , b = 0 无变化
2. a > 1 对比度增加
3. a < 1 对比度减小
4. b > 0 亮度增加
5. b < 0 亮度减小
线性变换的参数需要根据不同的应用及图像自身的信息进行合理的选择,可能需要进行多次测试,所以选择合适的参数是相当麻烦的。我们希望有一种基于当前图像情况自动选择 a 和 b 的值的办法,下面介绍一种显而易见却很有效的方法,通常称为直方图正规化。
假设输入图像为,高为,宽为,代表的第r行第c列的灰度值,将中出现的最小灰度级记为,最大灰度级记为即,为使输出图像的灰度级范围为,和做以下映射关系:
其中,代表的第r行第c列的灰度值。这个过程就是常称的直方图正规化。因为,所以,一般令。显然,直方图正规化是一种自动选取a和b的线性变换方法,其中,
在直方图正规化中需要计算出原图中出现的最大灰度级和最小灰度级,Numpy提供的函数max和min可以计算出ndarray中的最大值和最小值,其他步骤与线性变换是类似的。具体代码如下:
import cv2
import numpy as np
import sys
#主函数
I=cv2.imread("pic2.jpg",0)
#求I的最大值、最小值
Imax=np.max(I)
Imin=np.min(I)
#要输出的最小灰度级和最大灰度级
Omin,Omax=0,255
#计算a和b的值
a=float(Omax-Omin)/(Imax-Imin)
b=Omin-a*Imin
#矩阵的线性变换
O=a*I+b
#数据类型转换
O=O.astype(np.uint8)
#显示原图和直方图正规化的效果
cv2.imshow("I",I)
cv2.imshow("O",O)
cv2.waitKey(0)
cv2.destroyAllWindows()
原图 变换后
原图 变换后
OpenCV提供的函数:
void normalize(InputArray src, OutputArray dst, double alpha=1, double beta=0,
int norm_type=NORM_x, int dtype=-1, InputArray mask=noArray())
实现了多种正规化操作,其参数解释如表所示:
参数 | 解释 | ||
src | 输入图像矩阵,可以是彩色或二值图像 | ||
dst | 输出图像矩阵 | ||
alpha | 归一化后矩阵的范数,缺省为1 | ||
beta | 归一化后的最大值(只在NORM_MINMAX类型为有效参数),缺省为0 | ||
norm_type | 归一化的类型 | NORM_L1 | 按L1范数计算公式计算 |
NORM_L2 | 按L2范数计算公式计算 | ||
NORM_INF | 按INF范数计算公式计算 | ||
NORM_MINMAX | 按MINMAX范数计算公式计算 | ||
dtype | 输出图像的数据类型,缺省为-1,表示dst类型和src一致 | ||
mask | 可选的掩码,用于指定要归一化的像素区域,缺省为noArray(),表示对整个图像归一化 |
接下来说明alpha和beta参数的选择对最后图像呈现效果的影响:
alpha参数的选择取决于你的目的和需求。一般来说,alpha越小,表示归一化后的向量或矩阵的大小越小,alpha越大,表示归一化后的向量或矩阵的大小越大。
alpha的选择也会影响归一化后的向量或矩阵的分布和方差。一般来说,alpha越小,表示归一化后的向量或矩阵的分布越集中,方差越小,alpha越大,表示归一化后的向量或矩阵的分布越分散,方差越大。
alpha的选择还会影响归一化后的向量或矩阵的稳定性和鲁棒性。一般来说,alpha越小,表示归一化后的向量或矩阵对原始数据的变化更敏感,稳定性和鲁棒性越差,alpha越大,表示归一化后的向量或矩阵对原始数据的变化更不敏感,稳定性和鲁棒性越好。
所以,alpha的选择要根据你的具体情况和目标来决定,没有一个固定的答案。以上介绍的都是灰度值的线性变换,从1.3.2可以看到直方图正规化在处理灰度级比较集中的图像时效果不显著,同时线性变换受限于以256为周期的像素限制(超过255的需要截断,也就是说变换后像素值为260和像素值为516的像素效果一致),这些都是线性变换的缺陷。
下一章将介绍常用的增加图像对比度的非线性变换。
[1] 张平. OpenCV算法精解:基于Python和C++[M]. 北京: 电子工业出版社, 2017.