《OpenCV轻松入门》学习打卡第九天

第十七章 图像分割与提取(重点)

在图像处理过程中,经常需要从图像中将前景图像作为目标图像分割或提取出来,在前面的章节中,我们讨论了如何使用诸如图像形态学变换、阈值算法、图像金字塔、图像轮廓、边缘提取等方法对图像进行分割

1. 用分水岭算法实现图像分割与提取

图像分割是图像处理过程中一种非常重要的操作,分水岭算法将图像形象的比喻为地理学上的地形表面,实现图像分割,该算法十分有效

1)算法原理

任何一幅灰度图像,都可以被看作是地理学上的地形表面,灰度值高的区域可以被看作为山峰,灰度值低的区域可以被看作为山谷。如果我们向每一个山谷中“注灌”不同颜色的水,那么随着水位的不断升高,不同山谷的水就会汇集到一起,在这个过程中,为了防止不同山谷的水交汇,我们需要在水流可能汇合的地方构建堤坝,该过程将图像分为两个不同的集合,集水盆地和分水岭线,我们构建的堤坝就是分水岭线,也即对原始图像的分割,这就是分水岭算法。
由于噪声等因素的影响,采用上述基础分水岭算法经常会得到过度分割的结果,过度分割会将图像分为划分为一个个稠密的独立小块,让分割失去了意义。为了改善,人们提出了基于掩模的改进的分水岭算法,改进的分水岭算法允许用户将它认为是同一个分割区域的部分标注出来(被标注的部分就叫做掩模),这样,分水岭算法在处理时,就会将标注的部分处理为同一个分割区域。

2)相关函数介绍

在OpenCV中,可以使用函数cv2.watershed()实现分水岭算法,在实现过程中,还需要借助形态学函数,距离变换函数cv2.distanceTransform(),cv2.connectedComponents()来完成图像分割
1>形态学函数回顾
在使用分水岭算法对图像进行分割以前,需要对图像进行简单的形态学处理
<1> 开运算
开运算是先腐蚀后膨胀的操作,能够去除图像内的噪声,在使用分水岭算法以前,我们要先使用开运算去除图像内的噪声,以免噪声对图像分割可能造成的干扰
<2> 获取图像边界
通过形态学操作和减法运算能够获取图像的边界,但是形态学操作仅适用于比较简单的图像,如果图像内的前景对象存在连接的情况,使用形态学操作就无法准确获取各个子图像的边界了
2>距离变换函数distanceTransform
当图像内各个子图没有连接时,可以直接使用形态学的腐蚀操作确定前景图像。但是如果子图连接在一起的话,就很难确定前景对象了,此时,借助于距离变换函数cv2.distanceTransform()可以方便的将前景对象提取出来。
距离变换函数计算二值图像内任意点到最近背景点的距离,一般情况下,该函数计算的是图像内非零值像素点到最近的零值像素点的距离,也就是说计算所有像素点到最近值为0的像素点的距离,当然如果像素点本身的值为0,则这个距离也为0
如果对上述计算结果进行阈值化,就可以得到图像内子图的中心,骨架等信息,距离变换函数cv2.distanceTransform()可以用于计算对象的中心,还能细化轮廓、获取图像前景等,有多种功能。语法格式为:
dst = cv2.distanceTransform( src, distanceType, maskSize, dstType )
参数src是8位单通道的二值图像,distanceType为距离类型参数,可以为如下值:
· cv2.DIST_USER:用户自定义距离
· cv2.DIST_L1:distance = |x1 - x2| + |y1 - y2|
· cv2.DIST_L2:简单欧几里得距离
· cv2.DIST_C:distance = max(|x1 - x2|, |y1 - y2|) …等等
参数maskSize为掩模的尺寸,需要注意,当distanceType = cv2.DIST_L1或cv2.DIST_C时,maskSize强制为3,dstType为目标图像的类型,默认值为CV_32F,dst表示计算得到的目标图像,可以是8位或者32位浮点数,尺寸与src相同
3>确定未知区域
使用形态学的膨胀操作能够将图像内的前景“膨胀放大”,当图像内的前景被放大后,背景就会被压缩,所以此时得到的背景信息一定小于实际背景,不包含前景的“确定背景”,确定背景称为B,距离变换函数cv2.distanceTranform()能够获取图像的“中心”,得到“确定前景”,为了方便说明,将确定前景称为F。图像中有了确定前景F和确定背景B,剩下的区域就是未知区域UN了,这部分正是分水岭算法要进一步明确的区域,针对一幅图像O

        未知区域UN = 图像O - 确定背景B - 确定前景F

