影象處理(十一)影象分割(3)泛函能量LevelSet、snake分割
一、level set相關理論
基於水平集的影象分割演算法是一種進化版的Snake演算法,也是需要給定初始的輪廓曲線,然後根據泛函能量最小化,進行曲線演化。水平集的方法,用的是一種隱式函式的方法,這個演算法比較難理解,我一年前開始搞這個演算法的時候,雖然知道程式碼怎麼寫,但是它的原理推導完全不懂,因為這個演算法比較難理解,所以我這邊將講的稍微詳細一點。
跟傳統的snake演算法相比,思想完全不一樣,snake算法曲線演化的時候,是曲線上離散點顯示座標的位置更新移動,只要懂得能量最小化的曲線演化規則,就可以很快理解演算法,並寫出程式碼。然而水平集的方法,更新的不是曲線離散點的座標,而是更新整張圖片畫素點到曲線的有向距離場。因此演算法最關鍵的是理解這個距離場的更新規則,當然這個更新規則跟能量最小化相關。
開始這個演算法之前,我們需要非常熟悉,顯式二維的曲線與隱式曲線(水平集函式)的相互轉換公式。給定初始的輪廓曲線C,我們怎麼把它轉換成水平集函式,這個是實現演算法的第一步。水平集函式的定義:
公式1:
也就是說,如果給你一條初始封閉輪廓曲線C,進行水平集影象分割,我們需要寫的第一個函式就是計算影象的每個畫素點p(x,y)到曲線的最短距離d,如果該畫素點p位於曲線C的內部,那麼有向距離為-d;反之為d。這樣遍歷影象每個畫素點,每個畫素點都可以求得對應的有向距離u(x,y)。反過來,如果我已經知道了影象上每個畫素點的有向距離u(x,y),那麼我要怎麼把這個隱式函式轉換成顯示函式呢?
其實很簡單,只要求出滿足u(x,y)=0 的畫素點,就是曲線上的點,因為如果該畫素點到曲線C的最短距離為0,那麼這個畫素點肯定在這條曲線上,據此我們就可以把所有滿足u(x,y)=0的畫素點全部提取出來,獲得這些畫素點的座標p(x,y),而這些點便是曲線C的離散點,這樣就完成了從隱式距離場到顯式離散曲線的轉換。
據此我們可以得到演算法的大體流程:
輸入:給定離散的初始輪廓曲線C,待分割影象T
輸出:分割結果曲線C’
Algorithm:
Begin:
1、根據公式1,計算每個畫素點到離散曲線C的最短有向距離u(x,y)
2、根據影象梯度等資訊,對u(x,y)進行演化,使得其沿著能量最小化的方向演化,這個過程說的簡單一點,就是更新每個畫素點的u(x,y)值。
3、根據第2步的演化結果,遍歷每個畫素點(x,y),判斷其水平集函式值是否為零。
If u(x,y)==0:
儲存畫素點座標(x,y)(因為這個點就是曲線C’上的點)
得到所有u(x,y)==0的點,就是最後我們想要的影象分割結果曲線C’
End
二、演算法實現:
這裡我選擇Mean Separation (MS) Energy能量最小化為例,講解區域性活動輪廓影象分割,具體的參考文獻為:《Localizing Region-Based Active Contours》。這裡為直接把文中最後演算法實現需要使用的公式,截出來,以便學習。
水平集總演化公式為:
其中:
dt是一個較小的數,選擇範圍為0.1~40都可以,當然迭代步長還是選擇的越小效果越好,就是需要的迭代次數越多。然後下面的公式為公式(17)對應的引數求解公式:
其中:
公式3是一個卷積核,上面的大部分過程的計算都涉及到用B(x,y)進行卷積,卷積半徑在我的專案使用的時候,我是選擇R=8,而且B(x,y)是一個均值濾波的卷積核,因此如果要對演算法用快速均值濾波,演算法可提高五六倍的速度。具體演算法程式碼實現如下:
function seg = local_AC_MS(Img,mask_init,rad,alpha,num_it,epsilon)
%引數Img為灰度影象
%mask_init為初始曲線的一個mask,曲線上的點座標在mask中的值為1
%rad 為卷積半徑
%alpha為公式6中的λ
%num_it為迭代次數
%epsilon為公式1中的ε
phi0 = mask2phi(mask_init);%從顯式曲線轉換成水平集函式
phi = phi0;%計算距離場,即計算所有的畫素點位置(x,y)到曲線的最短距離,這就是所謂的水平集函式
B0 = ones(2*rad+1,2*rad+1); %mask,卷積核
KI=conv2(Img,B0,'same'); %對影象Img用卷積核B0進行卷積
KONE=conv2(ones(size(Img)),B0,'same');
%下面計算的是文獻的公式17,即使用公式17對曲線進行演化,所有的計算都是為了計算公式17
for ii = 1:num_it%開始迭代
mask = Heaviside2(phi,epsilon);%計算文獻公式1
I=Img.*mask;
temp1=conv2(mask,B0,'same'); %文獻的公式18
temp2=conv2(I,B0,'same');
c1=temp2./(temp1);
c2=(KI-temp2)./(KONE-temp1);
A1 = temp1;%文獻的公式18
A2 = conv2(1-mask,B0,'same');%文獻公式19
D = (A1.*A2+eps);
term1 = (A2-A1)./D;
term2 = (A2.*c1.^2-A1.*c2.^2)./D;
term3 = (A2.*c1-A1.*c2)./D;
%計算總公式的前半部分
dataForce = conv2(term1.*Dirac2(phi,epsilon),B0,'same').*Img.*Img + conv2(term2.*Dirac2(phi,epsilon),B0,'same')-2.*Img.*conv2(term3.*Dirac2(phi,epsilon),B0,'same'); %%% During the implementation, Img should be separated out of the filtering operation!!!
dataForce = dataForce/max(abs(dataForce(:)));
%計算總公式的後半部分,即水平集的散度
curvature = curvature_central(phi);
%總公式
dphi = Dirac2(phi,epsilon).*(-dataForce + alpha*curvature);
dt = 1/(max(abs(dphi(:)))+eps);%時間步長,該引數可人為設定為恆定引數
%曲線演化公式,即完成曲線的迭代
phi = phi + dt.*dphi;
%繪製曲線,(x,y)的值為0的點即為曲線上的點
if(mod(ii,10) == 0)
showCurveAndPhi(Img,phi,ii); %繪製值為0的等值線
end
end
seg = (phi>=0);
%為了保證水平集的光滑性,需要對水平集進行重新計算,保證水平集的梯度模為1
%具體計算公式見
function D = sussman(D, dt)
%前後差分
a = D - shiftR(D); % 後差分
b = shiftL(D) - D; % 前差分
c = D - shiftD(D);
d = shiftU(D) - D;
a_p = a; a_n = a; % a+ and a-
b_p = b; b_n = b;
c_p = c; c_n = c;
d_p = d; d_n = d;
a_p(a < 0) = 0;
a_n(a > 0) = 0;
b_p(b < 0) = 0;
b_n(b > 0) = 0;
c_p(c < 0) = 0;
c_n(c > 0) = 0;
d_p(d < 0) = 0;
d_n(d > 0) = 0;
dD = zeros(size(D));
D_neg_ind = find(D < 0);
D_pos_ind = find(D > 0);
dD(D_pos_ind) = sqrt(max(a_p(D_pos_ind).^2, b_n(D_pos_ind).^2) ...
+ max(c_p(D_pos_ind).^2, d_n(D_pos_ind).^2)) - 1;
dD(D_neg_ind) = sqrt(max(a_n(D_neg_ind).^2, b_p(D_neg_ind).^2) ...
+ max(c_n(D_neg_ind).^2, d_p(D_neg_ind).^2)) - 1;
D = D - dt .* sussman_sign(D) .* dD;
function shift = shiftD(M)
shift = shiftR(M')';
function shift = shiftL(M)
shift = [ M(:,2:size(M,2)) M(:,size(M,2)) ];
function shift = shiftR(M)
shift = [ M(:,1) M(:,1:size(M,2)-1) ];
function shift = shiftU(M)
shift = shiftL(M')';
function S = sussman_sign(D)
S = D ./ sqrt(D.^2 + 1);
%計算有向距離場函式
function phi = mask2phi(init_a)
phi=bwdist(init_a)-bwdist(1-init_a)+im2double(init_a);
phi = -double(phi);
%水平集提取函式,也就是把隱式函式轉換成顯式函式,所得簡單一點,就是提取值為0的等高線
function showCurveAndPhi(I, phi, i)
imshow(I,'initialmagnification',200,'displayrange',[ ]);
hold on; contour(phi, [0 0], 'y','LineWidth',2);
hold off; title([num2str(i) ' Iterations']); drawnow;
%文獻公式2
function f = Dirac2(x, sigma)
f = (sigma/pi)./(sigma^2+x.^2);
%計算文獻公式1
function f = Heaviside2(x, epsilon)
f = 0.5*(1+2/pi*atan(x./epsilon));
%散度計算
function k = curvature_central(u)
[ux,uy] = gradient(u);
normDu = sqrt(ux.^2+uy.^2+1e-10);
Nx = ux./normDu;
Ny = uy./normDu;
[nxx,junk] = gradient(Nx);
[junk,nyy] = gradient(Ny);
k = nxx+nyy;
個人觀點:我覺得傳統的snake演算法,只能用爛來解釋,基本上以遇到一點噪聲,就不行了,分割精度真不是一般的差。而水平集的分割只能用高大上來形容,可以進行自動分裂合併等,就是速度有點慢,因為每次都要對水平集函式進行更新,更新一次就相當於遍歷一張圖片,因此速度可想而知,當然還有很多加速版的水平集方法,有待測試學習。本文地址:http://blog.csdn.net/hjimce/article/details/45586727 作者:hjimce 聯絡qq:1393852684 更多資源請關注我的部落格:http://blog.csdn.net/hjimce 原創文章,版權所有,轉載請保留本行資訊