OpenCV Tutorials 04 - 图像阈值和平滑处理

3,300 阅读8分钟

图像阈值和图像滤波

一、图像阈值

阈值处理一般有三种类型:简单阈值操作、自适应阈值操作和Otsu阈值操作。OpenCV提供了两个函数cv.threshold 和 cv.adaptiveThreshold实现

1. 简单阈值操作

把每个像素都根据相同的阈值进行处理。若像素值比阈值更小,则会被置为0,反之则会将其置为最大值(8比特表示法的最大值时255)。

cv.threshold 就是专用于阈值处理的函数,包括了以下参数:

  1. img:输入图像,一定要为一张灰度图,因为阈值处理只能对单通道图像进行处理
  2. threshold-value : 用于对像素值进行分类
  3. Maximum: 超过阈值后被赋予的最大值大小
  4. type:阈值处理类型,有以下取值:
    1. cv.THRESH_BINARY : 普通二值阈值处理,最常用
    2. cv.THRESH_BINARY_INV:反转普通二值阈值处理,也就是超过阈值的被赋值为0,未超过的被赋值为Maximum
    3. cv.THRESH_TRUNC:取限阈值处理,超过阈值被赋值为阈值,未超过则不变,也就是非二元
    4. cv.THRESH_TOZERO:取下限阈值处理,超过阈值则不变,未超过则归0
    5. cv.THRESH_TOZERO_INV:反转取下限阈值处理,超过阈值则归0,未超过则不变
  • 注意:cv.threshold函数有两个输出,前者是被使用的阈值,后者是返回的灰度图,不要弄混了!
import cv2 as cv
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
%matplotlib inline
def cv_show(name, img):
    cv.imshow(name, img)
    cv.waitKey(0)
    cv.destroyAllWindows()
def compare(imgs):
  #  for i in range(len(imgs)):
 #       imgs[i][:,-3:-1,:] = [255,255,255]
    res = np.hstack(imgs)
    cv_show('Compare', res)
gray = cv.imread('gradient.png',0)

res1 = cv.threshold(gray, 127, 255, cv.THRESH_BINARY)[1]
res2 = cv.threshold(gray, 127, 255, cv.THRESH_BINARY_INV)[1]
res3 = cv.threshold(gray, 127, 255, cv.THRESH_TRUNC)[1]
res4 = cv.threshold(gray, 127, 255, cv.THRESH_TOZERO)[1]
res5 = cv.threshold(gray, 127, 255, cv.THRESH_TOZERO_INV)[1]

titles = ['Original Image','BINARY','BINARY_INV','TRUNC','TOZERO','TOZERO_INV']
images = [gray, res1, res2, res3, res4, res5]
for i in range(6):
    # 组合使用插入子图
    plt.subplot(2,3,i+1),plt.imshow(images[i],'gray',vmin=0,vmax=255)
    # 插入子图标题
    plt.title(titles[i])
    # 去除各子图的x,y轴
    plt.xticks([]),plt.yticks([])
plt.show()

output_9_0.png

2. 自适应阈值处理

在简单阈值操作中,我们使用一个全局值作为阈值。但这显然并不适用于大多数情况,例如,一个图像在不同的区域有不同的照明条件。在这种情况下,自适应阈值可以帮助我们更好地处理图片。在自适应算法中,会根据像素周围的一个小区域来确定阈值。 对于同一图像的不同区域,我们得到了不同的阈值,对于光照不同的图像,我们可以获得更好的结果。

cv.adaptiveThreshold函数用于实现自适应阈值操作,除了上面的threshold接受参数以外,还需提供额外的三个参数

  1. img
  2. Maximum
  3. 自适应滤波种类:有以下取值:
    1. cv.ADAPTIVE_THRESH_MEAN_C: 类似于均值滤波,取邻域内的均值再减去一个常数偏移量作为阈值
    2. cv.ADAPTIVE_THRESH_GAUSSIAN_C: 阈值是邻域值的高斯加权和减去常数 c 。
  4. 阈值处理的类型:上述五种
  5. blockSize:邻域矩阵大小,一般取奇数
  6. 偏移常数
gray = cv.imread('lena.png',0)

