OpenCV GrabCut图像分割算法的使用及其原理

本文架构

  • GrabCut算法和Graphcut算法的关系
  • GrabCut函数参数的介绍
  • GrabCut算法的使用案例
  • GrabCut算法实现的步骤
  • GrabCut算法实现的原理

知识扩展

图像分割:选取图像中的指定目标,并将背景色置为黑色。识别目标
实例分割:在像素级识别对象轮廓的任务【最困难的视觉任务之一】。识别目标特征


GrabCut算法和GraphCut算法的关系

  • OpenCV中的GrabCut算法是GraphCut算法的改进
  • GraphCut是一种直接基于图割算法的图像分割技术, 仅仅需要确认前景和背景输入, 该算法就可以完成前景和背景的最优分割,该算法利用了图像中的纹理(颜色)信息和边界(反差)信息, 只要少量的用户交互操作即可得到比较好的分割结果, 和分水岭算法比较相似, 但是计算速度比较慢, 得到的结果比较精确。
  • 如果要从静态图像中提取前景物体(如从一个图像剪切物体到另一个图像), 采用GrabCut算法是最好的选择。

GrabCut函数参数的介绍

def grabCut(img, mask, rect, bgdModel, fgdModel, iterCount, mode=None):
# grabCut(img, mask, rect, bgdModel, fgdModel, iterCount[, mode]) -> mask, bgdModel, fgdModel
  • img:待分割原图像, 需为8位三通道彩色图像
  • mask:8位单通道掩码图像, 如果使用掩码进行初始化, 那么mask保存掩码信息, 在执行分割的时候, 也可以将用户交互所设定的前景与背景保存到mask中,然后再传入grabCut函数, 在处理结束之后,mask中会保存结果。
  • rect:包含分割对象的矩形ROI, 矩形外部的像素为背景, 矩形内部的像素为前景,当参数mode=GC_INIT_WITH_RECT使用
  • bgdModel:背景模型(内部使用), 大小为 (1,65),数据类型为 np.float64 的数组
  • fgdModel:前景模型(内部使用 ), 大小为 (1,65),数据类型为 np.float64 的数组
  • iterCount: 迭代次数, 必须大于0(并不是越大越好的)
  • mode:用于指示grabCut函数进行什么操作

mask只能选取四种情况:

  1. GC_BGD:表示明确属于背景的像素
  2. GC_FGD:表示明确属于前景的像素
  3. GC_PR_BGD:表示可能属于背景的像素
  4. GC_PR_FGD:表示可能属于前景的像素
  5. 如果没有手动标记GC_BGD或者GC_FGD, 那么结果只会有GC_PR_BGD或GC_PR_FGD

mode可以选择的值有:

  1. GC_INIT_WITH_RECT:用矩形框初始化GrabCut
  2. GC_INIT_WITH_MASK:用掩码图像初始化GrabCut
  3. GC_EVAL:执行分割

GrabCut算法的使用案例

运行结果
OpenCV GrabCut图像分割算法的使用及其原理_第1张图片
代码及解析

import numpy as np
from matplotlib import pyplot as plt
import cv2

img = cv2.imread('../../data/messi5.jpg')  # 读入目标图像
plt.imshow(img) # 显示图片,方便记录目标图像的起始点和大小,为rectangle函数做准备
plt.show()

mask = np.zeros(img.shape[:2],np.uint8)  # 制作mask图像(注意:必须与原图的大小相同)
#mask = mask.ravel()
#print(set(mask)) # {0}

bgdModel = np.zeros((1,65),np.float64) # 定义前景模块:65为固定值

fgdModel = np.zeros((1,65),np.float64) # 定义背景模块

rect = (0,0,0,0) # 次数rect参数对结果无影响

cv2.rectangle(mask,(58,41),(58+445,41+283),(3,3,3),-1, cv2.LINE_AA)  # 在mask图像中框出所要截取图像的目标

cv2.grabCut(img,mask,rect,bgdModel,fgdModel,10,cv2.GC_INIT_WITH_MASK)  # 使用grabcut函数,注意mask值得变化
#mask = mask.ravel()
#print(set(mask)) # {0, 2, 3}

