OpenCV中的图像处理 —— 图像阈值+图像平滑+形态转换

举报
ErrorError! 发表于 2022/04/20 10:12:04 2022/04/20
【摘要】 首先我们要了解什么是阈值,阈值能干什么?简单阈值是我们设置的一个临界值,这个临界值的作用就是对应图像中的每一个像素,如果它小于这个临界值就将其设置为0,若其大于这个临界值则将其设置为最大值(一般为255),在使用阈值之后的图像就会只剩两个颜色像素:最大值和最小值,在掩膜的运用比较多,我们后续详细讲

OpenCV中的图像处理 —— 图像阈值+图像平滑+形态转换

1. 图像阈值

关于图像阈值主要涉及到两个函数:cv.thresholdcv.adaptiveThreshold(即简单阈值和自适应阈值)

1.1 简单阈值

首先我们要了解什么是阈值,阈值能干什么?简单阈值是我们设置的一个临界值,这个临界值的作用就是对应图像中的每一个像素,如果它小于这个临界值就将其设置为0,若其大于这个临界值则将其设置为最大值(一般为255),在使用阈值之后的图像就会只剩两个颜色像素:最大值和最小值,在掩膜的运用比较多,我们后续详细讲

我们先说简单阈值,简单阈值涉及的函数是cv.threshold(),其中需要传入4个参数,第一个参数即是我们的图像对象,需要注意的是一般我们需要在这里传入一个单通道灰度图,第二个参数是阈值,用于对整个图像的像素做一个分类,第三个参数是分配的最大值,即当像素大于我们设置的阈值时,使其等于这个我们设置的最大值即可,第四个参数是一个表示不同类型的标志,其取值可以是:cv.THRESH_BINARY,cv.THRESH_BINARY_INV,cv.THRESH_TRUNC,cv.THRESH_TOZERO,cv.THRESH_TOZERO_INV

下面我们通过一个例子来展示这些不同类型阈值的使用结果

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')
     plt.title(titles[i])
     plt.xticks([]),plt.yticks([])
plt.show()

在上面的代码的显示图像过程中出现了一个很重要的plt.subplot()函数,这是由matplotlib库提供的一个绘图函数,其使用方法也比较简单,就是将若干个图像按照行列的形式展示出来,plt.subplot()传入的参数有三个,第一二个参数指的是行和列数,第三个指的是显示的图片是在第几个位置

plt还提供了plt.imshow()、pli.title() 和 plt.x/yticks()用来完善我们的图像展示

1.2 自适应阈值

在简单阈值中我们指定了一个阈值作为整张图片的固定阈值,但是有时候一些图片的各个部分的光照角度乃至角度都不一样,这个时候我们如果还使用简单阈值,那效果可想而知

只要思想不滑坡,办法总比困难多!这个时候我们就可以使用自适应阈值解决这种问题

自适应阈值关系到函数cv.adaptiveThreshold(),关于这个函数需要传入的参数就有点小复杂了,我们借助一篇文章来了解:图像的二值化-cv2.threshold()、cv2.adaptiveThreshold()

先来观察一下cv.adaptiveThredshold()函数使用时的样子

th3 = cv.adaptiveThreshold(img,255,cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, 11, 2)

是不是发现这参数有些小多,我们一个一个来看,第一个参数老生常谈,就是我们的图像资源src,第二个参数指的就是像素值上限,第三个参数就有意思了,它指的是自适应方法,而自适应方法可供我们使用的有两种(不知道还有没有其他的,感兴趣的小伙伴可以去查一查):

  • cv2.ADAPTIVE_THRESH_MEAN_C :领域内均值
  • cv2.ADAPTIVE_THRESH_GAUSSIAN_C :领域内像素点加权和,权重为一个高斯窗口

第四个参数只有两个值可以赋给它:cv2.THRESH_BINARY 和cv2.THRESH_BINARY_INV,第五个参数Block size指的是规定领域大小,BlockSize值越大,参与计算阈值的区域也越大,细节轮廓就变得越少,整体轮廓越粗越明显,第六个参数是常数C,C越大,每个像素点的N*N邻域计算出的阈值就越小,中心点大于这个阈值的可能性也就越大,设置成255的概率就越大,整体图像白色像素就越多,反之亦然

1.3 Otsu的二值化

