【OpenCV】Chapter8.形态学图像处理

最近想对OpenCV进行系统学习,看到网上这份教程写得不错,于是跟着来学习实践一下。
[email protected], youcans 的 OpenCV 例程, https://youcans.blog.csdn.net/article/details/125112487
程序仓库:https://github.com/zstar1003/OpenCV-Learning

形态学的基本思想是利用结构元素测量或提取输入图像中的形状或特征,以便进行图像分析和目标识别。

形态学图像处理的运算是用集合定义的,基本运算包括:二值腐蚀和膨胀,二值开闭运算,骨架抽取,极限腐蚀,击中击不中变换,形态学梯度,顶帽变换,颗粒分析,流域变换,灰值腐蚀和膨胀,灰值开闭运算,灰值形态学梯度等。

图像的腐蚀

腐蚀就是原图中的高亮部分被腐蚀,腐蚀的效果拥有比原图更小的高亮区域,可以去掉毛刺,去掉孤立的像素,提取骨干信息。

腐蚀的原理是求局部最小值的操作,将 0 值扩充到邻近像素,从而扩大黑色值范围、压缩白色值范围。

用卷积来描述腐蚀操作,结构元素 B 是中心为 1、其它为 0 的卷积核:
(1)卷积核 B 沿着图像滑动,扫描图像 A 的每一个像素;
(2)用结构元素与其覆盖的二值图像进行 “或操作”;
(3)如果图像与卷积核对应区域的所有像素值都是 1,则图像的该像素值仍为 1;否则为 0。

OpenCV 提供了函数cv.erode可以实现图像的腐蚀。

cv.erode(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]]) → dst

参数说明:

  • src:输入图像,可以为单通道或多通道,图像深度必须为 CV_8U, CV_16U, CV_16S, CV_32F 或 CV_64F
  • dst:输出图像,大小和类型与 src 相同
  • kernel:结构元(卷积核),null 时使用 3*3 矩形结构元素
  • anchor:卷积核的锚点位置,默认值 (-1, -1) 表示以卷积核的中心为锚点
  • iterations:应用腐蚀操作的次数,可选项,默认值为 1
  • borderType:边界扩充的类型
  • borderValue:当 borderType=BORDER_CONSTANT 时以常量 value 填充扩充边界,默认值为 (0,0,0)

示例程序:
首先需要对原图进行二值化处理

"""
图像的腐蚀
"""
import cv2
import matplotlib.pyplot as plt
import numpy as np


imgGray = cv2.imread("../img/lena.jpg", 0)
ret, imgBin = cv2.threshold(imgGray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)  # 二值化处理

# 图像腐蚀
kSize = (3, 3)  # 卷积核的尺寸
kernel = np.ones(kSize, dtype=np.uint8)  # 生成盒式卷积核
imgErode1 = cv2.erode(imgBin, kernel=kernel)  # 图像腐蚀

kSize = (9, 9)
kernel = np.ones(kSize, dtype=np.uint8)
imgErode2 = cv2.erode(imgBin, kernel=kernel)

kSize = (25, 25)
kernel = np.ones(kSize, dtype=np.uint8)
imgErode3 = cv2.erode(imgBin, kernel=kernel)

plt.figure(figsize=(10, 5))
plt.subplot(141), plt.axis('off'), plt.title("Origin")
plt.imshow(imgBin, cmap='gray', vmin=0, vmax=255)
plt.subplot(142), plt.title("eroded kSize=(3,3)"), plt.axis('off')
plt.imshow(imgErode1, cmap='gray', vmin=0, vmax=255)
plt.subplot(143), plt.title("eroded kSize=(9,9)"), plt.axis('off')
plt.imshow(imgErode2, cmap='gray', vmin=0, vmax=255)
plt.subplot(144), plt.title("eroded kSize=(25,25)"), plt.axis('off')
plt.imshow(imgErode3, cmap='gray', vmin=0, vmax=255)
plt.tight_layout()
plt.show()

可以看到卷积核越大,高亮部分被腐蚀得越严重。
在这里插入图片描述

图像的膨胀

膨胀是腐蚀的逆过程。膨胀的原理是求局部最大值的操作,将 1 值扩充到邻近像素,从而扩大白色值范围、压缩黑色值范围。

用卷积来描述膨胀操作,结构元素 B 是中心为 1、其它为 0 的卷积核:
(1)卷积核 B 沿着图像滑动,扫描图像 A 的每一个像素;
(2)用结构元素与其覆盖的二值图像进行 “与操作”;
(3)如果图像与卷积核对应区域的所有像素值都是 0,则图像的该像素值仍为 0;否则为 1。

