形态学是生物学的一个分支,主要研究动植物的形态和结构。这里,我们应用形态学简化图像的数据,取出不重要的结构,仅仅保持图像的基本形状特性.
形态学的基础是集合论。将感兴趣的区域称为前景像素点,不感兴趣的部分称为背景像素点。通过定义的结构元SE(structural element) 对前景像素点操作,从而达到处理图像的效果。
结构元类似之前的滤波器模板,不同的是结构元需要定义原点。原点对应像素点的输出,原点可以在SE的内部或者外部。当结构元对称且未指定原点的时候,原点位于对称中心处
膨胀和腐蚀
腐蚀的过程定义如下:结构元原点访问每个像素点,若结构元都被包含在前景像素点当中,则输出前景像素点。因此,腐蚀是一种缩小前景目标的操作.
膨胀的过程定义如下:结构元原点访问每个像素点,(膨胀的时候需要SE绕原点翻转,通常使用的SE都是对称的,所以可以不用管)若结构元与前景像素点有交集,则输出前景像素点。因此,膨胀是一种扩大前景目标的操作
import cv2
import numpy as np
img = cv2.imread('./img1.png', 0)
ret,img_binary=cv2.threshold(img,0,255,cv2.THRESH_BINARY-cv2.THRESH_OTSU)
"""
cv2.MORPH_RECT 矩形 cv2.MORPH_CROSS十字架 cv2.MORPH_ELLIPSE椭圆
"""
SE=cv2.getStructuringElement(cv2.MORPH_CROSS,(5,5))
dst = cv2.erode(img,SE)
cv2.imshow('img', np.hstack((img, dst)))
cv2.imwrite('./image.png', np.hstack((img, dst)))
cv2.waitKey()
cv2.destroyAllWindows()
import cv2
import numpy as np
img = cv2.imread('./img1.png', 0)
ret,img_binary=cv2.threshold(img,0,255,cv2.THRESH_BINARY-cv2.THRESH_OTSU)
"""
cv2.MORPH_RECT 矩形 cv2.MORPH_CROSS十字架 cv2.MORPH_ELLIPSE椭圆
"""
SE=cv2.getStructuringElement(cv2.MORPH_CROSS,(5,5))
dst = cv2.dilate(img,SE)
cv2.imshow('img', np.hstack((img, dst)))
cv2.imwrite('./image.png', np.hstack((img, dst)))
cv2.waitKey()
cv2.destroyAllWindows()
开运算和闭运算
膨胀和腐蚀操作都会有一个通病,就是会改变原目标的大小
开运算:先对目标腐蚀在膨胀
开运算能够平滑物体的轮廓、断开狭窄的狭颈、消除细长的突出物等等
闭运算:先膨胀在腐蚀
闭运算能够弥合狭窄的断裂和细长的沟壑、消除小孔、填补轮廓中的缝隙等等
看开、闭运算的作用,主要看第一次操作是膨胀还是腐蚀,因为第一次占的是主导作用,而第二次的膨胀腐蚀操作只是还原目标
import cv2
import numpy as np
img = cv2.imread('./img1.png', 0)
ret,img_binary=cv2.threshold(img,0,255,cv2.THRESH_BINARY-cv2.THRESH_OTSU)
"""
cv2.MORPH_RECT 矩形 cv2.MORPH_CROSS十字架 cv2.MORPH_ELLIPSE椭圆
"""
SE=cv2.getStructuringElement(cv2.MORPH_CROSS,(5,5))
"""
这里iteration 代表open运算的迭代次数。
这里是先进行五次腐蚀,然后再五次膨胀
"""
dst = cv2.morphologyEx(img,cv2.MORPH_OPEN,SE,iterations=5)
cv2.imshow('img', np.hstack((img, dst)))
cv2.imwrite('./image.png', np.hstack((img, dst)))
cv2.waitKey()
cv2.destroyAllWindows()
import cv2
import numpy as np
img = cv2.imread('./img1.png', 0)
ret,img_binary=cv2.threshold(img,0,255,cv2.THRESH_BINARY-cv2.THRESH_OTSU)
"""
cv2.MORPH_RECT 矩形 cv2.MORPH_CROSS十字架 cv2.MORPH_ELLIPSE椭圆
"""
SE=cv2.getStructuringElement(cv2.MORPH_CROSS,(5,5))
"""
这里iteration 代表open运算的迭代次数。
这里是先进行五次腐蚀,然后再五次膨胀
"""
dst = cv2.morphologyEx(img,cv2.MORPH_CLOSE,SE,iterations=5)
cv2.imshow('img', np.hstack((img, dst)))
cv2.imwrite('./image.png', np.hstack((img, dst)))
cv2.waitKey()
cv2.destroyAllWindows()
边界提取
前景像素点的边界可以通过如下方式得到:首先将原图A腐蚀得到腐蚀图B,然后将原图A减去腐蚀图B得到差集 。
opencv 库里面的形态学梯度是:原图的膨胀图 - 原图的腐蚀图
因为膨胀会使目标扩大,腐蚀会使目标减少,所以差值产生的边界宽度会比较大
import cv2
import numpy as np
img = cv2.imread('./img1.png', 0)
ret, img = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) # 阈值处理
# 边界提取
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) # kernel
img_erode = cv2.erode(img, kernel) # 腐蚀
dst = img - img_erode # 原图 - 腐蚀图
# 形态学梯度
"""
opencv 库里面的形态学梯度是:原图的膨胀图 - 原图的腐蚀图
因为膨胀会使目标扩大,腐蚀会使目标减少,所以差值产生的边界宽度会比较大
"""
dst_gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel) # 膨胀图 - 腐蚀图
cv2.imshow('img', np.hstack((img, dst, dst_gradient))) # 原图、边界提取图、梯形态学梯度
cv2.imwrite('./image.png',np.hstack((img, dst, dst_gradient)))
cv2.waitKey()
cv2.destroyAllWindows()
孔洞填充
孔洞指的是被前景像素点或者说感兴趣的像素点包围起来的区域,这个区域是我们不感兴趣的背景区域。
# -*- coding: utf-8 -*-
import cv2
import numpy as np
def holefill(img):
img_copy = img.copy()
mask = np.zeros((img.shape[0] + 2, img.shape[1] + 2), dtype=np.uint8)
"""
floodFill 会对原图像进行操作,所以事先需要拷贝图像,将漫水的种子设为(0,0)也就是图像的左上角,
填充的颜色为255。虽然通过计算找到孔洞的位置,然后直接填充就可以,但是这样比较麻烦,且孔洞较多的时候不好处理。
这里我们将除了 前景像素点和孔洞 的位置都填充为前景像素点,然后通过求反就可以得到所有的孔洞的位置
"""
cv2.floodFill(img, mask, (0, 0), 255)
img_inverse = cv2.bitwise_not(img)
dst = cv2.bitwise_or(img_copy, img_inverse)
return dst
img = cv2.imread('img1.png', 0)
thresh, img = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
img_copy = img.copy()
dst = holefill(img)
cv2.imshow('img', np.hstack((img_copy, dst)))
cv2.imwrite('./image.png',np.hstack((img, dst)))
cv2.waitKey()
cv2.destroyAllWindows()
细化
import numpy as np
import cv2
def thin(img): # 细化算法
# 8 个细化 kernel
B1 = np.array([[-1, -1, -1], [0, 1, 0], [1, 1, 1]])
B2 = np.array([[0, -1, -1], [1, 1, -1], [1, 1, 0]])
B3 = np.array([[1, 0, -1], [1, 1, -1], [1, 0, -1]])
B4 = np.array([[1, 1, 0], [1, 1, -1], [0, -1, -1]])
B5 = np.array([[1, 1, 1], [0, 1, 0], [-1, -1, -1]])
B6 = np.array([[0, 1, 1], [-1, 1, 1], [-1, -1, 0]])
B7 = np.array([[-1, 0, 1], [-1, 1, 1], [-1, 0, 1]])
B8 = np.array([[-1, -1, 0], [-1, 1, 1], [0, 1, 1]])
while True: # 循环迭代
tmp = img # 将上一步的操作暂存
for i in range(8): # 循环迭代八次
ret1 = cv2.morphologyEx(img, cv2.MORPH_HITMISS, B1) # B1 对图像做 击中-击不中变换
ret1 = img - ret1 # 原图 减去 上一步击中-击不中的结果
ret2 = cv2.morphologyEx(ret1, cv2.MORPH_HITMISS, B2) # 将上步的结果作为新的输入
ret2 = ret1 - ret2
ret3 = cv2.morphologyEx(ret2, cv2.MORPH_HITMISS, B3)
ret3 = ret2 - ret3
ret4 = cv2.morphologyEx(ret3, cv2.MORPH_HITMISS, B4)
ret4 = ret3 - ret4
ret5 = cv2.morphologyEx(ret4, cv2.MORPH_HITMISS, B5)
ret5 = ret4 - ret5
ret6 = cv2.morphologyEx(ret5, cv2.MORPH_HITMISS, B6)
ret6 = ret5 - ret6
ret7 = cv2.morphologyEx(ret6, cv2.MORPH_HITMISS, B7)
ret7 = ret6 - ret7
ret8 = cv2.morphologyEx(ret7, cv2.MORPH_HITMISS, B8)
ret8 = ret7 - ret8
img = ret8 # 八次迭代完成 保存结果
if (img == tmp).all(): # 如果所有结构元遍历的结果不再发生变化,则操作完成
dst = img # 保留细化结果
break
return dst.astype(np.uint8)
a = np.array([
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
[0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
], dtype=np.uint8) * 255
img = cv2.imread('./img1.png', 0)
_, img_bin = cv2.threshold(img, 120, 255, cv2.THRESH_BINARY) # 二值化处理
ret = thin(img_bin.copy()) # 传入拷贝图像
cv2.imshow('img', np.hstack((img_bin, ret)))
cv2.imwrite('./image.png',np.hstack((img_bin, ret)))
cv2.waitKey()
cv2.destroyAllWindows()
骨架
骨架的定义很简单:就是目标的前景像素点集合内部最大的内切圆盘,将所有的内切圆盘的圆心连起来就是骨架。
import numpy as np
import cv2
def skeleton_extraction(img): # 骨架抽取算法
"""
1. 建立一个canvas,用来保存骨架的子集或者全集。并建立3*3 十字架的结构元
2. 做循环迭代,循环停止的条件是原图被腐蚀成空集。img.any() 可以理解为,img 这个二维矩阵里,所有元素为零的话,返回False
3. 对原图做开运算,然后得到原图和开图像的差。这里减号不会溢出,因为二值图像非0即255,而开图像一定是原图像的子集,所以不会溢出
4. 将结果并在canvas里面,这里canvas 和自己并的话就是可以当骨架子集,又可以当骨架全集
5. 腐蚀原图
"""
canvas = np.zeros(img.shape, dtype=np.uint8) # 将结果保留在这
kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (3, 3)) # 3*3 方形结构元
while img.any(): # 循环迭代
img_open = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel) # 做开运算
img_diff = img - img_open # 原图 - 开运算图
canvas = cv2.bitwise_or(canvas, img_diff) # 将结果并在一起
img = cv2.erode(img, kernel) # 腐蚀
dst = canvas # dst 返回的图片
return dst.astype(np.uint8)
img = cv2.imread('./img1.png', 0)
_, img_bin = cv2.threshold(img, 100, 255, cv2.THRESH_BINARY) # 二值化处理
ret = skeleton_extraction(img_bin.copy()) # 传入拷贝图像
cv2.imshow('img', np.hstack((img_bin, ret)))
cv2.imwrite('./image.png',np.hstack((img_bin, ret)))
cv2.waitKey()
cv2.destroyAllWindows()