目标

在本章中,将学习

  • 使用分水岭算法实现基于标记的图像分割
  • 函数:cv2.watershed()

理论

任何灰度图像都可以看作是一个地形表面,其中高强度的像素表示山峰,低强度表示山谷。可以用不同颜色的水(标签)填充每个孤立的山谷(局部最小值)。随着水位的上升,根据附近的山峰(坡度),来自不同山谷的水明显会开始合并,颜色也不同。为了避免这种情况,要在水融合的地方建造屏障。继续填满水,建造障碍,直到所有的山峰都在水下。然后创建的屏障将返回分割结果。这就是Watershed(分水岭算法)背后的“思想”。

但是这种方法会由于图像中的噪声或其他不规则性而产生过度分割的结果。因此OpenCV实现了一个基于标记的分水岭算法,可以指定哪些是要合并的山谷点,哪些不是。这是一个交互式的图像分割。所做的是给我们知道的对象赋予不同的标签用一种颜色(或强度)标记我们确定为前景或对象的区域,用另一种颜色标记我们确定为背景或非对象的区域,最后用0标记我们不确定的区域。 这是我们的标记。然后应用分水岭算法。然后标记将使用我们给出的标签进行更新,对象的边界值将为-1。

代码

下面将看到一个有关如何使用距离变换和分水岭来分割相互接触的对象的示例。

考虑下面的硬币图像,硬币彼此接触。即使设置阈值,它们也会彼此接触。

先从寻找硬币的近似估计开始。因此,可以使用Otsu的二值化。

import cv2
import numpy
from matplotlib import pyplot as pltimg = cv2.imread('coins.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)  # ret是阈值,thresh是结果
cv2.imshow('coins', thresh)
cv2.waitKey(0)
cv2.destroyAllWindows()

现在需要去除图像中的白点噪声,可以使用形态学膨胀。要去除对象中的任何小孔,可以使用形态学腐蚀。因此,现在可以确定,靠近对象中心的区域是前景,而离对象中心很远的区域是背景。不确定的唯一区域是硬币的边界区域。

因此,需要提取可确定为硬币的区域。腐蚀会去除边界像素。因此,无论剩余多少,都可以肯定它是硬币。如果物体彼此不接触,那将起作用。但是,由于它们彼此接触,因此另一个好选择是找到距离变换并应用适当的阈值。接下来,需要找到我们确定它们不是硬币的区域。为此,对其进行了膨胀,膨胀将对象边界增加到背景。这样,由于边界区域已删除,因此可以确保结果中背景中的任何区域实际上都是背景。

剩下的区域是不确定的区域,无论是硬币还是背景。分水岭算法应该找到它。这些区域通常位于前景和背景相遇(甚至两个不同的硬币相遇)的硬币边界附近,我们称之为边界。可以通过从sure_bg区域中减去sure_fg区域来获得。

import cv2
import numpy as np
from matplotlib import pyplot as plt# noise removal
kernel = np.ones((3, 3), np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)# sure background area
sure_bg = cv2.dilate(opening, kernel, iterations=3)# finding sure foreground area
dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5)
ret, sure_fg = cv2.threshold(dist_transform, 0.7*dist_transform.max(), 255, 0)# finding unknow region
sure_fg = np.uint8(sure_fg)
unknow = cv2.subtract(sure_bg, sure_fg)plt.subplot(121)
plt.imshow(dist_transform, cmap='gray')
plt.title('distance transform')
plt.xticks([])
plt.yticks([])plt.subplot(122)
plt.imshow(thresh, cmap='gray')
plt.title('threshold')
plt.xticks([])
plt.yticks([])plt.show()

查看结果。在阈值图像中,得到了一些硬币区域,确定它们是硬币,并且现在已分离它们。(在某些情况下,可能只对前景分割感兴趣,而不对分离相互接触的对象感兴趣。在那种情况下,无需使用距离变换,只需侵蚀就足够了。侵蚀只是提取确定前景区域的另一种方法。)

现在可以确定哪些是硬币的区域,哪些是背景。因此,我们创建了标记(它的大小与原始图像的大小相同,但具有int32数据类型),并标记其中的区域。肯定知道的区域(无论是前景还是背景)都标有任何正整数,但是带有不同的整数,而不确定的区域则保留为零。为此,使用cv2.connectedComponents()。它用0标记图像的背景,然后其他对象用从1开始的整数标记。

但是,如果背景标记为0,则分水岭会将其视为未知区域。所以我们想用不同的整数来标记它。相反,将未知定义的未知区域标记为0。

