OpenCV分水岭算法图像分割

声明

声明:本系列博客是我在学习OpenCV官方教程中文版(For Python)(段力辉 译)所做的笔记。所以,其中的绝大部分内容引自这本书,博客中的代码也是其配套所附带的代码或书中的代码,侵删。其中部分代码可能会因需要而改动。在本系列博客中,其中包含书中的引用,也包括我自己对知识的理解,思考和总结。本系列博客的目的主要有两个,一个是可以作为我自己的学习笔记,时常复习巩固。第二个是可以为想学习基于python的opencv 3 相关知识的朋友提供一些参考。

正文

一、原理简介

任何一副灰度图像都可以被看成拓扑平面,灰度值高的区域可以被看成是山峰,灰度值低的区域可以被看成是山谷。我们向每一个山谷中灌不同颜色的水。随着水的位的升高,不同山谷的水就会相遇汇合,为了防止不同山谷的水汇合,我们需要在水汇合的地方构建起堤坝。不停的灌水,不停的构建堤坝知道所有的山峰都被水淹没。我们构建好的堤坝就是对图像的分割。这就是分水岭算法的背后原理。但是这种方法通常都会得到过度分割的结果,这是由噪声或者图像中其他不规律的因素造成的。

为了减少这种影响,OpenCV 采用了基于掩模的分水岭算法,在这种算法中我们要设置哪些山谷点会汇合,哪些不会。这是一种交互式的图像分割。我们要做的就是给我们已知的对象打上不同的标签。如果某个区域肯定是前景或对象,就使用某个颜色(或灰度值)标签标记它。如果某个区域肯定不是对象而是背景就使用另外一个颜色标签标记。而剩下的不能确定是前景还是背景的区域就用 0 标记。这就是我们的标签。然后实施分水岭算法。每一次灌水,我们的标签就会被更新,当两个不同颜色的标签相遇时就构建堤坝,直到将所有山峰淹没,最后我们得到的边界对象(堤坝)的值为 -1。

二、函数说明

cv.pyrMeanShiftFiltering()

pyrMeanShiftFiltering()对图像进行均值偏移滤波,这个函数作用是图像在色彩层面的平滑滤波,它可以中和色彩分布相近的颜色,平滑色彩细节,侵蚀掉面积较小的颜色区域,函数的输出是一个“色调分离”的新图像,意味着去除了精细纹理、颜色梯度大部分变得平坦。

pyrMeanShiftFiltering( InputArray src, OutputArray dst,double sp,
 double sr, int maxLevel=1,termcrit);
参数参数说明
第一个参数scr输入图像,8位,三通道的彩色图像
第二个参数dst输出图像,跟输入src有同样的大小和数据格式
第三个参数sp定义的偏移物理空间半径大小
第四个参数sr定义的偏移色彩空间半径大小
第五个参数maxLevel定义金字塔的最大层数
第六个参数termcrit定义的漂移迭代终止条件

Otsu’s 二值化

首先介绍简单阈值这个概念,当像素值高于阈值时,我们给这个像素赋予一个新值,否则我们给它赋予另外一种颜色。这个函数就是 cv2.threshold()。这个函数的第一个参数就是原图像,原图像应该是灰度图。第二个参数就是用来对像素值进行分类的阈值。第三个参数就是当像素值高于(有时是小于)阈值时应该被赋予的新的像素值。OpenCV提供了多种不同的阈值方法,这是由第四个参数来决定的。

而这个函数有两个返回值,第一个为 retVal,第二个就是阈值化之后的结果图像了。这个 retVal,当我们使用 Otsu 二值化时就会用到它。
Otsu 二值化要做的,简单来说就是对一幅双峰图像自动根据其直方图计算出一个阈值。(对于非双峰图像,这种方法得到的结果可能会不理想)。这里用到到的函数还是 cv2.threshold(),但是需要多传入一个参数(flag):cv2.THRESH_OTSU。这时要把阈值设为 0。然后算法会找到最优阈值,这个最优阈值就是返回值 retVal。如果不使用 Otsu 二值化,返回的retVal 值与设定的阈值相等。

cv.dilate()

形态学操作是根据图像形状进行的简单操作。一般情况下对二值化图像进行的操作。需要输入两个参数,一个是原始图像,第二个被称为结构化元素或核,它是用来决定操作的性质的。两个基本的形态学操作是腐蚀和膨胀。
膨胀:与腐蚀相反,与卷积核对应的原图像的像素值中只要有一个是 1,中心元素的像素值就是 1。所以这个操作会增加图像中的白色区域(前景)。一般在去噪声时先用腐蚀再用膨胀。因为腐蚀在去掉白噪声的同时,也会使前景对象变小。所以我们再对他进行膨胀。这时噪声已经被去除了,不会再回来了,但是前景还在并会增加。膨胀也可以用来连接两个分开的物体。

