首页 > 编程语言 >opencv-python 4.15. 基于分水岭算法的图像分割

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

时间:2023-04-07 17:11:21浏览次数:57  
标签:sure 4.15 python imshow markers opencv 区域 fg cv

理论

任何灰度图像都可以看作是地形表面,其中高强度表示峰和丘陵,而低强度表示山谷。你开始用不同颜色的水(标签)填充每个孤立的山谷(局部最小值)。随着水的上升,取决于附近的峰值(梯度),来自不同山谷的水,明显具有不同的颜色将开始融合。为避免这种情况,你需要在水合并的位置建立障碍。你继续填补水和建筑障碍的工作,直到所有的山峰都在水下。然后,你创建的障碍将为你提供分割结果。这是分水岭背后的“哲学”。你可以访问分水岭上的CMM网页,以便在某些动画的帮助下了解它。

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

分水岭算法实现步骤

步骤1-构建梯度图像。
步骤2-通过一定规则生成n个最初的注水区域(先验知识或局部梯度最小值)。
步骤3-往注水区域内加水,当两注水区域即将合并时,记录下此时的边界。
步骤4-当图像边缘彻底被分割成n个独立区域是算法结束。
具体的实现过程如下图所示:
image

代码

下面我们将看到一个如何使用距离变换和分水岭来分割相互接触的物体的示例。

考虑下面的硬币图像,硬币互相接触。即使你达到阈值,它也会相互接触。
image

我们首先找到硬币的近似估计值。 为此,我们可以使用最大类间方差法(Otsu)的二值化。

import cv2 as cv
import numpy as np

img = cv.imread(r'C:\Users\yuyalong\Pictures\Saved Pictures\water_coins.jpg')

# 最大类间方差法 二值化
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(gray, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)

# cv.imshow('img', img)
cv.imshow('thresh', thresh)
cv.waitKey(0)

image

现在我们需要去除图像中的任何小白噪声。为此,我们可以使用形态开放。要移除对象中的任何小孔,我们可以使用形态学开运算。所以,现在我们确切地知道靠近物体中心的区域是前景,而远离物体的区域是背景。只有我们不确定的区域是硬币的边界区域。

所以我们需要提取我们确定它们是硬币的区域。侵蚀消除了边界像素。所以无论如何,我们可以肯定它是硬币。如果物体没有相互接触,这将起作用。但由于它们相互接触,另一个好的选择是找到距离变换并应用适当的阈值。接下来我们需要找到我们确定它们不是硬币的区域。为此,我们扩大了结果。膨胀将物体边界增加到背景。这样,我们可以确保结果中背景中的任何区域都是背景,因为边界区域已被删除。见下图。

import cv2 as cv
import numpy as np

img = cv.imread(r'C:\Users\yuyalong\Pictures\Saved Pictures\water_coins.jpg')

# 最大类间方差法 二值化
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(gray, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)
cv.imshow('thresh', thresh)

kernel = np.ones((3, 3), np.uint8)
# 开运算
opening = cv.morphologyEx(thresh, cv.MORPH_OPEN, kernel, iterations = 2)
cv.imshow('opening', opening)

# 膨胀
dilation = cv.dilate(opening, kernel, iterations=3)
cv.imshow('dilation', dilation)

# 距离变换
dist_transform = cv.distanceTransform(opening, cv.DIST_L2, 5)
cv.imshow('dist_transform', dist_transform)

cv.waitKey(0)

image

开运算相较于二值化,剔除了图像中的一些噪点,根据上边的理论,距离变换得到的是前景,膨胀结果得到的是背景。

开运算,膨胀 见章节4.5形态变换

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

import cv2 as cv
import numpy as np

img = cv.imread(r'C:\Users\yuyalong\Pictures\Saved Pictures\water_coins.jpg')

# 最大类间方差法 二值化
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(gray, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)
cv.imshow('thresh', thresh)

kernel = np.ones((3, 3), np.uint8)
# 开运算
opening = cv.morphologyEx(thresh, cv.MORPH_OPEN, kernel, iterations=2)
cv.imshow('opening', opening)

# 膨胀
dilation = cv.dilate(opening, kernel, iterations=3)
cv.imshow('dilation', dilation)

# 距离变换
dist_transform = cv.distanceTransform(opening, cv.DIST_L2, 5)
cv.imshow('dist_transform', dist_transform)

ret, sure_fg = cv.threshold(dist_transform, 0.7 * dist_transform.max(), 255, 0)
cv.imshow('sure_fg', sure_fg)

# 背景 - 前景
sure_fg = np.uint8(sure_fg)
unknown = cv.subtract(dilation, sure_fg)
cv.imshow('unknown', unknown)

cv.waitKey(0)

image

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

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

# Marker labelling
ret, markers = cv.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[unknown==255] = 0

查看JET色彩映射中显示的结果。 深蓝色区域显示未知区域。 肯定的硬币用不同的颜色着色。 与未知区域相比,确定背景的剩余区域以浅蓝色显示。
image
现在我们的标记准备好了。 现在是最后一步的时候,应用分水岭。 然后将修改标记图像。 边界区域将标记为-1。

markers = cv.watershed(img,markers)
img[markers == -1] = [255,0,0]

image

完整代码