# Marker labelling
ret, markers = cv2.connectedComponents(sure_fg)
# Add one to all labels so that sure background is not 0, but 1
markers = markers + 1
# Now, mark the region of unknown with zero
markers[unknow==255] = 0plt.imshow(markers)
plt.xticks([])
plt.yticks([])
plt.show

参见JET colormap中显示的结果。深蓝色区域显示未知区域。当然,硬币的颜色不同。剩下,肯定为背景的区域显示在较浅的蓝色,跟未知区域相比。

现在标记已准备就绪。到了最后一步的时候了,使用分水岭算法。然后标记图像将被修改,边界区域将标记为-1。

void watershed( InputArray image, InputOutputArray markers );
第一个参数 image,必须是一个8bit 3通道彩色图像矩阵序列,第一个参数没什么要说的。关键是第二个参数 markers,Opencv官方文档的说明如下:
Before passing the image to the function, you have to roughly outline the desired regions in the image markers with positive (>0) indices. So, every region is represented as one or more connected components with the pixel values 1, 2, 3, and so on. Such markers can be retrieved from a binary mask using findContours() and drawContours(). The markers are “seeds” of the future image regions. All the other pixels in markers , whose relation to the outlined regions is not known and should be defined by the algorithm, should be set to 0’s. In the function output, each pixel in markers is set to a value of the “seed” components or to -1 at boundaries between the regions.
在执行分水岭函数watershed之前,必须对第二个参数markers进行处理,它应该包含不同区域的轮廓,每个轮廓有一个自己唯一的编号,轮廓的定位可以通过Opencv中findContours方法实现,这个是执行分水岭之前的要求。
接下来执行分水岭会发生什么呢?算法会根据markers传入的轮廓作为种子(也就是所谓的注水点),对图像上其他的像素点根据分水岭算法规则进行判断,并对每个像素点的区域归属进行划定,直到处理完图像上所有像素点。而区域与区域之间的分界处的值被置为“-1”,以做区分。
简单概括一下就是说第二个入参markers必须包含了种子点信息。Opencv官方例程中使用鼠标划线标记,其实就是在定义种子,只不过需要手动操作,而使用findContours可以自动标记种子点。而分水岭方法完成之后并不会直接生成分割后的图像,还需要进一步的显示处理,如此看来,只有两个参数的watershed其实并不简单。

markers = cv2.watershed(img, markers)
img[markers == -1] = [255,0,0]
plt.subplot(121)
plt.imshow(markers)
plt.title('marker image after segmentation')
plt.xticks([])
plt.yticks([])plt.subplot(122)
plt.imshow(img)
plt.title('result')
plt.xticks([])
plt.yticks([])
plt.show()

可以从结果中看到,对某些硬币,它们接触的区域被正确地分割,而对于某些硬币,却没有被正确地分割。

import cv2
import numpyimg = cv2.imread("coins.jpg")
cv2.imshow("img", img)# 1.图像二值化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)# 2.噪声去除
kernel = numpy.ones((3, 3), dtype=numpy.uint8)
open = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)# 3.确定背景区域
sure_bg = cv2.dilate(open, kernel, iterations=3)# 4.寻找前景区域
dist_transform = cv2.distanceTransform(open, 1, 5)
ret, sure_fg = cv2.threshold(dist_transform, 0.5 * dist_transform.max(), 255, cv2.THRESH_BINARY)# 5.找到未知区域
sure_fg = numpy.uint8(sure_fg)
unknow = cv2.subtract(sure_bg, sure_fg)# 6.类别标记
ret, markers = cv2.connectedComponents(sure_fg)
# 为所有的标记加1,保证背景是0而不是1
markers = markers + 1
# 现在让所有的未知区域为0
markers[unknow == 255] = 0# 7.分水岭算法
markers = cv2.watershed(img, markers)
img[markers == -1] = (0, 0, 255)cv2.imshow("gray", gray)
cv2.imshow("thresh", thresh)
cv2.imshow("open", open)
cv2.imshow("sure_bg", sure_bg)
cv2.imshow("sure_fg", sure_fg)
cv2.imshow("unknow", unknow)
cv2.imshow("img_watershed", img)
cv2.waitKey(0)
cv2.destroyWindow()

附加资源

  • https://docs.opencv.org/4.1.2/d3/db4/tutorial_py_watershed.html
  • CMM page on Watershed Transformation
  • https://zhuanlan.zhihu.com/p/67741538
  • https://blog.csdn.net/dcrmg/article/details/52498440