在全局阈值化中,我们使用任意选择的值作为阈值。相反,Otsu的方法避免了必须选择一个值并自动确定它的情况

首先我们来了解一下什么是双峰图像,顾名思义就是仅有两个不同图像值的图像,其中直方图仅包含两个峰,而一个好的阈值就应该处于这两个峰之间才能达到最好的图像处理效果,而Otsu的方法就是从图像直方图中确定最佳的全局阈值(跟自适应阈值完全不一样,自适应阈值是对应不同的区域自动确定阈值,而Otsu方法是根据双峰图像来确定一个最佳的全局阈值)

我们依旧通过一个例子来掌握这几种方法

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt

img = cv.imread(r"E:\image\test03.png", 0)
# 全局阈值
ret1, th1 = cv.threshold(img, 127, 255, cv.THRESH_BINARY)
# Otsu阈值
ret2, th2 = cv.threshold(img, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)
# 高斯滤波后再采用Otsu阈值
blur = cv.GaussianBlur(img, (5, 5), 0)
ret3, th3 = cv.threshold(blur, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)
# 绘制所有图像及其直方图
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()

2. 图像平滑

2.1 2D卷积(图像过滤)

在学习图像平滑之前我们要先了解一下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()

我们发现在上述代码中出现了一个陌生的东西:kernel,这是一个5x5且数据类型为float32的数组,我们也可以理解为这就是我们的5x5平均滤波器的内核,这个5x5的数组中存储了25个低于内核像素的像素,把这个数组传入cv.filter2D()后会实现最终的图像平滑

接下来我们再说说cv.filter2D()这个函数,上面说到它是一个用来将图像和内核进行卷积的函数,而内核由我们自己作为参数传入,cv.filter2D()有三个必须传入的参数:src、ddepth和kernel,src当然指的就是我们的图像资源(原图像),ddepth是目标图像深度,这个东西有些不好理解,但是一般情况下我们都设为-1,kernel指的就是我们的卷积内核,它是一个numpy.ndarray 类型的矩阵,这个矩阵可以用numpy函数生成,但是在后续图像处理技术中,我们需要制造一些很复杂的卷积核,这个时候使用numpy的函数就显得不够用了,这个时候我们需要使用OpenCV的内置函数:getStructuringElement、getGaussianKernel等来满足我们的需求

2.2 图像平滑(图像模糊)

通过将图像与低通滤波器内核进行卷积来实现图像模糊,低通滤波器LPF对消除噪声非常有效,它从实际图片上消除了高频的部分(例如噪声和边缘),当然它对边缘不太友好,此操作的结果就是边缘比较模糊,OpenCV提供了四种类型的模糊技术

1、平均

我们上面演示了使用5x5平均滤波器内核来实现操作,但是“平均”这种技术也是有着它自己的函数方便我们操作

它仅获取内核区域下所有像素的平均值,并替换中心元素,这是通过功能函数**cv.blur()cv.boxFilter()**完成的,在进行操作时我们需要指定内核的宽度和高度

cv.boxFilter()函数是在我们不行使用标准化的框式过滤器时使用的,并将参数normalize = False传递进去

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()

2、高斯模糊

高斯模糊代替了盒式滤波器,使用了高斯核来实现图像平滑,这是通过功能cv.GaussianBlur() 完成的,我们应指定内核的宽度和高度,该宽度和高度应为正数和奇数。我们还应指定X和Y方向的标准偏差,分别为sigmaX和sigmaY

如果仅指定sigmaX,则将sigmaY与sigmaX相同,如果两个都为零,则根据内核大小进行计算

对于一些特殊的需求我们会使用cv.getGaussianKernel()函数来创建高斯核

#  我们可以通过修改上面的代码实现高斯模糊
blur = cv.GaussianBlur(img,(5,5),0)

上述代码中cv.GaussianBlur()中传入的参数img即是原图像,(5,5)则是高斯核的大小,0指的是sigmaX和sigmaY都为0,此时其值根据内核大小计算

3、中位模糊

函数cv.medianBlur() 提取内核区域下所有像素的中值,并将中心元素替换为该中值,这对于消除图像中的椒盐噪声非常有效,在平均中,内核中心元素是新计算的平均值,而中位模糊的内核中心元素是图像中的像素值或新值,但是在中位模糊中中心元素总是被某些像素代替

