常用的形态学处理方法包括:腐蚀、膨胀、开运算、闭运算、顶帽运算、底帽运算,其中腐蚀和膨胀是最基础的方法,其他方法是两者相互结合而产生的。
腐蚀
结构元: 与平滑操作类似,在平滑操作中使用的是矩形邻域,而在形态学处理中邻域可以是矩形结构,也可以是椭圆形、十字交叉形结构。同样也需要指定一个锚点。
在腐蚀操作中,是取结构元中的最小值作为锚点的值。可以对灰度图或二值图做腐蚀操作。以下图为例(均取中心点为锚点):
上方三个图中的邻域的最小值分别为 11、21、21,这些最小值输出到图像中的锚点位置,其他位置通过移动结构元以此类推,即可得到完整的输出图像。
由上图可知,因为取每个位置邻域内的最小值,所以腐蚀后输出图像的总体亮度的平均值比起原图会有所降低,图像中比较亮的区域的面积会变小甚至消失,而比较暗的区域的面积会增大。
因为对图像进行腐蚀操作后缩小了亮度区域的面积,所以针对阈值分割后前景时白色的二值图,可以通过原图与腐蚀后的图像相减得到前景的边界。
OpenCV 函数
// 腐蚀
// C++
void cv::erode(InputArray src,
OutputArray dst,
InputArray kernel,
Point anchor = Point(-1,-1),
int iterations = 1,
int borderType = BORDER_CONSTANT,
const Scalar & borderValue = morphologyDefaultBorderValue()
)
// Python
dst=cv.erode(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]])
参数解释:
参数 | 解释 |
---|---|
src | 输入矩阵,灰度图或二值图 |
dst | 输出矩阵 |
kernel | 结构元,可以通过 getStructuringElement 函数获取 |
anchor | 锚点的位置;默认值(-1,-1)表示锚点在结构元中心。 |
iterations | 腐蚀的次数 |
borderType | 边界扩充类型 |
borderValue | 边界扩充值 |
常用的 borderType 有 BORDER_CONSTANT 、BORDER_REPLICATE 和 BORDER_REFLECT ,BORDER_CONSTANT为用常量扩充,BORDER_REPLICATE 为边界复制扩充,BORDER_REFLECT 为镜像扩充。
// 获取结构元
// C++
Mat cv::getStructuringElement(int shape,
Size ksize,
Point anchor = Point(-1,-1)
)
// Python:
retval = cv.getStructuringElement( shape, ksize[, anchor])
参数 | 解释 |
---|---|
shape | 结构元形状 |
ksize | 结构元大小 |
anchor | 结构元锚点 |
结构元形状 shape
-
MORPH_RECT
产生矩形的结构元
-
MORPH_CROSS
产生十字交叉形的结构元
-
MORPH_ELLIPSE
产生椭圆形的结构元
Python 示例
import cv2 as cv
if __name__ == '__main__':
img_src = cv.imread("img2.jpg", 0)
# 创建矩形结构元
kernel = cv.getStructuringElement(cv.MORPH_RECT, (5, 5))
# 腐蚀图像
img_erode = cv.erode(img_src, kernel)
# 边界提取
img_edge = img_src - img_erode
cv.imwrite("./images/img2_src.jpg", img_src)
cv.imwrite("./images/img2_erode.jpg", img_erode)
cv.imwrite("./images/img2_edge.jpg", img_edge)
对二值图腐蚀
阈值分割后的图像难免会有很多零星的白色噪点,可以通过腐蚀来去除。由上图可知,腐蚀的结构元越大,目标物体(白色区域)的面积会越来越小,如果对图像反复进行腐蚀运算,则会消除整个目标物体。
对灰度图腐蚀
对灰度图的腐蚀,随着结构元尺寸的增大,灰度较暗的区域的面积也随着增大,同时灰度较亮的区域的面积就随着减小,而且处理后的效果可以隐约看出结构元的形状,即很多重叠的矩形快,很像马赛克效果。如果采用椭圆形或十字交叉形的结构元进行侵蚀,则同样会出现类似的椭圆或者十字交叉的形状。
膨胀
在膨胀操作中,是取结构元中的最大值作为锚点的值。可以对灰度图或二值图做膨胀操作。膨胀后输出图像的总体亮度的平均值比起原图会有所上升,图像中比较亮的区域的面积会变大,而比较暗的区域的面积会减小。
OpenCV 函数
// 膨胀
// C++
void cv::dilate(InputArray src,
OutputArray dst,
InputArray kernel,
Point anchor = Point(-1,-1),
int iterations = 1,
int borderType = BORDER_CONSTANT,
const Scalar & borderValue = morphologyDefaultBorderValue()
)
// Python:
dst =cv.dilate(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]])
参数与腐蚀操作的一样。
Python 示例
下面使用进度条调节结构半径,观察结构元的尺寸对形态学处理的影响
import cv2 as cv
def change_dilate_r(r):
# 创建结构元
kernel = cv.getStructuringElement(cv.MORPH_ELLIPSE, (2 * r + 1, 2 * r + 1))
# 腐蚀图像
img_dilate = cv.dilate(img_src, kernel)
# 显示膨胀效果
cv.imshow('dilate', img_dilate)
if __name__ == '__main__':
img_src = cv.imread("img5.jpg", 0)
# 显示原图
cv.imshow("src", img_src)
# 结构元半径
r = 1
max_r = 20
# 显示膨胀效果的窗口
cv.namedWindow('dilate', 1)
# 调节结构元半径
cv.createTrackbar('r', 'dilate', r, max_r, change_dilate_r)
change_dilate_r(0)
cv.waitKey(0)
开运算和闭运算
开运算 先腐蚀后膨胀叫开运算,具有消除亮度较高的细小区域、在纤细点处分离物体,对于较大物体,可以在不明显改变面积的情况下平滑其边界等作用。
闭运算 先膨胀后腐蚀叫闭运算,具有填充白色物体细小黑色空洞的区域、连接临近物体、同一个结构元、多次迭代处理,也可以在不明显改变其面积的情况下平滑其边界等作用。
开运算和闭运算是膨胀和腐蚀的组合,完全可以利用函数 erode 和 dilate 来完成,OpenCV也提供了函数直接使用
OpenCV 函数
// C++
void cv::morphologyEx(InputArray src,
OutputArray dst,
int op,
InputArray kernel,
Point anchor = Point(-1,-1),
int iterations = 1,
int borderType = BORDER_CONSTANT,
const Scalar & borderValue = morphologyDefaultBorderValue()
)
// Python:
dst =cv.morphologyEx(src, op, kernel[,dst[,anchor[,iterations[,borderType[, borderValue]]]]])
参数 | 解释 |
---|---|
op | 形态学操作的类型 |
kernel | 结构元,可以通过 getStructuringElement 函数获取 |
anchor | 锚点的位置;默认值(-1,-1)表示锚点在结构元中心。 |
iterations | 迭代次数 |
borderType | 边界扩充类型,见腐蚀 |
borderValue | 边界扩充值,见腐蚀 |
op 形态学操作的类型
- MORPH_ERODE 腐蚀 erode
- MORPH_DILATE 膨胀 dilate
- MORPH_OPEN 开运算(先腐蚀后膨胀)
- MORPH_CLOSE 闭运算(先膨胀后腐蚀)
- MORPH_GRADIENT 形态学梯度
- MORPH_TOPHAT 顶帽运算
- MORPH_BLACKHAT 底帽运算
Python 示例
使用进度条调节结构半径以及迭代次数,观察结构元的尺寸对形态学处理的影响
import cv2 as cv
def nothing(*args):
pass
if __name__ == '__main__':
img_src = cv.imread("img1.jpg", 0)
r, i = 1, 1
max_r, max_i = 20, 20
cv.namedWindow('morphology', 1)
cv.createTrackbar('r', 'morphology', r, max_r, nothing)
cv.createTrackbar('i', 'morphology', i, max_i, nothing)
while True:
r = cv.getTrackbarPos('r', 'morphology')
i = cv.getTrackbarPos('i', 'morphology')
kernel = cv.getStructuringElement(cv.MORPH_RECT, (2*r+1, 2*r+1))
# img_open = cv.morphologyEx(img_src, cv.MORPH_OPEN, kernel, iterations=i)
# cv.imshow('morphology', img_open)
img_erode = cv.erode(img_src, kernel, iterations=i)
cv.imshow('morphology', img_erode)
ch = cv.waitKey(5)
if ch==27:
break
elif ch==115:
# cv.imwrite(f'./images/img1_{i}.jpg', img_open)
cv.imwrite(f'./images/img1_{i}.jpg', img_erode)
cv.destroyAllWindows()
上图为开运算(上行)和腐蚀(下行)对二值图的处理效果,使用 3x3 矩形结构元,依次进行1次、5次、20次的迭代。随着迭代次数的增加,开运算使白色物体周围细小的亮度较高的区域被消除,且白色物体的面积没有明显的改变,边界在局部内也变的平滑。但是腐蚀操作会使物体的面积变小甚至消失,若果多次使用膨胀操作,则会使白色物体的面积逐渐增大。
开运算还有一个很重要的作用:消除暗背景下的较亮区域。如下图所示,目的是在不改变黑色球的面积的情况下,消除球上的白色区域。腐蚀操作会使球的面积增大,而开运算可以避免球的面积增大。
对于闭运算,如下图所示,目的是去掉骰子上的黑色区域。通过膨胀和闭运算都可以做到,但是膨胀会使骰子的面积增大,而闭运算不会。
顶帽变换、底帽变换和形态学梯度
顶帽变换和底帽变换是分别以开运算和闭运算为基础的
顶帽变换 原图像减去开运算的结果。由于开运算可以消除暗背景下的较亮区域,如果用原图像减去开运算结果就可以得到原图中灰度较亮的区域,所以又称白顶帽变换。可以校正不均匀光照。
底帽变换 原图像减去闭运算的结果。由于闭运算可以删除亮度较高背景下的较暗区域,如果用原图像减去闭运算结果就可以得到原图像灰度较暗的区域,所以又称黑底帽变换。
形态学梯度 膨胀的结果减去腐蚀的结果,因为膨胀是取邻域内的最大值,从而增大亮度高的区域的面积,而腐蚀是取邻域内的最小值,从而减小亮度高的区域的面积,所以得到的图像是图像中物体的边界。
Python 示例
import cv2 as cv
def nothing(*args):
pass
if __name__ == '__main__':
img_src = cv.imread("/img5.jpg", 0)
r, i = 1, 1
max_r, max_i = 20, 20
type = 0
max_type = 2
cv.namedWindow("morphology", 1)
cv.createTrackbar('r', 'morphology', r, max_r, nothing)
cv.createTrackbar('i', 'morphology', i, max_i, nothing)
cv.createTrackbar('t', 'morphology', type, max_type, nothing)
m_type = cv.MORPH_TOPHAT
while True:
r = cv.getTrackbarPos('r', 'morphology')
i = cv.getTrackbarPos('i', 'morphology')
t = cv.getTrackbarPos('t', 'morphology')
if t==0:
m_type = cv.MORPH_TOPHAT
elif t==1:
m_type = cv.MORPH_BLACKHAT
else:
m_type = cv.MORPH_GRADIENT
kernel = cv.getStructuringElement(cv.MORPH_RECT, (2*r+1, 2*r+1))
img_mor = cv.morphologyEx(img_src, m_type, kernel, iterations=i)
cv.imshow('morphology', img_mor)
ch = cv.waitKey(5)
if ch == 27:
break
elif ch == 115:
cv.imwrite(f'./images/img_{t}.jpg', img_mor)
cv.destroyAllWindows()
下图为使用 3x3 的矩形结构元进行 12 次的顶帽运算,使用 3x3 的矩形结构元进行10底帽运算的结果,形态学梯度操作后的结果。