OpenCV 提供了函数cv.dilate可以实现图像的膨胀。

cv.dilate(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]]) → dst

参数说明:

  • src:输入图像,可以为单通道或多通道,图像深度必须为 CV_8U, CV_16U, CV_16S, CV_32F 或 CV_64F
  • dst:输出图像,大小和类型与 src 相同
  • kernel:结构元(卷积核),null 时使用 3*3 矩形卷积核
  • anchor:卷积核的锚点位置,默认值 (-1, -1) 表示以卷积核的中心为锚点
  • iterations:应用膨胀的次数,可选项,默认值为 1
  • borderType:边界扩充的类型
  • borderValue:当 borderType=BORDER_CONSTANT 时以常量 value 填充扩充边界,默认值为 (0,0,0)

示例程序:

"""
图像的膨胀
"""
import cv2
import matplotlib.pyplot as plt
import numpy as np


imgGray = cv2.imread("../img/lena.jpg", 0)
ret, imgBin = cv2.threshold(imgGray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)  # 二值化处理

# 图像膨胀
kSize = (3, 3)  # 卷积核的尺寸
kernel = np.ones(kSize, dtype=np.uint8)  # 生成盒式卷积核
imgDilate1 = cv2.dilate(imgBin, kernel=kernel)  # 图像膨胀

kSize = (5, 5)
kernel = np.ones(kSize, dtype=np.uint8)
imgDilate2 = cv2.dilate(imgBin, kernel=kernel)  # 图像膨胀

kSize = (7, 7)
kernel = np.ones(kSize, dtype=np.uint8)
imgDilate3 = cv2.dilate(imgBin, kernel=kernel)  # 图像膨胀

plt.figure(figsize=(10, 5))
plt.subplot(141), plt.axis('off'), plt.title("Origin")
plt.imshow(imgBin, cmap='gray', vmin=0, vmax=255)
plt.subplot(142), plt.title("dilate kSize=(3,3)"), plt.axis('off')
plt.imshow(imgDilate1, cmap='gray', vmin=0, vmax=255)
plt.subplot(143), plt.title("dilate kSize=(5,5)"), plt.axis('off')
plt.imshow(imgDilate2, cmap='gray', vmin=0, vmax=255)
plt.subplot(144), plt.title("dilate kSize=(7,7)"), plt.axis('off')
plt.imshow(imgDilate3, cmap='gray', vmin=0, vmax=255)
plt.tight_layout()
plt.show()

在这里插入图片描述

开运算

开运算就是先腐蚀后膨胀的过程,通常用于去除噪点、断开狭窄的狭颈、消除细长的突出、平滑物体边界但不改变面积。

OpenCV 提供了函数cv.morphologyEx可以实现图像的开运算。

cv.morphologyEx(src, op, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]] )→ dst

参数说明:

  • src:输入图像,可以为单通道或多通道,图像深度必须为 CV_8U, CV_16U, CV_16S, CV_32F 或 CV_64F
  • dst:输出图像,大小和类型与 src 相同
  • op:形态学运算类型
    • cv.MORPH_ERODE:腐蚀
    • cv.MORPH_DILATE:膨胀
    • cv.MORPH_OPEN:开运算, 先腐蚀再膨胀
    • cv.MORPH_CLOSE:闭运算, 先膨胀再腐蚀
    • cv.MORPH_GRADIENT:形态学梯度, 膨胀图与腐蚀图之差
    • cv.MORPH_TOPHAT:顶帽变换, 原图像与开运算之差
    • cv.MORPH_BLACKHAT:黑帽变换, 闭运算图与原图像之差
    • cv.MORPH_HITMISS: 击中击不中运算
  • kernel:结构元(卷积核),null 时使用 3*3 矩形卷积核
  • anchor:卷积核的锚点位置,负值表示以卷积核的中心为锚点
  • iterations:应用腐蚀和膨胀的次数,可选项,默认值为 1
  • borderType:边界扩充的类型
  • borderValue:当 borderType=BORDER_CONSTANT 时以常量 value 填充扩充边界,默认值为 (0,0,0)

示例代码:

"""
图像开运算
"""
import cv2
import matplotlib.pyplot as plt
import numpy as np


imgGray = cv2.imread("../img/lena_noise.jpg", 0)
ret, imgBin = cv2.threshold(imgGray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)  # 二值化处理

# 图像的开运算
kSize = (3, 3)  # 卷积核的尺寸
kernel = np.ones(kSize, dtype=np.uint8)  # 生成盒式卷积核
imgOpen = cv2.morphologyEx(imgGray, cv2.MORPH_OPEN, kernel)

