【youcans 的 OpenCV 例程200篇】184.鼠标交互标记的分水岭算法

OpenCV 例程200篇 总目录-202205更新

【youcans 的 OpenCV 例程200篇】180.基于距离变换的分水岭算法
【youcans 的 OpenCV 例程200篇】181.基于 Sobel 梯度的分水岭算法
【youcans 的 OpenCV 例程200篇】182.基于形态学梯度的分水岭算法
【youcans 的 OpenCV 例程200篇】183.基于轮廓标记的分水岭算法
【youcans 的 OpenCV 例程200篇】184.鼠标交互标记的分水岭算法


【youcans 的 OpenCV 例程200篇】184.鼠标交互标记的分水岭算法


7. 图像分割之分水岭算法

分水岭算法是一种图像区域分割法,以临近像素间的相似性作为重要特征,从而将空间位置相近且灰度值相近的像素点互相连接起来,构成一个封闭的轮廓。

分水岭算法是基于形态学的图像分割方法,体现了边缘检测、阈值处理和区域提取的概念和思想,往往会产生更稳定的分割结果。算法的实现过程可以理解为洪水淹没的过程:最低点首先被淹没,然后水逐渐淹没整个山谷;水位升高到一定高度就会溢出,于是在溢出位置修建堤坝;不断提高水位,重复上述过程,直到所有的点全部被淹没;所建立的一系列堤坝就成为分隔各个盆地的分水岭。

分水岭的计算过程是一个迭代标注过程,通过寻找集水盆和分水岭对图像进行分割。经典的分水岭算法分为排序过程和淹没过程两个步骤,首先对每个像素的灰度级从低到高排序,然后在从低到高的淹没过程中,对每一个局部极小值在 h 阶高度的影响域进行判断及标注。

OpenCV 提供了函数 cv.watershed 实现基于标记的分水岭算法。

使用函数 cv.watershed 需要输入一个CV_32S 类型的标记图像,图像中每个非零像素代表一个标签。对图像中部分像素做标记,表明它的所属区域是已知的。

cv.watershed(image, markers[, ]	) → markers 

参数说明:

  • image:输入图像,8-bit/3-channel 彩色图像
  • markers:标记图像,32-bit 单通道图像,大小与 image 相同

注意事项:

  • 分水岭算法要求必须在标记图像 markers 中用索引勾勒出需要分割的区域,每个区域被赋值为 1、2、3… 等索引编号,对应于不同的目标物体。
  • 图像标记 markers 中未知区域的像素值设置为 0,通过分水岭算法确定这些像素属于背景还是前景区域。
  • 输出的图像标记 markers 中,每个像素都被赋值为 1、2、3… 等索引编号,或以 -1 表示区域之间的边界(分水岭)。

OpenCV 提供了函数 cv.distanceTransform 实现距离变换,计算图像中每个像素到最近的零像素点的距离。

函数说明:

cv.distanceTransform(src, distanceType, maskSize[, dst=None, dstType=CV_32F]) → dst
cv.distanceTransformWithLabels(src, distanceType, maskSize[, dst=None, labels=None, labelType=DIST_LABEL_CCOMP]) → dst, labels

参数说明:

  • src:输入图像,8-bit 单通道灰度图像
  • distanceType:距离的类型
    • cv.DIST_USER:用户定义的距离
    • cv.DIST_L1: d i s t = ∣ x 1 − x 2 ∣ + ∣ y 1 − y 2 ∣ dist = |x1-x2|+|y1-y2| dist=x1x2+y1y2
    • cv.DIST_L2:欧几里德距离
    • cv.DIST_C: d i s t = m a x ( ∣ x 1 − x 2 ∣ , ∣ y 1 − y 2 ∣ ) dist = max(|x1-x2|, |y1-y2|) dist=max(x1x2,y1y2)
  • maskSize:距离变换遮罩的大小,通常取 3, 5
  • labelType:生成的标签数组的类型
    • cv.DIST_LABEL_CCOMP:每个连接的零组件(及最接近连接组件的所有非零像素)被指定相同的标签
    • cv.DIST_LABEL_PIXEL:每个零像素(及离它最近的所有非零像素)都有自己的标签
  • dst:计算距离的输出图像,8-bit 或 32-bit 单通道图像,大小与 src 相同
  • labels:标签的输出图像,CV_32SC1类型, 大小与 src 相同

例程 11.41 鼠标交互标记的分水岭算法

基于标记点的改进算法在原始图像中寻找一些内部标记和外部标记来引导分割,关键就在于如何获得准确的标记图像,即如何准确地标记前景物体与背景。

