任何灰度图像都可以视为地形图表面,其中高强度表示山峰和丘陵,而低强度表示山谷。您开始用不同颜色的水(标签)填充每个孤立的山谷(局部最小值)。随着水的上升,取决于附近的峰(梯度),来自不同山谷(显然具有不同颜色)的水将开始合并。为了避免这种情况,您可以在水汇合的位置建造障碍。您将继续填充水和建造障碍物的工作,直到所有山峰都在水下。然后,您创建的障碍将为您提供细分结果。这就是分水岭背后的“哲学”。您可以访问分水岭上的CMM网页,借助一些动画来了解它。
但是,这种方法会由于噪声或图像中的任何其他不规则性而给您造成过分分割的结果。因此,OpenCV实施了基于标记的分水岭算法,您可以在其中指定要合并的所有山谷点,哪些不是。这是一个交互式图像分割。我们要做的是为我们知道的对象提供不同的标签。用一种颜色(或强度)标记我们确定为前景或对象的区域,用另一种颜色标记我们确定为背景或非对象的区域,最后标记为我们不确定的区域,将其标记为0。这就是我们的标记。然后应用分水岭算法。然后,我们的标记将使用给定的标签进行更新,并且对象的边界的值为-1。
1.将图像二值化和变为灰度图
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)
现在我们需要去除图像中的任何小白噪声。为此,我们可以使用形态学开放。要去除对象中的任何小孔,我们可以使用形态学封闭。因此,现在我们可以确定,靠近对象中心的区域是前景,而离对象中心很远的区域是背景。我们不确定的唯一区域是硬币的边界区域。
因此,我们需要提取我们确定它们是硬币的区域。侵蚀会去除边界像素。因此,无论剩余多少,我们都可以肯定它是硬币。如果物体彼此不接触,那将起作用。但是,由于它们彼此接触,因此另一个好选择是找到距离变换并应用适当的阈值。接下来,我们需要找到我们确定它们不是硬币的区域。为此,我们扩大了结果。膨胀将对象边界增加到背景。这样,由于边界区域已删除,因此我们可以确保结果中背景中的任何区域实际上都是背景。参见下图。
# noise removal
kernel = np.ones((3,3),np.uint8)
opening = cv.morphologyEx(thresh,cv.MORPH_OPEN,kernel, iterations = 2)
# sure background area
sure_bg = cv.dilate(opening,kernel,iterations=3)
# Finding sure foreground area
dist_transform = cv.distanceTransform(opening,cv.DIST_L2,5)
ret, sure_fg = cv.threshold(dist_transform,0.7*dist_transform.max(),255,0)
# Finding unknown region
sure_fg = np.uint8(sure_fg)
unknown = cv.subtract(sure_bg,sure_fg)
现在我们可以确定哪些是硬币区域,哪些是背景硬币以及所有硬币。因此,我们创建标记(它的大小与原始图像的大小相同,但具有int32数据类型),并标记其中的区域。我们肯定知道的区域(无论是前景还是背景)都标有任何正整数,但标记为不同的整数,而我们不确定的区域则保留为零。为此,我们使用cv.connectedComponents()。它用0标记图像的背景,然后其他对象用从1开始的整数标记。
但是我们知道,如果背景标记为0,则分水岭会将其视为未知区域。所以我们想用不同的整数来标记它。相反,我们将未知定义的未知区域标记为0。
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
现在我们的标记器已准备就绪。现在是最后一步的时候了,申请分水岭。然后标记图像将被修改。边界区域将标记为-1
markers = cv.watershed(src, markers=markers) # 图像的边界标记为-1
src[markers == -1] = [255, 0, 0]
markers=markers.astype('uint8')
cv.imshow('img',src)
cv.waitKey()