中位模糊的内核大小也应为整技术整数

median = cv.medianBlur(img,5)

img指图像资源,5指的是内核大小

4、双边滤波

cv.bilateralFilter() 在去除噪声的同时保持边缘清晰锐利非常有效,但是,与其他过滤器相比,该操作速度较慢

高斯滤波器采用像素周围的邻域并找到其高斯加权平均值,高斯滤波器仅仅是控件的函数,即它在工作时仅考虑附近的像素,而不考虑像素是否具有相同的强度也不考虑像素是否是边缘像素,所以它对边缘的清晰锐利保持很不友好

但是双边滤波器改善了这种缺陷,它内部有两个高斯滤波器,一个是上述的一般高斯滤波器,而另一个是像素差的函数,空间的高斯函数确保仅考虑附近元素的模糊,强度差的高斯函数确保仅考虑强度与中心元素相似的像素的模糊(即加了一个显示,如果像素的强度与中心元素差距较大,就不会令其模糊从而保持边缘清晰锐利)

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

3. 形态转换

这一块儿我们说说OpenCV处理图像时在形态学的操作,这些操作有:侵蚀和膨胀、开运算和闭运算、形态学梯度、顶帽和黑帽

首先我们要了解什么是形态学变换,形态学变换就是基于图像形状的简单操作,通常在二进制图像上执行,一般需要两个输入:原始图像和决定操作性质的结构元素或内核

3.1 侵蚀与膨胀

侵蚀:内核滑动通过图像(在2D卷积中),原始图像中的一个像素(无论是1还是0)只有当内核下的所有像素都是1时才被认为是1,否则它就会被侵蚀(变成0)

侵蚀的结果就是根据内核的大小,边界附近的所有像素都会被丢弃,因此,前景物体的厚度或大小减小,或只是图像中的白色区域减小,它有助于去除小的白色噪声

import cv2 as cv
import numpy as np
img = cv.imread('j.png',0)
# 创建内核
kernel = np.ones((5,5),np.uint8)
# 使用侵蚀函数处理图像
erosion = cv.erode(img,kernel,iterations = 1)
plt.subplot(121),plt.imshow(img),plt.title('Original')
plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(erosion),plt.title('Erosion')
plt.xticks([]), plt.yticks([])
plt.show()

膨胀:如果内核下的至少一个像素为“ 1”,则像素元素为“ 1”。因此,它会增加图像中的白色区域或增加前景对象的大小,通常,在消除噪音的情况下,腐蚀后会膨胀,因为腐蚀会消除白噪声,但也会缩小物体

dilation = cv.dilate(img,kernel,iterations = 1)

3.2 开运算与闭运算

开放只是“侵蚀后扩张”的另一个名称,它对于消除噪音很有用,为了实现这个操作我们要使用函数cv.morphologyEx()

闭运算与开运算相反,先扩张然后再侵蚀,在关闭前景对象内部的小孔或对象上的小黑点时很有用

opening = cv.morphologyEx(img, cv.MORPH_OPEN, kernel)  # 开运算
opening = cv.morphologyEx(img, cv.MORPH_CLOSE, kernel)  # 闭运算  

这里的参数我们有必要再留意一下,第一个参数即原图像,第二个参数指的是进行变化的方式,cv2.MORPH_OPEN 进行开运算,cv2.MORPH_CLOSE 进行闭运算

3.3 顶帽与黑帽

顶帽是输入图像和图像开运算之差,而黑帽是输入图像和图像闭运算之差

tophat = cv.morphologyEx(img, cv.MORPH_TOPHAT, kernel) # 顶帽
blackhat = cv.morphologyEx(img, cv.MORPH_BLACKHAT, kernel) # 黑帽

黑帽是输入图像和图像闭运算之差

3.4 结构元素

在Numpy的帮助下,我们在前面的示例中手动创建了一个结构元素(内核),它是矩形的,但是在某些情况下,我们可能需要椭圆形/圆形的内核,因此,OpenCV提供了函数cv.getStructuringElement(),我们只需传递内核的形状和大小,即可获得所需的内核


(注:文章内容参考OpenCV4.1中文官方文档)
如果文章对您有所帮助,记得一键三连支持一下哦

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

举报
请填写举报理由
0/200