快乐学习
前程无忧、中华英才非你莫属!

15-OpenCV-基于分水岭算法的图像分割

十五、基于分水岭算法的图像分割


目标:

本章节你需要学习以下内容:

*我们将学习使用分水岭算法使用基于标记的图像分割

*我们将看到:cv.watershed()

1、理论

任何灰度图像都可以看作是地形表面,其中高强度表示峰和丘陵,而低强度表示山谷。

你开始用不同颜色的水(标签)填充每个孤立的山谷(局部最小值)。随着水的上升,取决于附近的峰值(梯度),来自不同山谷的水,明显具有不同的颜色将开始融合。

为避免这种情况,你需要在水合并的位置建立障碍。

你继续填补水和建筑障碍的工作,直到所有的山峰都在水下。

然后,你创建的障碍将为你提供分割结果。这是分水岭背后的“哲学”。

你可以访问分水岭上的CMM网页,以便在某些动画的帮助下了解它。

但是,由于噪声或图像中的任何其他不规则性,此方法会为你提供过度调整结果。

因此,OpenCV实现了一个基于标记的分水岭算法,你可以在其中指定要合并的所有谷点,哪些不合并。

它是一种交互式图像分割。我们所做的是为我们所知道的对象提供不同的标签。用一种颜色(或强度)标记我们确定为前景或对象的区域,用另一种颜色标记我们确定为背景或非对象的区域,最后标记我们不确定的区域,用0标记它。这是我们的标记。然后应用分水岭算法。然后我们的标记将使用我们给出的标签进行更新,对象的边界将具有-1的值。

2、代码实现

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

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

我们首先找到硬币的近似估计值。 为此,我们可以使用Otsu的二值化。

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('coins.png')
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(gray,0,255,cv.THRESH_BINARY_INV+cv.THRESH_OTSU)

窗口将如下图显示:

现在我们需要去除图像中的任何小白噪声。为此,我们可以使用形态开放。

要移除对象中的任何小孔,我们可以使用形态学闭合。

所以,现在我们确切地知道靠近物体中心的区域是前景,而远离物体的区域是背景。

只有我们不确定的区域是硬币的边界区域。

所以我们需要提取我们确定它们是硬币的区域。

侵蚀消除了边界像素。所以无论如何,我们可以肯定它是硬币。

如果物体没有相互接触,这将起作用。但由于它们相互接触,另一个好的选择是找到距离变换并应用适当的阈值。

接下来我们需要找到我们确定它们不是硬币的区域。为此,我们扩大了结果。


膨胀将物体边界增加到背景。这样,我们可以确保结果中背景中的任何区域都是背景,因为边界区域已被删除。见下图。

剩下的区域是我们不知道的区域,无论是硬币还是背景。 分水岭算法应该找到它。 

这些区域通常围绕着前景和背景相遇的硬币边界(甚至两个不同的硬币相遇)。 我们称之为边界。 它可以从sure_bg区域中减去sure_fg区域获得。

# noise removalkernel = np.ones((3,3),np.uint8)
opening = cv.morphologyEx(thresh,cv.MORPH_OPEN,kernel, iterations = 2)
# sure background areasure_bg = cv.dilate(opening,kernel,iterations=3)
# Finding sure foreground areadist_transform = cv.distanceTransform(opening,cv.DIST_L2,5)
ret, sure_fg = cv.threshold(dist_transform,0.7*dist_transform.max(),255,0)
# Finding unknown regionsure_fg = np.uint8(sure_fg)

unknown = cv.subtract(sure_bg,sure_fg)

看到结果。在阈值图像中,我们得到了一些我们确定硬币的硬币区域,现在它们已经分离。 

(在某些情况下,你可能只对前景分割感兴趣,而不是分离相互接触的物体。

在这种情况下,你不需要使用距离变换,只需要侵蚀就足够了。侵蚀只是提取确定前景区域的另一种方法,那就是所有。)