plt.figure(figsize=(9, 5))
plt.subplot(121), plt.axis('off'), plt.title("Origin")
plt.imshow(imgGray, cmap='gray', vmin=0, vmax=255)
plt.subplot(122), plt.title("Opening kSize=(3,3)"), plt.axis('off')
plt.imshow(imgOpen, cmap='gray', vmin=0, vmax=255)
plt.tight_layout()
plt.show()

在这里插入图片描述

闭运算

闭运算就是先膨胀后腐蚀的过程,通常用于弥合狭窄的断裂和细长的沟壑,消除小孔,填补轮廓中的缝隙,消除噪点,连接相邻的部分。

OpenCV 中的函数cv.morphologyEx也可以实现图像的闭运算,但要将参数 op 设为MORPH_CLOSE

cv.morphologyEx(src, op, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]] )→ dst

参数说明:

  • src:输入图像,可以为单通道或多通道,图像深度必须为 CV_8U, CV_16U, CV_16S, CV_32F 或 CV_64F
  • dst:输出图像,大小和类型与 src 相同
  • op:形态学运算类型
    • cv.MORPH_ERODE:腐蚀
    • cv.MORPH_DILATE:膨胀
    • cv.MORPH_OPEN:开运算, 先腐蚀再膨胀
    • cv.MORPH_CLOSE:闭运算, 先膨胀再腐蚀
  • kernel:结构元(卷积核),null 时使用 3*3 矩形卷积核
  • anchor:卷积核的锚点位置,负值表示以卷积核的中心为锚点
  • iterations:应用腐蚀和膨胀的次数,可选项,默认值为 1

示例程序:

"""
图像闭运算
"""
import cv2
import matplotlib.pyplot as plt
import numpy as np


imgGray = cv2.imread("../img/lena_noise.jpg", 0)
mu, sigma = 0.0, 10.0
noiseGause = np.random.normal(mu, sigma, imgGray.shape)
imgNoisy = imgGray + noiseGause
imgNoisy = np.uint8(cv2.normalize(imgNoisy, None, 0, 255, cv2.NORM_MINMAX))  # 归一化为 [0,255]
ret, imgBin = cv2.threshold(imgNoisy, 125, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)  # 二值化处理

# 图像的闭运算
kSize = (2, 2)  # 卷积核的尺寸
kernel = np.ones(kSize, dtype=np.uint8)  # 生成盒式卷积核
imgClose1 = cv2.morphologyEx(imgBin, cv2.MORPH_CLOSE, kernel)

kSize = (3, 3)  # 卷积核的尺寸
kernel = np.ones(kSize, dtype=np.uint8)  # 生成盒式卷积核
imgClose2 = cv2.morphologyEx(imgBin, cv2.MORPH_CLOSE, kernel)

kSize = (5, 5)  # 卷积核的尺寸
kernel = np.ones(kSize, dtype=np.uint8)  # 生成盒式卷积核
imgClose3 = cv2.morphologyEx(imgBin, cv2.MORPH_CLOSE, kernel)

plt.figure(figsize=(10, 5))
plt.subplot(141), plt.axis('off'), plt.title("Origin")
plt.imshow(imgNoisy, cmap='gray', vmin=0, vmax=255)
plt.subplot(142), plt.title("Closed kSize=(2,2)"), plt.axis('off')
plt.imshow(imgClose1, cmap='gray', vmin=0, vmax=255)
plt.subplot(143), plt.title("Closed kSize=(3,3)"), plt.axis('off')
plt.imshow(imgClose2, cmap='gray', vmin=0, vmax=255)
plt.subplot(144), plt.title("Closed kSize=(5,5)"), plt.axis('off')
plt.imshow(imgClose3, cmap='gray', vmin=0, vmax=255)
plt.tight_layout()
plt.show()

在这里插入图片描述

顶帽运算和底帽运算

顶帽变换和底帽变换用结构元通过开操作或闭操作从一副图像中删除物体,得到仅保留已删除分量的图像。顶帽变换用于暗背景上的亮物体,而底帽变换则用于用于亮背景上的暗物体,常用于校正不均匀光照的影响。

顶帽运算可以提取图像的噪声信息,也用于校正不均匀光照的影响,用来分离比邻近点亮的斑块。

OpenCV 中的函数cv.morphologyEx可以实现图像的顶帽运算和底帽运算,参数 op 则要分别设为 MORPH_TOPHAT、MORPH_BLACKHAT。

