Python基于均值漂移算法和分水嶺算法實(shí)現(xiàn)圖像分割
一.基于均值漂移算法的圖像分割
均值漂移(Mean Shfit)算法是一種通用的聚類(lèi)算法,最早是1975年Fukunaga等人在一篇關(guān)于概率密度梯度函數(shù)的估計(jì)論文中提出[1]。它是一種無(wú)參估計(jì)算法,沿著概率梯度的上升方向?qū)ふ曳植嫉姆逯怠ean Shift算法先算出當(dāng)前點(diǎn)的偏移均值,移動(dòng)該點(diǎn)到其偏移均值,然后以此為新的起始點(diǎn),繼續(xù)移動(dòng),直到滿(mǎn)足一定的條件結(jié)束。
圖像分割中可以利用均值漂移算法的特性,實(shí)現(xiàn)彩色的圖像分割。在OpenCV中提供的函數(shù)為pyrMeanShiftFiltering(),該函數(shù)嚴(yán)格來(lái)說(shuō)并不是圖像分割,而是圖像在色彩層面的平滑濾波,它可以中和色彩分布相近的顏色,平滑色彩細(xì)節(jié),侵蝕掉面積較小的顏色區(qū)域,所以在OpenCV中它的后綴是濾波“Filter”,而不是分割“segment”。該函數(shù)原型如下所示:
dst = pyrMeanShiftFiltering(src, sp, sr[, dst[, maxLevel[, termcrit]]])
– src表示輸入圖像,8位三通道的彩色的圖像
– dst表示輸出圖像,需同輸入圖像具有相同的大小和類(lèi)型
– sp表示定義漂移物理空間半徑的大小
– sr表示定義漂移色彩空間半徑的大小
– maxLevel表示定義金字塔的最大層數(shù)
– termcrit表示定義的漂移迭代終止條件,可以設(shè)置為迭代次數(shù)滿(mǎn)足終止,迭代目標(biāo)與中心點(diǎn)偏差滿(mǎn)足終止,或者兩者的結(jié)合
均值漂移pyrMeanShiftFiltering()函數(shù)的執(zhí)行過(guò)程是如下:
- 構(gòu)建迭代空間。以輸入圖像上任一點(diǎn)P0為圓心,建立以sp為物理空間半徑,sr為色彩空間半徑的球形空間,物理空間上坐標(biāo)為x和y,色彩空間上坐標(biāo)為RGB或HSV,構(gòu)成一個(gè)空間球體。其中x和y表示圖像的長(zhǎng)和寬,色彩空間R、G、B在0至255之間。
- 求迭代空間的向量并移動(dòng)迭代空間球體重新計(jì)算向量,直至收斂。在上一步構(gòu)建的球形空間中,求出所有點(diǎn)相對(duì)于中心點(diǎn)的色彩向量之和,移動(dòng)迭代空間的中心點(diǎn)到該向量的終點(diǎn),并再次計(jì)算該球形空間中所有點(diǎn)的向量之和,如此迭代,直到在最后一個(gè)空間球體中所求得向量和的終點(diǎn)就是該空間球體的中心點(diǎn)Pn,迭代結(jié)束。
- 更新輸出圖像dst上對(duì)應(yīng)的初始原點(diǎn)P0的色彩值為本輪迭代的終點(diǎn)Pn的色彩值,完成一個(gè)點(diǎn)的色彩均值漂移。
- 對(duì)輸入圖像src上其他點(diǎn),依次執(zhí)行上述三個(gè)步驟,直至遍歷完所有點(diǎn)后,整個(gè)均值偏移色彩濾波完成。
下面的代碼是圖像均值漂移的實(shí)現(xiàn)過(guò)程:
# -*- 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所示。對(duì)比可以發(fā)現(xiàn),半徑為20時(shí),圖像色彩細(xì)節(jié)大部分存在,半徑為50時(shí),森林和水面的色彩細(xì)節(jié)基本都已經(jīng)丟失。

寫(xiě)到這里,均值偏移算法對(duì)彩色的圖像的分割平滑操作就完成了,為了達(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)水漲起來(lái),根據(jù)周?chē)纳椒澹ㄌ荻龋?,不同的山谷也就是不同顏色的像素點(diǎn)開(kāi)始合并,為了避免這個(gè)現(xiàn)象,可以在水要合并的地方建立障礙,直到所有山峰都被淹沒(méi)。所創(chuàng)建的障礙就是分割結(jié)果,這個(gè)就是分水嶺的原理[3]。
分水嶺算法的計(jì)算過(guò)程是一個(gè)迭代標(biāo)注過(guò)程,主要包括排序和淹沒(méi)兩個(gè)步驟。由于圖像會(huì)存在噪聲或缺失等問(wèn)題,該方法會(huì)造成分割過(guò)度。OpenCV提供了watershed()函數(shù)實(shí)現(xiàn)圖像分水嶺算法,并且能夠指定需要合并的點(diǎn),其函數(shù)原型如下所示:
markers = watershed(image, markers)
– image表示輸入圖像,需為8位三通道的彩色的圖像
– markers表示用于存儲(chǔ)函數(shù)調(diào)用之后的運(yùn)算結(jié)果,輸入/輸出32位單通道圖像的標(biāo)記結(jié)構(gòu),輸出結(jié)果需和輸入圖像的尺寸和類(lèi)型一致。
下面是分水嶺算法實(shí)現(xiàn)圖像分割的過(guò)程。假設(shè)存在一幅彩色硬幣圖像,如圖4所示,硬幣相互之間挨著。