现在我们确定哪个是硬币区域,哪个是背景和所有。所以我们创建标记(它是一个与原始图像大小相同的数组,但是使用int32数据类型)并标记其中的区域。

我们确切知道的区域(无论是前景还是背景)都标有任何正整数,但不同的整数,我们不确定的区域只是保留为零。

为此,我们使用cv.connectedComponents()。它用0标记图像的背景,然后其他对象用从1开始的整数标记。

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

# Marker labellingret, markers = cv.connectedComponents(sure_fg)
# Add one to all labels so that sure background is not 0, but 1markers = markers+1
# Now, mark the region of unknown with zeromarkers[unknown==255] = 0


查看JET色彩映射中显示的结果。 深蓝色区域显示未知区域。 肯定的硬币用不同的颜色着色。 与未知区域相比,确定背景的剩余区域以浅蓝色显示。

现在我们的标记准备好了。 现在是最后一步的时候,应用分水岭。 然后将修改标记图像。 边界区域将标记为-1

markers = cv.watershed(img,markers)

img[markers == -1] = [255,0,0]


十六、基于GrabCut算法的交互式前景提取


目标:

本章节你需要学习以下内容:

*我们将看到GrabCut算法来提取图像中的前景

*我们将为此创建一个交互式应用程序。

1、理论

GrabCut算法由英国剑桥微软研究院的Carsten Rother,Vladimir Kolmogorov和Andrew Blake设计。在他们的论文中,“GrabCut”:使用迭代图切割的交互式前景提取。前景提取需要一种算法,用户交互最少,结果就是GrabCut。

从用户的角度来看它是如何工作的?最初用户在前景区域周围绘制一个矩形(前景区域应该完全在矩形内)。然后算法迭代地对其进行分段以获得最佳结果。完成。但在某些情况下,分割将不会很好,例如,它可能已将某些前景区域标记为背景,反之亦然。在这种情况下,用户需要进行精细的修饰。只需对图像进行一些描述,其中存在一些错误结果。笔划基本上说*“嘿,这个区域应该是前景,你标记它的背景,在下一次迭代中纠正它”*或它的背景相反。然后在下一次迭代中,你将获得更好的结果。

见下图。第一名球员和足球被包围在一个蓝色矩形中。然后进行一些具有白色笔划(表示前景)和黑色笔划(表示背景)的最终修饰。我们得到了一个很好的结果。

那么背景会发生什么?

  • 用户输入矩形。这个矩形之外的所有东西都将被视为确定的背景(这就是之前提到的矩形应包括所有对象的原因)。

  • 矩形内的一切都是未知的。类似地,任何指定前景和背景的用户输入都被视为硬标签,这意味着它们不会在过程中发生变化。

  • 计算机根据我们提供的数据进行初始标记。它标记前景和背景像素(或硬标签)

  • 现在,高斯混合模型(GMM)用于模拟前景和背景。

  • 根据我们提供的数据,GMM学习并创建新的像素分布。也就是说,未知像素被标记为可能的前景或可能的背景,这取决于其在颜色统计方面与其他硬标记像素的关系(它就像聚类一样)。

  • 从该像素分布构建图形。图中的节点是像素。添加了另外两个节点,Source节点和Sink节点。每个前景像素都连接到Source节点,每个背景像素都连接到Sink节点。

  • 将像素连接到源节点/端节点的边的权重由像素是前景/背景的概率来定义。像素之间的权重由边缘信息或像素相似性定义。如果像素颜色存在较大差异,则它们之间的边缘将获得较低的权重。

  • 然后使用mincut算法来分割图形。它将图形切割成两个分离源节点和汇聚节点,具有最小的成本函数。成本函数是被切割边缘的所有权重的总和。切割后,连接到Source节点的所有像素都变为前景,连接到Sink节点的像素变为背景。

  • 该过程一直持续到分类收敛为止。