而图像O-确定背景B可以通过对图像进行形态学的膨胀操作得到
4>函数connectedComponents
明确了确定前景以后,就可以对确定前景图像进行标注了,在OpenCV中,可以使用函数cv2.connectedComponents()进行标注,该函数会将背景标注为0,将其他的对象使用从1开始的正整数标记。语法格式为:
retval, labels = cv2.connectedComponents(image)
参数image为8位单通道的待标注图像,retval为返回的标注的数量,labels为标注的结果图像
该函数在标注图像时,会将背景标注为0,将其他的对象用从1开始的正整数标注,具体的对应关系为:
· 数值0代表背景区域
· 从数值1开始的值,代表不同的前景区域
在分水岭算法中,标注值0代表未知区域,所以我们要对函数cv2.connectedComponents()的标注结果进行调整:将标注的结果都加上数值1,这样的话:
· 数值1代表背景区域
· 从数值1开始的值,代表不同的前景区域
为了使用分水岭算法,还需要对原始图像内的未知区域进行标注,将已经计算出来的未知区域标注为0即可,关键代码为:

 ret, markers = cv2.connectedComponents(fore)
 markers = markers + 1
 markers[未知区域] = 0

5>函数cv2.watershed()
完成上述处理后,就可以使用分水岭算法对预处理结果图像进行分割了,语法格式为:
markers = cv2.watershed(image, markers)
参数image为输入图像,必须是8位三通道图像,在对图像使用cv2.watershed()函数处理之前,必须先用正数大致勾画出图像中的期望分割区域,每一个分割的区域会被标注为1,2,3等,对于尚未确定的区域,需要将它们标注为0.我们可以将标注区域理解为进行分水岭算法分割的“种子”区域,markers是32位单通道的标注结果,它应该和image具有相等大小,在markers中,每一个像素要么被设置为初期的“种子值”,要么被设置为-1表示边界,markers可以省略

3)分水岭算法图像分割

使用分水岭算法进行图像分割时,基本的步骤为:
1.通过形态学开运算对原始图像O去噪
2.通过腐蚀操作获取“确定背景B”,需要注意,这里得到“O - B”即可
3.利用距离变换函数cv2.distanceTransform()对原始图像进行运算,并对其进行阈值处理,得到“确定前景F”
4)计算未知区域UN(UN = O - B - F)
5)利用函数cv2.connectedComponents()对原始图像O进行标注
6)对函数cv2.connectedComponents()的标注结果进行修正
7)使用分水岭算法完成对图像的分割

2. 交互式前景提取

