分水岭算法用于实现分割两个挨在一起的物体

参考博客:https://zhuanlan.zhihu.com/p/67741538

前提:任何两个相邻连接的物体不一定能被分水岭边界(marker标记为-1的像素)分开,比如在传递给 watershed 函数的初始标记图像中的前景是相互接触的话是分不开的

分水岭算法原理:

       灰度图像可以看成是一个地形表面,高强度值表示山峰,低强度值及较低强度值表示山谷及其影响区域。用不同颜色的水(标签)填充每个孤立的山谷(局部极小值)。当水上升时,根据附近的山峰(图像的梯度),不同山谷不同颜色的水显然会开始融合。为了避免这种情况,在水就要融合的地方及时增加屏障(增高水坝)。然后继续涨水,建立屏障,直到所有的山峰都被淹没,最后建立的屏障形成的线就是分割结果,所以分水岭表示的其实是输入图像的极大值点。

       分水岭就是分隔相邻两个山谷的山岭或高地,见下图所示(https://baike.baidu.com/item/流域分水岭/22183313):

由于opencv的实现中用到了距离变换,这里先简介距离变换的原理:

距离变换原理:

定义:计算二值图像中非零像素点到最近的零像素点的距离。

  距离变换的处理图像通常都是二值图像,而二值图像其实就是把图像分为两部分,即背景和物体两部分,物体通常又称为前景目标,通常前景目标的灰度值设置为255(白色),背景的灰度值设置为0(黑色),所以定义中的非零像素点即为前景目标,零像素点即为背景,所以图像中前景目标中的像素点距离背景越远,那么距离就越大,如果用这个距离值替代像素值,那么新生成的图像中这个点就越亮。

  如果想显示距离变换后的图,需要对经过距离变换后的图进行归一化处理,因为经过距离变换返回的图像数据是浮点数值,要想在浮点数表示的颜色空间(注:在整数表示的颜色空间中,数值范围是0-255)中,数值范围必须是0-1,所以要将其中的数值进行归一化处理,若是不做归一化处理,数值大于1的都会变为1.0处理(结果与二值化结果一样)。

对相互接触的硬币利用分水岭算法分割opencv实现:

Opencv是基于输入图像及其标记图像找到分水岭实现分割的。

在OpenCV中,我们需要给不同区域贴上不同的标签。用大于1的整数表示我们确定为前景或对象的区域,用1表示我们确定为背景或非对象的区域,最后用0表示我们无法确定的区域,相当于是分水岭中的注水点位置,从这些点开始注水使得水平面上升。然后应用分水岭算法,我们的标记图像将被更新,更新后的标记图像的边界像素值为-1,也就是分割结果。

0、原始图像:

1、利用阈值法分割得到二值图像:

import cv2 as cv

image = cv.imread(r'C:\Users\Desktop\a.jpg')
gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
ret, threshod_image = cv.threshold(gray, 0, 255, cv.THRESH_BINARY_INV+cv.THRESH_OTSU)
cv.imwrite(r'C:\Users\Desktop\threshod_image.jpg',threshod_image)
cv.imshow('threshod_image',threshod_image)
cv.waitKey(0)

2、使用开运算去除图像中的细小白色噪点

kernel = cv.getStructuringElement(cv.MORPH_RECT, (3,3))
opening = cv.morphologyEx(threshod_image, cv.MORPH_OPEN, kernel, iterations=2)
cv.imwrite(r'C:\Users\Desktop\opening.jpg',opening)
cv.imshow('opening_image',opening)
cv.waitKey(0)

3、距离变换后设置阈值分割确定确切的前景区域

dist_transform = cv.distanceTransform(opening, cv.DIST_L2, 5)
# Normalize the distance image for range = {0.0, 1.0}
cv.normalize(dist_transform, dist_transform, 0, 1.0, cv.NORM_MINMAX)
cv.imwrite(r'C:\Users\Desktop\dist_transform.jpg',dist_transform)
cv.imshow('dist_transform_image',dist_transform)
cv.waitKey(0)

      

4、对开操作后进行膨胀运算使得一部分背景成为了硬币的边界,得到的图像中的黑色区域肯定是真实背景,即远离硬币的区域

dilate_image = cv.dilate(opening, kernel, iterations=2)
cv.imwrite(r'C:\Users\Desktop\dilate_image.jpg',dilate_image)
cv.imshow('dilate_image',dilate_image)
cv.waitKey(0)

5、膨胀后图像减去确切的前景区域图像得到硬币边界处不确定是背景还是硬币本身的区域,即是未知的区域

unknown = cv.subtract(dilate_image,dist_transform_threshold_image)
cv.imwrite(r'C:\Users\Desktop\unknown.jpg',unknown)
cv.imshow('unknown',unknown)
cv.waitKey(0)

6、创建标记图像

现在我们可以确定哪些是硬币区域,哪些是背景区域。然后创建标记(marker,它是一个与原始图像大小相同的矩阵,int32数据类型),opencv中的分水岭算法将标记的0的区域视为不确定区域,将标记为1的区域视为背景区域,将标记大于1的正整数表示我们想得到的前景,使用 cv2.connectedComponents() 来实现标记,但它是用0标记图像的背景,用大于0的整数标记其他对象,所以我们需要对其进行加一,用1来标记图像的背景。

ret2, markers = cv.connectedComponents(dist_transform_threshold_image)
markers = markers+1
markers[unknown==255] = 0
markers_copy = markers.copy()
markers_copy[markers==0] = 150  # 灰色表示未知区域
markers_copy[markers==1] = 0    # 黑色表示背景
markers_copy[markers>1] = 255   # 白色表示前景
markers_copy = np.uint8(markers_copy)
cv.imwrite(r'C:\Users\Desktop\markers.jpg',markers_copy)
cv.imshow('markers_copy',markers_copy)
cv.waitKey(0)

注意:初始标记图像中的物体不能相互接触,不然用分水岭分不开

7、应用分水岭算法

markers = cv.watershed(image, markers)
markers[0:1,:]=1
markers[-1,:]=1
markers[:,0:1]=1
markers[:,-1]=1
print(markers)
image[markers==-1] = [0,0,255]  # 将边界标记为红色
cv.imwrite(r'C:\Users\Desktop\image.jpg',image)
cv.imshow('image',image)
cv.waitKey(0)

注意:THRESH_BINARY_INV是THRESH_BINARY的取反结果

 

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值