如下图所示(图片提供:http://www.cs.ru.ac.za/research/g02m1682/

2、示例

现在我们使用OpenCV进行抓取算法。 OpenCV具有此功能,cv.grabCut()。我们将首先看到它的论点:

  • img - 输入图像

  • mask - 这是一个掩码图像,我们指定哪些区域是背景,前景或可能的背景/前景等。它由以下标志cv.GC_BGD,cv.GC_FGD,cv.GC_PR_BGD,cv.GC_PR_FGD完成,或者只是通过图像0,1,2,3。

  • rect - 矩形的坐标,包括格式为(x,y,w,h)的前景对象

  • bdgModel,fgdModel - 这些是内部算法使用的数组。你只需创建两个大小为(n = 1.65)的np.float64类型零数组。

  • iterCount - 算法应运行的迭代次数。

  • mode - 它应该是cv.GC_INIT_WITH_RECT或cv.GC_INIT_WITH_MASK或组合,它决定我们是绘制矩形还是最终的触摸笔画。

首先让我们看看矩形模式。我们加载图像,创建一个类似的蒙版图像。我们创建了fgdModel和bgdModel。我们给出矩形参数。这一切都是直截了当的。让算法运行5次迭代。模式应该是cv.GC_INIT_WITH_RECT,因为我们使用矩形。然后运行抓取。它修改了蒙版图像。在新的掩模图像中,像素将被标记为表示背景/前景的四个标记,如上所述。因此,我们修改掩模,使得所有0像素和2像素都被置为0(即背景),并且所有1像素和3像素被置为1(即前景像素)。现在我们的最后面具准备好了。只需将其与输入图像相乘即可得到分割后的图像。

import numpy as npimport cv2 as cvfrom matplotlib import pyplot as pltimg = cv.imread('messi5.jpg')
mask = np.zeros(img.shape[:2],np.uint8)
bgdModel = np.zeros((1,65),np.float64)
fgdModel = np.zeros((1,65),np.float64)
rect = (50,50,450,290)
cv.grabCut(img,mask,rect,bgdModel,fgdModel,5,cv.GC_INIT_WITH_RECT)
mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8')
img = img*mask2[:,:,np.newaxis]
plt.imshow(img),plt.colorbar(),plt.show()

窗口将如下图显示:

哎呀,梅西的头发不见了。 没有头发谁喜欢梅西? 

我们需要把它带回来。 因此,我们将为其提供1像素(确定前景)的精细修饰。 

与此同时,有些地方已经出现了我们不想要的图片,还有一些标识。 我们需要删除它们。 

在那里我们提供一些0像素的修饰(确定背景)。 

因此,正如我们现在所说的那样,我们在之前的案

我实际上做的是,我在绘图应用程序中打开输入图像,并在图像中添加了另一层。 

在画中使用画笔工具,我在这个新图层上标记了带有黑色的白色和不需要的背景(如徽标,地面等)的前景(头发,鞋子,球等)。 

然后用灰色填充剩余的背景。 然后在OpenCV中加载该掩模图像,编辑我们在新添加的掩模图像中使用相应值的原始掩模图像。 检查以下代码:

# newmask is the mask image I manually labellednew
mask = cv.imread('newmask.png',0)
# wherever it is marked white (sure foreground), change mask=1
# wherever it is marked black (sure background), change mask=0
mask[newmask == 0] = 0mask[newmask == 255] = 1mask, bgdModel, fgdModel = cv.grabCut(img,mask,None,bgdModel,fgdModel,5,cv.GC_INIT_WITH_MASK)
mask = np.where((mask==2)|(mask==0),0,1).astype('uint8')
img = img*mask[:,:,np.newaxis]
plt.imshow(img),plt.colorbar(),plt.show()

窗口将如下图显示:

打赏

未经允许不得转载:同乐学堂 » 15-OpenCV-基于分水岭算法的图像分割

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

特别的技术,给特别的你!

联系QQ:1071235258QQ群:367203382
error: Sorry,暂时内容不可复制!