2004年,微软研究院的Rother等人提出了交互式前景提取技术,他们提出的算法,仅需要做很少的操作,就能够准确的提取出前景图像
GrabCut算法的具体实施过程:
1) 将前景所在的大致位置使用矩形框标注出来,值得注意的是,此时矩形框框出的仅仅是前景的大致位置,其中既包含前景,又包含背景,所以该区域实际上是未确定区域,但是,该区域以外的区域被认为是“确定背景”
2) 根据矩形框的外部的“确定背景”数据来区分矩形框区域内的前景和背景
3) 用高斯混合模型(GMM)对前景和背景建模,GMM会根据用户的输入学习并创建新的像素分布,对未分类的像素(可能是前景也可能是背景)根据其与已知分类像素的关系进行分类
4) 根据像素分布情况生成一幅图,图中的节点就是各个像素点,除了像素点之外,还有两个节点:前景节点和背景节点,所有的前景像素都和前景节点相连,所有的背景像素都和背景节点相连。每个像素连接到前景节点或者背景节点的边的权重由像素是前景或背景的概率来决定
5) 图中的每个像素除了与前景节点或背景节点相连以外,彼此之间还存在着连接,两个像素连接的边的权重由它们的相似性决定,两个像素的颜色越接近,边的权重越大
6) 完成节点连接以后,需要解决的问题就变成了一幅连通的图,在该图上根据各自边的权重关系进行切割,将不同的点划分成前景节点和背景节点
7) 不断重复上述过程,直至分类收敛为止
在OpenCV中,实现交互式前景提取的函数是cv2.grabCut(),其语法格式为:
mask, bgdModel, fgdModel = cv2.grabCut( img, mask, rect, bgdModel, fgdModel, iterCount, mode)
式中
· img为输入图像,要求是8位3通道的
· mask为掩模图像,要求是8位单通道的该参数用于确定前景区域、背景区域和不确定区域,可以设置为4种模式:
· cv2.GC_BGD:表示确定背景,也可以用数值0表示
· cv2.GC_FGD:表示确定前景,也可以用数值1表示
· cv2.GC_PR_BGD:表示可能的背景,也可以用数值2表示
· cv2.GC_PR_FGD:表示可能的前景,也可以用数值3表示
在最后使用模板提取前景时,会将参数值0和2合并为背景(均当作0处理),将参数值1和3合并为前景(均当作1处理),通常情况下,我们可以使用白色笔刷和黑色笔刷在掩模图像上做标记,再通过转换将其中的白色像素设置为0,黑色像素设置为1
· rect指包含前景对象的区域,该区域外的部分被认为是“确定背景”,因此在选取时,务必确保让前景包含在rect指定的范围内,否则,rect外的前景部分是不会被提取出来的,只有当参数mode的值被设置为矩形模式cv2.GC_INIT_WITH_RECT时,参数rect才有意义,其格式为(x, y, w, h),分别表示区域左上角像素的x轴和y轴坐标以及区域的宽度和高度,如果前景位于右下方,又不想判断原始图像的大小,对于w和h可以直接用一个很大的值,使用掩模模式时,将该值设置为none即可
· bgdModel为算法内部使用的数组,只需要创建大小为(1,65)的numpy.float64数组
· fgdModel为算法内部使用的数组,只需要创建大小为(1,65)的numpy.float64数组
· iterCount表示迭代的次数
· mode表示迭代模式,其可能的值为:
cv2.GC_INIT_WITH_RECT:使用矩形模板
cv2.GC_INIT_WITH_MASK:使用自定义模板,需要注意上面这个可以和该值组合使用,所有ROI区域外的像素会自动被处理为背景
cv2.GC_EVAL:修复模式
cv2.GC_EVAL_FREEZE_MODEL:使用固定模式
一般情况下,自定义模板的步骤为:
1. 先使用numpy.zeros构造一个内部像素值都是0(表示确定背景)的图像mask,以便在后续步骤中逐步对该模板图像进行细化
2. 使用mask[30: 512, 50: 400] = 3,将模板图像中第30行到第512行,第50列到第400列的区域划分为可能的前景(像素值为3,对应参数mask的含义为“可能的前景”)
3. 使用mask[50: 300, 150, 200] = 1,将模板图像中第50行到300行,第150列到200列的区域划分为确定前景(像素值为1,对应参数mask的含义为“确定前景”)
在此基础上,将mask作为自定义模板,使用GrabCut算法,完成前景的提取

若有侵权,请联系删除

本读书笔记来源于教材。1


  1. 李立宗. OpenCV轻松入门: 面向python[M].北京. 电子工业出版社: 李立宗. 2019.5 ↩︎

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值