cv.morphologyEx(src, op, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]] )→ dst

参数说明:

  • src:输入图像,可以为单通道或多通道,图像深度必须为 CV_8U, CV_16U, CV_16S, CV_32F 或 CV_64F 。
  • op:形态学运算类型
    • cv.MORPH_OPEN:开运算, 先腐蚀再膨胀
    • cv.MORPH_CLOSE:闭运算, 先膨胀再腐蚀
    • cv.MORPH_GRADIENT:形态学梯度, 膨胀图与腐蚀图之差
    • cv.MORPH_TOPHAT:顶帽变换, 原图像与开运算之差
    • cv.MORPH_BLACKHAT:黑帽变换, 闭运算图与原图像之差
  • kernel:结构元(卷积核),null 时使用 3*3 矩形卷积核

顶帽运算示例程序:

"""
图像顶帽运算
"""
import cv2
import matplotlib.pyplot as plt
import numpy as np


imgGray = cv2.imread("../img/lena_noise.jpg", 0)
ret, imgBin = cv2.threshold(imgGray, 205, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)  # 二值化处理

kernel = np.ones((5, 5), np.uint8)  # 卷积核
imgOpen = cv2.morphologyEx(imgBin, cv2.MORPH_OPEN, kernel)  # 开运算
imgThat = cv2.morphologyEx(imgBin, cv2.MORPH_TOPHAT, kernel)  # 顶帽运算

plt.figure(figsize=(9, 5))
plt.subplot(131), plt.axis('off'), plt.title("Origin")
plt.imshow(imgBin, cmap='gray', vmin=0, vmax=255)
plt.subplot(132), plt.title("MORPH_OPEN"), plt.axis('off')
plt.imshow(imgOpen, cmap='gray', vmin=0, vmax=255)
plt.subplot(133), plt.title("MORPH_TOPHAT"), plt.axis('off')
plt.imshow(imgThat, cmap='gray', vmin=0, vmax=255)
plt.tight_layout()
plt.show()

在这里插入图片描述

底帽运算突出了比原图轮廓周围的区域更暗的区域,而且效果与核的大小相关,可以分离比邻近点暗的斑块。

底帽运算示例程序:

"""
图像底帽运算
"""
import cv2
import matplotlib.pyplot as plt
import numpy as np


imgGray = cv2.imread("../img/lena_noise.jpg", 0)
ret, imgBin = cv2.threshold(imgGray, 127, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)  # 二值化处理

kernel = np.ones((5, 5), np.uint8)  # 卷积核
imgClose = cv2.morphologyEx(imgBin, cv2.MORPH_CLOSE, kernel)  # 闭运算
imgBhat = cv2.morphologyEx(imgBin, cv2.MORPH_BLACKHAT, kernel)  # 底帽运算

plt.figure(figsize=(9, 5))
plt.subplot(131), plt.axis('off'), plt.title("Origin")
plt.imshow(imgBin, cmap='gray', vmin=0, vmax=255)
plt.subplot(132), plt.title("MORPH_CLOSE"), plt.axis('off')
plt.imshow(imgClose, cmap='gray', vmin=0, vmax=255)
plt.subplot(133), plt.title("MORPH_BLACKHAT"), plt.axis('off')
plt.imshow(imgBhat, cmap='gray', vmin=0, vmax=255)
plt.tight_layout()
plt.show()

在这里插入图片描述

形态学梯度

图像的形态学梯度运算,是膨胀图像与腐蚀图像之差 ,可以得到图像的轮廓,通常用于提取物体边缘。

OpenCV 中的函数cv.morphologyEx可以实现形态学梯度运算,但要将参数 op 设为MORPH_GRADIENT

该函数和上一节一样。

示例程序:

"""
形态学梯度运算
"""
import cv2
import matplotlib.pyplot as plt
import numpy as np


imgGray = cv2.imread("../img/lena.jpg", 0)
ret, imgBin = cv2.threshold(imgGray, 15, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)  # 二值化处理

# 图像的形态学梯度
kSize = (3, 3)  # 卷积核的尺寸
kernel = np.ones(kSize, dtype=np.uint8)  # 生成盒式卷积核
imgGrad1 = cv2.morphologyEx(imgBin, cv2.MORPH_GRADIENT, kernel)  # 形态学梯度

kSize = (5, 5)  # 卷积核的尺寸
kernel = np.ones(kSize, dtype=np.uint8)  # 生成盒式卷积核
imgGrad2 = cv2.morphologyEx(imgBin, cv2.MORPH_GRADIENT, kernel)  # 形态学梯度

