大家看这篇博文前可以先看一看下面这篇博文,下面这篇博文是这篇博文的基础: 详解图像形态学操作之图形的腐蚀和膨胀的概念和运算过程,并利用OpenCV的函数erode()和函数dilate()对图像进行腐蚀和膨胀操作
击中击不中变换的最常见作用是去匹配一幅图像中像素的特定结构,比如孤立的前景像素或是线段的端点像素。 简单来说击中击不中变换用于找寻图像中与结构一样的结构,并将找到的结构的中心点的值置为1。
击中击不中变换处理的对像是二值图像,这一点大家要注意。
击中击不中变换的英文缩写为HMT(Hit-Miss Transform)。
其运算规律是: 如果图像中某个区域与结构相同,那么这个区域与结构腐蚀的结果与上这个区域的补集与结构的补集的腐蚀结果是:这个区域的中心点被置为1,这个区域的其它值被置为0。 所以,由此我们定义图像A和结构B的击中击不中变换为以下的运算:
A
?
B
=
(
A
?
B
)
∩
(
A
c
?
B
c
)
A\otimes B=(A\ominus B )\cap (A^{c}\ominus B^{c})
A?B=(A?B)∩(Ac?Bc)
上面的式子具体表述为: 1 利用结构B对原图像A进行腐蚀变换 2. 利用结构B的补集对图像A的补集进行腐蚀变换。 3. 对上述二者取交集得结果。
从以上叙述我们可以看出,图像中的某区域若与结构相同,那么这个区域的中心点在以下两个运算中的值都为1。 第①个运算为这个区域与结构作腐蚀运算。 第②个运算为这个区域的补集与结构的补集作腐蚀运算。 通过这段叙述大家还可以理解击中击不中变换这个名字的来历:如果某个像素点的击中击不中变换的结果值为1,那么它相当于: ①在上述的第①个运算中被结构B击中了;②在上述的第②个运算中其补集被结构
B
c
B^{c}
Bc击中了,由于是它的补集被结构
B
c
B^{c}
Bc击中了,相当于它本身没有被击中的。这就是击中击不中变换名字的来历。 严格来说,击中和击不中变换是其更准确的名字,但人们称击中或击不中变换更习惯了,所以大家通常见到的名字是“击中或击不中变换”,英文中叫“hit or miss”,比如OpenCV就称其为“hit or miss”,截图如下: 另外,在更高级的应用中,两个腐蚀结构并不一定要是相互补集的,它们可以有某些元素值同为0,而不一定非得是一个为0,另一个为1,这个元素叫不关心元素,即我们在进行结构匹配时这样的元素的像素值并不影响结果,这样就可以提高我们匹配的模糊性。
我们根据上面的叙述用下面的几个实例程序来演示击中击不中变换的运算过程,大家读懂下面的代码并看下运行结果就会对击中击不中变换的运算过程和作用有个完整的理解了。
示例代码一:
import numpy as np
import cv2 as cv
A = np.zeros((9, 9), dtype='uint8')
A[3:6, 3:6] = 1
A[3, 3] = 0
A[3, 5] = 0
A[5, 3] = 0
A[5, 5] = 0
B = cv.getStructuringElement(cv.MORPH_CROSS, (3, 3))
A_erode = cv.erode(A, B)
A_c = abs(A-1)
A_c = A_c//255
B_c = abs(B-1)
B_c = B_c//255
A_c_erode = cv.erode(A_c, B_c)
A_HMT = cv.bitwise_and(A_erode, A_c_erode)
运行结果如下: 从上面的运行结果可以看出,A中和结构B一样的区域的中心被标记为了1。
示例代码二: 示例代码一中的A是下面这样的: 我们在示例代码二中把A改成下面这样,看下能不能检测出A中的B结构。 示例二的代码如下:
import numpy as np
import cv2 as cv
A = np.ones((9, 9), dtype='uint8')
A[3, 3] = 0
A[3, 5] = 0
A[5, 3] = 0
A[5, 5] = 0
B = cv.getStructuringElement(cv.MORPH_CROSS, (3, 3))
A_erode = cv.erode(A, B)
A_c = abs(A-1)
A_c = A_c//255
B_c = abs(B-1)
B_c = B_c//255
A_c_erode = cv.erode(A_c, B_c)
A_HMT = cv.bitwise_and(A_erode, A_c_erode)
运行结果如下: 从上面的运行结果我们可以看出,A中的B结构也被检测出来了。
示例代码三: 示例代码三中我们把A改成下面这样的,结构B不变,结果应该是A没有与结构B相同的区域。 我们用代码测试一下是不是A中没有与结构B相同的区域。
import numpy as np
import cv2 as cv
A = np.zeros((9, 9), dtype='uint8')
A[3:6, 3:6] = 1
B = cv.getStructuringElement(cv.MORPH_CROSS, (3, 3))
A_erode = cv.erode(A, B)
A_c = abs(A-1)
A_c = A_c//255
B_c = abs(B-1)
B_c = B_c//255
A_c_erode = cv.erode(A_c, B_c)
A_HMT = cv.bitwise_and(A_erode, A_c_erode)
运行结果如下: 从结果来看,和我们的预测结果是一样的。
OpenCV4中的函数morphologyEx()的参数op增加了做"击中击不中变换"的可选值,如下:
虽然增加了,但是大家别高兴得太早,根据博主的实测,发现OpenCV4的击中击不变换是有问题的,测试代码如下:
import numpy as np
import cv2 as cv
A = np.zeros((9, 9), dtype='uint8')
A[3:6, 3:6] = 1
B = cv.getStructuringElement(cv.MORPH_CROSS, (3, 3))
A_opencv_HMT = cv.morphologyEx(A, cv.MORPH_HITMISS, B)
关于函数morphologyEx()的使用,大家可参考下面这篇博文: https://blog.csdn.net/wenhao_ir/article/details/124797255 上面的代码运行结果如下:
从上面的A和B来看,A中没有和B相同的结构区域,所以A_opencv_HMT中应该全为0才对,但是上面的结果却不全为0,所以OpenCV中的击中击不中变换操作是有问题的,大家还是根据击中击不中变换操作自己去用腐蚀操作、求补集操作和按位与实现吧。 我另外又测试了几个例子,发现OpenCV中的击中击不中变换操作似乎就只是单纯的腐蚀操作而已。
参考资料:《数字图像处理的MATLAB实现(第2版)》冈萨雷斯著 第9.3.2节,如下图所示: 扩展阅读: 详解图像形态学操作之图形的腐蚀和膨胀的概念和运算过程,并利用OpenCV的函数erode()和函数dilate()对图像进行腐蚀和膨胀操作 图像的形态学开操作(开运算)和闭操作(闭运算)的概念和作用,并用OpenCV的函数morphologyEx()实现对图像的开闭操作 图像的形态学梯度运算(基本梯度、外部梯度、内部梯度、X方向梯度、Y方向梯度)的概念、作用以及相关的OpenCV示例代码 图像形态学操作之顶帽操作(TopHat)与黑帽操作(BlackHat)
|