Python基于均值漂移算法和分水嶺算法實(shí)現(xiàn)圖像分割
一.基于均值漂移算法的圖像分割
均值漂移(Mean Shfit)算法是一種通用的聚類算法,最早是1975年Fukunaga等人在一篇關(guān)于概率密度梯度函數(shù)的估計(jì)論文中提出[1]。它是一種無參估計(jì)算法,沿著概率梯度的上升方向?qū)ふ曳植嫉姆逯?。Mean Shift算法先算出當(dāng)前點(diǎn)的偏移均值,移動該點(diǎn)到其偏移均值,然后以此為新的起始點(diǎn),繼續(xù)移動,直到滿足一定的條件結(jié)束。
圖像分割中可以利用均值漂移算法的特性,實(shí)現(xiàn)彩色的圖像分割。在OpenCV中提供的函數(shù)為pyrMeanShiftFiltering(),該函數(shù)嚴(yán)格來說并不是圖像分割,而是圖像在色彩層面的平滑濾波,它可以中和色彩分布相近的顏色,平滑色彩細(xì)節(jié),侵蝕掉面積較小的顏色區(qū)域,所以在OpenCV中它的后綴是濾波“Filter”,而不是分割“segment”。該函數(shù)原型如下所示:
dst = pyrMeanShiftFiltering(src, sp, sr[, dst[, maxLevel[, termcrit]]])
– src表示輸入圖像,8位三通道的彩色的圖像
– dst表示輸出圖像,需同輸入圖像具有相同的大小和類型
– sp表示定義漂移物理空間半徑的大小
– sr表示定義漂移色彩空間半徑的大小
– maxLevel表示定義金字塔的最大層數(shù)
– termcrit表示定義的漂移迭代終止條件,可以設(shè)置為迭代次數(shù)滿足終止,迭代目標(biāo)與中心點(diǎn)偏差滿足終止,或者兩者的結(jié)合
均值漂移pyrMeanShiftFiltering()函數(shù)的執(zhí)行過程是如下:
- 構(gòu)建迭代空間。以輸入圖像上任一點(diǎn)P0為圓心,建立以sp為物理空間半徑,sr為色彩空間半徑的球形空間,物理空間上坐標(biāo)為x和y,色彩空間上坐標(biāo)為RGB或HSV,構(gòu)成一個(gè)空間球體。其中x和y表示圖像的長和寬,色彩空間R、G、B在0至255之間。
- 求迭代空間的向量并移動迭代空間球體重新計(jì)算向量,直至收斂。在上一步構(gòu)建的球形空間中,求出所有點(diǎn)相對于中心點(diǎn)的色彩向量之和,移動迭代空間的中心點(diǎn)到該向量的終點(diǎn),并再次計(jì)算該球形空間中所有點(diǎn)的向量之和,如此迭代,直到在最后一個(gè)空間球體中所求得向量和的終點(diǎn)就是該空間球體的中心點(diǎn)Pn,迭代結(jié)束。
- 更新輸出圖像dst上對應(yīng)的初始原點(diǎn)P0的色彩值為本輪迭代的終點(diǎn)Pn的色彩值,完成一個(gè)點(diǎn)的色彩均值漂移。
- 對輸入圖像src上其他點(diǎn),依次執(zhí)行上述三個(gè)步驟,直至遍歷完所有點(diǎn)后,整個(gè)均值偏移色彩濾波完成。
下面的代碼是圖像均值漂移的實(shí)現(xiàn)過程:
# -*- 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 #金字塔層數(shù) #圖像均值漂移分割 dst = cv2.pyrMeanShiftFiltering( img, spatialRad, colorRad, maxPyrLevel) #顯示圖像 cv2.imshow('src', img) cv2.imshow('dst', dst) cv2.waitKey() cv2.destroyAllWindows()
當(dāng)漂移物理空間半徑設(shè)置為50,漂移色彩空間半徑設(shè)置為50,金字塔層數(shù) 為2,輸出的效果圖如圖1所示。
當(dāng)漂移物理空間半徑設(shè)置為20,漂移色彩空間半徑設(shè)置為20,金字塔層數(shù) 為2,輸出的效果圖如圖2所示。對比可以發(fā)現(xiàn),半徑為20時(shí),圖像色彩細(xì)節(jié)大部分存在,半徑為50時(shí),森林和水面的色彩細(xì)節(jié)基本都已經(jīng)丟失。
寫到這里,均值偏移算法對彩色的圖像的分割平滑操作就完成了,為了達(dá)到更好地分割目的,借助漫水填充函數(shù)進(jìn)行下一步處理,在下一篇文章將詳細(xì)介紹,這里只是引入該函數(shù)。完整代碼如下所示:
# -*- 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 #金字塔層數(shù) #圖像均值漂移分割 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)是將圖像的邊緣輪廓轉(zhuǎn)換為“山脈”,將均勻區(qū)域轉(zhuǎn)換為“山谷”,從而提升分割效果的算法[3]。分水嶺算法是基于拓?fù)淅碚摰臄?shù)學(xué)形態(tài)學(xué)的分割方法,灰度圖像根據(jù)灰度值把像素之間的關(guān)系看成山峰和山谷的關(guān)系,高亮度(灰度值高)的地方是山峰,低亮度(灰度值低)的地方是山谷。接著給每個(gè)孤立的山谷(局部最小值)不同顏色的水(Label),當(dāng)水漲起來,根據(jù)周圍的山峰(梯度),不同的山谷也就是不同顏色的像素點(diǎn)開始合并,為了避免這個(gè)現(xiàn)象,可以在水要合并的地方建立障礙,直到所有山峰都被淹沒。所創(chuàng)建的障礙就是分割結(jié)果,這個(gè)就是分水嶺的原理[3]。
分水嶺算法的計(jì)算過程是一個(gè)迭代標(biāo)注過程,主要包括排序和淹沒兩個(gè)步驟。由于圖像會存在噪聲或缺失等問題,該方法會造成分割過度。OpenCV提供了watershed()函數(shù)實(shí)現(xiàn)圖像分水嶺算法,并且能夠指定需要合并的點(diǎn),其函數(shù)原型如下所示:
markers = watershed(image, markers)
– image表示輸入圖像,需為8位三通道的彩色的圖像
– markers表示用于存儲函數(shù)調(diào)用之后的運(yùn)算結(jié)果,輸入/輸出32位單通道圖像的標(biāo)記結(jié)構(gòu),輸出結(jié)果需和輸入圖像的尺寸和類型一致。
下面是分水嶺算法實(shí)現(xiàn)圖像分割的過程。假設(shè)存在一幅彩色硬幣圖像,如圖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()
輸出結(jié)果如圖5所示。
第二步,通過形態(tài)學(xué)開運(yùn)算過濾掉小的白色噪聲。同時(shí),由于圖像中的硬幣是緊挨著的,所以不能采用圖像腐蝕去掉邊緣的像素,而是選擇距離轉(zhuǎn)換,配合一個(gè)適當(dāng)?shù)拈撝颠M(jìn)行物體提取。這里引入一個(gè)圖像膨脹操作,將目標(biāo)邊緣擴(kuò)展到背景,以確定結(jié)果的背景區(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) #圖像開運(yùn)算消除噪聲 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) #距離運(yùn)算確定前景區(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) #用來正常顯示中文標(biāo)簽 plt.rcParams['font.sans-serif']=['SimHei'] #顯示圖像 titles = ['原始圖像', '閾值化', '開運(yùn)算', '背景區(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()
輸出結(jié)果如圖6所示,包括原始圖像、閾值化處理、開運(yùn)算、背景區(qū)域、前景區(qū)域、未知區(qū)域等。由圖可知,在使用閾值過濾的圖像里,確認(rèn)了圖像的硬幣區(qū)域,而在有些情況,可能對前景分割更感興趣,而不關(guān)心目標(biāo)是否需要分開或挨著,那時(shí)可以采用腐蝕操作來求解前景區(qū)域。
第三步,當(dāng)前處理結(jié)果中,已經(jīng)能夠區(qū)分出前景硬幣區(qū)域和背景區(qū)域。接著我們創(chuàng)建標(biāo)記變量,在該變量中標(biāo)記區(qū)域,已確認(rèn)的區(qū)域(前景或背景)用不同的正整數(shù)標(biāo)記出來,不確認(rèn)的區(qū)域保持0,使用cv2.connectedComponents()函數(shù)來將圖像背景標(biāo)記成0,其他目標(biāo)用從1開始的整數(shù)標(biāo)記。注意,如果背景被標(biāo)記成0,分水嶺算法會認(rèn)為它是未知區(qū)域,所以要用不同的整數(shù)來標(biāo)記。
最后,調(diào)用watershed()函數(shù)實(shí)現(xiàn)分水嶺圖像分割,標(biāo)記圖像會被修改,邊界區(qū)域會被標(biāo)記成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) #圖像開運(yùn)算消除噪聲 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) #距離運(yùn)算確定前景區(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) #標(biāo)記變量 ret, markers = cv2.connectedComponents(sure_fg) #所有標(biāo)簽加一,以確保背景不是0而是1 markers = markers+1 #用0標(biāo)記未知區(qū)域 markers[unknown==255]=0 #分水嶺算法實(shí)現(xiàn)圖像分割 markers = cv2.watershed(img, markers) img[markers == -1] = [255,0,0] #用來正常顯示中文標(biāo)簽 plt.rcParams['font.sans-serif']=['SimHei'] #顯示圖像 titles = ['標(biāo)記區(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中心輪廓的效果圖。
分水嶺算法對微弱邊緣具有良好的響應(yīng),圖像中的噪聲、物體表面細(xì)微的灰度變化,都會產(chǎn)生過度分割的現(xiàn)象。但同時(shí)應(yīng)當(dāng)看出,分水嶺算法對微弱邊緣具有良好的響應(yīng),是得到封閉連續(xù)邊緣的保證。另外,分水嶺算法所得到的封閉的集水盆,為分析圖像的區(qū)域特征提供了可能。
三.總結(jié)
本文主要講解了圖像分割方法,包括基于均值漂移算法的圖像分割方法、基于分水嶺算法的圖像分割方法,通過這些處理能有效分割圖像的背景和前景,識別某些圖像的區(qū)域。
以上就是Python基于均值漂移算法和分水嶺算法實(shí)現(xiàn)圖像分割的詳細(xì)內(nèi)容,更多關(guān)于Python圖像分割的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
正確理解python中的關(guān)鍵字“with”與上下文管理器
這篇文章主要介紹了關(guān)于python中關(guān)鍵字"with"和上下文管理器的相關(guān)資料,文中介紹的非常詳細(xì),相信對大家學(xué)習(xí)或者使用python具有一定的參考價(jià)值,需要的朋友們下面來一起看看吧。2017-04-04python代碼 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-09python數(shù)據(jù)分析必會的Pandas技巧匯總
用Python做數(shù)據(jù)分析光是掌握numpy和matplotlib可不夠,numpy雖然能夠幫我們處理處理數(shù)值型數(shù)據(jù),但很多時(shí)候,還有字符串,還有時(shí)間序列等,比如:我們通過爬蟲獲取到了存儲在數(shù)據(jù)庫中的數(shù)據(jù),一些Pandas必會的用法,讓你的數(shù)據(jù)分析水平更上一層樓2021-08-08關(guān)于python基礎(chǔ)數(shù)據(jù)類型bytes進(jìn)制轉(zhuǎn)換
Python 3.x之后,Python自帶字符默認(rèn)使用utf-8格式編碼和顯示,bytes數(shù)據(jù)類型是utf-8格式的二進(jìn)制形式的不可變序列,需要的朋友可以參考下2023-05-05Python簡單格式化時(shí)間的方法【strftime函數(shù)】
這篇文章主要介紹了Python簡單格式化時(shí)間的方法,結(jié)合實(shí)例形式分析了Python使用strftime函數(shù)進(jìn)行時(shí)間格式化的操作技巧,需要的朋友可以參考下2016-09-09pytorch中關(guān)于distributedsampler函數(shù)的使用
這篇文章主要介紹了pytorch中關(guān)于distributedsampler函數(shù)的使用,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02