Python opencv影象處理基礎總結(七) 基於分水嶺演演算法的影象分割

2020-09-20 11:00:41

一、 原理

1. 分水嶺演演算法原理

  • 任何一副灰度影象都可以被看成拓撲平面,灰度值高的區域可以被看成是山峰,灰度值低的區域可以被看成是山谷。我們向每一個山谷中灌不同顏色的水。隨著水的位的升高,不同山谷的水就會相遇匯合,為了防止不同山谷的水匯合,我們需要在水匯合的地方構建起堤壩。不停地灌水,不停地構建堤壩知道所有的山峰都被水淹沒。我們構建好的堤壩就是對影象的分割,這就是分水嶺演演算法的背後原理。
  • OpenCV 採用了基於掩模的分水嶺演演算法,在這種演演算法中我們要設定那些山谷點會匯合,那些不會。這是一種互動式的影象分割,我們要做的就是給我們已知的物件打上不同的標籤。如果某個區域肯定是前景或物件,就使用某個顏色(或灰度值)標籤標記它。如果某個區域肯定不是物件而是背景就使用另外一個顏色標籤標記。而剩下的不能確定是前景還是背景的區域就用 0 標記,這就是我們的標籤。然後實施分水嶺演演算法。每一次灌水,我們的標籤就會被更新,當兩個不同顏色的標籤相遇時就構建堤壩,直到將所有山峰淹沒,最後我們得到的邊界物件(堤壩)的值為 -1。

2. 距離變換

  • 距離變換的基本含義是計算一個影象中非零畫素點到最近的零畫素點的距離,也就是到零畫素點的最短距離
  • 最常見的距離變換演演算法就是通過連續的腐蝕操作來實現,腐蝕操作的停止條件是所有前景畫素都被完全
  • 腐蝕。這樣根據腐蝕的先後順序,我們就得到各個前景畫素點到前景中心骨架畫素點的距離
  • 根據各個畫素點的距離值,設定為不同的灰度值。這樣就完成了二值影象的距離變換

3. opencv有關函數的用法

cv2.distanceTransform(src, distanceType, maskSize, dst=None, dstType=None)
  • src:輸入二值影象
  • distanceType:計算距離的方式
  • maskSize:蒙板尺寸
cv2.connectedComponents(image, labels=None, connectivity=None, ltype=None)
  • image:輸入8位元單通道影象
  • labels:輸出標籤地圖
  • connectivity:連通性,預設8,還可以取4
  • Itype:輸出標籤型別 ,預設 CV_32S, 還可以取CV_16U
cv2.watershed(image, markers)
  • image:輸入影象
  • markers:標記

二、基於距離的分水嶺分割流程

在這裡插入圖片描述

  • 輸入影象,有噪聲的話,先進行去噪
  • 轉成灰度影象
  • 二值化處理、形態學操作
  • 距離變換
  • 尋找種子、生成marker
  • 實施分水嶺演演算法、輸出分割後的影象

三、python程式碼實現

import cv2 as cv
import numpy as np


def watershed_algorithm(image):
    # 邊緣保留濾波EPF  去噪
    blur = cv.pyrMeanShiftFiltering(image,sp=10,sr=100)
    # 轉成灰度影象
    gray = cv.cvtColor(blur, cv.COLOR_BGR2GRAY)
    # 得到二值影象   自適應閾值
    ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
    cv.imshow('binary image', binary)

    # 形態學操作   獲取結構元素  開操作
    kernel = cv.getStructuringElement(cv.MORPH_RECT, (3, 3))
    opening = cv.morphologyEx(binary, cv.MORPH_OPEN, kernel=kernel, iterations=2)
    # 確定區域
    sure_bg = cv.dilate(opening, kernel, iterations=3)
    # cv.imshow('mor-opt', sure_bg)

    # 距離變換
    dist = cv.distanceTransform(opening, cv.DIST_L2, 3)
    dist_out = cv.normalize(dist, 0, 1.0, cv.NORM_MINMAX)
    # cv.imshow('distance-', dist_out * 50)
    ret, surface = cv.threshold(dist_out, dist_out.max() * 0.6, 255, cv.THRESH_BINARY)
    # cv.imshow('surface-markers', surface)

    surface_fg = np.uint8(surface)    # 轉成8位元整型
    unkonown = cv.subtract(sure_bg, surface_fg)        # 找到位置區域
    # Marker labelling
    ret, markers = cv.connectedComponents(surface_fg)  # 連通區域
    print(ret)

    # 分水嶺變換
    # 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[unkonown == 255] = 0
    # 實施分水嶺演演算法了。標籤影象將會被修改,邊界區域的標記將變為 -1
    markers = cv.watershed(image, markers=markers)
    image[markers == -1] = [0, 0, 255]      # 被標記的區域   設為紅色
    cv.imshow('result', image)


src = cv.imread(r'./test/042.png')
src = cv.resize(src, None, fx=0.5, fy=0.5)
cv.imshow('input image', src)
watershed_algorithm(src)
cv.waitKey(0)
cv.destroyAllWindows()

執行效果如下:

在這裡插入圖片描述
在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述