首先明确一点形态学操作都是针对与二值图像,在灰度图像的形态学处理也是先根据一些前置操作转化为二值图像(如高帽运算后再阈值化),这是因为形态学的 任何操作都是需要击中这个概念,膨胀开闭其实可以看成包含 击中的是一个点,其他操作可以看成 击中的是一个模版!!!,在后面我会在代码中给出解释,因此当所有操作都有击中这个操作的时候就能知道为什么形态学需要二值图像而非灰度图!!!
腐蚀膨胀
腐蚀是对图像瘦小的操作,就是当模版击中的时候,图像该像素置1否则置0,(模版击中)
膨胀是对图像变胖的操作,就是当模版与对应位置的图像矩阵只要有一个点1击中,图像该像素置1否则置0,(一个点击中)
击中概念是图像与模版与运算结果都为1时为击中,在代码中可以矩阵相乘做法判断后面代码会给出
步骤分为3步:
- 边界处理 考虑边界是否处理
- 模版选择 opencv3中 有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