小古在本学期选修了《计算机视觉原理与应用》,最近有一份作业 —— 利用matlab或者OpenCV对图像进行一些处理,由于完全没有接触过matlab和OpenCV,但是学习了一些python语言,所以便利用opencv-python来完成作业。
1.1 图像二值化算法
图像二值化就是将灰度化图像像素值设置为0和255,使得图像中的每个像素只能是黑或者是白,没有中间的过渡,是图像分割的一种简单方法。图像二值化通过设定一个阈值,对图像中像素值小于阈值的设置为0,超过像素值的设置为255,从而将整个图像处理为黑白二值效果。图像二值化处理是为了更好地做图像处理判别。图像二值化算法分为全局固定阈值和局部动态阈值即自适应阈值。
全局阈值二值化对整个图像采用单一的阈值,将图像中每个像素灰度值与阈值进行比较,再依据比较结果将其分为背景或目标对象。典型的全局阈值方法包括迭代算法、OTSU方法、灰度期望值算法等,全局阈值法算法简单, 对于目标和背景明显分离、直方图分布呈双峰的图像效果良好,但其对输入图像有噪声或不均匀光照等情况抵抗能力差,应用受到极大限制。
局部阈值二值化是按照一定规则将数字图像分块划为N个子图像,并为每个子图像确定相应的阈值(T值不同),即对指定区域构造灰度直方图。根据像素的邻域块的像素值分布来确定该像素位置上的二值化阈值。这样做的好处在于每个像素位置处的二值化阈值不是固定不变的,而是由其周围邻域像素的分布来决定的。亮度较高的图像区域的二值化阈值通常会较高,而亮度较低的图像区域的二值化阈值则会相适应地变小。不同亮度、对比度、纹理的局部图像区域将会拥有相对应的局部二值化阈值。非均匀光照条件或噪声存在等情况虽然影响整体图像的灰度分布却不影响局部的图像性质, 使得局部阈值法较全局阈值法有更广泛的应用。常用的局部阈值法有Bernsen算法、Niblack算法等。常用的局部自适应阈值有:1)局部邻域块的均值;2)局部邻域块的高斯加权和。
这里使用了全局阈值与局部阈值算法。采用全局阈值算法时,利用了OTSU方法处理,OTSU即大津法又名最大类间方法,此方法通过统计整个图像的直方图特性来实现全局阈值的自动选取。使用方法cv2.threshold(src, thresh, maxval, type),有两个返回值,阈值和阈值处理后的图像。
其中 src:表示的是图片源。
thresh:表示的是阈值(分割值)。
maxval:表示的是最大值。
type:表示的是这里划分的时候使用的是什么类型的算法,包含以下几种
各个阈值类型图示如下:
采用局部阈值算法时,通过计算某个邻域(局部)的均值、中值、高斯加权平均(高斯滤波)来确定阈值,此处采用高斯法。使用方法adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C, dst=None)
其中 src:表示输入图像(8位单通道图像)。
maxValue:表示使用 THRESH_BINARY 和 THRESH_BINARY_INV 的最大值.
adaptiveMethod:表示自适应阈值算法,平均 (ADAPTIVE_THRESH_MEAN_C)或高斯(ADAPTIVE_THRESH_GAUSSIAN_C)。
thresholdType:表示阈值类型,必须为THRESH_BINARY或THRESH_BINARY_INV的阈值类型。
blockSize:表示块大小(奇数且大于1,比如3,5,7........ )。
C:常数,表示从平均值或加权平均值中减去的数。 通常情况下,这是正值,但也可能为零或负值。
dst:输出图像,可以忽略。
1.2 在Pycharm IDE 利用opencv-python进行图像二值化实现
import cv2
def binarization_whole(img):
""" 图像全局阈值二值化处理 """
# 将图像转为灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imwrite('img/gray.png', gray)
# 最大类间方差法(大津算法),thresh会被忽略,参数0可改为任意数字但不起作用,自动计算一个阈值
ret, dst = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
print("threshold value %s" % ret) # 打印选取的阈值
# # 创建窗口
# cv2.namedWindow("binarization_whole", 0)
cv2.imshow("binarization_whole", dst) # imshow()函数有两个参数:显示图像的帧名称、要显示的图像本身。
# 储存图像
cv2.imwrite('img/1.png', dst)
def binarization_part(img):
""" 图像局部阈值二值化处理 """
# 将图像转为灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 图像二值化
dst = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 145, 3)
cv2.imshow("binarization_part", dst)
# 储存图像
cv2.imwrite('img/2.png', dst)
img = cv2.imread("2.jpg")
binarization_whole(img)
binarization_part(img)
cv2.waitKey(0) # 进程不结束则会一直保持显示状态
cv2.destroyAllWindows() # 调用 destroyWindows()函数可以释放由 OpenCV创建的所有窗口。
效果图如下:
原图:
二值化处理前的灰度图:
处理后二值图像
全局阈值二值化处理:
局部阈值二值化处理:
1.3比较不同阈值下二值化处理后图像的变化
全局阈值二值化处理时,灰度图、整个图像的直方图特性、设定二值化类型THRESH_BINARY和OSTU’s二值化图(这是一个更加适合于图像灰度直方图具有双峰的情况,他会在双峰之间找到一个值作为阈值)如下:
根据程序运行结果可得到设定二值化类型THRESH_BINARY和OTSU’s二值化法的阈值分别为127和105,由此也产生了不同的效果,很明显,阈值为105时的二值化图更加清楚地展现了房屋的结构和轮廓。阈值为127时,由于阈值高于最佳阈值105而使得图像整体较暗,导致不如阈值为105时的效果图清晰。
局部阈值二值化处理时,使用方法adaptiveThreshold( )根据各像素点邻域内像素点加权得到各点邻域阈值,然后在各个子图像内进行二值化处理,这样做的好处很明显,与全局阈值处理相比,房屋的房檐和窗户轮廓更加明显清晰。(左图为全局阈值化,右图为局部阈值化)
为了更加明显,下面展示在不同阈值下的全局阈值二值化处理效果图:
随着阈值由0开始增加,在阈值为120时图像中橙子轮廓开始凸显,在阈值为180左右时效果最佳,在阈值为220时虽仍有轮廓但已几乎无法辨认出图像中元素,阈值继续增加,阈值为250时已完全无法辨认图像中元素。
测试代码:
import cv2
def threshold(thresh):
img = cv2.imread("4.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
cv2.imwrite('img/gray_orange.png', gray)
ret, dst = cv2.threshold(gray, thresh, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
print("threshold value %s" % ret) # 打印选取的阈值
cv2.imshow("threshold", dst) # imshow()函数有两个参数:显示图像的帧名称、要显示的图像本身。
# 储存图像
cv2.imwrite('img/orange.png', dst)
threshold(50) # 参数为阈值
cv2.waitKey(0) # 进程不结束则会一直保持显示状态
cv2.destroyAllWindows() # 调用 destroyWindows()函数可以释放由 OpenCV创建的所有窗口。
用OTSU方法验证一下:
ret, dst = cv2.threshold(gray, thresh, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
通过运行打印最佳阈值:
效果图:
与阈值为180时的图像相近,所以与上述结论是一致的。
2.1 二维傅里叶变换原理
傅里叶变换实际上是将信号f(t)与一组不同频率的复正弦做内积,这一组复正弦是变换的基向量,傅里叶系数或傅里叶变换是f(t)在这一组基向量上的投影。傅里叶变换是一种分析信号的方法,它可分析信号的成分,也可用这些成分合成信号,是线性系统分析的一个有力工具,它能够定量地分析诸如数字图像之类的数字化系统,把傅里叶变换的理论与物理解释相结合,将有利于解决大多数图像处理问题,傅里叶变换在图像处理中的应用十分广泛,如图像特征提取、频率域滤波、图像复原、纹理分析等。傅里叶变换主要分为连续傅立叶变换和离散傅立叶变换。
傅里叶变换的原理是将一个信号分离为无穷多多正弦/复指数信号的加成,即把信号变成正弦信号相加的形式——既然是无穷多个信号相加,那对于非周期信号来说,每个信号的加权应该都是零,但有密度上的差别,因为落到每一个点的概率都是无限小,但这些无限小是有差别的,所以傅里叶变换之后,横坐标即为分离出的正弦信号的频率,纵坐标对应的是加权密度。
灰度图像是由二维的离散的点构成的,二维离散傅里叶变换常用于图像处理中,对图像进行傅里叶变换后得到其频谱图。频谱图中频率高低表征图像中灰度变化的剧烈程度。图像中边缘和噪声往往是高频信号,而图像背景往往是低频信号。我们在频率域内可以很方便地对图像的高频或低频信息进行操作,完成图像去噪,图像增强,图像边缘提取等操作,因此,对图像进行傅里叶变换的原理如下公式:
其中u和v是频率变量,X和y是空间或图像变量。
2.2 图像二维傅里叶变换实现
利用OpenCV库函数实现傅里叶变换,OpenCV 中进行二维傅里叶变换的函数是
cv2.dft(src, dst=None, flags=None, nonzeroRows=None)
输出的结果是双通道的,第一个通道是结果的实数部分,第二个通道是结果的虚数部分,输入图像src要首先转换成 np.float32 格式。
dst表示输出图像,包括输出大小和尺寸。
nonzeroRows表示当参数不为零时,函数假定只有nonzeroRows输入矩阵的第一行(未设置)或者只有输出矩阵的第一个(设置)包含非零,因此函数可以处理其余的行更有效率,可以节省一些时间。
flags为转换标识,flags= cv2.DFT_COMPLEX_OUTPUT,输出一个复数矩阵,则需要调用cv2.magnitude()函数将傅里叶变换的双通道结果转换为0到255的范围:
返回值 = cv2.magnitude(x, y)
x: 浮点型X坐标值,即实部
y: 浮点型Y坐标值,即虚部
经过cv2.magnitude()函数处理后得到的结果为
代码:
# OpenCV傅里叶库函数实现图像变换
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图像且读为灰度图
img = cv2.imread("4.jpg", 0)
# 使用OpneCV傅里叶变换函数对图像进行傅里叶变换
f_img = cv2.dft(np.float32(img), flags=cv2.DFT_COMPLEX_OUTPUT)
# 默认结果中心点位置是在左上角,将频谱低频从左上角移动至中心位置
dft_shift = np.fft.fftshift(f_img)
# 将频谱图双通道复数转换为(0,255)区间
dft_img = 20 * np.log(cv2.magnitude(f_img[:, :, 0], f_img[:, :, 1]))
dft_shift_img = 20 * np.log(cv2.magnitude(dft_shift[:, :, 0], dft_shift[:, :, 1]))
# 图像显示
plt.rcParams['font.sans-serif'] = ['SimHei'] # 显示中文
plt.subplot(131), plt.imshow(img, 'gray'), plt.title('原图像')
plt.axis('off')
plt.subplot(132), plt.imshow(dft_img, 'gray'), plt.title('二维傅里叶频谱图')
plt.axis('off')
plt.subplot(133), plt.imshow(dft_shift_img, 'gray'), plt.title('二维傅里叶中心化频谱图')
plt.axis('off')
plt.show()
图像处理效果图:
通过这次作业,小古也对OpenCV有了浓厚的兴趣,以后会继续学习利用OpenCV进行图像处理的技能。
图片来源:
避暑山庄 · 如意州岛 —— 一片云 - 承德 - PhotoFans摄影网
橙子 —— 百度图片