本例程给出通过鼠标交互进行标注的例子。由于在例程中并未区分前景或背景,对分割目标和背景都要做标记。

    # 11.41 鼠标交互标记的分水岭算法
    drawing = False  # 绘图状态
    mode = False  # 绘图模式
    ix, iy = -1, -1
    index = 0

    def mouseHandler(event, x, y, flags, param):
        global ix, iy, index, drawing, mode
        if event == cv2.EVENT_LBUTTONDOWN:  # 鼠标左键点击事件
            drawing = True  # 开启绘图状态
            ix, iy = x, y  # 绘图起点
            print("鼠标左键点击:", drawing, mode, ix, iy)
        elif event == cv2.EVENT_MOUSEMOVE:  # 鼠标拖动事件
            if (drawing == True) and (flags == cv2.EVENT_FLAG_LBUTTON):  # 左键绘图状态
                cv2.cile(src, (x, y), 5, (255, 255, 255), -1)  # 白色圆点,填充,宽度为 5
                cv2.circle(mask, (x, y), 5, 255, -1)  # 白色圆点,填充,宽度为 5
                # cv2.line(src, (ix, iy), (x, y), 255, 5, -1)  # 蓝色直线,填充,宽度为 5
                # cv2.line(mask, (ix, iy), (x, y), 255, 5, -1)  # 白色直线,填充,宽度为 5
        elif event == cv2.EVENT_LBUTTONUP:  # 鼠标左键释放事件
            drawing = False  # 结束绘图状态
            index += 1  # 完成一次标注
            print("鼠标左键释放:", drawing, mode, ix, iy)
            print("完成第 {:d} 个左键目标标记".format(index))

    # if __name__ == '__main__':  # 标记符控制的分水岭算法
    img = cv2.imread("../images/imgCoin01.png", flags=1)  # 读取彩色图像(BGR)
    hImg, wImg = img.shape[0], img.shape[1]
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 转为灰度图像
    img0 = img.copy()

    cv2.namedWindow('image')
    cv2.setMouseCallback('image', mouseHandler)

    src = img.copy()
    mask = np.zeros((hImg, wImg), np.uint8)
    markers = np.zeros((hImg, wImg), np.int32)  # 标识图像,背景 255,待定 0
    while True:
        markers[mask == 255] = index  # 将 mask 白色区域标记为第 index 个目标
        cv2.imshow('image', src)
        mask = np.zeros((hImg, wImg), np.uint8)  # mask 复位
        key = 0xFF & cv2.waitKey(1)
        if key == 27:  # esc to exit
            break

    # 图像的形态学梯度
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))  # 生成 5*5 结构元
    grad = cv2.morphologyEx(gray, cv2.MORPH_GRADIENT, kernel)  # 形态学梯度
    # 阈值分割,将灰度图像分为黑白二值图像
    _, thresh = cv2.threshold(np.uint8(grad), 0.2 * grad.max(), 255, cv2.THRESH_BINARY)
    # 形态学操作,生成 "确定背景" 区域
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))  # 生成 3*3 结构元
    opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)  # 开运算,消除噪点
    sure_bg = cv2.dilate(opening, kernel, iterations=3)  # 膨胀操作,生成 "确定背景" 区域
    # 去除连通域中的背景区域部分
    sure_fg = markers.copy()

    # 分水岭算法标注目标的轮廓
    markers = cv2.watershed(img, markers)  # 分水岭算法,将分水岭的像素点标注为 -1

    kinds = markers.max() + 1  # 目标索引数 + 分水岭标注
    maxKind = np.argmax(np.bincount((markers + 1).flatten()))  # 出现最多的序号,所占面积最大,选为底色
    bgrMarkers = np.zeros_like(img)
    bgrMarkers[markers == -1] = [0, 0, 0]  # 轮廓/分水岭 设为黑色
    for i in range(1, kinds):
        if (i == maxKind - 1):  # 出现最多的序号,所占面积最大,判断为底色
            bgrMarkers[markers == i] = [255, 255, 255]  # 将底色设为白色
        else:
            colorKind = [np.random.randint(0, 255), np.random.randint(0, 255), np.random.randint(0, 255)]
            bgrMarkers[markers == i] = colorKind
    bgrFilled = cv2.addWeighted(img, 0.4, bgrMarkers, 0.6, 0)  # 填充后与原始图像融合
    # markers1D = markers.flatten()
    # print(markers1D.shape, kinds, maxKind, np.unique(markers1D))
    # print(np.bincount((markers+1).flatten()))

    plt.figure(figsize=(10, 6))
    plt.subplot(231), plt.axis('off'), plt.title("Origin image")
    plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))  # 显示 img(RGB)
    plt.subplot(232), plt.axis('off'), plt.title("Gray")
    plt.imshow(gray, 'gray')
    plt.subplot(233), plt.axis('off'), plt.title("sure_background")
    plt.imshow(sure_bg, 'gray')  # 确定背景
    plt.subplot(234), plt.axis('off'), plt.title("sure_frontground")
    plt.imshow(sure_fg, 'gray')  # 图像标注
    plt.subplot(235), plt.axis('off'), plt.title("Color Markers")
    plt.imshow(cv2.cvtColor(bgrMarkers, cv2.COLOR_BGR2RGB))
    plt.subplot(236), plt.axis('off'), plt.title("Cutted image")
    plt.imshow(cv2.cvtColor(bgrFilled, cv2.COLOR_BGR2RGB))
    plt.tight_layout()
    plt.show()