dilate(
  InputArray src,
  OutputArray dst,
  InputArray kernel,
  Point anchor=Point(-1,-1),
  int iterations=1,
  int borderType=BORDER_CONSTANT,
  const Scalar& borderValue=morphologyDefaultBorderValue()
     );
参数参数说明
scr原图像
dst目标图像
kernel膨胀操作的核,若为NULL时,表示的是使用参考点位于中心3x3的核
anchor锚的位置,其有默认值(-1,-1),表示锚位于中心
iteration迭代使用erode()函数的次数,默认值为1
bordertype用于推断图像外部像素的某种边界模式
borderValue当边界为常数时的边界值

cv.morphologyEx()

开运算:表示先进行腐蚀,再进行膨胀操作 ,它被用来去除噪声。
闭运算:表示先进行膨胀操作,再进行腐蚀操作。它经常被用来填充前景物体中的小洞,或者前景物体上的小黑点。

op = cv2.MORPH_OPEN 进行开运算
op = cv2.MORPH_CLOSE 进行闭运算

cv2.morphologyEx(src, op, kernel)
#参数依次表示:传入的图片、进行变化的方式、表示方框的大小

cv.distanceTransform()

当图像内的各个子图没有连接时,可以直接使用形态学的腐蚀操作确定前景对象,但是如果图像内的子图连接在一起时,就很难确定前景对象了。此时,借助于距离变换函数cv2.distanceTransform()可以方便地将前景对象提取出来。

距离变换的基本含义是计算一个图像中非零像素点到最近的零像素点的距离,也就是到零像素点的最短距离。

cv2.distanceTransform(src, distanceType, maskSize)
#参数依次表示:输入图片、计算距离的类型、距离变换掩码矩阵的大小。
# 第二个参数 0,1,2 分别表示 CV_DIST_L1, CV_DIST_L2 , CV_DIST_C

cv.connectedComponents()

opencv中,对于一张二值化的图像,后续处理方式有两种。第一种方式就是利用findContours、drawContours等函数进行轮廓分析(opencv以对轮廓的处理为主)。第二种方式就是计算连通域进行区域分析,计算连通域的函数有两个,一个是这个,另一个是cv.connectedComponentsWithStats()

cv.connectedComponents (image, labels, connectivity, ltype)
#参数依次代表:输入8位单通道二值图像、目标标记图像、连通域(默认是8连通)、输出图像标签类型

cv.watershed()

cv.watershed(image, markers)

第一个参数代表原图像,必须是一个8位3通道彩色图像矩阵序列。
第二个参数,在执行分水岭函数watershed之前,必须对第二个参数markers进行处理,它应该包含不同区域的轮廓,每个轮廓有一个自己唯一的编号,轮廓的定位可以通过Opencv中findContours方法实现,这个是执行分水岭之前的要求。

三、完整代码

import cv2 as cv
import numpy as np

def watershed_demo(image):
    print(image.shape)
    blurred = cv.pyrMeanShiftFiltering(image, 10, 100)
    gray = cv.cvtColor(blurred, cv.COLOR_BGR2GRAY)
    ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
    cv.imshow("binary", binary)

    # morphology operation
    kernel = cv.getStructuringElement(cv.MORPH_RECT, (3, 3))
    opening = cv.morphologyEx(binary, cv.MORPH_OPEN, kernel=kernel, iterations=2)
    sure_bg = cv.dilate(opening, kernel, iterations=3)
    cv.imshow("morphology operation", sure_bg)
    # Finding sure foreground area
    ist_transform = cv.distanceTransform(opening, 1,5)
    ret, sure_fg = cv.threshold(dist_transform, 0.7*dist_transform.max(), 255, 0)

    # Finding unknown region
    sure_fg = np.uint8(sure_fg)
    unknown = cv.subtract(sure_bg,sure_fg)

    ret, markers1 =cv.connectedComponents(sure_fg)
    print(ret)

    # watershed transform
    markers = markers1 + 1
    markers[unknown==255] = 0
    markers3 = cv.watershed(image, markers=markers)
    image[markers3 == -1] =[0, 0, 255]
    cv.namedWindow('result', 0)
    cv.imshow("result", image)


def main():
    src = cv.imread("circle.png")
    cv.imshow("demo",src)
    watershed_demo(src)
    cv.waitKey(0)  
    cv.destroyAllWindows()  


if __name__ == '__main__':
    main()

以下是运行结果:
在这里插入图片描述

感谢观看!

如有错误,欢迎批评指正!

  • 4
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值