计算机视觉(八):图像分割

      • 一、阈值处理
          • 1. 基础知识
          • 2. 基本的全局阈值处理
          • 3. 用图像平滑改善全局阈值处理
          • 4. 利用边缘改进全局阈值处理
      • 二、基于区域的分割
          • 1. 区域生长
          • 2. 区域分裂与聚合
      • 三、使用GrabCut算法分割图像
      • 四、用形态学分水岭的分割
          • 1. 背景知识
          • 2. 分水岭分割算法
      • 五、代码实现(Python+OpenCV)

一、阈值处理

      由于阈值处理直观、实现简单且计算速度快,因此图像阈值处理在图像分割应用中处于核心地位。

1. 基础知识

      假设图1(a)中的灰度直方图对应于图像 f ( x , y ) f(x,y) f(x,y),该图像由暗色背景上的较亮物体组成,以这样的组成方式,物体像素和背景像素所具有的灰度值组成了两种支配模式。从背景中提取物体的一种明显方法是,选择一个将这些模式分开的阈值 T T T。然后, f ( x , y ) > T f(x,y)>T f(x,y)>T 的任何点 ( x , y ) (x,y) (x,y) 称为一个对象点;否则该点称为背景点。分割后的图像 g ( x , y ) g(x,y) g(x,y) 由下式给出:
