【官⽹翻译】OpenCV.js基于分⽔岭算法的图像分割
⽬标
js合并两个数组使⽤分⽔岭算法进⾏基于标记的图像分割
cv.watershed()
理论
任何灰度图像都可以看作是地形表⾯,其中⾼强度表⽰⼭峰和丘陵,⽽低强度表⽰⼭⾕。你开始⽤不同颜⾊的⽔(标签)填充每个孤⽴的⼭⾕(局部最⼩值)。随着⽔位上升,取决于附近的峰值(梯度),来⾃不同⼭⾕的不同的颜⾊⽔将开始融合。为避免这种情况,需要在⽔合并的位置设置障碍。你得⼀直进⾏填补⽔和设置障碍的⼯作,直到所有的⼭峰都在⽔下。然后,你创建的障碍提供的就是分割结果。这是分⽔岭背后的“哲学”。你可以访问分⽔岭上的,以便在动画的帮助下了解它。
但是,由于噪声或图像中的任何其他不规则性,此⽅法会为你提供过度调整结果。因此,OpenCV实现了⼀个基于标记的分⽔岭算法,你可以在其中指定要合并的所有⾕点,哪些不合并。它是⼀种交互式图像分割。我们所做的是为我们所知道的对象提供不同的标签。⽤⼀种颜⾊(或强度)标记我们确定为前景或对象的区域,⽤另⼀种颜⾊标记我们确定为背景或⾮对象的区域,最后标记我们不确定的区域,⽤0
标记它。这是我们的标记。然后应⽤分⽔岭算法。然后我们的标记将使⽤我们给出的标签进⾏更新,对象的边界将具有-1的值。
代码
下⾯我们将看到⼀个使⽤距离变换和分⽔岭来分割相互接触的物体的⽰例。考虑下⾯的硬币图像,硬币互相接触。即使你达到阈值,它也会相互接触。我们⾸先到硬币的近似估计值。为此,我们可以使⽤Otsu的⼆值化。
let src = cv.imread('canvasInput');
let dst = new cv.Mat();
let gray = new cv.Mat();
// 转换成⼆值图
cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY, 0);
cv.threshold(gray, gray, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU);
cv.imshow('canvasOutput', gray);
src.delete(); dst.delete(); gray.delete();
现在我们需要去除图像中的任何⼩的⽩噪声。为此,我们可以使⽤形态学开运算。要移除对象中的任何⼩孔,我们可以使⽤形态学闭运算。所以,现在我们确切地知道靠近物体中⼼的区域是前景,⽽远离物体的区域是背景。只有我们不确定的区域是硬币的边界区域。
所以我们需要提取我们确定它们是硬币的区域。侵蚀消除了边界像素。所以⽆论如何,我们可以肯定它是硬币。如果物体没有相互接触,这将起作⽤。但由于它们相互接触,另⼀个好的选择是到距离变换并应⽤适当的阈值。接下来我们需要到我们确定它们不是硬币的区域。为此,我们扩⼤了结果。
膨胀将物体边界增加到背景。这样,我们可以确保结果中背景中的任何区域都是背景,因为边界区域已被删除。
let src = cv.imread('canvasInput');
let dst = new cv.Mat();
let gray = new cv.Mat();
let opening = new cv.Mat();
let coinsBg = new cv.Mat();
// 转换成⼆值图
cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY, 0);
cv.threshold(gray, gray, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU);
// get background
let M = s(3, 3, cv.CV_8U); // 结构元素
cv.dilate(gray, opening, M);
cv.dilate(opening, coinsBg, M, new cv.Point(-1, -1), 3);
cv.imshow('canvasOutput', coinsBg);
src.delete(); dst.delete(); gray.delete(); opening.delete(); coinsBg.delete(); M.delete();
剩下的区域是我们不知道的区域,⽆论是硬币还是背景。分⽔岭算法应该到它。这些区域通常围绕着前景和背景相遇的硬币边界(甚⾄两个不同的硬币相遇)。我们称之为边界。它可以从sure_bg区域中减去sure_fg区域获得。
我们使⽤这个函数:cv.distanceTransform (src, dst, distanceType, maskSize, labelType = cv.CV_32F)
参数说明
src - 8位,单通道(⼆进制)源图像。
dst - 输出具有计算距离的图像。它是⼀个8位或32位浮点单通道图像,⼤⼩与src相同。
distanceType - 距离类型(参见 )。
maskSize - 距离变换掩码的⼤⼩,(参见 )。
labelType - 输出图像的类型。它可以是cv.CV_8U或cv.CV_32F。类型cv.CV_8U只能⽤于函数的第⼀个变量⽽distanceType == DIST_L1。
let coinsBg = new cv.Mat();
let coinsFg = new cv.Mat();
let distTrans = new cv.Mat();
// 转换成⼆值图
cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY, 0);
cv.threshold(gray, gray, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU);
let M = s(3, 3, cv.CV_8U);
cv.dilate(gray, opening, M);
cv.dilate(opening, coinsBg, M, new cv.Point(-1, -1), 3);
// distance transform
cv.distanceTransform(opening, distTrans, cv.DIST_L2, 5);
cv.imshow('canvasOutput', distTrans);
src.delete(); dst.delete(); gray.delete(); opening.delete();
coinsBg.delete(); coinsFg.delete(); distTrans.delete(); M.delete();
在阈值图像中,我们得到了⼀些我们确定硬币的硬币区域,现在它们已经分离。(在某些情况下,你可能只对前景分割感兴趣,⽽不是分离相互接触的物体。在这种情况下,你不需要使⽤距离变换,只需要侵蚀就⾜够了。侵蚀只是提取确定前景区域的另⼀种⽅法,那就是所有)
let coinsBg = new cv.Mat();
let coinsFg = new cv.Mat();
let distTrans = new cv.Mat();
// 转换成⼆值图
cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY, 0);
cv.threshold(gray, gray, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU);
let M = s(3, 3, cv.CV_8U);
cv.dilate(gray, opening, M);
cv.dilate(opening, coinsBg, M, new cv.Point(-1, -1), 3);
cv.distanceTransform(opening, distTrans, cv.DIST_L2, 5);
// get foreground
cv.threshold(distTrans, coinsFg, 0.7 * 1, 255, cv.THRESH_BINARY);
cv.imshow('canvasOutput', coinsFg);
src.delete(); dst.delete(); gray.delete(); opening.delete();
coinsBg.delete(); coinsFg.delete(); distTrans.delete(); M.delete();
现在我们确定哪个是硬币区域,哪个是背景和所有。所以我们创建标记(它是⼀个与原始图像⼤⼩相同的数组,但是使⽤int32数据类型)并标记其中的区域。我们确切知道的区域(⽆论是前景还是背景)都标有任何正整数,但不同的整数,我们不确定的区域只是保留为零。为此,我们使⽤cv.connectedComponents()。它⽤0标记图像的背景,然后其他对象⽤从1开始的整数标记。
但我们知道,如果背景标记为0,分⽔岭会将其视为未知区域。所以我们想⽤不同的整数来标记它。相
反,我们将⽤0表⽰由未知定义的未知区域。
现在我们的标记准备好了。现在是最后⼀步的时候,应⽤分⽔岭。然后将修改标记图像。边界区域将标记为-1。
我们使⽤这个函数:cv.connectedComponents (image, labels, connectivity = 8, ltype = cv.CV_32S)
参数说明
image - 要标记的8位单通道图像。
labels - ⽬标标记图像(cv.CV_32SC1类型)。
connectivity - 8或4分别⽤于8路或4路连接。
ltype - 输出图像标签类型。⽬前⽀持cv.CV_32S和cv.CV_16U。
我们使⽤这个函数:cv.watershed (image, markers)
参数说明
image - 输⼊8位3通道图像。
markers - 输⼊/输出标记的32位单通道图像(映射)。它应该与图像⼤⼩相同。最终代码和结果如下所⽰:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论