mask2 = np.where((mask==1)|(mask==3),1,0).astype('uint8') # 提取前景

# mask2[:,:,np.newaxis]:将二维图像转换成三维图像
"""
mask2.shape
(342, 548)
mask2[:,:,np.newaxis].shape
(342, 548, 1)
"""
# 将原图与mask的每一个颜色空间相乘(广播)
img = img * mask2[:,:,np.newaxis] # 改变数组维度与原图保持一致

cv2.imshow('result',img) # 显示图片

cv2.waitKey(0)

cv2.destroyAllWindows()

GrabCut算法实现的步骤

  1. 在目标图像中创建一个或多个矩形(矩形外部区域默认为背景)
  2. 利用高斯混合模型(GMM)对背景和前景进行建模,并将未定义的像素标记为可能的前景或背景
  3. 图形中的每一个像素都被看作通过虚拟边与周围像素相连,基于周边像素颜色的相似度
  4. 每一个像素(即算法中的节点)会与一个前景或背景节点连接
  5. 节点连接完成后,若节点属于不同的终端,则切断他们之间的边
  6. 完成图像分割

GrabCut算法实现的原理

GrabCut分割算法在GraphCut算法的基础做了三方面的改进

  1. 利用高斯混和模型(Gaussian Mix—ture Model,GMM )取代直方图 ,将灰度图像扩展到彩色图像
  2. 用估计和参数学习过程中可进化的迭代算法代替一次最小估计来完成能量最小化
  3. 通过非完全编号降低了对交互工作的要求

1.灰度图像扩展到彩色图像

彩色图像是由RGB三色空间上的像素组成的,所以创建足够的彩色空间直方图是很难现实的,因此采用GMM模型来创建彩色空间数据模型。
GMM可以看作是一个 K K K维的协方差(通常为5)
为了方便处理GMM,在优化的过程中引入向量 K ( K 1 . . . K n ) K(K_1...K_n) KK1...Kn作为每个像素的独立GMM(前景或者背景)参数
并且每个像素点的不透明度为0或者1

故Gibbs能量函数可以改写为: E ( α , k , θ , z ) = U ( α , k , θ , z ) + V ( α , z ) E(α,k,θ,z) = U(α,k,θ,z) + V(α,z) E(α,k,θ,z)=U(α,k,θ,z)+V(α,z)

  • α α α为不透明度, α ∈ [ 0 , 1 ] α∈[0,1] α[0,1] 0 0 0为背景, 1 1 1为前景目标
  • θ θ θ为图像前景与背景的灰度直方图, θ = h ( z , α ) , α = 0 , 1 θ = {h(z,α),α=0,1} θ=h(z,α),α=0,1
  • z z z为图像灰度值数组, z z z=( z 1 , . . . z n , . . . z N z_1,...z_n,...z_N z1,...zn,...zN)

E ( α , k , θ , z ) = U ( α , k , θ , z ) + V ( α , z ) E(α,k,θ,z) = U(α,k,θ,z) + V(α,z) E(α,k,θ,z)=U(α,k,θ,z)+V(α,z):主要受到 k k k值的影响

引入GMM的彩色数据模型,数据项可以定义为: U ( α , k , θ , z ) = ∑ i = 1 n D ( α n , k n , θ , z n ) U(α,k,θ,z) = \sum_{i=1}^{n} D(α_n,k_n,θ,z_n) U(α,k,θ,z)=i=1nD(αn,kn,θ,zn)
其中: D ( α n , k n , θ , z n ) = − l o g p ( z n ∣ α n ) − l o g π ( α n , k n ) D(α_n,k_n,θ,z_n) = -log^p(z_n|α_n)-log^π(α_n,k_n) D(αn,kn,θ,zn)=logp(znαn)logπ(αn,kn)

  • p ( z n ∣ α n ) p(z_n|α_n) p(znαn)是高斯概率分布
  • π ( α n , k n ) π(α_n,k_n) π(αn,kn)是混和权重系数(累积和为常数)

