完成Python计算机视觉 图像处理基础:直方图、直方图均衡化、高斯滤波的基础操作。
灰度图,Gray Scale Image 或是Grey Scale Image,又称灰阶图。把白色与黑色之间按对数关系分为若干等级,称为灰度。灰度分为256阶。
任何颜色都有红、绿、蓝三原色组成,即三通道,而灰度图只有一个通道,他有256个灰度等级,255代表全白,0表示全黑。
灰度图像是指用灰度表示的图像,即一个像元用1个Byte(bits)表示辐射值。
1.浮点法:Gray=R0.3+G0.59+B*0.11
2.整数法:Gray=(R30+G59+B*11)/100
3.移位法:Gray =(R77+G151+B*28)>>8;
4.平均值法:Gray=(R+G+B)/3;
5.仅取绿色:Gray=G;
PIL中convert()方法原理:
(1)img = img.convert()
convert()函数,用于不同模式图像之间的转换,PIL中有九种不同模式,分别为1,L,P,RGB,RGBA,CMYK,YCbCr,I,F。
(2)img = img.convert(‘1’)
转化为为二值图像,非黑即白。每个像素用8个bit表示,0表示黑,255表示白。
(3)img = img.convert(‘L’)
转换为为灰度图像,每个像素用8个bit表示,0表示黑,255表示白,其他数字表示不同的灰度。
转换公式:L = R * 299/1000 + G * 587/1000+ B * 114/1000。
# 利用PIL库读取图像
from PIL import Image
import matplotlib.pyplot as plt
import numpy as np
img = Image.open('./pictures/pp.jpg')
# 灰度变换
img_er = img.convert('1') # 1位像素,黑白,每字节一个像素存储
# 第一种方式:convert()函数
img_gray = np.array(img.convert('L')) # 8位像素,黑白
# img_gray = np.array(img_gray)
# 第二种方式:
im2 = 255 - img_gray # 对图像进行反相处理
# 第三种方式
im3 = (100.0 / 255) * img_gray + 100 # 将图像像素值变换到100到200之间
# 第四种方式
im4 = 255.0 * (img_gray / 255.0) ** 2 # 对图像像素值求平方后得到的图像
# 使其标题可以显示中文
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
plt.figure('灰度变换')
plt.subplot(2, 4, 2)
plt.imshow(img)
plt.title('原图')
plt.axis('off')
plt.subplot(2, 4, 3)
plt.imshow(img_er, plt.cm.gray)
plt.title('二值图像')
plt.axis('off')
plt.subplot(2, 4, 5)
plt.imshow(img_gray, plt.cm.gray) # plt 是用于显示三通道的,而灰度图是单通道的,所以用plt.cm.gray
plt.axis('off')
plt.subplot(2, 4, 6)
plt.imshow(im2, plt.cm.gray)
plt.axis('off')
plt.subplot(2, 4, 7)
plt.imshow(im3, plt.cm.gray)
plt.axis('off')
plt.subplot(2, 4, 8)
plt.imshow(im4, plt.cm.gray)
plt.axis('off')
plt.show()
图像轮廓:轮廓可以简单认为成将连续的点(连着边界)连在一起的曲线,具有相同的颜色或者灰度。代表了物体的基本外形,相对于边缘,轮廓是连续的,边缘并不全部连续。为了更加准确,要使用二值化图像。所以在寻找轮廓之前,要进行阈值化处理或者 Canny 边界检测先得到二值化图像。
查找轮廓的函数会修改原始图像。如果你在找到轮廓之后还想使用原始图像的话,你应该将原始图像存储到其他变量中。
直方图:直方图是可以对整幅图的灰度分布进行整体了解的图示,通过直方图我们可以对图像的对比度、亮度和灰度分布等有一个直观了解。图像的直方图用来表征该图像像素值的分布情况。用一定数目的小区间(bin)来指定表征像素值的范围,每个小区间会得到落入该小区间表示范围的像素数目。因此直方图不能显示图像中某像素所在的空间位置信息。
绘制图像轮廓:因为绘制轮廓需要对每个坐标[x,y]的像素值施加同一个阈值,所以首先将图像灰度化。
绘制直方图:使用Matplotlib自带的绘制工具plt.hist()绘制,hist(img,flatten(),128)。
hist()只接受一维数组作为输入,所以在绘制图像直方图之前,必须先对图像进行压平处理。flatten()方法将任意数组按照行优先准则转换成一维数组。第二个参数指定小区间的数目。
代码展示:
# 利用PIL库读取图像
from PIL import Image
import matplotlib.pyplot as plt
import numpy as np
# 使其标题可以显示中文
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 将图像转成灰度图并读取到数组中
img = np.array(Image.open('./pictures/pp.jpg').convert('L'))
# 使用matplotlib的库绘制图片进行显示
# 使其标题可以显示中文
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
plt.figure() # 新建一个图像
plt.subplot(1, 3, 1)
plt.imshow(img)
plt.title('原图')
plt.axis('off') # 不显示坐标轴
# 图像轮廓
plt.subplot(1, 3, 2)
plt.gray() # 不使用颜色信息
plt.contour(img, origin='image') # 在原点的左上角使用轮廓图像
plt.axis('equal')
plt.axis('off')
plt.title('图像轮廓')
# 图像直方图
plt.subplot(1, 3, 3)
plt.hist(img.flatten(), 128)
plt.title('直方图')
plt.xlim([0, 300])
plt.ylim([0, 12000])
plt.show()
一副效果好的图像通常在直方图上的分布比较均匀,直方图均衡化就是用来改善图像的全局亮度和对比度。
直方图均衡化是指将一幅图像的灰度直方图变平,使变换后的图像中每个灰度值的分布概率都相同。在对图像做进一步处理之前,直方图均衡化通常是对图像灰度值进行归一化的一个非常好的方法,并且可以增强图像的对比度。
直方图均衡化的变换函数是图像中像素值的累积分布函数(cumulative distribution function,简写为 cdf,将像素值的范围映射到目标范围的归一化操作)。
均衡化函数:histeq()
import numpy as np
# 直方图均衡化
def histeq(img,nbr_bins=256):
"""
对一幅灰度图像进行直方图均衡化
:param img: 灰度图像
:param nbr_bins:直方图中使用小区间的数目
:return:直方图均衡化后的图像,以及用来做像素值映射的累积分布函数
"""
# 计算图像的直方图
imhist,bins = np.histogram(img.flatten(), nbr_bins, density=True)
# 累计分布函数
cdf = imhist.cumsum()
cdf = 255 * cdf / cdf[-1] # 归一化
# 使用累积分布函数的线性插值,计算新的像素值
img2 = np.interp(img.flatten(), bins[:-1], cdf)
return img2.reshape(img.shape),cdf
注:函数中使用到累积分布函数的最后一个元素(下标为-1),目的是将其归一化到[0…1]的范围。
显示代码:
# 利用PIL库读取图像
from PIL import Image
import matplotlib.pyplot as plt
import numpy as np
import jkjk
# 将图像转成灰度图并读取到数组中
img = np.array(Image.open('./pictures/pp.jpg').convert('L'))
img2, cdf = jkjk.histeq(img)
# 使用matplotlib的库绘制图片进行显示
# 使其标题可以显示中文
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
plt.figure() # 新建一个图像
plt.subplot(2, 2, 1)
plt.imshow(img, cmap='gray')
plt.title('原图')
plt.axis('off') # 不显示坐标轴
plt.subplot(2, 2, 2)
plt.imshow(img2, cmap='gray')
plt.title('均衡化之后图像')
plt.axis('off')
plt.subplot(2, 2, 3)
plt.hist(img.flatten(), 128)
plt.title('原图直方图')
plt.axis('off')
plt.subplot(2, 2, 4)
img2 = np.array(img2)
plt.hist(img2.flatten(), 128)
plt.title('均衡化之后图像直方图')
plt.axis('off')
plt.show()
均衡化后图像的对比度增强了,原先图像灰色区域的细节变得清晰,同时在直方图上也看到像素分布比较均匀。
高斯滤波是一种线性平滑滤波,适用于消除高斯噪声,广泛应用于图像处理的减噪过程。 可以简单的理解为,高斯滤波就是对整幅图像进行加权平均的过程,每一个像素点的值,都由其本身和邻域内的其他像素值经过加权平均后得到。
高斯滤波和高斯模糊:
高斯滤波也叫做高斯平滑和高斯滤波。为什么叫做高斯滤波呢?那是因为对图像进行滤波操作。那为什么要加上高斯呢,那是因为卷积核(掩膜)是由高斯分布计算出来的所以就需要加上高斯两字。其实滤波范围比模糊要大,滤波还有高通滤波、低通滤波等。为什么叫做高斯模糊呢,是因为通过这个操作可以让图片变得模糊。那为什么又叫高斯平滑呢,那是因为使图像更加平滑了(平滑是图片变得更加平缓,更加模糊,不那么尖锐)。
高斯滤波的具体操作是:用一个模板(或称卷积、掩模)扫描图像中的每一个像素,用模板确定的邻域内像素的加权平均灰度值去替代模板中心像素点的值。
采用均值掩膜对输入信号进行卷积的滤波方式叫均值滤波;
采用高斯掩膜对输入信号进行卷积的滤波方式叫高斯滤波;
图像的高斯模糊是非常经典的图像卷积例子。本质上,图像模糊就是将灰度图像和一个高斯核进行卷积操作:
其中 * 表示卷积操作,σ是标准差,Gσ是标准差为σ的二维高斯核。
σ 值的意义及选取:
我们来看看图像模糊是什么一个概念,首先,图像中的像素每一个都有自己的像素值,这些像素值决定了图像最终的显示。来想象一幅模糊的图,是不是感觉越模糊,这些像素点之间的差别就越小呢?确实是这样,这也是图像模糊的原理,也就是一种像素的平滑化,通过对图像中的像素值进行平均处理,让这些像素值越来越来接近,来达到一种人尽量无法辨识出这些像素点的差别,从来产生模糊的效果。因此我们得到启示,那就是对于取均值的半径大小越大,那么整个模糊效果也就越好,很好理解,因为取的范围越大,意味着相似的像素点越多,达到的模糊效果也就越好了。
标准差代表着数据的离散程度,如果σ较小,那么生成的模板的中心系数较大,而周围的系数较小,这样对图像的平滑效果就不是很明显(即邻域像素点与中心像素点的距离较近,这样用模板确定的邻域内像素的加权平均灰度值越接近原灰度值,对图像的平滑效果不明显);反之,σ较大,则生成的模板的各个系数相差就不是很大,比较类似均值模板,对图像的平滑效果比较明显。
均值滤波:
均值滤波也称为线性滤波,其采用的主要方法为邻域平均法。线性滤波的基本原理是用均值代替原图像中的各个像素值,即对待处理的当前像素点(x,y),选择一个模板,该模板由其近邻的若干像素组成,求模板中所有像素的均值,再把该均值赋予当前像素点(x,y),作为处理后图像在该点上的灰度g(x,y),即g(x,y)=∑f(x,y)/m m为该模板中包含当前像素在内的像素总个数。
python中SciPy有用来做滤波操作的scipy.ndimage.filters 模块。该模块使用快速一维分离的方式来计算卷积。
# 利用PIL库读取图像
from PIL import Image
import matplotlib.pyplot as plt
import numpy as np
from scipy.ndimage import filters
# 将图像转成灰度图并读取到数组中
img_gray = np.array(Image.open('./pictures/pp.jpg').convert('L'))
im1 = filters.gaussian_filter(img_gray,2)
im2 = filters.gaussian_filter(img_gray,15)
# 模糊一幅彩色图像
img2 =np.array(Image.open('./pictures/pp2.jpg'))
im3 = np.zeros(img2.shape)
im4 = np.zeros(img2.shape)
for i in range(3):
im3[:, :, i] = filters.gaussian_filter(img2[:, :, i], 5)
im4[:, :, i] = filters.gaussian_filter(img2[:, :, i], 15)
im3 = np.uint8(im3)
im4 = np.uint8(im4)
# 使用matplotlib的库绘制图片进行显示
# 使其标题可以显示中文
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus'] = False
plt.figure() # 新建一个图像
plt.subplot(2,3,1)
plt.imshow(img_gray,cmap='gray')
plt.title('原灰度图像')
plt.axis('off')
plt.subplot(2,3,2)
plt.imshow(im1,cmap='gray')
plt.title('标准差 = 2')
plt.axis('off')
plt.subplot(2,3,3)
plt.imshow(im2,cmap='gray')
plt.title('标准差 = 15')
plt.axis('off')
plt.subplot(2,3,4)
plt.imshow(img2)
plt.title('原彩色图像')
plt.axis('off')
plt.subplot(2,3,5)
plt.imshow(im3)
plt.title('标准差 = 5')
plt.axis('off')
plt.subplot(2,3,6)
plt.imshow(im4)
plt.title('标准差 = 15')
plt.axis('off')
plt.show()
结果展示:
由上述结果可知,标准差σ越大,处理后的图像细节丢失越多,变得更加模糊。