腐蚀是对图像瘦小的操作,就是当模版击中的时候,图像该像素置1否则置0,(模版击中)
膨胀是对图像变胖的操作,就是当模版与对应位置的图像矩阵只要有一个点1击中,图像该像素置1否则置0,(一个点击中)
击中概念是图像与模版与运算结果都为1时为击中,在代码中可以矩阵相乘做法判断后面代码会给出
步骤分为3步:
代码如下:
# 边缘填充,边缘填充在opencv 中是个大类,有填充类别,和填充行列数这里主要为了介绍形态学处理,故填充只考虑 类别为复制,填充行列数为1
def copyMakeBorder(f):
rows, cols = f.shape
newF = np.zeros((rows + 2, cols + 2))
newF[0, 1:-1] = f[0, :]
newF[1:-1, 1:-1] = f[0:, :]
newF[rows, 1:-1] = f[rows - 1:]
newF[:, 0] = newF[:, 1]
newF[:, -1] = newF[:, -2]
return newF
# 腐蚀 后面的B为模版矩阵 这里的命名易读性差希望读者谅解
def imerode(f, B=np.ones(9).reshape(3, 3)):
rowsB, colsB = B.shape
rb = int(rowsB / 2)
cb = int(colsB / 2)
f = copyMakeBorder(f)
newF = np.ndarray(f.shape)
rows, cols = f.shape
n = np.sum(B)
for i in range(rb, rows - rb):
for j in range(cb, cols - cb):
newF[i, j] = 1 if np.sum(f[i - rb:i + rb + 1, j - cb:j + cb + 1] * B) == n else 0
return newF[1:-1, 1:-1]
# 膨胀
def imdilate(f, B=np.ones(9).reshape(3, 3)):
rowsB, colsB = B.shape
rb = int(rowsB / 2)
cb = int(colsB / 2)
f = copyMakeBorder(f)
newF = np.ndarray(f.shape)
rows, cols = f.shape
for i in range(rb, rows - rb):
for j in range(cb, cols - cb):
newF[i, j] = 1 if np.sum(f[i - rb:i + rb + 1, j - cb:j + cb + 1] * B) >= 1 else 0
return newF[1:-1, 1:-1]
开运算是对图像整体粗细几乎不变,光滑断开的操作,先腐蚀后膨胀
闭运算是对图像整体粗细几乎不变,光滑连接的操作,先膨胀后腐蚀
tip 当进行开闭运算的时候膨胀和腐蚀根据需要可以使用不同的模版可能会带来更好的效果
代码如下:
# 开运算
def lockageon(f):
f = f / 255 # 二值化
mask1 = np.array([0, 1, 0, 1, 1, 1, 0, 1, 0]).reshape(3, 3)
mask2 = np.ones(9).reshape(3, 3)
return imdilate(imerode(f, mask1), mask2)
# 闭运算
def lockageoff(f):
f = f / 255 #二值化
mask1 = np.array([0, 1, 0, 1, 1, 1, 0, 1, 0]).reshape(3, 3)#十字架类型模版
mask2 = np.ones(9).reshape(3, 3)#矩阵类型模版
return imerode(imdilate(f, mask1), mask2)
效果如下,这里因为开和闭是两个相反的操作,故这里只给出开运算效果,闭运算科自己运行查看
这个比较简单, o = f - ef
整个轮廓提取的过程需要注意的一点是模板的选择为矩形模板,这是为了保证每次腐蚀的元素都是图像内部元素!!!
代码如下:
contourImg = original - imerode(original, np.ones(9).reshape(3, 3))
这个其实算是冈萨雷斯书里一个很重要的坑!!!,读者应该都知道形态学那章中击中与击不中的那个示意图,下面说一下本人所看到的一些点
这个也就导致了我们在实际应用场景中根本就做不到外部轮廓的击中,因为在实际二值图像里我们大部分都是处理的一些边缘图,外部轮廓更是无规律可循也就完全做不到外部轮廓击中即我们实际场景中都是考虑的内容击中即可!!!这个就很容易做到,只用模板完全匹配 这个在下面的细化骨架提取得到呈现。
这个大类里的步骤比孔洞填充,连通域的繁琐复杂因此这两个部分的内容在这里省略,这一章内容过多篇幅太长如有需要可以在评论处说明!
这里其实我觉得细化和骨架提取这两个是密不可分的,这一点在冈萨雷斯这本书更体现的淋漓尽致
结构元的设定冈萨雷斯书中的8种结构元,这里可能读者会有疑问,我的一些同学也都是这样认为的是16中结构元这两种说法都是正确的下面给出我的结构元定义:
# 细化模板
B1 = np.array([-1, -1, -1, 0, 1, 0, 1, 1, 1]).reshape(3, 3)
B2 = np.array([0, -1, -1, 1, 1, -1, 1, 1, 0]).reshape(3, 3)
B3 = np.array([1, 0, -1, 1, 1, -1, 1, 0, -1]).reshape(3, 3)
B4 = np.array([1, 1, 0, 1, 1, -1, 0, -1, -1]).reshape(3, 3)
B5 = np.array([1, 1, 1, 0, 1, 0, -1, -1, -1]).reshape(3, 3)
B6 = np.array([0, 1, 1, -1, 1, 1, -1, -1, 0]).reshape(3, 3)
B7 = np.array([-1, 0, 1, -1, 1, 1, -1, 0, 1]).reshape(3, 3)
B8 = np.array([-1, -1, 0, -1, 1, 1, 0, 1, 1]).reshape(3, 3)
这8种分别对应了书中的T形和矩形的4个方位对应的结构元
这里会有两个疑问?~~?(好想打个疑惑脸)
代码如下:
def refining(f):
rows, cols = f.shape
# 细化模板
B1 = np.array([-1, -1, -1, 0, 1, 0, 1, 1, 1]).reshape(3, 3)
B2 = np.array([0, -1, -1, 1, 1, -1, 1, 1, 0]).reshape(3, 3)
B3 = np.array([1, 0, -1, 1, 1, -1, 1, 0, -1]).reshape(3, 3)
B4 = np.array([1, 1, 0, 1, 1, -1, 0, -1, -1]).reshape(3, 3)
B5 = np.array([1, 1, 1, 0, 1, 0, -1, -1, -1]).reshape(3, 3)
B6 = np.array([0, 1, 1, -1, 1, 1, -1, -1, 0]).reshape(3, 3)
B7 = np.array([-1, 0, 1, -1, 1, 1, -1, 0, 1]).reshape(3, 3)
B8 = np.array([-1, -1, 0, -1, 1, 1, 0, 1, 1]).reshape(3, 3)
maskList = [B1, B2, B3, B4, B5, B6, B7, B8]
count = 0
# skemask1 = np.array([1, 0, 1, 0, 1, 0, 1, 0, 1]).reshape(3, 3)
while True:
temp = f.copy
for m in maskList:
mas = []
for i in range(1, rows - 1):
for j in range(1, cols - 1):
if f[i, j] == 0:
continue
elif np.sum(m * f[i - 1:i + 2, j - 1:j + 2]) == 4:
# 击中时标记删除点
mas.append((i, j))
for it in mas:
x, y = it
f[x, y] = 0
if (temp == f).all:
count += 1
else:
count = 0
if count == 8:
break
return f
裁剪这里其实并没有任何的理解难度,当然结构元的设定也是有迹可循的,读者可以自己想下通细化的结构元定义类似的思路
代码如下:
# 裁剪算法
def cut(f):
rows, cols = f.shape
f2 = f.copy()
# 裁剪模板
A = np.array([0, -1, -1, 1, 1, -1, 0, -1, -1]).reshape(3, 3)
A1 = np.rot90(A)
A2 = np.rot90(A1)
A3 = np.rot90(A2)
B = np.array([1, -1, -1, -1, 1, -1, -1, -1, -1]).reshape(3, 3)
B1 = np.rot90(B)
B2 = np.rot90(B1)
B3 = np.rot90(B2)
maskList = [A, A1, A2, A3, B, B1, B2, B3]
for k in range(3):
for m in maskList:
mas = []
for i in range(1, rows - 1):
for j in range(1, cols - 1):
if f2[i, j] == 0:
continue
elif np.sum(m * f2[i - 1:i + 2, j - 1:j + 2]) == 2:
mas.append((i, j))
for it in mas:
x, y = it
f2[x, y] = 0
# 得到首位端点,即膨胀初始点
f3 = np.zeros(f.shape)
for i in range(1, rows - 1):
for j in range(1, cols - 1):
if f2[i, j] == 0:
continue
else:
for m in maskList:
if np.sum(m * f2[i - 1:i + 2, j - 1:j + 2]) == 2:
f3[i, j] = 1
# 膨胀
H = np.ones(9).reshape(3, 3)
rows, cols = f3.shape
for i in range(1, rows - 1):
for j in range(1, cols - 1):
# 每一次膨胀与上A
f3[i, j] = f[i, j] * (1 if np.sum(f3[i - 1:i + 2, j - 1:j + 2] * H) >= 1 else 0)
newf = (f2 + f3) / 2
ret, newf = cv.threshold(newf, 0, 1, cv.THRESH_BINARY)
return newf
效果如下:其实这里的从这个图看起来并不是很理想,原因有以下几点
这部分内容只作为扩展内容,因为理论知识过多下面给出代码,有兴趣的朋友可以作为参考
代码如下:
# 距离变换骨架提取
def distance(f):
f2 = np.zeros(f.shape)
rows, cols = f.shape
# 距离变换操作
for i in range(1, rows - 1):
for j in range(1, cols - 1):
f[i, j] = sys.maxsize if f[i, j] == 0 else 1
for i in range(1, rows - 1):
for j in range(1, cols - 1):
temp0 = f[i, j]
temp1 = min(f[i, j - 1] + 3, temp0)
temp2 = min(f[i - 1, j - 1] + 4, temp1)
temp3 = min(f[i - 1, j] + 3, temp2)
temp4 = min(f[i - 1, j + 1] + 4, temp3)
f[i, j] = temp4
for i in range(rows - 2, 0, -1):
for j in range(cols - 2, 0, -1):
temp0 = f[i, j]
temp1 = min(f[i, j + 1] + 3, temp0)
temp2 = min(f[i + 1, j + 1] + 4, temp1)
temp3 = min(f[i + 1, j] + 3, temp2)
temp4 = min(f[i + 1, j - 1] + 4, temp3)
f[i, j] = temp4
# 骨架提取
for i in range(3, rows - 3):
for j in range(3, cols - 3):
if 5 <= f[i, j] <= 10:
if f[i, j] == np.max(f[i - 3:i + 4, j - 3:j + 4]):
f2[i, j] = 1
elif np.sum(f[i - 1, j - 1:j + 2] * [-1, -1, 1]) == 1:
f2[i, j] = 1
elif np.sum(f[i - 1, j - 1:j + 2] * [-1, -1, 1]) == 0 and f[i, j + 1] > f[i, j]:
f2[i, j] = 1
return f2
def main():
# 边缘和距离骨架提取
contourImg = original - imerode(original, np.ones(9).reshape(3, 3))
out = contourImg.copy()
distanceImg = distance(contourImg)
plt.figure()
show(out, "contourImg", 1, 2, 1)
show(distanceImg, "distanceImg", 1, 2, 2)
plt.show()
效果如下:距离变换的效果很不理想,我已经在代码里尽量保证提取后的连通性可是仍然如此
这里感兴趣的朋友可以去网上看看思路以及效果和速度,这里不多做介绍,本人跑过就这张指纹图片,博客里代码优化后效果上会比Hilditch算法好,速度上从理论上就根本比不过,会比Hilditch算法慢大约两秒
总结:以上就是本人对数字图像处理形态学方面内容的浅显见解请读者多多提意见,
转载我博客应当经我允许,至少要把原文链接放在文章最前面,这是对本人辛苦原创基本的尊重。
上一篇:数字图像处理冈萨雷斯-图像增强篇