第一步,通過(guò)圖像灰度化和閾值化處理提取圖像灰度輪廓,采用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所示。

第二步,通過(guò)形態(tài)學(xué)開(kāi)運(yùn)算過(guò)濾掉小的白色噪聲。同時(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)
#圖像開(kāi)運(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)
#用來(lái)正常顯示中文標(biāo)簽
plt.rcParams['font.sans-serif']=['SimHei']
#顯示圖像
titles = ['原始圖像', '閾值化', '開(kāi)運(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所示,包括原始圖像、閾值化處理、開(kāi)運(yùn)算、背景區(qū)域、前景區(qū)域、未知區(qū)域等。由圖可知,在使用閾值過(guò)濾的圖像里,確認(rèn)了圖像的硬幣區(qū)域,而在有些情況,可能對(duì)前景分割更感興趣,而不關(guān)心目標(biāo)是否需要分開(kāi)或挨著,那時(shí)可以采用腐蝕操作來(lái)求解前景區(qū)域。

第三步,當(dāng)前處理結(jié)果中,已經(jīng)能夠區(qū)分出前景硬幣區(qū)域和背景區(qū)域。接著我們創(chuàng)建標(biāo)記變量,在該變量中標(biāo)記區(qū)域,已確認(rèn)的區(qū)域(前景或背景)用不同的正整數(shù)標(biāo)記出來(lái),不確認(rèn)的區(qū)域保持0,使用cv2.connectedComponents()函數(shù)來(lái)將圖像背景標(biāo)記成0,其他目標(biāo)用從1開(kāi)始的整數(shù)標(biāo)記。注意,如果背景被標(biāo)記成0,分水嶺算法會(huì)認(rèn)為它是未知區(qū)域,所以要用不同的整數(shù)來(lái)標(biāo)記。
最后,調(diào)用watershed()函數(shù)實(shí)現(xiàn)分水嶺圖像分割,標(biāo)記圖像會(huì)被修改,邊界區(qū)域會(huì)被標(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)
#圖像開(kāi)運(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]
#用來(lái)正常顯示中文標(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中心輪廓的效果圖。

分水嶺算法對(duì)微弱邊緣具有良好的響應(yīng),圖像中的噪聲、物體表面細(xì)微的灰度變化,都會(huì)產(chǎn)生過(guò)度分割的現(xiàn)象。但同時(shí)應(yīng)當(dāng)看出,分水嶺算法對(duì)微弱邊緣具有良好的響應(yīng),是得到封閉連續(xù)邊緣的保證。另外,分水嶺算法所得到的封閉的集水盆,為分析圖像的區(qū)域特征提供了可能。
三.總結(jié)
本文主要講解了圖像分割方法,包括基于均值漂移算法的圖像分割方法、基于分水嶺算法的圖像分割方法,通過(guò)這些處理能有效分割圖像的背景和前景,識(shí)別某些圖像的區(qū)域。
以上就是Python基于均值漂移算法和分水嶺算法實(shí)現(xiàn)圖像分割的詳細(xì)內(nèi)容,更多關(guān)于Python圖像分割的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Python 如何實(shí)現(xiàn)文件自動(dòng)去重
這篇文章主要介紹了Python 實(shí)現(xiàn)文件自動(dòng)去重操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06
python讀寫(xiě)Excel表格的實(shí)例代碼(簡(jiǎn)單實(shí)用)
這篇文章主要介紹了python讀寫(xiě)Excel表格的方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-12-12
python基于OpenCV模塊實(shí)現(xiàn)視頻流數(shù)據(jù)切割為圖像幀數(shù)據(jù)(流程分析)
這篇文章主要介紹了python基于OpenCV模塊實(shí)現(xiàn)視頻流數(shù)據(jù)切割為圖像幀數(shù)據(jù),這里今天主要是實(shí)踐一下視頻流數(shù)據(jù)的預(yù)處理工作,需要的朋友可以參考下2022-05-05
linux環(huán)境下安裝python虛擬環(huán)境及注意事項(xiàng)
這篇文章主要介紹了linux環(huán)境下安裝python虛擬環(huán)境,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-01-01
python將一個(gè)英文語(yǔ)句以單詞為單位逆序排放的方法
今天小編就為大家分享一篇python將一個(gè)英文語(yǔ)句以單詞為單位逆序排放的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-12-12
python實(shí)現(xiàn)linux下使用xcopy的方法
這篇文章主要介紹了python實(shí)現(xiàn)linux下使用xcopy的方法,可實(shí)現(xiàn)模仿windows下的xcopy命令功能,需要的朋友可以參考下2015-06-06
簡(jiǎn)單分析python的類(lèi)變量、實(shí)例變量
在本篇文章中小編給大家整理的是關(guān)于python類(lèi)變量、實(shí)例變量的知識(shí)點(diǎn)內(nèi)容,有需要的朋友們可以學(xué)習(xí)下。2019-08-08