kSize = (3, 3)  # 卷积核的尺寸
kernel = np.ones(kSize, dtype=np.uint8)  # 生成盒式卷积核
imgOpen = cv2.morphologyEx(imgBin, cv2.MORPH_OPEN, kernel)  # 开运算
imgOpenGrad = cv2.morphologyEx(imgOpen, cv2.MORPH_GRADIENT, kernel)  # 形态学梯度

plt.figure(figsize=(10, 5))
plt.subplot(141), plt.axis('off'), plt.title("Origin")
plt.imshow(imgGray, cmap='gray', vmin=0, vmax=255)
plt.subplot(142), plt.title("Gradient (size=3)"), plt.axis('off')
plt.imshow(imgGrad1, cmap='gray', vmin=0, vmax=255)
plt.subplot(143), plt.title("Gradient (size=5)"), plt.axis('off')
plt.imshow(imgGrad2, cmap='gray', vmin=0, vmax=255)
plt.subplot(144), plt.title("Opening -> Gradient"), plt.axis('off')
plt.imshow(imgOpenGrad, cmap='gray', vmin=0, vmax=255)
plt.tight_layout()
plt.show()

在这里插入图片描述

击中-击不中变换

击中-击不中是形态检测的基本工具,可以实现对象的细化和剪枝操作,常用于物体识别、图像细化。

击中击不中变换有两个结构元,B1 是当前位置可以有的形状,B2是当前位置不可以有的形状,在实际运算中是先后进行两次腐蚀运算,然后取交集。

OpenCV 中的函数cv.morphologyEx可以实现击中-击不中变换,但要将参数 op 设为cv.MORPH_HITMISS

示例程序

"""
击中-击不中变换
"""
import cv2
import matplotlib.pyplot as plt
import numpy as np


imgGray = cv2.imread("../img/lena.jpg", 0)
ret, imgBin = cv2.threshold(imgGray, 25, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)  # 二值化处理

# 闭运算
kernel = np.ones((3, 3), dtype=np.uint8)  # 生成盒式卷积核
imgClose = cv2.morphologyEx(imgBin, cv2.MORPH_CLOSE, kernel)  # 闭运算
# 击中击不中变换
kernB1 = np.array([[0, 0, 0], [0, -1, 1], [0, 0, 0]], dtype=np.int32)  # B1
kernB2 = np.array([[0, 0, 0], [1, -1, 0], [0, 0, 0]], dtype=np.int32)  # B2
imgH1 = cv2.morphologyEx(imgClose, cv2.MORPH_HITMISS, kernB1)
imgH2 = cv2.morphologyEx(imgClose, cv2.MORPH_HITMISS, kernB2)
imgHMT = cv2.add(imgH1, imgH2)  # 击中击不中

plt.figure(figsize=(10, 5))
plt.subplot(141), plt.axis('off'), plt.title("Origin")
plt.imshow(imgBin, cmap='gray', vmin=0, vmax=255)
plt.subplot(142), plt.title("kern B1"), plt.axis('off')
plt.imshow(imgH1, cmap='gray', vmin=0, vmax=255)
plt.subplot(143), plt.title("kern B2"), plt.axis('off')
plt.imshow(imgH2, cmap='gray', vmin=0, vmax=255)
plt.subplot(144), plt.title("HMT"), plt.axis('off')
plt.imshow(imgHMT, cmap='gray', vmin=0, vmax=255)
plt.tight_layout()
plt.show()

在这里插入图片描述

灰度腐蚀

灰度腐蚀和灰度膨胀与腐蚀和膨胀类似,只是并不在二值化图像中,而是在灰度图像中。

OpenCV 提供了函数cv.erode可以实现图像的灰度腐蚀:

cv.erode(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]]) → dst

函数cv.getStructuringElement可以构造不同形状和尺寸的结构元:

cv.getStructuringElement(shape, ksize[, anchor] ) -> retval

参数说明:

  • shape:结构元素形状的类型
    • cv.MORPH_RECT:矩形,全 1 矩阵
    • cv.MORPH_CROSS:交叉型, 十字交叉为 1,其它为 0
    • cv.MORPH_ELLIPSE: 椭圆型,椭圆内为 1,其它为 0
  • ksize:内核的尺寸
  • anchor:卷积核的锚点位置,可选项,默认值 (-1, -1) 表示锚点为中心点
  • retval:返回指定形状和尺寸的结构元素

示例程序:

"""
灰度腐蚀
"""
import cv2
import matplotlib.pyplot as plt
import numpy as np


