參考博客:
https://www.mathworks.com/matlabcentral/fileexchange/19567-active-contour-segmentation
數字圖像處理-圖像分割:Snake主動輪廓模型 Matlab代碼及運行結果
簡介
在“圖像分割之(一)概述”中咱們簡單瞭解了目前主流的圖像分割方法。下面咱們主要學習下基於能量泛函的分割方法。這裏學習下Snake模型簡單的知識,Level Set(水平集)模型會在後面的博文中說到。
基於能量泛函的分割方法:
該類方法主要指的是活動輪廓模型(active contour model)以及在其基礎上發展出來的算法,其基本思想是使用連續曲線來表達目標邊緣,並定義一個能量泛函使得其自變量包括邊緣曲線,因此分割過程就轉變爲求解能量泛函的最小值的過程,一般可通過求解函數對應的歐拉(Euler.Lagrange)方程來實現,能量達到最小時的曲線位置就是目標的輪廓所在。
主動輪廓線模型是一個自頂向下定位圖像特徵的機制,用戶或其他自動處理過程通過事先在感興趣目標附近放置一個初始輪廓線,在內部能量(內力)和外部能量(外力)的作用下變形外部能量吸引活動輪廓朝物體邊緣運動,而內部能量保持活動輪廓的光滑性和拓撲性,當能量達到最小時,活動輪廓收斂到所要檢測的物體邊緣。
一、曲線演化理論
曲線演化理論在水平集中運用到,但我感覺在主動輪廓線模型的分割方法中,這個知識是公用的,所以這裏我們簡單瞭解下。
曲線可以簡單的分爲幾種:
曲線存在曲率,曲率有正有負,於是在法向曲率力的推動下,曲線的運動方向之間有所不同:有些部分朝外擴展,而有些部分則朝內運動。這種情形如下圖所示。圖中藍色箭頭處的曲率爲負,而綠色箭頭處的曲率爲正。
簡單曲線在曲率力(也就是曲線的二次導數)的驅動下演化所具有的一種非常特殊的數學性質是:一切簡單曲線,無論被扭曲得多麼嚴重,只要還是一種簡單曲線,那麼在曲率力的推動下最終將退化成一個圓,然後消逝(可以想象下,圓的所有點的曲率力都向着圓心,所以它將慢慢縮小,以致最後消逝)。
所以曲線的演變過程,就是不同力在曲線上的作用過程,力也可以表達爲能量。世界萬物都趨向於能量最小而存在。因爲此時它是最平衡的,消耗最小的(不知理解對不?)。那麼在圖像分割裏面,我們目標是把目標的輪廓找到,那麼在目標的輪廓這個地方,整個輪廓的能量是最小的,那麼曲線在圖像任何一個地方,都可以因爲力朝着這個能量最小的輪廓演變,當演變到目標的輪廓的時候,因爲能量最小,力平衡了,速度爲0了,也就不動了,這時候目標就被我們分割出來了。
那現在關鍵就在於:1)這個輪廓我們怎麼表示;2)這些力怎麼構造,構造哪些力纔可以讓目標輪廓這個地方的能量最小?
這兩個問題的描述和解決就衍生出了很多的基於主動輪廓線模型的分割方法。第一個問題的回答,就形成了兩大流派:如果這個輪廓是參數表示的,那麼就是參數活動輪廓模型(parametric active contour model),典型爲snake模型,如果這個輪廓是幾何表示的,那麼就是幾何活動輪廓模型(geometric active contour model),即水平集方法(Level Set),它是把二維的輪廓嵌入到三維的曲面的零水平面來表達的(可以理解爲一座山峯的等高線,某個等高線把山峯切了,這個高度山峯的水平形狀就出來了,也就是輪廓了),所以低維的演化曲線或曲面,表達爲高維函數曲面的零水平集的間接表達形式(這個輪廓的變化,直觀上我們就可以調整山峯的形狀或者調整登高線的高度來得到)。
那對於第二個問題,是兩大流派都遇到的問題,是他們都需要解決的最關鍵的問題。哪些力纔可以達到分割的目標呢?這將在後面聊到。
二、Snakes模型
自1987年Kass提出Snakes模型以來,各種基於主動輪廓線的圖像分割理解和識別方法如雨後春筍般蓬勃發展起來。Snakes模型的基本思想很簡單,它以構成一定形狀的一些控制點爲模板(輪廓線),通過模板自身的彈性形變,與圖像局部特徵相匹配達到調和,即某種能量函數極小化,完成對圖像的分割。再通過對模板的進一步分析而實現圖像的理解和識別。
簡單的來講,SNAKE模型就是一條可變形的參數曲線及相應的能量函數,以最小化能量目標函數爲目標,控制參數曲線變形,具有最小能量的閉合曲線就是目標輪廓。
構造Snakes模型的目的是爲了調和上層知識和底層圖像特徵這一對矛盾。無論是亮度、梯度、角點、紋理還是光流,所有的圖像特徵都是局部的。所謂局部性就是指圖像上某一點的特徵只取決於這一點所在的鄰域,而與物體的形狀無關。但是人們對物體的認識主要是來自於其外形輪廓。如何將兩者有效地融合在一起正是Snakes模型的長處。Snakes模型的輪廓線承載了上層知識,而輪廓線與圖像的匹配又融合了底層特徵。這兩項分別表示爲Snakes模型中能量函數的內部力和圖像力。
模型的形變受到同時作用在模型上的許多不同的力所控制,每一種力所產生一部分能量,這部分能量表示爲活動輪廓模型的能量函數的一個獨立的能量項。
Snake模型首先需要在感興趣區域的附近給出一條初始曲線,接下來最小化能量泛函,讓曲線在圖像中發生變形並不斷逼近目標輪廓。
Kass等提出的原始Snakes模型由一組控制點:v(s)=[x(s), y(s)] s∈[0, 1] 組成,這些點首尾以直線相連構成輪廓線。其中x(s)和y(s)分別表示每個控制點在圖像中的座標位置。 s 是以傅立葉變換形式描述邊界的自變量。在Snakes的控制點上定義能量函數(反映能量與輪廓之間的關係):
彈性能量和彎曲能量合稱內部能量(內部力),用於控制輪廓線的彈性形變,起到保持輪廓連續性和平滑性的作用。而第三項代表外部能量,也被稱爲圖像能量,表示變形曲線與圖像局部特徵吻合的情況。內部能量僅僅跟snake的形狀有關,而跟圖像數據無關。而外部能量僅僅跟圖像數據有關。在某一點的α和β的值決定曲線可以在這一點伸展和彎曲的程度。
最終對圖像的分割轉化爲求解能量函數Etotal(v)極小化(最小化輪廓的能量)。在能量函數極小化過程中,彈性能量迅速把輪廓線壓縮成一個光滑的圓,彎曲能量驅使輪廓線成爲光滑曲線或直線,而圖像力則使輪廓線向圖像的高梯度位置靠攏。基本Snakes模型就是在這3個力的聯合作用下工作的。
因爲圖像上的點都是離散的,所以我們用來優化能量函數的算法都必須在離散域裏定義。所以求解能量函數Etotal(v)極小化是一個典型的變分問題(微分運算中,自變量一般是座標等變量,因變量是函數;變分運算中,自變量是函數,因變量是函數的函數,即數學上所謂的泛函。對泛函求極值的問題,數學上稱之爲變分法)。
在離散化條件(數字圖像)下,由歐拉方程可知最終問題的答案等價於求解一組差分方程:(歐拉方程是泛函極值條件的微分表達式,求解泛函的歐拉方程,即可得到使泛函取極值的駐函數,將變分問題轉化爲微分問題。)
記外部力 F = −∇ P, Kass等將上式離散化後,對x(s)和y(s)分別構造兩個五對角陣的線性方程組,通過迭代計算進行求解。在實際應用中一般先在物體周圍手動點出控制點作爲Snakes模型的起始位置,然後對能量函數迭代求解。
Reference:
李天慶等,Snake模型綜述,計算機工程,2005,第31卷 第9期
代碼實現
% =========================================================================
% Snakes:Active Contour Models
% =========================================================================
% By gujinjin 2012/12/10-12 Sunny
% 基於KASS等的論文思想
% 參考文獻:
% [1] KASS etc.Snakes:Active Contour Models
% [2] CSDN 博客 - Author:樂不思蜀Tone
% [3] Ritwik Kumar(Harvard University),D.Kroon(Twente University)的程序
% [4] 《數學建模與數學實驗》
% ------
clc;clf;clear all;
% =========================================================================
% 獲取手動取點座標
% =========================================================================
% 讀取顯示圖像
%I = imread('Coronary_MPR.jpg');
I = imread('test3.png');
% 轉化爲雙精度型
%I = im2double(I);
% 若爲彩色,轉化爲灰度
if(size(I,3)==3), I=rgb2gray(I); end
figure(1),imshow(I);
%---------------------------
% 高斯濾波
%---------------------------
sigma=1.0;
% 創建特定形式的二維高斯濾波器H
H = fspecial('gaussian',ceil(3*sigma), sigma);
% 對圖像進行高斯濾波,返回和I等大小矩陣
Igs = filter2(H,I,'same');
%---------------------------
% 獲取Snake的點座標
%---------------------------
figure(2),imshow(Igs);
x=[];y=[];c=1;N=100; %定義取點個數c,上限N
% 獲取User手動取點的座標
% [x,y]=getpts
while c<N
[xi,yi,button]=ginput(1);
% 獲取座標向量
x=[x xi];
y=[y yi];
hold on
% text(xi,yi,'o','FontSize',10,'Color','red');
plot(xi,yi,'ro');
% 若爲右擊,則停止循環
if(button==3), break; end
c=c+1;
end
% 將第一個點複製到矩陣最後,構成Snake環
xy = [x;y];
c=c+1;
xy(:,c)=xy(:,1);
% 樣條曲線差值
t=1:c;
ts = 1:0.1:c;
xys = spline(t,xy,ts);
xs = xys(1,:);
ys = xys(2,:);
% 樣條差值效果
hold on
temp=plot(x(1),y(1),'ro',xs,ys,'b.');
legend(temp,'原點','插值點');
% =========================================================================
% Snakes算法實現部分
% =========================================================================
NIter =1000; % 迭代次數
alpha=0.2; beta=0.2; gamma = 1; kappa = 0.1;
wl = 0; we=0.4; wt=0;
[row col] = size(Igs);
% 圖像力-線函數
Eline = Igs;
% 圖像力-邊函數
[gx,gy]=gradient(Igs);
Eedge = -1*sqrt((gx.*gx+gy.*gy));
% 圖像力-終點函數
% 卷積是爲了求解偏導數,而離散點的偏導即差分求解
m1 = [-1 1];
m2 = [-1;1];
m3 = [1 -2 1];
m4 = [1;-2;1];
m5 = [1 -1;-1 1];
cx = conv2(Igs,m1,'same');
cy = conv2(Igs,m2,'same');
cxx = conv2(Igs,m3,'same');
cyy = conv2(Igs,m4,'same');
cxy = conv2(Igs,m5,'same');
for i = 1:row
for j= 1:col
Eterm(i,j) = (cyy(i,j)*cx(i,j)*cx(i,j) -2 *cxy(i,j)*cx(i,j)*cy(i,j) + cxx(i,j)*cy(i,j)*cy(i,j))/((1+cx(i,j)*cx(i,j) + cy(i,j)*cy(i,j))^1.5);
end
end
%figure(3),imshow(Eterm);
%figure(4),imshow(abs(Eedge));
% 外部力 Eext = Eimage + Econ
Eext = wl*Eline + we*Eedge + wt*Eterm;
% 計算梯度
[fx,fy]=gradient(Eext);
xs=xs';
ys=ys';
[m n] = size(xs);
[mm nn] = size(fx);
% 計算五對角狀矩陣
% 附錄: 公式(14) b(i)表示vi係數(i=i-2 到 i+2)
b(1)=beta;
b(2)=-(alpha + 4*beta);
b(3)=(2*alpha + 6 *beta);
b(4)=b(2);
b(5)=b(1);
A=b(1)*circshift(eye(m),2);
A=A+b(2)*circshift(eye(m),1);
A=A+b(3)*circshift(eye(m),0);
A=A+b(4)*circshift(eye(m),-1);
A=A+b(5)*circshift(eye(m),-2);
% 計算矩陣的逆
[L U] = lu(A + gamma.* eye(m));
Ainv = inv(U) * inv(L);
% =========================================================================
% 畫圖部分
% =========================================================================
%text(10,10,'+','FontName','Time','FontSize',20,'Color','red');
% 迭代計算
figure(3)
for i=1:NIter;
ssx = gamma*xs - kappa*interp2(fx,xs,ys);
ssy = gamma*ys - kappa*interp2(fy,xs,ys);
% 計算snake的新位置
xs = Ainv * ssx;
ys = Ainv * ssy;
% 顯示snake的新位置
imshow(I);
hold on;
plot([xs; xs(1)], [ys; ys(1)], 'r-');
hold off;
pause(0.001)
end