opencv-python学习笔记(五)—— 图像处理之图片阈值、滤波

系列文章目录

前言

本教程将学习简单阈值,自适应阈值和Otsu的阈值:cv.thresholdcv.adaptiveThreshold. 以及使用低通滤波器模糊图像、2D卷积等等

一、图像阈值

函数cv.thresholdcv.adaptiveThreshold

1. 简单阈值

对于每个像素,应用相同的阈值。如果像素值小于阈值,则设置为0,否则设置为最大值。函数cv.Threshold用于应用阈值。第一个参数是源图像,它应该是灰度图像。第二个参数是用于对像素值进行分类的阈值。第三个参数是分配给超过阈值的像素值的最大值。OpenCV提供了不同类型的阈值,由第四个参数给出。上面描述的基本阈值是通过使用类型cv.THRESH_BINARY来实现的。所有简单阈值类型为:

请参阅这些类型的文档以了解它们的区别:
在这里插入图片描述
在这里插入图片描述

该方法返回两个输出。第一个是所使用的阈值,第二个输出是阈值图像。

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('gradient.png',0)
ret,thresh1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
ret,thresh2 = cv.threshold(img,127,255,cv.THRESH_BINARY_INV)
ret,thresh3 = cv.threshold(img,127,255,cv.THRESH_TRUNC)
ret,thresh4 = cv.threshold(img,127,255,cv.THRESH_TOZERO)
ret,thresh5 = cv.threshold(img,127,255,cv.THRESH_TOZERO_INV)
titles = ['Original Image','BINARY','BINARY_INV','TRUNC','TOZERO','TOZERO_INV']
images = [img, thresh1, thresh2, thresh3, thresh4, thresh5]
for i in range(6):
    plt.subplot(2,3,i+1),plt.imshow(images[i],'gray',vmin=0,vmax=255)
    plt.title(titles[i])
    plt.xticks([]),plt.yticks([])
plt.show()

注意:为了绘制多个图像,我们使用了plt.subplot()函数。请查看matplotlib文档以获得更多细节。

在这里插入图片描述

2. 自适应阈值

在前一节中,我们使用一个全局值作为阈值。但这可能不是在所有情况下都是好的,例如,如果一个图像在不同的区域有不同的照明条件。在这种情况下,自适应阈值可以有所帮助。这里,算法根据像素周围的小区域来确定阈值。因此,我们对同一幅图像的不同区域得到不同的阈值,从而使不同光照下的图像得到更好的效果。

除上述参数外,函数cv.adaptiveThreshold接受三个输入参数:

  • adaptiveMethod决定如何计算阈值:cv.ADAPTIVE_THRESH_MEAN_C:阈值是邻域面积的平均值减去常数C。

    cv.ADAPTIVE_THRESH_GAUSSIAN_C:阈值是邻域值减去常数C的高斯加权和。

  • blockSize决定了邻域面积的大小,

  • C是一个常量,从邻域像素的平均值或加权和中减去。

下面的代码对不同光照下的图像进行全局阈值和自适应阈值的比较:

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('sudoku.png',0)
img = cv.medianBlur(img,5)
ret,th1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
th2 = cv.adaptiveThreshold(img,255,cv.ADAPTIVE_THRESH_MEAN_C,\
            cv.THRESH_BINARY,11,2)
th3 = cv.adaptiveThreshold(img,255,cv.ADAPTIVE_THRESH_GAUSSIAN_C,\
            cv.THRESH_BINARY,11,2)
titles = ['Original Image', 'Global Thresholding (v = 127)',
            'Adaptive Mean Thresholding', 'Adaptive Gaussian Thresholding']
images = [img, th1, th2, th3]
for i in range(4):
    plt.subplot(2,2,i+1),plt.imshow(images[i],'gray')
    plt.title(titles[i])
    plt.xticks([]),plt.yticks([])
plt.show()

在这里插入图片描述

3. OTSU二值化

在全局阈值中,我们使用任意选择的值作为阈值。相比之下,Otsu的方法避免了选择一个值,并自动确定它。

考虑一个只有两个不同图像值的图像(双峰图像),其中直方图只包含两个峰。一个好的阈值应该位于这两个值的中间。类似地,Otsu的方法从图像直方图中确定一个最优的全局阈值。

为了做到这一点,使用了cv.threshold()函数。THRESH_OTSU作为一个额外的标志传递。阈值可以任意选择。然后算法找到最优阈值,作为第一个输出返回。

看看下面的例子。输入图像是一个噪声图像。在第一种情况下,应用值为127的全局阈值。在第二种情况下,直接应用Otsu的阈值。在第三种情况下,首先对图像进行5x5高斯核滤波去噪,然后进行Otsu阈值分割。看看噪声滤波如何改善结果。

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
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([])

在这里插入图片描述

OTSU原理

Otsu的算法试图找到一个阈值(t),使由关系给出的类内加权方差最小:

在这里插入图片描述
它实际上找到了一个值t,它位于两个峰值之间,这样两个类别的方差都是最小的。它可以简单地用Python实现如下:

OTSU python实现