imgGray = cv2.imread("../img/lena.jpg", 0)

# 图像腐蚀
element = cv2.getStructuringElement(cv2.MORPH_CROSS, (3, 3))
imgErode1 = cv2.erode(imgGray, kernel=element)  # 图像腐蚀

element = cv2.getStructuringElement(cv2.MORPH_CROSS, (5, 5))
imgErode2 = cv2.erode(imgGray, kernel=element)  # 图像腐蚀

element = cv2.getStructuringElement(cv2.MORPH_CROSS, (9, 9))
imgErode3 = cv2.erode(imgGray, kernel=element)  # 图像腐蚀

plt.figure(figsize=(10, 5))
plt.subplot(141), plt.axis('off'), plt.title("Origin")
plt.imshow(imgGray, cmap='gray', vmin=0, vmax=255)
plt.subplot(142), plt.title("eroded kSize=(3,3)"), plt.axis('off')
plt.imshow(imgErode1, cmap='gray', vmin=0, vmax=255)
plt.subplot(143), plt.title("eroded kSize=(9,9)"), plt.axis('off')
plt.imshow(imgErode2, cmap='gray', vmin=0, vmax=255)
plt.subplot(144), plt.title("eroded kSize=(25,25)"), plt.axis('off')
plt.imshow(imgErode3, cmap='gray', vmin=0, vmax=255)
plt.tight_layout()
plt.show()

在这里插入图片描述

灰度膨胀

OpenCV 提供了函数cv.dilate可以实现图像的灰度膨胀:

cv.dilate(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]]) → dst

示例程序:

"""
灰度膨胀
"""
import cv2
import matplotlib.pyplot as plt
import numpy as np


imgGray = cv2.imread("../img/lena.jpg", 0)

# 灰度膨胀
element = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
imgDilate1 = cv2.dilate(imgGray, kernel=element)  # 图像膨胀

element = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
imgDilate2 = cv2.dilate(imgGray, kernel=element)  # 图像膨胀

plt.figure(figsize=(9, 6))
plt.subplot(131), plt.axis('off'), plt.title("Origin")
plt.imshow(imgGray, cmap='gray', vmin=0, vmax=255)
plt.subplot(132), plt.title("Dilate kSize=(3,3)"), plt.axis('off')
plt.imshow(imgDilate1, cmap='gray', vmin=0, vmax=255)
plt.subplot(133), plt.title("Dilate kSize=(5,5)"), plt.axis('off')
plt.imshow(imgDilate2, cmap='gray', vmin=0, vmax=255)
plt.tight_layout()
plt.show()

在这里插入图片描述

灰度开运算和闭运算

OpenCV 提供了函数cv.morphologyEx可以实现图像的灰度开运算和灰度闭运算。

cv.morphologyEx(src, op, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]] )→ dst

参数说明:

  • src:输入图像,可以为单通道或多通道,图像深度必须为 CV_8U, CV_16U, CV_16S, CV_32F 或 CV_64F
  • dst:输出图像,大小和类型与 src 相同
  • op:形态学运算类型
    • cv.MORPH_ERODE:腐蚀
    • cv.MORPH_DILATE:膨胀
    • cv.MORPH_OPEN:开运算, 先腐蚀再膨胀
    • cv.MORPH_CLOSE:闭运算, 先膨胀再腐蚀
    • cv.MORPH_GRADIENT:形态学梯度, 膨胀图与腐蚀图之差
    • cv.MORPH_TOPHAT:顶帽变换, 原图像与开运算之差
    • cv.MORPH_BLACKHAT:黑帽变换, 闭运算图与原图像之差
    • cv.MORPH_HITMISS: 击中击不中运算
  • kernel:结构元(卷积核),null 时使用 3*3 矩形卷积核
  • anchor:卷积核的锚点位置,负值表示以卷积核的中心为锚点
  • iterations:应用腐蚀和膨胀的次数,可选项,默认值为 1
  • borderType:边界扩充的类型
  • borderValue:当 borderType=BORDER_CONSTANT 时以常量 value 填充扩充边界,默认值为 (0,0,0)

示例程序(包含灰度服饰膨胀、开运算、闭运算、形态学梯度:

"""
灰度开运算和闭运算
"""
import cv2
import matplotlib.pyplot as plt
import numpy as np


imgGray = cv2.imread("../img/lena.jpg", 0)

element = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))  # 全 1 结构元
imgErode = cv2.erode(imgGray, kernel=element)  # 灰度腐蚀
imgDilate = cv2.dilate(imgGray, kernel=element)  # 灰度膨胀
imgOpen = cv2.morphologyEx(imgGray, cv2.MORPH_OPEN, element)  # 灰度开运算
imgClose = cv2.morphologyEx(imgGray, cv2.MORPH_CLOSE, element)  # 灰度闭运算
imgGrad = cv2.morphologyEx(imgGray, cv2.MORPH_GRADIENT, element)  # 形态学梯度