g ( x , y ) = { 1 , f ( x , y ) > T 0 , f ( x , y ) ≤ T            ( 1 ) g(x,y) = \begin{cases} 1, & \text{$f(x,y) > T$} \\ 0, & \text{$f(x,y) ≤ T$}\ \ \ \ \ \ \ \ \ \ (1) \end{cases} g(x,y)={1,0,f(x,y)>Tf(x,y)T          (1) T T T 是一个适用于整个图像的常数时,上式给出的处理称为全局阈值处理。当 T T T 值在一幅图像上改变时,我们把该处理称为可变阈值处理(局部阈值处理或区域阈值处理有时用于表示可变阈值处理)。若 T T T 取决于空间坐标 ( x , y ) (x,y) (x,y) 本身,则可变阈值处理通常称为动态阈值处理或自适应阈值处理。
      图1(b)显示了一个更为困难的阈值处理问题,它包含有三个支配模式的直方图。分割的图像由下式给出:
g ( x , y ) = { a , f ( x , y ) > T 2 b , T 1 < f ( x , y ) ≤ T 2 c , f ( x , y ) ≤ T 1 g(x,y) = \begin{cases} a, & \text{$f(x,y) > T_2$} \\ b, & \text{$T_1 < f(x,y) ≤ T_2$} \\ c, & \text{$f(x,y) ≤ T_1$} \end{cases} g(x,y)=a,b,c,f(x,y)>T2T1<f(x,y)T2f(x,y)T1式中,a、b和c是任意三个不同的灰度值。
计算机视觉(八):图像分割_第1张图片

图1

2. 基本的全局阈值处理

      当物体和背景像素的灰度分布十分明显时,可以用适用于整个图像的单个(全局)阈值。能对每幅图像自动估计阈值的算法如下:
① 为全局阈值 T T T 选择一个初始估计值。
② 在式(1)中用 T T T 分割该图像。这将产生两组像素: G 1 G_1 G1 由灰度值大于 T T T 的所有像素组成, G 2 G_2 G2 由所有小于等于 T T T 的像素组成。
③ 对 G 1 G_1 G1 G 2 G_2 G2 的像素分别计算平均灰度值(均值) m 1 m_1 m1 m 2 m_2 m2
④ 计算一个新的阈值: T = 1 2 ( m 1 + m 2 ) T = {1\over 2}(m_1+m_2) T=21(m1+m2)
⑤ 重复步骤2到步骤4,直到连续迭代中的 T T T 值间的差小于一个预定义的参数 Δ T \Delta T ΔT 为止。
      通常, Δ T \Delta T ΔT 越大,则算法执行的迭代次数越少。所选的初始阈值必须大于图像中的最小灰度级而小于最大灰度级。图像的平均灰度对于 T T T 来说是较好的初始选择。

3. 用图像平滑改善全局阈值处理

      噪声会将简单的阈值处理问题变为不可解决的问题。当噪声不能在源头减少,并且阈值处理又是所选择的分割方法时,通常能增强性能的一种技术是,在阈值处理之前平滑图像。
      经平滑和分割后的图像,由于对边界的模糊,会造成物体和背景间的边界稍微有点失真。对一幅图像平滑越多,分割后的结果中的边界误差就越大。

4. 利用边缘改进全局阈值处理

       f ( x , y ) f(x,y) f(x,y) 表示输入图像,利用边缘改进全局阈值处理算法如下:
① 采用特征检测中讨论的任何一种方法来计算一幅边缘图像,无论是 f ( x , y ) f(x,y) f(x,y) 梯度的幅度还是拉普拉斯的绝对值均可。
② 指定一个阈值 T T T
③ 用步骤2中的阈值对步骤1中的图像进行阈值处理,产生一幅二值图像 g T ( x , y ) g_T(x,y) gT(x,y)。在从 f ( x , y ) f(x,y) f(x,y) 中选取对应于“强”边缘像素的下一步中,该图像用做一幅模板图像。
④ 仅用 f ( x , y ) f(x,y) f(x,y) 中对应于 g T ( x , y ) g_T(x,y) gT(x,y) 中像素值为1的位置的像素计算直方图。
⑤ 用步骤4中的直方图全局地分割 f ( x , y ) f(x,y) f(x,y),例如使用Ostu 方法。



二、基于区域的分割

      本段讨论以直接寻找区域为基础的分割技术。

1. 区域生长

      区域生长是根据预先定义的生长准则,将像素或子区域组合为更大区域的过程。基本方法是从一组“种子”点开始,将与种子预先定义的性质相似的那些邻域像素添加到每个种子上,来形成这些生长区域(如特定范围的灰度或颜色)。
      令 f ( x , y ) f(x, y) f(x,y) 表示一个输入图像阵列; S ( x , y ) S(x, y) S(x,y) 表示一个种子阵列,阵列中种子点位置处为1,其他位置处为0; Q Q Q 表示在每个位置 ( x , y ) (x, y) (x,y) 处所用的属性。假设阵列 f f f S S S 的尺寸相同。基于8连接的一个基本区域生长算法如下:
① 在 S ( x , y ) S(x, y) S(x,y) 中寻找所有连通分量,并把每个连通分量腐蚀为一个像素;把找到的所有这种像素标记为1,把 S S S 中的所有其他像素标记为0.
② 在坐标对 ( x , y ) (x, y) (x,y) 处形成图像 f Q f_Q fQ:若输入图像在该坐标处满足给定的属性 Q Q Q,则令 f Q ( x , y ) = 1 f_Q(x,y) = 1 fQ(x,y)=1,否则令 f Q ( x , y ) = 0 f_Q(x,y) = 0 fQ(x,y)=0
③ 令 g g g 是这样形成的图像:即把 f Q f_Q fQ 中为8连通种子点的所有1值点,添加到 S S S 中的每个种子点。
④ 用不同的区域标记标出 g g g 中的每个连通分量。这就是由区域生长得到的分割图像。

2. 区域分裂与聚合

      区域分裂与聚合指,首先将一幅图像细分为一组任意的不相交区域,然后聚合和/或分裂这些区域,试图满足我们所需的分割条件。
      令 R R R 表示整幅图像区域,并选择一个属性 Q Q Q。对 R R R 进行分割的一种方法是,依次将它细分为越来越小的四象限区域,以便对于任何区域 R i R_i Ri Q ( R i ) = T R U E Q(R_i)=TRUE Q(Ri)=TRUE。具体过程如下:
① 把满足 Q ( R i ) = F A L S E Q(R_i) = FALSE Q(Ri)=FALSE 的任何区域 R i R_i Ri 分裂为4个不相交的象限区域。
② 不可能进一步分裂时,对满足条件 Q ( R j ⋃ R k ) = T R U E Q(R_j \bigcup R_k) = TRUE Q(RjRk)=TRUE 的任意两个邻域区域 R j R_j Rj R k R_k Rk 进行聚合。
③ 无法进一步聚合时,停止操作。



三、使用GrabCut算法分割图像

      GrabCut算法的实现步骤为:
① 在图片中定义含有(一个或多个)物体的矩形。
② 矩形外的区域被自动认为是背景。
③ 对于用户定义的矩形区域,可用背景中的数据来区别它里面的前景和背景区域。
④ 用高斯混合模型来对背景和前景建模,并将未定义的像素标记为可能的前景或背景。
⑤ 图像中的每一个像素都被看作通过虚拟边与周围像素相连接,而每条边都有一个属于前景或背景的概率,这基于它与周围像素颜色上的相似性。
⑥ 每一个像素(即算法中的节点)会与一个前景或背景节点连接。
⑦ 在节点完成连接后(可能与背景或前景连接),若节点之间的边属于不同终端,则会切断它们之间的边,这就能将图像各部分分割出来。



四、用形态学分水岭的分割

1. 背景知识

      形态学分水岭分割将前面讨论的分割方法中的许多概念进行了具体化,因此通常会产生更稳定的分割结果,包括连接的分割边界。
      分水岭的概念是以三维方式来形象化一幅图像为基础的:两个空间坐标作为灰度的函数,如图2所示。在这种“地形学”解释中,我们考虑三种类型的点:
(a) 属于一个区域最小值的点;
(b) 把一点视为一个水滴,如果把这些点放在任意位置上,水滴一定会下落到某个最小值点;
(c) 处在该点的水会等概率地流向不止一个这样的最小值点。
对于一个特定的区域最小值,满足条件(b)的最小值点的集合称为该最小值的汇水盆地或分水岭。满足条件(c)的点形成地面的峰线,它称为分割线或分水线。
计算机视觉(八):图像分割_第2张图片

图2

      基于这些概念的分割算法的主要目标是找出分水线,其基本思想非常简单。假设在每个区域的最小值上打一个洞,并且让水通过洞以均匀的速率上升,从低到高淹没整个地形。当不同汇水盆地中上升的水聚集时,修建一个水坝来阻止这种聚合。水将达到在水线上只能见到各个水坝的顶部的程度。这些大坝的边界对应于分水岭的分割线。具体见图3。
      分水岭分割的主要应用之一是,从背景中提取近乎一致的物体。由变化较小的灰度表征的区域有较小的梯度值。因此,我们经常见到分水岭分割方法用于一幅图像的梯度,而不是图像本身。

图3    (a) 原图像;(b) 地形俯视图;(c)~(d) 被水淹没的两个阶段;(e) 进一步淹没的结果;
(f) 来自两个汇水盆地的水开始汇聚(两个汇水盆地之间构筑了一个较短的水坝);(g) 较长的水坝;(h) 最终的分水(分割)线

2. 分水岭分割算法

      令 M 1 , M 2 , . . . , M R M_1,M_2,...,M_R M1,M2,...,MR 是梯度图像 g ( x , y ) g(x,y) g(x,y) 中区域最小值点的坐标集。令 C ( M i ) C(M_i) C(Mi) 是与区域最小值 M i M_i Mi 相关的汇水盆地中的点的坐标集。符号 m i n min min m a x max max 表示 g ( x , y ) g(x,y) g(x,y) 的最小值和最大值。最后,令 T [ n ] T[n] T[n] 表示满足 g ( s , t ) < n g(s,t)<n g(s,t)<n 的坐标 ( s , t ) (s,t) (s,t) 的集合,即:
T [ n ] = { ( s , t ) ∣ g ( s , t ) < n } T[n] = \{(s,t) | g(s,t)<n\} T[n]={(s,t)g(s,t)<n} C n ( M i ) C_n(M_i) Cn(Mi) 表示汇水盆地中与淹没阶段 n n n 的最小值 M i M_i Mi 相关联的点的坐标集,即:
C n ( M i ) = C ( M i ) ⋂ T [ n ] C_n(M_i) = C(M_i) \bigcap T[n] Cn(Mi)=C(Mi)T[n]接下来,令 C [ n ] C[n] C[n] 表示阶段 n n n 中已被水淹没的汇水盆地的并集:
C [ n ] = ⋃ i = 1 R C n ( M i ) C[n] = \bigcup_{i=1}^RC_n(M_i) C[n]=i=1RCn(Mi)然后,令 C [ m a x + 1 ] C[max+1] C[max+1] 表示所有汇水盆地的并集:
C [ m a x + 1 ] = ⋃ i = 1 R C ( M i ) C[max+1] = \bigcup_{i=1}^RC(M_i) C[max+1]=i=1RC(Mi)      寻找分水线的算法使用 C [ m i n + 1 ] = T [ m i n + 1 ] C[min+1] = T[min + 1] C[min+1]=T[min+1] 来初始化。然后,该算法进行递归处理,由 C [ n − 1 ] C[n-1] C[n1] 计算 C [ n ] C[n] C[n]。由 C [ n − 1 ] C[n-1] C[n1] 求得 C [ n ] C[n] C[n] 的过程如下:令 Q Q Q 表示 T [ n ] T[n] T[n] 中的连通分量的集合。然后,对于每个连通分量 q ∈ Q [ n ] q\in Q[n] qQ[n],有如下三种可能性:
q ⋂ C [ n − 1 ] q \bigcap C[n-1] qC[n1] 为空集。
q ⋂ C [ n − 1 ] q \bigcap C[n-1] qC[n1] 包含 C [ n − 1 ] C[n-1] C[n1] 的一个连通分量
q ⋂ C [ n − 1 ] q \bigcap C[n-1] qC[n1] 包含 C [ n − 1 ] C[n-1] C[n1] 的一个以上的连通分量
      由 C [ n − 1 ] C[n-1] C[n1] 构建 C [ n ] C[n] C[n] 取决于这三个条件中的哪个条件成立。
      遇到一个新的最小值时,条件①发生,这种情况下,连通分量 q q q 并入 C [ n − 1 ] C[n-1] C[n1] 中形成 C [ n ] C[n] C[n]
      当 q q q 位于某些局部最小值的汇水盆地内时,条件②发生,这种情况下, q q q 并入 C [ n − 1 ] C[n-1] C[n1] 中形成 C [ n ] C[n] C[n]
      当遇到全部或部分分隔两个或多个汇水盆地的山脊线时,条件③发生。进一步淹没会导致这些汇水盆地中的水位聚合。因此,必须在 q q q 内构筑一个水坝(如果涉及两个以上的汇水盆地,就要构筑多个水坝)以阻止汇水盆地间的水溢出。



五、代码实现(Python+OpenCV)

使用GrabCut算法和分水岭算法进行图像分割

1、cv2.grabCut(img,mask,rect,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_RECT)

img:输入图像。
mask:一个指定哪些区域是背景、前景、可能的背景、可能的前景的蒙版图像。它将下面的标志cv2.GC_BGD、cv2.GC_FGD、cv2.GC_PR_BGD、cv2.GC_PR_FGD或简单的0、1、2、3传递给图像。
rect:包含前景对象的矩形的坐标,格式为(x,y,w,h)。
bdgModel 和 fgdModel:这些是内部算法使用的数组,只需创建两个大小为 (1,65) 的 np.float64 类型零数组。
iterCount:算法运行的迭代次数。
mode:值为 cv2.GC_INIT_WITH_RECT 或 cv2.GC_INIT_WITH_MASK 或 它们的组合,决定我们是画矩形还是最后的触点。

2、cv2.threshold(img, T, newValue, method):两个返回值,第二个就是阈值化后的图像。 

img:输入图像。
T:阈值。
newValue:高于(低于)阈值时赋予的新值。
method:可能的值 cv2.THRESH_BINARY(黑白二值)、cv2.THRESH_BINARY_INV(黑白二值反转)、cv2.THRESH_TRUNC、cv2.THRESH_TOZERO、cv2.THRESH_TOZERO_INV
import cv2
import numpy as np


'''
GrabCut算法
'''
img = cv2.imread('img1.jpg')
mask = np.zeros(img.shape[:2], np.uint8) # 0,表示指定为背景

# 内部算法使用的数组,大小为 (1,65) 的 np.float64 类型零数组
bgdModel = np.zeros((1,65), np.float64)
fgdModel = np.zeros((1,65), np.float64)

rect = (375,235,70,250) # 该矩阵区域包含前景对象
cv2.grabCut(img, mask, rect, bgdModel, fgdModel, 10, cv2.GC_INIT_WITH_RECT)

# mask现在包含0~3之间的值,将值为0、2的转为0,值为1、3的转为1
mask2 = np.where((mask==2)|(mask==0), 0, 1).astype('uint8') 

grab = img*mask2[:,:,np.newaxis] # 使用mask2过滤值为0的像素,保留前景像素
cv2.imwrite('grab.jpg', grab)


'''
分水岭算法
'''
img = cv2.imread('img2.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 设置阈值,将图像中非白像素转化成黑色像素,并将黑白二值反转
ret, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
cv2.imwrite('thresh.jpg', thresh)

# 获取前景区域与背景区域
kernel = np.ones((3,3), np.uint8)
opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel,iterations = 2) # 通过 morphologyEx 变换去除噪声数据
sure_bg = cv2.dilate(opening,kernel,iterations = 3) # 通过对 morphologyEx 变换之后的图像进行膨胀操作,可以得到大部分都是背景的区域
dist_transform = cv2.distanceTransform(opening,cv2.DIST_L2,5) # 将远离背景区域的边界的点确定为前景
ret, sure_fg = cv2.threshold(dist_transform, 0.7*dist_transform.max(),255,0) # 应用阈值处理使获得确定的前景区域概率更高
cv2.imwrite('sure_bg.jpg', sure_bg)
cv2.imwrite('sure_fg.jpg', sure_fg)

sure_fg = np.uint8(sure_fg)
unknown = cv2.subtract(sure_bg,sure_fg) # sure_bg与sure_fg可能存在重合,可从sure_bg与sure_fg的集合相减得到该不确定区域
ret, markers = cv2.connectedComponents(sure_fg) # 设定“栅栏”阻止水汇聚

# 在背景区域上加1,将unknown区域设为0
markers = markers + 1
markers[unknown==255] = 0

# 最后打开门,让水漫起来并把栅栏绘成青色
markers = cv2.watershed(img, markers)
img[markers == -1] = [255, 255, 0]
cv2.imwrite('water.jpg', img)

计算机视觉(八):图像分割_第3张图片









以上全部内容参考书籍如下:
冈萨雷斯《数字图像处理(第三版)》
Joe Minichino、Joseph Howse《OpenCV 3计算机视觉Python语言实现(原书第2版)》

你可能感兴趣的:(计算机视觉)