img = cv.imread('noisy2.png',0)
blur = cv.GaussianBlur(img,(5,5),0)
# find normalized_histogram, and its cumulative distribution function
hist = cv.calcHist([blur],[0],None,[256],[0,256])
hist_norm = hist.ravel()/hist.sum()
Q = hist_norm.cumsum()
bins = np.arange(256)
fn_min = np.inf
thresh = -1
for i in range(1,256):
    p1,p2 = np.hsplit(hist_norm,[i]) # probabilities
    q1,q2 = Q[i],Q[255]-Q[i] # cum sum of classes
    if q1 < 1.e-6 or q2 < 1.e-6:
        continue
    b1,b2 = np.hsplit(bins,[i]) # weights
    # finding means and variances
    m1,m2 = np.sum(p1*b1)/q1, np.sum(p2*b2)/q2
    v1,v2 = np.sum(((b1-m1)**2)*p1)/q1,np.sum(((b2-m2)**2)*p2)/q2
    # calculates the minimization function
    fn = v1*q1 + v2*q2
    if fn < fn_min:
        fn_min = fn
        thresh = i
# find otsu's threshold value with OpenCV function
ret, otsu = cv.threshold(blur,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
print( "{} {}".format(thresh,ret) )

4. 参考资料

Digital Image Processing, Rafael C. Gonzalez

二、图片光滑

1. 2D卷积

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

OpenCV提供了一个函数cv.filter2D()来将一个内核与一个图像进行卷积。作为一个例子,我们将在图像上尝试一个平均过滤器。一个5x5平均滤波器内核如下所示:
在这里插入图片描述
操作如下:把这个核覆盖在图像上,将这个核之下的所有25个像素相加,取平均值,并用新的平均值替换中心像素。对图像中的所有像素继续执行此操作。尝试下面的代码并检查结果:

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('opencv_logo.png')
kernel = np.ones((5,5),np.float32)/25
dst = cv.filter2D(img,-1,kernel)
plt.subplot(121),plt.imshow(img),plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(dst),plt.title('Averaging')
plt.xticks([]), plt.yticks([])
plt.show()

在这里插入图片描述

2. 图像模糊

图像模糊是通过将图像与低通滤波器核进行卷积来实现的。这对消除噪声很有用。它实际上从图像中去除高频内容(如:噪声,边缘)。所以在这个操作中边缘会被模糊一点(也有一些模糊技术不会模糊边缘)。OpenCV提供了四种主要的模糊技术。

均值模糊

这是通过将图像与标准化的框过滤器进行卷积来实现的。它只是取kernel下所有像素的平均值,然后替换中心元素。这是由函数cv.blur()或cv.boxFilter()完成的。关于kernel的更多细节请查看文档。我们应该指定核的宽度和高度。一个3x3的归一化盒子滤波器看起来如下所示:
在这里插入图片描述

注意:如果你不想使用归一化盒子滤波器,可以使用cv.boxFilter()。向函数传递一个参数normalize=False。

下面是一个5x5大小的内核示例:

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('opencv-logo-white.png')
blur = cv.blur(img,(5,5))
plt.subplot(121),plt.imshow(img),plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(blur),plt.title('Blurred')
plt.xticks([]), plt.yticks([])
plt.show()

在这里插入图片描述

高斯模糊

该方法采用高斯核代替盒子滤波器。这是通过函数cv.GaussianBlur()完成的。我们应该指定核的宽度和高度,并且应该是正奇数。我们还应该分别指定X和Y方向上的标准差sigmaX和sigmaY。如果只指定了sigmaX,则sigmaY与sigmaX相同。如果两者都以0表示,则从内核大小计算它们。高斯模糊是去除图像高斯噪声的有效方法。

如果需要,可以使用函数cv.getGaussianKernel()创建高斯核。

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

在这里插入图片描述

中值模糊

函数cv.medianBlur()取内核区域下所有像素的中值,中心元素被这个中值替换。这是非常有效的对抗椒盐噪声的图像。有趣的是,在上面的滤波中,中心元素是一个新计算的值,可能是图像中的像素值,也可能是一个新值。但在中值模糊中,中心元素总是被图像中的某个像素值所替代。有效地降低了噪声。它的核大小应该是一个正奇数。

下面给原始图像添加50%的噪声,并应用中值模糊。

median = cv.medianBlur(img,5)

在这里插入图片描述

双边滤波

cv.bilateralFilter()在去除噪声的同时保持边缘的锐利。但与其他过滤器相比,操作要慢一些。我们已经看到,高斯滤波器取像素周围的邻域并求其高斯加权平均值。该高斯滤波器是一个单独的空间函数,即在滤波时考虑附近的像素。它没有考虑像素是否具有几乎相同的强度,也没有考虑一个像素是否为边缘像素。所以它也模糊了边缘,这是我们不想做的。

双边滤波在空间上也采用高斯滤波器,但多了一个高斯滤波器,它是像素差的函数。空间的高斯函数保证了只考虑附近的像素进行模糊处理,而强度差的高斯函数保证了只考虑与中心像素强度相近的像素进行模糊处理。所以它保留了边缘,因为边缘上的像素会有很大的强度变化。

下面的示例展示了双边过滤器的使用(有关参数的详细信息,请访问docs)。

blur = cv.bilateralFilter(img,9,75,75)

在这里插入图片描述
看,表面的纹理消失了,但边缘还在。

3. 扩展资料

  1. Details about the bilateral filtering

参考:https://docs.opencv.org/4.5.2/d6/d00/tutorial_py_root.html

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

薛定猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值