plt.figure(figsize=(9, 7))
plt.subplot(231), plt.axis('off'), plt.title("Origin")
plt.imshow(imgGray, cmap='gray', vmin=0, vmax=255)
plt.subplot(232), plt.title("Eroded image"), plt.axis('off')
plt.imshow(imgErode, cmap='gray', vmin=0, vmax=255)
plt.subplot(233), plt.title("Dilated image"), plt.axis('off')
plt.imshow(imgDilate, cmap='gray', vmin=0, vmax=255)
plt.subplot(234), plt.title("Opening image"), plt.axis('off')
plt.imshow(imgOpen, cmap='gray', vmin=0, vmax=255)
plt.subplot(235), plt.title("Closing image"), plt.axis('off')
plt.imshow(imgClose, cmap='gray', vmin=0, vmax=255)
plt.subplot(236), plt.title("Gradient image"), plt.axis('off')
plt.imshow(imgGrad, cmap='gray', vmin=0, vmax=255)
plt.tight_layout()
plt.show()

在这里插入图片描述

边界提取

边界提取的原理是先将原图二值化,然后进行腐蚀,原图二值化减去腐蚀结果就可以图像边界。

示例程序:

"""
边界提取
"""
import cv2
import matplotlib.pyplot as plt
import numpy as np


imgGray = cv2.imread("../img/lena.jpg", 0)
ret, imgBin = cv2.threshold(imgGray, 25, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)  # 二值化处理

kSize = (3, 3)  # 卷积核的尺寸
kernel = np.ones(kSize, dtype=np.uint8)  # 生成盒式卷积核
imgErode1 = cv2.erode(imgBin, kernel=kernel)  # 图像腐蚀
imgBound1 = imgBin - imgErode1  # 图像边界提取

plt.figure(figsize=(9, 5))
plt.subplot(131), plt.axis('off'), plt.title("Origin")
plt.imshow(imgBin, cmap='gray', vmin=0, vmax=255)
plt.subplot(132), plt.title("Eroded kSize=(3,3)"), plt.axis('off')
plt.imshow(imgErode1, cmap='gray', vmin=0, vmax=255)
plt.subplot(133), plt.title("Boundary extraction"), plt.axis('off')
plt.imshow(imgBound1, cmap='gray', vmin=0, vmax=255)
plt.tight_layout()
plt.show()

在这里插入图片描述

凸包提取

凸包也称凸壳,就是用一个"橡皮圈”来包裹物体。具体原理这里不作细究,这个算法在静态手势识别中,经常被用到。

OpenCV 中提供了函数cv.convexHull可以获取轮廓的凸包

cv.convexHull(points[, hull[, clockwise[, returnPoints]]]) -> hull

参数说明:

  • points:输入图像,可以为单通道或多通道
  • hull:输出凸包,凸包点的索引向量,或凸包点集的向量
  • clockwise:方向标志,True 表示顺时针方向输出凸包,否则为逆时针方向输出
  • returnPoints:操作标志,True 表示返回凸包点集,否则返回凸包点的索引

示例程序:

"""
凸包提取
"""
import cv2
import matplotlib.pyplot as plt
import numpy as np


img = cv2.imread("../img/img.jpg", 1)
imgGray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 灰度图像
imgBlur = cv2.blur(imgGray, (3, 3))  # 去除噪点
ret, imgBin = cv2.threshold(imgBlur, 225, 255, cv2.THRESH_BINARY)  # 二值化处理

img2, contours, hierarchy = cv2.findContours(imgBin, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)  # 寻找所有的轮廓
hullAll = []  # 所有的凸包
for i in range(len(contours)):
    hull = cv2.convexHull(contours[i], False)  # 计算轮廓 contours[i] 的凸包
    hullAll.append(hull)

colorContours = (0, 255, 0)  # 设置轮廓的颜色
colorConvexHull = (255, 255, 255)  # 设置凸包的颜色
imgContours = np.zeros(img.shape, np.uint8)
for i in range(len(contours)):  # 绘制轮廓线
    cv2.drawContours(imgContours, contours, i, colorContours, 2, 8, hierarchy)