# 都使用二值阈值
# 全局阈值处理阈值为127,
G_threshold = cv.threshold(gray, 127, 255, cv.THRESH_BINARY)[1]
Guass_threshold = cv.adaptiveThreshold(gray, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C,cv.THRESH_BINARY, 11, 2)
Mean_threshold = cv.adaptiveThreshold(gray, 255, cv.ADAPTIVE_THRESH_MEAN_C, cv.THRESH_BINARY,11, 2)

imgs = [gray, G_threshold, Guass_threshold, Mean_threshold]
titles = ['Original','Global','Guass','Mean']

for i in range(len(imgs)):
    # 这里要传入子图,同时为子图显示要传入gray灰度
    plt.subplot(2,2,i + 1),plt.imshow(imgs[i],'gray')
    plt.title(titles[i])
    plt.xticks([])
    plt.yticks([])
    
plt.show()

output_14_0.png

3. Otsu's二值化

在全局阈值处理中,我们使用一个任意的既定值作为阈值。相比之下,Otsu 的方法避免了手动选择,而是自动确定。考虑一个图像只有两个不同的图像值(双峰图像) ,其中的直方图将只包含两个峰值。一个好的阈值应该位于这两个值的中间。类似地,Otsu 的方法通过图像直方图确定一个最佳的全局阈值。

OpenCV提供了cv.thresh_otsu参数让threshold函数具备自动选择合适的阈值来处理双峰灰度图。

下面我们看一个例子:

输入图像是一个噪声图像。在第一种情况下,使用值为127的全局阈值分割。在第二种情况下,直接应用Otsu 的阈值处理。在第三种情况下,首先用一个5x5高斯核滤除噪声,然后应用 Otsu 阈值。看看过滤噪声方法是如何改善结果的。

