本文參考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()
主要用於確定前景和背景區域,同時找到未知區域(物體邊界)。
thresh
執行兩次開運算,清除圖像中的小斑點或小區域opening
圖像進行三次膨脹操作,確定背景區域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()
左圖:像素到背景的距離圖
中間的圖:找到的前景圖
右圖:物體邊界圖