计算机视觉编程——图像分割
文章目錄
- 圖像分割
- 1 圖割
- 1.1 從圖像創建圖
- 1.2 用戶交互式分割
- 2 利用聚類進行分類
- 3 變分法
圖像分割
圖像分割是將一幅圖像分割成有意義區域的過程。區域可以是圖像的前景與背景或圖像中一些單獨的對象。這些區域可以利用一些諸如顏色、邊界或近鄰相似性等特征進行構建。
1 圖割
在圖論中,圖是由若干個節點和連接節點的邊構成的集合。
圖割是將一個有向圖分割成兩個互不相交的集合,可以用來解決諸如立體深度重建、圖像拼接和圖像分割等計算機視覺方面的不同問題。
圖割的基本思想是:相似且彼此相近的像素應該劃分到同一個區域。
圖割C(C是圖中所有邊的集合)的“代價”函數定義為所有割的邊的權重求和相加,即:
圖割圖像分割的思想是用圖來表示圖像,并對圖進行劃分以使割代價最小。在用圖表示圖像時,增加兩個額外節點,即源點和匯點,并僅考慮那些將源點和匯點分開的割。
在Python計算機視覺編程中,圖割使用python-graph工具包,該工具包包含許多非常有用的圖算法。在cmd中鍵入命令安裝:
pip install python-graph-core下面這個例子就是利用python-graph工具包創建的一個簡單有向圖并計算圖的最大流/最小割:
from pygraph.classes.digraph import digraph from pygraph.algorithms.minmax import maximum_flowgr = digraph() gr.add_nodes([0,1,2,3]) gr.add_edge((0,1), wt=4) gr.add_edge((1,2), wt=3) gr.add_edge((2,3), wt=5) gr.add_edge((0,2), wt=3) gr.add_edge((1,3), wt=4)flows,cuts = maximum_flow(gr, 0, 3) print ('flow is:' , flows) print ('cut is:' , cuts)得到的結果如下圖所示:
代碼首先創建有4個節點的有向圖,4個節點的索引為0,1,2,3,然后用add_edge()增添邊并為每條邊指定特定的權重。邊的權重用來衡量邊的最大流容量。以節點0位源點,3位匯點,計算最大流。其中python字典包含了流穿過每條邊和每個節點的標記,0是包含圖源點的部分,1是與匯點相連的節點。
1.1 從圖像創建圖
給定一個鄰域結構,可以利用圖像像素作為節點定義一個圖。下面給出創建圖的步驟:
- 每個像素節點都有一個從源點的傳入邊
- 每個像素節點都有一個到匯點的傳出邊
- 每個像素節點都有一條傳入邊和傳出邊連接到它的近鄰
為確定邊的權重,需要一個能夠確定這些像素點之間,像素點和源點、匯點之間邊的權重的分割模型,建立模型如下:
利用這一模型,可以將每個像素和前景背景連接起來,權重等于上面歸一化后的概率。
要實現這一模型,需要用到下面這個函數完成從一幅圖像創建圖:
def build_bayes_graph(im,labels,sigma=1e2,kappa=1):m,n = im.shape[:2]vim = im.reshape((-1,3))foreground = im[labels==1].reshape((-1,3))background = im[labels==-1].reshape((-1,3)) train_data = [foreground,background]bc = bayes.BayesClassifier()bc.train(train_data)bc_lables,prob = bc.classify(vim)prob_fg = prob[0]prob_bg = prob[1]gr = digraph()gr.add_nodes(range(m*n+2))source = m*n sink = m*n+1 for i in range(vim.shape[0]):vim[i] = vim[i] / (linalg.norm(vim[i]) + 1e-9)for i in range(m*n):gr.add_edge((source,i), wt=(prob_fg[i]/(prob_fg[i]+prob_bg[i])))gr.add_edge((i,sink), wt=(prob_bg[i]/(prob_fg[i]+prob_bg[i])))if i%n != 0: # left existsedge_wt = kappa*exp(-1.0*sum((vim[i]-vim[i-1])**2)/sigma)gr.add_edge((i,i-1), wt=edge_wt)if (i+1)%n != 0: # right existsedge_wt = kappa*exp(-1.0*sum((vim[i]-vim[i+1])**2)/sigma)gr.add_edge((i,i+1), wt=edge_wt)if i//n != 0: # up existsedge_wt = kappa*exp(-1.0*sum((vim[i]-vim[i-n])**2)/sigma)gr.add_edge((i,i-n), wt=edge_wt)if i//n != m-1: # down existsedge_wt = kappa*exp(-1.0*sum((vim[i]-vim[i+n])**2)/sigma)gr.add_edge((i,i+n), wt=edge_wt)return gr這里用到了用1標記前景訓練數據,用-1標記背景訓練數據的一幅標記圖像。基于這種標記,在RGB值上可以訓練出一個樸素貝葉斯分類器,然后計算每一像素的分類概率,這些計算出的分類概率便是從源點出來和到匯點去的邊的權重。
為了在圖像可視化覆蓋的標記區域,利用contourf()函數填充圖像等高線間的區域,參數alpha用于設置透明度。
def show_labeling(im,labels): imshow(im)contour(labels,[-0.5,0.5])contourf(labels,[-1,-0.5],colors='b',alpha=0.25)contourf(labels,[0.5,1],colors='r',alpha=0.25)#axis('off')xticks([])yticks([])圖建立起來后便需要在最優位置對圖進行分割,下面這個函數可以計算最小割并將輸出結果重新格式化為一個帶像素標記的二值圖像:
def cut_graph(gr,imsize):m,n = imsizesource = m*n sink = m*n+1 flows,cuts = maximum_flow(gr,source,sink)res = zeros(m*n)for pos,label in cuts.items()[:-2]: res[pos] = labelreturn res.reshape((m,n))下面的例子會讀取一幅圖像,從圖像的兩個矩形區域估算出類概率,然后創建一個圖:
"""from scipy.misc import imresize""" import graphcut from PIL import Image from numpy import * from pylab import * from skimage.transform import resize import numpy as npim = array(Image.open("empire.jpg")) """im = imresize(im, 0.07, interp = 'bilinear')""" im = resize(im, output_shape=(40, 56)) size = im.shape[:2] print ("OK!!")labels = zeros(size) labels[3:18, 3:18] = -1 labels[-18:-3, -18:-3] = 1 print ("OK!!")g = graphcut.build_bayes_graph(im, labels, kappa=1) print ("OK!!")res = graphcut.cut_graph(g, size) print ("OK!!")figure() graphcut.show_labeling(im, labels)figure() imshow(res) gray() axis('off')show()利用貝葉斯概率模型進行圖像分割,最終得到的采樣圖像結果如下圖所示:
左圖為用于模型訓練的標記圖像,右圖為分割的結果。變量kappa決定了近鄰像素間邊的相對權重,改變K值可以改變分割的效果。如下圖所示分別為k=1,k=5的兩種取值,可以看出隨著K值的增大,分割邊界將變得更平滑,并且細節部分也逐漸丟失。
1.2 用戶交互式分割
利用一些方法可以將圖像分割與用戶交互結合起來。例如用戶可以在一幅圖像上為前景和背景提供一些標記。
這里給出一個完整的代碼示例,它會載入一幅圖像及對應的標注信息,然后將其傳遞到我們的圖像分割路徑中:
from skimage.transform import resize import graphcut from PIL import Image from numpy import * from pylab import *def create_msr_labels(m,lasso=False):labels = zeros(im.shape[:2])labels[m==0] = -1labels[m==64] = -1if lasso:labels[m == 255] = 1else:labels[m == 128] = 1return labelsim = array(Image.open('fisherman.jpg')) m = array(Image.open('fisherman.jpg'))im = resize(im, output_shape=(54, 38)) m = resize(m, output_shape=(54, 38))labels = create_msr_labels(m,False)g = graphcut.build_bayes_graph(im, labels, kappa=1)res = graphcut.cut_graph(g, im.shape[:2])res[m==0] = 1 res[m==64] = 1figure() imshow(res) gray() xticks([]) yticks([]) savefig('1.pdf')在這段代碼中,首先定義一個輔助函數用以讀取這些標注圖像,格式化這些標注圖像便于將其傳遞給背景和前景訓練模型,矩形框中只包含背景標記。下一步創建圖并分割。由于需要有用戶輸入,所以要移除那些在標記背景區域里的任何前景的結果。最后繪制出分割結果。
2 利用聚類進行分類
這一部分是基于譜圖理論的歸一化分割算法,它將像素相似和空間近似結合起來對圖像進行分割。
這一方法來自定義一個分割損失函數,該損失函數不僅需要考慮組的大小而且還用劃分的大小對該損失函數進行“歸一化”。A和B表示割集,并在圖中分別對A和B中所有的其他節點的權重求和相加,相加求和稱為association。對于那些像素與其他像素具有相同連接數的圖像,它是對劃分大小的一種粗糙度量方式。
定義W為邊的權重矩陣,矩陣中的元素wij為連接像素i和j邊的權重。D為對W每行元素求和后構成的對角矩陣,即D=diag(di)。這樣,歸一化分割就可以通過最小化下面的優化問題而求得:
向量y包含的是離散標記,這些離散標記滿足對于b為常數yi的約束,yTD求和為0。但是由于這些約束條件,這個問題并不容易求解,需要通過松弛約束條件并讓y取任意實數,則該問題變成了求解拉普拉斯矩陣特征向量問題:
將其體現在代碼中:
該函數獲取圖像數組,并利用輸入的彩色圖像RGB值或灰度圖像的灰度值創建一個特征向量。由于邊的權重包含了距離部件,對于每個像素的特征向量,利用meshgrid()獲取x和y值,然后該函數會在N個像素上循環,并在N*N歸一化矩陣W中填充值。
在這里可以順序分割每個特征向量或獲取一些特征向量對他們進行聚類來計算分割結果。將拉普拉斯矩陣進行特征分解后的前ndim個特征向量合并在一起構成矩陣W,并對這些像素進行聚類。聚類過程如下:
def cluster(S,k,ndim):if sum(abs(S-S.T)) > 1e-10:print ('not symmetric')rowsum = sum(abs(S),axis=0)D = diag(1 / sqrt(rowsum + 1e-6))L = dot(D,dot(S,D))U,sigma,V = linalg.svd(L,full_matrices=False)features = array(V[:ndim]).Tfeatures = whiten(features)centroids,distortion = kmeans(features,k)code,distance = vq(features,centroids)return code,V接下來對這組實驗進行測試:
import ncut from skimage.transform import resize from PIL import Image from numpy import * from pylab import *im = array(Image.open('C-uniform01.ppm')) m,n = im.shape[:2]wid = 50rim = resize(im, (wid, wid)) rim = array(rim, 'f')A = ncut.ncut_graph_matrix(rim, sigma_d = 1, sigma_g = 1e-2)code, V = ncut.cluster(A, k =10, ndim = 3)codeim= resize(code.reshape(wid, wid), (m, n))figure() imshow(codeim) gray() show()這里用到靜態手勢庫中的一種手勢圖像,并且設置聚類數k=3。得到的結果如下圖所示:
當改變不同的聚類數,得到的結果也有所不同:
相比較而言,聚類分割效果比用戶交互式分割效果更差,細節上會有鋸齒狀效果,考慮到采用基于特征向量圖像值的 K-means 聚類算法對像素進行分組,所以調整過多次k值,依舊不理想,說明選一個合適的k值難度也比較大,運行時間比用戶交互式分割方法快一些。
上述例子中的特征向量以數組V返回,可以通過下面的代碼可視化為圖像:
imshow(resize(V[i].reshape(wid, wid), (m,n)))它以原圖像尺寸將特征向量i顯示為一幅圖像。值得注意的是,對圖像進行閾值處理不會給出相同的結果,對RGB值或者灰度值進行聚類也一樣,原因是它們都沒有考慮像素的近鄰。
3 變分法
在優化過程中,當優化對象是函數時,該問題稱為變分問題,解決這類問題的方法是變分法。
Chan-Vese分割模型對于待分割區域假定一個分片常數圖像模型,集中關注前景和背景。若用一組曲線τ將圖像分離成兩個區域Ω1和Ω2,分割是通過最小化Chan-Vese模型能量函數給出的:
該能量函數用來度量與內部平均灰度常數c1和平均外部灰度常數c2的偏差。這里這兩個積分是對各自區域的積分,分離曲線τ的長度用以選擇更加平滑的方案。
由分片常數圖像U=x1c1+x2c2,可以將上式重寫為(x1和x2是兩區域Ω1和Ω2的特征函數):
最小化Chan-Vese模型現在轉變為設定閾值的ROF降噪問題:
在該實例中調低ROF迭代終止時的容忍閾值以確保得到足夠的迭代次數。
得到的結果如下圖所示:
經過對比發現,該算法應該是三個算法中最優秀的,在該算法中,可見灰度值越復雜,迭代時間就越長。同其他算法相比,用戶交互式分割算法不能處理好復雜背景圖像的分割,而該算法的分割效果則更好。對于運行時間,也是三個算法中用時最短,運行最快的。
總結
以上是生活随笔為你收集整理的计算机视觉编程——图像分割的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算机视觉编程——图像内容分类
- 下一篇: 计算机视觉编程——OpenCV