imgDrawing = imgContours.copy()
for i in range(len(contours)):  # 绘制凸包线
    cv2.drawContours(imgDrawing, hullAll, i, colorConvexHull, 2, 8)

plt.figure(figsize=(9, 6))
plt.subplot(221), plt.axis('off'), plt.title("origin")
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.subplot(222), plt.title("binary"), plt.axis('off')
plt.imshow(imgBin, cmap='gray', vmin=0, vmax=255)
plt.subplot(223), plt.title("contours"), plt.axis('off')
plt.imshow(cv2.cvtColor(imgContours, cv2.COLOR_BGR2RGB))
plt.subplot(224), plt.title("convex hull"), plt.axis('off')
plt.imshow(cv2.cvtColor(imgDrawing, cv2.COLOR_BGR2RGB))
plt.tight_layout()
plt.show()

注意,如果opencv是最新版4.6.0.66,运行cv2.findContours时,会出现报错:

ValueError: not enough values to unpack (expected 3, got 2)

此时,需要将版本降至opencv-python==3.4.3.18

在这里插入图片描述

边缘检测和角点检测

边缘检测原理:图像中的物体在膨胀时向周围扩张,在腐蚀时会发生收缩,变化的区域都只发生在物体的边缘。二者相减,得到边缘检测结果。

角点检测同理,如果使用不同的结构元进行膨胀和腐蚀,再将两者相减,就可以得到角点检测结果。
下面的示例程序使用了两个结构元:

  • 一号结构元先用十字形结构元膨胀,图像在水平和垂直方向膨胀,而在 45度、135度的斜向没有膨胀;再用菱形核对膨胀结果进行腐蚀,使得膨胀结果在水平和垂直方向被腐蚀,而在45度、135度的斜向也有腐蚀
  • 二号结构元先用 X 形结构元膨胀,图像在水平、垂直方向、45度、135度斜向都发生膨胀;再用正方形核对膨胀结果进行腐蚀,使原图的角点被恢复,而水平、垂直方向的边缘被腐蚀。
"""
边缘检测和角点检测
"""
import cv2
import matplotlib.pyplot as plt
import numpy as np


img = cv2.imread("../img/lena.jpg", flags=1)
imgSign = img.copy()
imgGray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 图片格式转换:BGR(OpenCV) -> Gray
# ret, imgBin = cv2.threshold(imgGray, 127, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)  # 二值化处理

# 边缘检测
element = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
imgEdge = cv2.morphologyEx(imgGray, cv2.MORPH_GRADIENT, element)  # 形态学梯度

# 构造 5×5 结构元素,十字形、菱形、方形、X 型
cross = cv2.getStructuringElement(cv2.MORPH_CROSS, (5, 5))  # 十字型结构元
square = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))  # 矩形结构元
xShape = cv2.getStructuringElement(cv2.MORPH_CROSS, (5, 5))  # X 形结构元
diamond = cv2.getStructuringElement(cv2.MORPH_CROSS, (5, 5))  # 构造菱形结构元
diamond[1, 1] = diamond[3, 3] = 1
diamond[1, 3] = diamond[3, 1] = 1
print(diamond)

imgDilate1 = cv2.dilate(imgGray, cross)  # 用十字型结构元膨胀原图像
imgErode1 = cv2.erode(imgDilate1, diamond)  # 用菱形结构元腐蚀图像

imgDilate2 = cv2.dilate(imgGray, xShape)  # 使用 X 形结构元膨胀原图像
imgErode2 = cv2.erode(imgDilate2, square)  # 使用方形结构元腐蚀图像

imgDiff = cv2.absdiff(imgErode2, imgErode1)  # 将两幅闭运算的图像相减获得角点
retval, thresh = cv2.threshold(imgDiff, 40, 255, cv2.THRESH_BINARY)  # # 二值化处理

# 在原图上用半径为 5 的圆圈标记角点
for j in range(thresh.size):
    y = int(j / thresh.shape[0])
    x = int(j % thresh.shape[0])
    if (thresh[x, y] == 255):
        cv2.circle(imgSign, (y, x), 5, (255, 0, 255))

plt.figure(figsize=(9, 6))
plt.subplot(131), plt.title("Origin"), plt.axis('off')
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.subplot(132), plt.title("Edge"), plt.axis('off')
plt.imshow(imgEdge, cmap='gray', vmin=0, vmax=255)
plt.subplot(133), plt.title("Corner"), plt.axis('off')
plt.imshow(cv2.cvtColor(imgSign, cv2.COLOR_BGR2RGB))
plt.tight_layout()
plt.show()

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq1198768105/article/details/126592056