即可以推导出:
D ( α n , k n , θ , z n ) = − l o g π ( α n , k n ) + 1 2 l o g d e t ( α n , k n ) + 1 2 [ z n − μ ( α n , k n ) ] T ∑ ( α n , k n ) [ z n − μ ( α n , k n ) ] D(α_n,k_n,θ,z_n) = -log^π(α_n,k_n) + \frac{1}{2}logdet(α_n,k_n)+ \frac{1}{2}[z_n-μ(α_n,k_n)]^T\sum(α_n,k_n)[z_n-μ(α_n,k_n)] D(αn,kn,θ,zn)=logπ(αn,kn)+21logdet(αn,kn)+21[znμ(αn,kn)]T(αn,kn)[znμ(αn,kn)]

因此模型的参数就确定为
θ = π ( α n , k n ) , μ ( α n , k n ) , ∑ ( α , k ) , k = 1 , 2 , . . . K θ = { π(α_n,k_n),μ(α_n,k_n),\sum(α,k), k = 1,2,...K } θ=π(αn,kn),μ(αn,kn),(α,k),k=1,2,...K

彩色图像的平滑项为:
V ( α , z ) = γ ∑ m n [ α m ≠ α n ] e x p ( − β ∥ z m − z n ∥ 2 ) V(α,z) = γ\sum_{m}^{n}[α_m≠α_n]exp(-β ∥z_m-z_n∥^2) V(α,z)=γmn[αm=αn]exp(βzmzn2)

2.迭代算法代替一次最小估计来完成能量最小化

GrabCut中的能量最小化(能量越低,越稳定)通过迭代来实现的,不像GraphCuts算法一次就完成
迭代最小化的优点是可以自动修改不透明度的值,并利用初始三元图的 T U T_U TU像素中重新确定的像素来校正彩色模型的参数。
主要流程如下

  1. 用户通过设定背景 T B T_B TB来初始化三元图T,前景设置为空集,即 T F = Ø T_F=Ø TF=Ø, T U 取 背 景 的 补 集 T_U取背景的补集 TU
  2. 对于 n ∈ T B n∈T_B nTB α n = 0 α_n=0 αn=0; n ∈ T U n∈T_U nTU α n = 1 α_n=1 αn=1
  3. 分别用 α n = 0 α_n=0 αn=0 α n = 1 α_n=1 αn=1两个集合来初始化前景和背景的GMM模型
  4. 求得 T U T_U TU中的每个像素n所对应的GMM参数 k n k_n kn, k n = a r g m i n D n ( α n , k n , θ , z n ) k_n = argminD_n(α_n,k_n,θ,z_n) kn=argminDn(αn,kn,θ,zn)
  5. 从数据 z z z中获取GMM参数 θ θ θ θ = a r g m i n U ( α , k , θ , z ) θ=argminU(α,k,θ,z) θ=argminU(α,k,θ,z)
  6. 用最小能量来得到初始分割: m i n m i n E ( α , k , θ , z ) minminE(α,k,θ,z) minminE(α,k,θ,z)
  7. 从第四步开始重复执行,直到收敛
  8. 执行边界优化

迭代最小化过程可看成总能量 E E E k k k θ θ θ α α α三个方面上的单调递减。
这样可以保证算法最终收敛到 E E E的最小值。
当判断出 E E E大幅度衰减时,自动终止迭代。

3.用户交互与不完全三元图(非完全编号)

非完全编号取代完整三元图,能够对用户的交互带来更大的灵活性。
用户的初始交互只需要确定背景区域 T B T_B TB,并不需要确定前景(从代码中即可看出),可令 T F = 0 T_F=0 TF=0
迭代能量最小化是通过允许一些编号临时表示前景像素,而背景的编号是固定不变的。
GrabCut中的初始值 T B T_B TB是用户通过标定的矩形区域来确定的


GrabCut算法实现的原理参考于《GrabCut彩色图像分割算法的研究》部分节选

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