点击查看代码
import cv2 as cv
import numpy as np

img = cv.imread(r'C:\Users\yuyalong\Pictures\Saved Pictures\water_coins.jpg')

# 最大类间方差法 二值化
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(gray, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)
cv.imshow('thresh', thresh)

kernel = np.ones((3, 3), np.uint8)
# 开运算
opening = cv.morphologyEx(thresh, cv.MORPH_OPEN, kernel, iterations=2)
cv.imshow('opening', opening)

# 膨胀
dilation = cv.dilate(opening, kernel, iterations=3)
cv.imshow('dilation', dilation)

# 距离变换
dist_transform = cv.distanceTransform(opening, cv.DIST_L2, 5)
cv.imshow('dist_transform', dist_transform)

ret, sure_fg = cv.threshold(dist_transform, 0.7 * dist_transform.max(), 255, 0)
cv.imshow('sure_fg', sure_fg)

# 获取未知区域 背景 - 前景
sure_fg = np.uint8(sure_fg)  # 保持色彩空间一致才能进行运算,现在是背景空间为整型空间,前景为浮点型空间,所以进行转换
unknown = cv.subtract(dilation, sure_fg)
cv.imshow('unknown', unknown)

# 获取maskers,在markers中含有种子区域
ret, markers = cv.connectedComponents(sure_fg)

# 分水岭变换
markers = markers + 1
markers[unknown == 255] = 0

markers = cv.watershed(img, markers=markers)
img[markers == -1] = [0, 0, 255]

cv.imshow("result", img)

cv.waitKey(0)
输出图像

image

参考链接:
https://www.cnblogs.com/ssyfj/p/9278815.html
https://blog.csdn.net/WZZ18191171661/article/details/96634083

标签:sure,4.15,python,imshow,markers,opencv,区域,fg,cv
From: https://www.cnblogs.com/yimeimanong/p/17296409.html

相关文章

  • 【python基础】五大数据类型及常用方法
    1.数据类型概述 python中的字符串,列表,元组,字典,集合这五种数据类型均是可迭代的,可以使用for循环访问,涵盖了三类数据结构分别为序列、散列、集合。序列: 字符串str 列表list() 元组tuple() 散列: 字典dict() 集......
  • python中的二分查找
    二分查找的前提是查找的数据按照顺序排序二分查找的核心思想是递归#arr:查找的对象#left:arr的左边界#right:arr的右边界#x:需要查找的数defbinary_search(arr,left,right,x):#左边界小于等于右边界ifleft<=right:#得到中位数mid=int((lef......
  • Python Qt 文件转换
    PythonQt文件转换ui文件编译成py文件用windows操作系统的cmd窗口转换进入cmd所在ui文件路径下,执行如下命令:pyside6-uicstudent.ui-ostudent.py用批处理形式转换建立扩展名为bat的文件,双击打开就好。cd/ee:\pythonpyside6-uicstudent.ui-ostudent.py编写pytho......
  • OpenCV图像像素读写操作
    常用类型介绍uchar类型typedefunsigneduint;typedefsignedcharschar;typedefunsignedcharuchar;typedefunsignedshortushort;Vec系列Vec+数字+字母:C++STLvector容器类似数字:Vec的长度字母:类型b:uchars:shortw:ushorti:intf:floatd:doubletypedefVec<uch......
  • 获取Python函数信息的方法
    Python的反射机制可以动态获取对象信息以及动态调用对象,本文介绍如何获取对象中的函数注释信息以及参数信息。定义一个Person类:classPerson():deftalk(self,name,age,height=None):"""talkfunction:return:"""print(f"Mynamei......
  • python操作git
    安装模块pip3installgitpython#coding:utf-8importosfromgit.repoimportRepofromgit.repo.funimportis_git_dir#pip3installgitpythonclassGitRepository(object):"""git仓库管理"""def__init__(self,......
  • Python 之生成验证码
    一、代码importrandomfromioimportBytesIOfromPILimportImage,ImageDraw,ImageFont,ImageFilterclassCaptcha:def__init__(self,width,height,code_num=4,code_type=1,font_size=24,is_blur=True,font='Arial.ttf',x_......
  • Python求100以内的素数常用方法!
    与其他编程语言对比,Python拥有十分独特的优势代码量少,相同功能其他编程语言需要上百行代码才可以实现,而Python只需要十几行就可以实现。而且在Python中,我们只需要学会一些基础的语法就可以实现简单的数值计算,那么Python求100内的所有素数方法是什么?具体内容请看下文。质数......
  • Python中的时间函数strftime与strptime对比
    一、striftime将给定格式的日期时间对象转换为字符串。日期时间对象=>字符串,控制输出格式.date、datetime、time对象都支持strftime(format) 方法,可用来创建由一个显式格式字符串所控制的表示时间的字符串。用法:datetime.strftime(format)importdatetimedt=datetime.dateti......
  • python+playwright 学习-50 pytest-playwright 多账号操作解决方案
    前言pytest-playwright插件可以让我们快速编写pytest格式的测试用例,它提供了一个内置的page对象,可以直接打开页面操作。但是有时候我们需要2个账号是操作业务流程,比如A账号创建了一个任务,需要用到B账号去操作审批动作等。如果需要2个账号同时登录,可以使用context上下文,它可......