img = cv.imread('noisy2.png',0)
# global thresholding
ret1,th1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
# Otsu's thresholding,注意下面的调用方法
ret2,th2 = cv.threshold(img,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
# Otsu's thresholding after Gaussian filtering
blur = cv.GaussianBlur(img,(5,5),0)
ret3,th3 = cv.threshold(blur,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
# plot all the images and their histograms
images = [img, 0, th1,
          img, 0, th2,
          blur, 0, th3]
titles = ['Original Noisy Image','Histogram','Global Thresholding (v=127)',
          'Original Noisy Image','Histogram',"Otsu's Thresholding",
          'Gaussian filtered Image','Histogram',"Otsu's Thresholding"]
for i in range(3):
    plt.subplot(3,3,i*3+1),plt.imshow(images[i*3],'gray')
    plt.title(titles[i*3]), plt.xticks([]), plt.yticks([])
    plt.subplot(3,3,i*3+2),plt.hist(images[i*3].ravel(),256)
    plt.title(titles[i*3+1]), plt.xticks([]), plt.yticks([])
    plt.subplot(3,3,i*3+3),plt.imshow(images[i*3+2],'gray')
    plt.title(titles[i*3+2]), plt.xticks([]), plt.yticks([])
plt.show()

output_19_0.png

二、平滑处理

一般图像的平滑处理是通过各种滤波实现的,这里我们讲述一下低通滤波的用法以及自定义滤波器来实现2D卷积

1. 2D卷积(图像过滤)

和一维信号一样,图像也可以用各种低通滤波器(LPF)、高通滤波器(HPF)等进行滤波。LPF 有助于去除噪音,模糊图像等。HPF 过滤器有助于在图像中找到边缘。

OpenCV 提供了一个 cv.filter2D ()函数,用于卷积内核中的映像。作为一个例子,我们将尝试一个图像平均过滤器。一个5x5平均值的过滤器内核看起来如下:

K=125[1111111111111111111111111]K=\frac{1}{25}\left[\begin{array}{ccccc} 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 \end{array}\right]

具体操作流程如下:让卷积核中心移动到每一个像素点,若卷积核有部分不在图像内,则使用0填充完毕后,再计算平均值来代替原来的像素值。

cv.filter2D的参数列表为:

  1. img
  2. 目标图像通道数:-1 就是和原图保持一致
  3. kernel:卷积核,也就是narray阵列
img = cv.imread('xy.png')

# 卷积核的数据类型不能为整数,若为整数则直接对结果取整了
# 注意此处的卷积核数据,要除以卷积核尺寸的
kernel = np.ones((5,5), np.float32)/25
res = cv.filter2D(img, -1, kernel)

compare([img,res])

5均值滤波.PNG

2. 图像平滑(滤波)

图像模糊是通过卷积与低通滤波器内核的图像。它对消除噪声很有用。它实际上消除了图像中的高频内容(如: 噪音,边缘,其实也就变化较大的像素点)。所以在这个操作中边缘有点模糊(也有不模糊边缘的模糊技术)。OpenCV 提供了四种主要的模糊技术。

A. 均值滤波

这是通过卷积一个图像与规范化框过滤器。它只是获取核心区域下所有像素的平均值并替换中心元素。这是通过函数 cv.blur ()或 cv.boxFilter()实现的。我们应该指定内核的宽度和高度。一个3x3的标准化盒式过滤器看起来如下: K=19[111111111]K=\frac{1}{9}\left[\begin{array}{ccc} 1 & 1 & 1 \\ 1 & 1 & 1 \\ 1 & 1 & 1 \end{array}\right]

  • 注意:如果不想使用规范化的盒式过滤器,可以使用 cv.boxFilter(),传入对应的normalise = False 即可使用自定义的卷积核。
img = cv.imread('xy.png')

Blur = cv.blur(img,(5, 5))

compare([img, Blur])

5均值滤波.PNG

B. 高斯滤波

该方法采用高斯核函数代替盒子滤波器。它是用 cv.GaussianBlur()函数来完成的。我们应该指定内核的宽度和高度,它们应该是正奇数。我们还应该分别在 x 和 y 方向、 sigmaX 和 sigmaY 中指定标准差。如果只指定 sigmaX,则 sigmaY 被视为与 sigmaX 相同。如果两者都以零的形式给出,则根据内核大小计算它们。高斯模糊对于消除高斯噪声很有效。

用法如下:cv.GaussianBlur( src, ksize, sigmaX[, dst[, sigmaY[, borderType]]] ) -> dst

另外,OpenCV还提供了 cv.getGaussianKernel()用于创建高斯内核。

用法如下:cv.getGaussianKernel( ksize, sigma[, ktype] ) -> retval

img = cv.imread('lena.png')

blur = cv.GaussianBlur(img,(11,11),0)

compare([img,blur])

高斯滤波.PNG

C. 中值滤波

函数 cv.medianBlur()取核心区域下所有像素的中位数,中心像素值用这个中位数值代替。这对于图像中的椒盐噪声非常有效。在上面的过滤器(均值滤波或高斯滤波)中,中心元素是一个新计算的值,它可能是图像中的一个像素值或一个新值。但是在中值模糊中,中心元素总是被图像中的一些像素值所代替。中值滤波能有效地降低噪声。它的内核大小应该是一个正奇整数。

# 读取椒盐噪声图
img = cv.imread('jy.png')
median_5 = cv.medianBlur(img,5)
median_9 = cv.medianBlur(img,9)
median_13 = cv.medianBlur(img,13)
compare([img, median_5, median_9,median_13])

中值滤波.PNG

D. 双边滤波

cv.bilateralFilter()双边滤波器能够在去除噪声同时保持边缘锐利。但与其他过滤器相比,这种过滤器的运行速度要慢一些。高斯滤波器在像素周围找到邻域,并获得了它的高斯加权平均数。这个高斯滤波器是一个单独的空间函数,也就是说,附近的像素被考虑过滤。它没有考虑像素是否具有几乎相同的强度。它不考虑像素是否是边缘像素。所以它也模糊了边缘,这是我们不想做的。

双边滤波在空间上也采用高斯滤波,但是多一个高斯滤波是像素差的函数。空间的高斯函数确保只有附近的像素被认为是模糊的,而强度差异的高斯函数确保只有那些与中心像素有相似强度的像素被认为是模糊的。因此它保留了边缘,因为像素的边缘将有很大的强度变化。

用法: cv.bilateralFilter( src, d, sigmaColor, sigmaSpace[, dst[, borderType]] ) -> dst

img = cv.imread('lena.png')

Guass = cv.GaussianBlur(img, (5,5),0 )
Bilateral = cv.bilateralFilter(img,9, 75,75)

compare([img, Guass, Bilateral])

双边滤波.PNG