在这里插入图片描述



(本节完)


版权声明:

OpenCV 例程200篇 总目录-202205更新
youcans@xupt 原创作品,转载必须标注原文链接:(https://blog.csdn.net/youcans/article/details/124813769)

Copyright 2022 youcans, XUPT
Crated:2022-5-18


欢迎关注 『youcans 的 OpenCV 例程 200 篇』 系列,持续更新中
欢迎关注 『youcans 的 OpenCV学习课』 系列,持续更新中

【youcans 的 OpenCV 例程200篇】147. 图像分割之孤立点检测
【youcans 的 OpenCV 例程200篇】148. 图像分割之线检测
【youcans 的 OpenCV 例程200篇】149. 图像分割之边缘模型
【youcans 的 OpenCV 例程200篇】150. 边缘检测梯度算子
【youcans 的 OpenCV 例程200篇】151. 边缘检测中的平滑处理
【youcans 的 OpenCV 例程200篇】152. 边缘检测之 LoG 算子
【youcans 的 OpenCV 例程200篇】153. 边缘检测之 DoG 算子
【youcans 的 OpenCV 例程200篇】154. 边缘检测之 Canny 算子
【youcans 的 OpenCV 例程200篇】155. 边缘连接的局部处理方法
【youcans 的 OpenCV 例程200篇】156. 边缘连接局部处理的简化算法
【youcans 的 OpenCV 例程200篇】157. 霍夫变换直线检测
【youcans 的 OpenCV 例程200篇】158. 阈值处理之固定阈值法
【youcans 的 OpenCV 例程200篇】159. 图像分割之全局阈值处理
【youcans 的 OpenCV 例程200篇】160. 图像处理之OTSU 方法
【youcans 的 OpenCV 例程200篇】161. OTSU 阈值处理算法的实现
【youcans 的 OpenCV 例程200篇】162. 全局阈值处理改进方法
【youcans 的 OpenCV 例程200篇】163. 基于边缘信息改进全局阈值处理
【youcans 的 OpenCV 例程200篇】164.使用 Laplace 边缘信息改进全局阈值处理
【youcans 的 OpenCV 例程200篇】165.多阈值 OTSU 处理方法
【youcans 的 OpenCV 例程200篇】166.自适应阈值处理
【youcans 的 OpenCV 例程200篇】167.基于移动平均的可变阈值处理
【youcans 的 OpenCV 例程200篇】168.图像分割之区域生长
【youcans 的 OpenCV 例程200篇】169.图像分割之区域分离
【youcans 的 OpenCV 例程200篇】170.图像分割之K均值聚类
【youcans 的 OpenCV 例程200篇】171.SLIC 超像素区域分割
【youcans 的 OpenCV 例程200篇】172.SLIC 超像素区域分割算法比较
【youcans 的 OpenCV 例程200篇】173.SEEDS 超像素区域分割
【youcans 的 OpenCV 例程200篇】174.LSC 超像素区域分割
【youcans 的 OpenCV 例程200篇】175.超像素区域分割方法比较
【youcans 的 OpenCV 例程200篇】176.图像分割之均值漂移算法
【youcans 的 OpenCV 例程200篇】177.图像分割之 GraphCuts 图割法
【youcans 的 OpenCV 例程200篇】178.图像分割之 GrabCut 图割法(框选前景)
【youcans 的 OpenCV 例程200篇】179.图像分割之 GrabCut 图割法(掩模图像)
【youcans 的 OpenCV 例程200篇】180.基于距离变换的分水岭算法
【youcans 的 OpenCV 例程200篇】181.基于 Sobel 梯度的分水岭算法
【youcans 的 OpenCV 例程200篇】182.基于形态学梯度的分水岭算法
【youcans 的 OpenCV 例程200篇】183.基于轮廓标记的分水岭算法
【youcans 的 OpenCV 例程200篇】184.鼠标交互标记的分水岭算法

猜你喜欢

转载自blog.csdn.net/youcans/article/details/124813769