更新於 2024/03/15閱讀時間約 16 分鐘

[OpenCV應用][Python]應用watershed分割圖像(硬幣分割)

本文參考OpenCV官方Image Segmentation with Watershed Algorithm來實作分割相黏的硬幣

相較於官方範例,多新增取出分割後的物件的中心點來標註,大部分在分割圖像後我們都想知道分割後物件的位置(x,y)。

結果圖

二值化 -> 像素到背景的距離圖 -> 前景圖 ->分割背景圖

標記圖 -> 分割後的圖 -> 套用到原圖並畫出中心

分割結果圖

OpenCV 實作了一種基於標記的分水嶺演算法物件賦予不同的標籤用一種顏色(或強度)標記我們確定是前景或物體的區域,用另一種顏色標記我們確定是背景或非物體的區域最後標記我們不確定的區域,用0 標記它。然後應用分水嶺演算法。將使用我們給出的標籤進行更新,並且物件的邊界的值為-1。

標記圖像的數值對應於不同的標籤,常見的標籤值為正整數,但有一些特殊值:

正整數: 表示已知的區域,其中每個數字代表一個不同的標籤。

例如,1 表示前景,2 表示背景,0表示不確定的區域。

-1: 在分水嶺演算法後,標記圖像的邊界值會被設為-1。這樣可以用來標示物體的邊界。

程式範例

import numpy as np
import cv2
from matplotlib import pyplot as plt
import copy
img = cv2.imread('coins.jpg')
assert img is not None, "file could not be read, check with os.path.exists()"
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)

# noise removal 噪聲去除
kernel = np.ones((3,3),np.uint8)
opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel, iterations = 2)
sure_bg = cv2.dilate(opening,kernel,iterations=3)
dist_transform = cv2.distanceTransform(opening,cv2.DIST_L2,5)
_, sure_fg = cv2.threshold(dist_transform,0.7*dist_transform.max(),255,0)

# Finding unknown region 找到未知區域
sure_fg = np.uint8(sure_fg) #
unknown = cv2.subtract(sure_bg,sure_fg)

# Marker labelling
ret, markers = cv2.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

#watershed
markers = cv2.watershed(img,markers)
img[markers == -1] = [255,0,0]

# 在 'markers' 陣列中找到唯一的標籤
unique_labels = np.unique(markers)

# 排除標籤 -1(表示watershed邊界)
unique_labels = unique_labels[unique_labels != -1]

# 將 'img' 轉換為 RGB 以便視覺化
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# 逐一處理每個標籤,找到其中心
for label in unique_labels:
mask = (markers == label) # 建立當前標籤的遮罩
ys, xs = np.where(mask) # 找到當前標籤所有像素的座標

# 計算中心座標
centroid = (np.mean(xs), np.mean(ys)) # (mean x, mean y)

# 將中心座標轉換為整數
center_coordinates = (int(centroid[0]), int(centroid[1])) # (x, y)

# 在中心繪製一個圓
cv2.circle(img_rgb, center_coordinates, radius=5, color=(255, 0, 0), thickness=-1) # -1 fills the circle

# Display the result
plt.imshow(img_rgb)
plt.title('Centroids Marked')
plt.axis('off')
plt.show()

程式結果圖



程 式 解 析

將會拆解程式範例,並輸出圖片,直觀的了解每個步驟輸出的圖

載入圖片

使用 Otsu 的二值化。

import numpy as np
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('coins.jpg')
assert img is not None, "file could not be read, check with os.path.exists()"
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
#因用ipynb呈現,需plt與opencv默認RGB不同需轉換
gray = cv2.cvtColor(thresh, cv2.COLOR_GRAY2BGR)
gray = cv2.cvtColor(thresh, cv2.COLOR_BGR2RGB)
plt.imshow(gray)
plt.axis('off')
plt.show()

前處理步驟

主要用於確定前景背景區域,同時找到未知區域(物體邊界)。

  1. 通過對二值化圖像 thresh 執行兩次開運算,清除圖像中的小斑點或小區域
  2. 膨脹操作(Dilation),通過對 opening 圖像進行三次膨脹操作,確定背景區域
  3. 距離變換(Distance Transform)和二值化操作,將這些距離轉換為二進制圖像,形成確定的前景區域
  4. 背景區域減去前景區域。未知區域是分水嶺演算法中需要進一步處理的區域
import copy
# noise removal 去除雜訊
kernel = np.ones((3,3),np.uint8)
opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel, iterations = 2)
#通過對二值化圖像 thresh 執行兩次開運算,清除圖像中的小斑點或小區域。

# sure background area 確定背景區域
sure_bg = cv2.dilate(opening,kernel,iterations=3)
#通過對 opening 圖像進行三次膨脹操作,進一步確定背景區域。

# Finding sure foreground area 找到確定的前景區域:
dist_transform = cv2.distanceTransform(opening,cv2.DIST_L2,5)
#使用 distanceTransform 函數計算二值化圖像 opening 中每個像素到最近的零像素的距離。
_, sure_fg = cv2.threshold(dist_transform,0.7*dist_transform.max(),255,0)
# 通過 threshold 函數將這些距離轉換為二進制圖像,形成確定的前景區域

# Finding unknown region 找到未知區域
sure_fg = np.uint8(sure_fg) # 將確定的前景區域(二進制圖像)轉換為 8 位的無符號整數型別 uint8
unknown = cv2.subtract(sure_bg,sure_fg) #通過 subtract 函數找到未知區域,即背景區域減去前景區域。

#因用ipynb呈現,需plt與opencv默認RGB不同需轉換
unknown_show = cv2.cvtColor(unknown, cv2.COLOR_GRAY2BGR)
unknown_show = cv2.cvtColor(unknown_show, cv2.COLOR_BGR2RGB)
sure_fg_Show = cv2.cvtColor(sure_fg, cv2.COLOR_GRAY2BGR)
sure_fg_Show = cv2.cvtColor(sure_fg_Show, cv2.COLOR_BGR2RGB)
plt.subplot(1, 3, 1), plt.imshow(dist_transform), plt.title('dist_transform'), plt.axis('off')
plt.subplot(1, 3, 2), plt.imshow(sure_fg_Show), plt.title('sure_fg'), plt.axis('off')
plt.subplot(1, 3, 3), plt.imshow(unknown_show), plt.title('unknown'), plt.axis('off')
plt.show()

左圖:像素到背景的距離圖

中間的圖:找到的前景圖

右圖:物體邊界圖

建立標記

分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.