opencv28:分水岭算法的图像分割相关推荐

  1. OpenCV | 分水岭算法进行图像分割

    分水岭算法进行图像分割 分水岭分割方法,是一种基于拓扑理论的数学形态学的分割方法,其基本思想是把图像看作是测地学上的拓扑地貌,图像中每一点像素的灰度值表示该点的海拔高度,每一个局部极小值及其影响区域称 ...

  2. Python+OpenCV:基于分水岭算法的图像分割(Image Segmentation with Watershed Algorithm)

    Python+OpenCV:基于分水岭算法的图像分割(Image Segmentation with Watershed Algorithm) ############################ ...

  3. 图像处理:分水岭算法(图像分割)

    图像处理:分水岭算法(图像分割) 分水岭算法 分水岭算法是一种图像区域分割法,分割的过程中将图片转化为灰度图,然后我会将灰度值看作是海拔,然后向较低点注水,这种基于地形学的解释,我们着重考虑三种点: ...

  4. opencv进阶学习笔记14:分水岭算法 实现图像分割

    基础版学习笔记目录: python3+opencv学习笔记汇总目录(适合基础入门学习) 进阶版笔记目录链接: python+opencv进阶版学习笔记目录(适合有一定基础) 分水岭算法原理 分水岭算法 ...

  5. opencv python 基于分水岭算法的图像分割

    Image Segmentation with Watershed Algorithm 理论 任何灰度图像都可以看作是地形表面,其中高强度表示山峰和丘陵,而低强度表示山谷.用不同颜色的水(标签)填充每 ...

  6. MATLAB应用实战系列( 七十五) -图像处理应用 MATLAB实现基于分水岭算法的图像分割 (附matlab代码)

    一.简介 二.源代码 clear, close all; clc; %1.读取图像并求取图像的边界.rgb = imread('tree.jpeg');%读取原图像 I = rgb2gray(rgb) ...

  7. 机器视觉实验三: 基于分水岭算法的肺部图像分割实验(OpenCV-python代码)

    一.实验目的 用OpenCV编写一个基于分水岭算法的图像分割程序能对肺部医学图像进行分割,辅助医生进行病情诊断,强化和巩固学生对图像分割知识的掌握和灵活应用. 二.实验要求 1.用OpenCV编写一个 ...

  8. OpenCV学习(二十) :分水岭算法:watershed()

    OpenCV学习(二十) :分水岭算法:watershed() 参考博客: OpenCV-分水岭算法 图像处理--分水岭算法 OpenCV学习(7) 分水岭算法(1) Opencv分水岭算法--wat ...

  9. 分水岭算法java,OpenCV 学习笔记 04 深度估计与分割——GrabCut算法与分水岭算法...

    1 使用普通摄像头进行深度估计 1.1 深度估计原理 这里会用到几何学中的极几何(Epipolar Geometry),它属于立体视觉(stereo vision)几何学,立体视觉是计算机视觉的一个分 ...

最新文章

  1. docker安装Mysql5.7以及远程登陆链接配置
  2. VSCode搭建Vue项目
  3. 【怎样写代码】小技巧 -- .NET配置文件详解
  4. vsftpd的配置文件路径,是在哪里指定的?
  5. 任天堂新音樂遊戲上市
  6. arm汇编:.balignl伪指令理解
  7. mailcore -- Mail port
  8. linux查用户的家目录,详解Linux误删用户家目录的恢复方法
  9. bzoj3214 [Zjoi2013]丽洁体 dp
  10. canvas读取图片,输入文字,调整文字属性,拖拽文字位置,并保存图片
  11. 《深度学习笔记》——深度神经网络的调试笔记
  12. 标准模板库 STL 使用之 —— vector 使用 tricks
  13. Ubuntu 18.04搭建Moodle
  14. c++ 开源grid控件
  15. 海明贴近度matlab,Matlab学习系列23.-模糊聚类分析原理及实现.docx
  16. 角色建模师来谈谈VR游戏角色制作流程
  17. python用smtp发邮件怎么抄送_Python发送邮件并抄送
  18. day10 爬虫导言
  19. Android国际化多语言切换
  20. 将frpc注册成windows系统服务

热门文章

  1. C++中模板的特化与偏特化
  2. shipyard管理多HOST
  3. 请将标为Service Pack 2 CD-ROM的光盘插入CD-ROM驱动器(D:),然后单击确定。如何解决? 在windows server 2003服务器上。
  4. 图灵学院:淘宝大秒系统设计详解
  5. win10合并硬盘合区(win10怎样合并硬盘的两个分区)
  6. 【脑洞大开】神经网络vs非公理化推理系统(NARS)
  7. navicat删除注册表文件_如何彻底删除mysql服务(清理注册表)详解
  8. dropbox为什么被屏蔽_Python社区和Dropbox为增加多样性而采取的步骤
  9. 音视频:AVAudioPlayer:中断处理
  10. C# WPF、Winform中Show()和ShowDialog()区别