文章目录
- 二值化
- 图像骨骼
- 连通域分割
二值化
所谓图像分割,就是将图像的目标和背景分离开来,更直观一点,就是把目标涂成白色,背景涂成黑色,言尽于此,是不是恍然大悟:这不就是二值化么?
【threshold]是此前提到的二值化函数,但只讲解了固定阈值分割模式,而并未讲解其自动分割的OTSU模式。
【adaptiveThreshold】是opencv提供的自适应阈值函数,可根据不同的卷积核来对局部进行二值化,可以更加细致地得到物体边缘。
OTSU算法,mean核,高斯核的分割结果如下图所示,其中150是手动设置的分割阈值;100是OTSU自动计算出的分割阈值。
提示说图像违规,也不知道哪违规了。
代码如下
import numpy as np
import matplotlib.pyplot as plt
from scipy.misc import ascent
import cv2
path = 'coins.jpg'
coins = {}
coins["original"] = plt.imread(path)
coins["gray"] = cv2.cvtColor(coins["original"],cv2.COLOR_RGB2GRAY)
_, coins['th150'] = cv2.threshold(coins["gray"], 150, 255,
cv2.THRESH_BINARY)
th, bImg = cv2.threshold(coins["gray"], 0, 255,
cv2.THRESH_BINARY+cv2.THRESH_OTSU)
coins[f'otsu({th})'] = bImg
method = {"mean":cv2.ADAPTIVE_THRESH_MEAN_C,
"gaussian":cv2.ADAPTIVE_THRESH_GAUSSIAN_C}
for key in method:
coins[key] = cv2.adaptiveThreshold(coins["gray"], 255,
method[key], cv2.THRESH_BINARY, 11, 2)
for i,key in enumerate(coins,1):
plt.subplot(2,3,i)
plt.imshow(coins[key], cmap='gray')
plt.title(key)
plt.axis('off')
plt.show()
图像骨骼
如果把二值图像理解成地形,黑色表示海洋,白色表示陆地,那么陆地上任意一点,到海洋都有一个最近的距离,如下图所示。由于硬币图案的颜色并不完全一致,所以在二值化时可能会出现不一致的情况,为此,需要通过腐蚀或者膨胀等形态学处理,将其内部涂抹均匀,从而得到一张目标与背景完全分割的图像,此即【dilate】图。对dilate图而言,【dist-bg】为其黑色区域的骨骼;【dist-fg】为白色区域的骨骼。
实现代码如下
import numpy as np
import matplotlib.pyplot as plt
from scipy.misc import ascent
import cv2
bImg = coins[f'otsu({th})']
kernel = np.ones((5,5),np.uint8)
coins["dilate"] = cv2.dilate(coins[f'otsu({th})'], kernel)
coins["dist-fg"] = cv2.distanceTransform(
coins["dilate"], cv2.DIST_L2,5)
coins["dist-bg"] = cv2.distanceTransform(
255-coins["dilate"], cv2.DIST_L2,5)
keys = ['dilate', 'dist-bg', 'dist-fg']
for i,key in enumerate(keys,1):
plt.subplot(1,3,i)
plt.imshow(coins[key], cmap='gray')
plt.title(key)
plt.axis('off')
plt.show()
【distanceTransform】函数的功能是,计算当前像素点到零像素点的最短距离,其输入参数有三,分别是输入的二值图像;求解距离的类型,以及掩膜尺寸,一般可设为3或者5。
在一张图像中,两点之间的距离有多种计算方式,比如
- a a a 水平和数竖直方向的变化量
- b b b 对角方向的变化量
- c c c 条约移动的变化量
距离变换函数综合了这三种距离,根据各种距离的权重不同,提供了下面几种不同的距离类别
distanceType | maskSize | 参数 |
---|---|---|
CV_DIST_C | 3 ( 3 × 3 ) (3\times3) (3×3) | a = 1 , b = 1 a=1, b=1 a=1,b=1 |
CV_DIST_L1 | 3 ( 3 × 3 ) (3\times3) (3×3) | a = 1 , b = 2 a=1, b=2 a=1,b=2 |
CV_DIST_L2 | 3 ( 3 × 3 ) (3\times3) (3×3) | a = 0.955 , b = 1.3693 a=0.955, b=1.3693 a=0.955,b=1.3693 |
CV_DIST_L2 | 5 ( 5 × 5 ) (5\times5) (5×5) | a = 1 , b = 1.4 , c = 2.1969 a=1, b=1.4, c=2.1969 a=1,b=1.4,c=2.1969 |
连通域分割
所谓连通域,即Connected Component,是一组彼此相连的像素点的集合,这些像素点彼此之间可以假设一条互相链接的路径,路径上所有像素的灰度一致,或者符合某个特定的条件。
通过连通域分割,可以将图像中不同的目标区分开来,为进一步的处理打下基础,最常用的连通域滤波流程大致如下:图像灰度化->二值化->形态学处理->标记连通域,其前面的几个步骤已经在二值化以及距离变换中得以体现,其生成标签的结果如下图所示
其中,dilate是膨胀二值图。对其进行连通域分割,得到labels图像,其中每一枚硬币所在区域,都被分配到了一个编号,即Label,最后的三维图,便是这张图像的标签值。
处理和绘图代码如下
ret, coins["labels"] = cv2.connectedComponents(coins["dilate"])
for i,key in enumerate(['dilate', 'labels'],1):
plt.subplot(1,3,i)
plt.imshow(coins[key], cmap='gray')
plt.title(key)
plt.axis('off')
ax = plt.subplot(133, projection='3d')
ys, xs = np.indices(coins['labels'].shape)
ax.plot_surface(xs, ys, coins['labels'])
plt.title("labels")
plt.show()
【connectedComponents】是opencv提供的连通域分割函数,其必不可少的输入参数是一个二值图像,此外还有两个整型参数,分别用于规定邻域形式和输出的Labels类型。其中,邻域形式主要分为4-邻域和8邻域,前者把当前像素的上下左右四个像素算作邻域,换言之,这四个像素与当前像素是连通的;8-邻域则将一个像素周围的8个像素视作邻域。