Python基于均值漂移算法和分水嶺算法實現圖像分割
一.基于均值漂移算法的圖像分割
均值漂移(Mean Shfit)算法是一種通用的聚類算法,最早是1975年Fukunaga等人在一篇關于概率密度梯度函數的估計論文中提出[1]。它是一種無參估計算法,沿著概率梯度的上升方向尋找分布的峰值。Mean Shift算法先算出當前點的偏移均值,移動該點到其偏移均值,然后以此為新的起始點,繼續(xù)移動,直到滿足一定的條件結束。
圖像分割中可以利用均值漂移算法的特性,實現彩色的圖像分割。在OpenCV中提供的函數為pyrMeanShiftFiltering(),該函數嚴格來說并不是圖像分割,而是圖像在色彩層面的平滑濾波,它可以中和色彩分布相近的顏色,平滑色彩細節(jié),侵蝕掉面積較小的顏色區(qū)域,所以在OpenCV中它的后綴是濾波“Filter”,而不是分割“segment”。該函數原型如下所示:
dst = pyrMeanShiftFiltering(src, sp, sr[, dst[, maxLevel[, termcrit]]])
– src表示輸入圖像,8位三通道的彩色的圖像
– dst表示輸出圖像,需同輸入圖像具有相同的大小和類型
– sp表示定義漂移物理空間半徑的大小
– sr表示定義漂移色彩空間半徑的大小
– maxLevel表示定義金字塔的最大層數
– termcrit表示定義的漂移迭代終止條件,可以設置為迭代次數滿足終止,迭代目標與中心點偏差滿足終止,或者兩者的結合
均值漂移pyrMeanShiftFiltering()函數的執(zhí)行過程是如下:
- 構建迭代空間。以輸入圖像上任一點P0為圓心,建立以sp為物理空間半徑,sr為色彩空間半徑的球形空間,物理空間上坐標為x和y,色彩空間上坐標為RGB或HSV,構成一個空間球體。其中x和y表示圖像的長和寬,色彩空間R、G、B在0至255之間。
- 求迭代空間的向量并移動迭代空間球體重新計算向量,直至收斂。在上一步構建的球形空間中,求出所有點相對于中心點的色彩向量之和,移動迭代空間的中心點到該向量的終點,并再次計算該球形空間中所有點的向量之和,如此迭代,直到在最后一個空間球體中所求得向量和的終點就是該空間球體的中心點Pn,迭代結束。
- 更新輸出圖像dst上對應的初始原點P0的色彩值為本輪迭代的終點Pn的色彩值,完成一個點的色彩均值漂移。
- 對輸入圖像src上其他點,依次執(zhí)行上述三個步驟,直至遍歷完所有點后,整個均值偏移色彩濾波完成。
下面的代碼是圖像均值漂移的實現過程:
# -*- coding: utf-8 -*- # By: Eastmount import cv2 import numpy as np import matplotlib.pyplot as plt #讀取原始圖像灰度顏色 img = cv2.imread('scenery.png') spatialRad = 50 #空間窗口大小 colorRad = 50 #色彩窗口大小 maxPyrLevel = 2 #金字塔層數 #圖像均值漂移分割 dst = cv2.pyrMeanShiftFiltering( img, spatialRad, colorRad, maxPyrLevel) #顯示圖像 cv2.imshow('src', img) cv2.imshow('dst', dst) cv2.waitKey() cv2.destroyAllWindows()
當漂移物理空間半徑設置為50,漂移色彩空間半徑設置為50,金字塔層數 為2,輸出的效果圖如圖1所示。
當漂移物理空間半徑設置為20,漂移色彩空間半徑設置為20,金字塔層數 為2,輸出的效果圖如圖2所示。對比可以發(fā)現,半徑為20時,圖像色彩細節(jié)大部分存在,半徑為50時,森林和水面的色彩細節(jié)基本都已經丟失。
寫到這里,均值偏移算法對彩色的圖像的分割平滑操作就完成了,為了達到更好地分割目的,借助漫水填充函數進行下一步處理,在下一篇文章將詳細介紹,這里只是引入該函數。完整代碼如下所示:
# -*- coding: utf-8 -*- # By: Eastmount import cv2 import numpy as np import matplotlib.pyplot as plt #讀取原始圖像灰度顏色 img = cv2.imread('scenery.png') #獲取圖像行和列 rows, cols = img.shape[:2] #mask必須行和列都加2且必須為uint8單通道陣列 mask = np.zeros([rows+2, cols+2], np.uint8) spatialRad = 100 #空間窗口大小 colorRad = 100 #色彩窗口大小 maxPyrLevel = 2 #金字塔層數 #圖像均值漂移分割 dst = cv2.pyrMeanShiftFiltering( img, spatialRad, colorRad, maxPyrLevel) #圖像漫水填充處理 cv2.floodFill(dst, mask, (30, 30), (0, 255, 255), (100, 100, 100), (50, 50, 50), cv2.FLOODFILL_FIXED_RANGE) #顯示圖像 cv2.imshow('src', img) cv2.imshow('dst', dst) cv2.waitKey() cv2.destroyAllWindows()
輸出的效果圖如圖3所示,它將天空染成黃色。
二.基于分水嶺算法的圖像分割
圖像分水嶺算法(Watershed Algorithm)是將圖像的邊緣輪廓轉換為“山脈”,將均勻區(qū)域轉換為“山谷”,從而提升分割效果的算法[3]。分水嶺算法是基于拓撲理論的數學形態(tài)學的分割方法,灰度圖像根據灰度值把像素之間的關系看成山峰和山谷的關系,高亮度(灰度值高)的地方是山峰,低亮度(灰度值低)的地方是山谷。接著給每個孤立的山谷(局部最小值)不同顏色的水(Label),當水漲起來,根據周圍的山峰(梯度),不同的山谷也就是不同顏色的像素點開始合并,為了避免這個現象,可以在水要合并的地方建立障礙,直到所有山峰都被淹沒。所創(chuàng)建的障礙就是分割結果,這個就是分水嶺的原理[3]。
分水嶺算法的計算過程是一個迭代標注過程,主要包括排序和淹沒兩個步驟。由于圖像會存在噪聲或缺失等問題,該方法會造成分割過度。OpenCV提供了watershed()函數實現圖像分水嶺算法,并且能夠指定需要合并的點,其函數原型如下所示:
markers = watershed(image, markers)
– image表示輸入圖像,需為8位三通道的彩色的圖像
– markers表示用于存儲函數調用之后的運算結果,輸入/輸出32位單通道圖像的標記結構,輸出結果需和輸入圖像的尺寸和類型一致。
下面是分水嶺算法實現圖像分割的過程。假設存在一幅彩色硬幣圖像,如圖4所示,硬幣相互之間挨著。
第一步,通過圖像灰度化和閾值化處理提取圖像灰度輪廓,采用OTSU二值化處理獲取硬幣的輪廓。
# -*- coding: utf-8 -*- # By: Eastmount import numpy as np import cv2 from matplotlib import pyplot as plt #讀取原始圖像 img = cv2.imread('coin.jpg') #圖像灰度化處理 gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #圖像閾值化處理 ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU) #顯示圖像 cv2.imshow('src', img) cv2.imshow('res', thresh) cv2.waitKey() cv2.destroyAllWindows()
輸出結果如圖5所示。
第二步,通過形態(tài)學開運算過濾掉小的白色噪聲。同時,由于圖像中的硬幣是緊挨著的,所以不能采用圖像腐蝕去掉邊緣的像素,而是選擇距離轉換,配合一個適當的閾值進行物體提取。這里引入一個圖像膨脹操作,將目標邊緣擴展到背景,以確定結果的背景區(qū)域。
# -*- coding: utf-8 -*- # By: Eastmount import numpy as np import cv2 from matplotlib import pyplot as plt #讀取原始圖像 img = cv2.imread('coin.jpg') #圖像灰度化處理 gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #圖像閾值化處理 ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU) #圖像開運算消除噪聲 kernel = np.ones((3,3),np.uint8) opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel, iterations = 2) #圖像膨脹操作確定背景區(qū)域 sure_bg = cv2.dilate(opening,kernel,iterations=3) #距離運算確定前景區(qū)域 dist_transform = cv2.distanceTransform(opening,cv2.DIST_L2,5) ret, sure_fg = cv2.threshold(dist_transform, 0.7*dist_transform.max(), 255, 0) #尋找未知區(qū)域 sure_fg = np.uint8(sure_fg) unknown = cv2.subtract(sure_bg, sure_fg) #用來正常顯示中文標簽 plt.rcParams['font.sans-serif']=['SimHei'] #顯示圖像 titles = ['原始圖像', '閾值化', '開運算', '背景區(qū)域', '前景區(qū)域', '未知區(qū)域'] images = [img, thresh, opening, sure_bg, sure_fg, unknown] for i in range(6): plt.subplot(2,3,i+1), plt.imshow(images[i], 'gray') plt.title(titles[i]) plt.xticks([]),plt.yticks([]) plt.show()
輸出結果如圖6所示,包括原始圖像、閾值化處理、開運算、背景區(qū)域、前景區(qū)域、未知區(qū)域等。由圖可知,在使用閾值過濾的圖像里,確認了圖像的硬幣區(qū)域,而在有些情況,可能對前景分割更感興趣,而不關心目標是否需要分開或挨著,那時可以采用腐蝕操作來求解前景區(qū)域。
第三步,當前處理結果中,已經能夠區(qū)分出前景硬幣區(qū)域和背景區(qū)域。接著我們創(chuàng)建標記變量,在該變量中標記區(qū)域,已確認的區(qū)域(前景或背景)用不同的正整數標記出來,不確認的區(qū)域保持0,使用cv2.connectedComponents()函數來將圖像背景標記成0,其他目標用從1開始的整數標記。注意,如果背景被標記成0,分水嶺算法會認為它是未知區(qū)域,所以要用不同的整數來標記。
最后,調用watershed()函數實現分水嶺圖像分割,標記圖像會被修改,邊界區(qū)域會被標記成0,完整代碼如下所示。
# -*- coding: utf-8 -*- # By: Eastmount import numpy as np import cv2 from matplotlib import pyplot as plt #讀取原始圖像 img = cv2.imread('coin.jpg') #圖像灰度化處理 gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #圖像閾值化處理 ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU) #圖像開運算消除噪聲 kernel = np.ones((3,3),np.uint8) opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel, iterations = 2) #圖像膨脹操作確定背景區(qū)域 sure_bg = cv2.dilate(opening,kernel,iterations=3) #距離運算確定前景區(qū)域 dist_transform = cv2.distanceTransform(opening,cv2.DIST_L2,5) ret, sure_fg = cv2.threshold(dist_transform, 0.7*dist_transform.max(), 255, 0) #尋找未知區(qū)域 sure_fg = np.uint8(sure_fg) unknown = cv2.subtract(sure_bg, sure_fg) #標記變量 ret, markers = cv2.connectedComponents(sure_fg) #所有標簽加一,以確保背景不是0而是1 markers = markers+1 #用0標記未知區(qū)域 markers[unknown==255]=0 #分水嶺算法實現圖像分割 markers = cv2.watershed(img, markers) img[markers == -1] = [255,0,0] #用來正常顯示中文標簽 plt.rcParams['font.sans-serif']=['SimHei'] #顯示圖像 titles = ['標記區(qū)域', '圖像分割'] images = [markers, img] for i in range(2): plt.subplot(1,2,i+1), plt.imshow(images[i], 'gray') plt.title(titles[i]) plt.xticks([]),plt.yticks([]) plt.show()
最終分水嶺算法的圖像分割如圖7所示,它將硬幣的輪廓成功提取。
圖8是采用分水嶺算法提取圖像Windows中心輪廓的效果圖。
分水嶺算法對微弱邊緣具有良好的響應,圖像中的噪聲、物體表面細微的灰度變化,都會產生過度分割的現象。但同時應當看出,分水嶺算法對微弱邊緣具有良好的響應,是得到封閉連續(xù)邊緣的保證。另外,分水嶺算法所得到的封閉的集水盆,為分析圖像的區(qū)域特征提供了可能。
三.總結
本文主要講解了圖像分割方法,包括基于均值漂移算法的圖像分割方法、基于分水嶺算法的圖像分割方法,通過這些處理能有效分割圖像的背景和前景,識別某些圖像的區(qū)域。
以上就是Python基于均值漂移算法和分水嶺算法實現圖像分割的詳細內容,更多關于Python圖像分割的資料請關注腳本之家其它相關文章!
相關文章
python代碼 if not x: 和 if x is not None: 和 if not x is None:使用
這篇文章主要介紹了python代碼 if not x: 和 if x is not None: 和 if not x is None:使用介紹,需要的朋友可以參考下2016-09-09pytorch中關于distributedsampler函數的使用
這篇文章主要介紹了pytorch中關于distributedsampler函數的使用,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-02-02