从零到一实现Chan-Vese/CV算法
1、Chan-Vese(CV) 算法原理
同样是构造能量函数,以snake为代表的基于边缘的主动轮廓模型,其目的在于使曲线演化至图像特定边缘,而以Chan-CV模型为代表的基于区域的主动轮廓模型目的则在于演化出以目标曲线为边界的特定区域。总的来说,snake等基于边缘的模型目的在于找目标曲线,而CV模型则在于找出特定区域,虽然本质任务相同,但实现方法上还是存在巨大差异的。
CV模型使用图像的像素灰度信息作为能量,构造能量,从而将曲线演化至目标区域,对于某一图像
(
x
,
y
)
∈
Ω
(x,y) \in{\Omega}
(x,y)∈Ω, 用
I
(
x
,
y
)
I(x,y)
I(x,y)表示图像上点
(
x
,
y
)
(x,y)
(x,y)的像素值,定义封闭曲线
C
C
C为图像上一闭合区域
ω
\omega
ω 的边界,
i
n
s
i
d
e
(
C
)
inside(C)
inside(C)表示曲线内闭合区域
ω
\omega
ω(
C
=
∂
ω
C = \partial{\omega}
C=∂ω),
o
u
t
s
i
d
e
(
C
)
outside(C)
outside(C)则表示曲线外区域
ω
‾
\overline{\omega}
ω,
C
1
C_1
C1和
C
2
C_2
C2分别表示图像上曲线内和曲线外像素平均值,并定义如下的能量项:
E
1
(
C
)
+
E
2
(
C
)
=
∫
i
n
s
i
d
e
(
C
)
∣
I
0
(
x
,
y
)
−
C
1
∣
2
d
x
d
y
+
∫
o
u
t
s
i
d
e
(
C
)
∣
I
0
(
x
,
y
)
−
C
2
∣
2
d
x
d
y
E_1(C) + E_2(C) = \int_{inside(C)}{|I_0(x,y)-C_1|^2}\,{\rm d}x{\rm d}y + \int_{outside(C)}{|I_0(x,y)-C_2|^2}\,{\rm d}x{\rm d}y
E1(C)+E2(C)=∫inside(C)∣I0(x,y)−C1∣2dxdy+∫outside(C)∣I0(x,y)−C2∣2dxdy 曲线
C
C
C和目标物体无非有以下四种关系:
可以分析当曲线C在目标图像中目标边界时,上述能量项取极小值,在补充一些正则化项目后,我们可以得出如下的能量泛函:
E
(
C
)
=
μ
L
e
n
g
t
h
(
C
)
+
υ
A
r
e
a
(
C
)
+
E
1
(
C
)
+
E
2
(
C
)
E(C) = \mu Length(C) + \upsilon Area(C) + E_1(C) + E_2(C)
E(C)=μLength(C)+υArea(C)+E1(C)+E2(C)
2、基于水平集的实现方法
2.1演化方程推导
具体的实现过程需要依赖水平集方法,我相信,即使看过网上很多的分析帖子,但是大家尤其是水平集初次接触者都会觉得这种方法很表面上看起来很有道理,但是仔细琢磨琢磨又说不出个所以然,心里可能存在一些困惑比如说,到底啥是水平集方法?为什么水平集函数的演化结果就能够实现和直接曲线演化同样的结果?这个水平集方法我回头会单独开一节进行讨论,不过一下几点心得方法可能会帮助大家理解水平集方法:
1.水平集是一种曲线演化的方法,本质上来讲就图像分割而言,和主动轮廓模型并没有什么关系,只不过水平集方法具有巨大的计算优势,能够极大方便模型后续的实现过程,因此很多时候大家都直接会把水平集函数嵌入到能量泛函,然后再将利用能量泛函极小值化(变分水平集方法),获得演化方程;其实除了这种方法外,完全可以在能量函数中先不引进水平集函数,直接对能量泛函求极值获得演化方程,然后再引入水平集函数进行求解,这个我会专门写一篇文章进行讲解;
2. 回答一下上面我提出的问题:为什么用水平集函数的演化能够替代直接曲线演化,或者说基于同样的分割原理,为什么水平集函数的演化能够和直接用曲线演化产生同样的结果,并且效率更高呢?因为对于曲线演化来讲,任一点的演化速度都可以用曲线上的切线方向速度和法线速度构成,而切线方向速度分量并不会改变曲线形状(至于为什么以后我会用数学公式推导证明) 而曲线上任一点的法向速度方向恰好和三维水平集函数的梯度方向相同,那么你会法向,只要控制能量泛函的外力相同,那么你会发现控制曲线演化的速度本质上和控制水平集函数演化的函数本质上是一样的(以后再补图)。
3. 在具体的实现过程中,希望大家能够摆脱一种曲线演化的概念,在采用水平集方法CV算法实现时,我们的焦点已经不再局限于曲线以及曲线的演化之上,而在于基于整个图像区域的水平集函数演化,简言之,水平集是面(图像面)的函数,而不是演化曲线的函数,当水平集函数演化时,零水平集即目标曲线也会随之演化(从而可以自然地改变其拓扑结构),当水平集函数的演化停止时,目标曲线也就自动勾勒出来了。
4. 使用水平集方法确实存在很多优势,但是这个优势是需要和曲线演化进行对比分析的,这个我以后写点东西让大家慢慢品。
有了上面这些概念,大家对水平集函数应该有一些具体的概念了,那么接下来我们就要引入水平集函数
ϕ
(
x
,
y
)
\phi(x,y)
ϕ(x,y)对能量函数进行调整,首先对水平集函数做如下定义,
{
C
=
∂
ω
=
{
(
x
,
y
)
∈
Ω
:
ϕ
(
x
,
y
)
=
0
}
i
n
s
i
d
e
(
C
)
=
ω
=
{
(
x
,
y
)
∈
Ω
:
ϕ
(
x
,
y
)
>
0
}
o
u
t
s
i
d
e
(
C
)
=
Ω
\
ω
‾
=
{
(
x
,
y
)
∈
Ω
:
ϕ
(
x
,
y
)
<
0
}
\begin{cases} C = \partial {\omega} = \lbrace(x,y)\in \Omega :\phi(x,y) =0 \rbrace \\[2ex] inside(C) = \omega = \lbrace(x,y)\in \Omega :\phi(x,y) >0 \rbrace \\[2ex] outside(C) = \Omega \backslash \overline{\omega} = \lbrace(x,y)\in \Omega :\phi(x,y) < 0 \rbrace \end{cases}
⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧C=∂ω={(x,y)∈Ω:ϕ(x,y)=0}inside(C)=ω={(x,y)∈Ω:ϕ(x,y)>0}outside(C)=Ω\ω={(x,y)∈Ω:ϕ(x,y)<0}这样就完成了使用水平集函数对演化曲线的替代,进一步我们定义Heaviside Fucntion(阶跃函数):
H
(
z
)
=
{
1
,
if
z
>0
0
,
if
z
<0
,
,
δ
0
(
z
)
=
d
d
z
H
(
z
)
H(z) = \begin{cases} 1, & \text{if $z$>0} \\[2ex] 0, & \text{if $z$<0}, \end{cases} , \delta_0(z) = \frac{\rm d}{\rm dz}H(z)
H(z)=⎩⎨⎧1,0,if z>0if z<0,,δ0(z)=dzdH(z)
从而可得:
L
e
n
g
t
h
(
ϕ
=
0
)
=
∫
Ω
∣
∇
H
(
ϕ
(
x
,
y
)
)
∣
d
x
d
y
Length(\phi = 0) = \int_\Omega{|\nabla H(\phi(x,y))|}dxdy
Length(ϕ=0)=∫Ω∣∇H(ϕ(x,y))∣dxdy
A
r
e
a
(
ϕ
≥
0
)
=
∫
Ω
H
(
ϕ
(
x
,
y
)
)
d
x
d
y
Area(\phi \geq 0) = \int_\Omega{H( \phi(x,y))}dxdy
Area(ϕ≥0)=∫ΩH(ϕ(x,y))dxdy
∫
i
n
s
i
d
e
(
C
)
∣
I
0
(
x
,
y
)
−
C
1
∣
2
d
x
d
y
=
∫
Ω
∣
I
0
(
x
,
y
)
−
C
1
∣
2
H
(
ϕ
(
x
,
y
)
d
x
d
y
\int_{inside(C)}{|I_0(x,y)-C_1|^2}\,{\rm d}x{\rm d}y = \int_{\Omega}{|I_0(x,y)-C_1|^2 H( \phi(x,y)}\,{\rm d}x{\rm d}y
∫inside(C)∣I0(x,y)−C1∣2dxdy=∫Ω∣I0(x,y)−C1∣2H(ϕ(x,y)dxdy
∫
o
u
t
s
i
d
e
(
C
)
∣
I
0
(
x
,
y
)
−
C
2
∣
2
(
1
−
H
(
ϕ
(
x
,
y
)
)
d
x
d
y
\int_{outside(C)}{|I_0(x,y)-C_2|^2 (1-H( \phi(x,y))}\,{\rm d}x{\rm d}y
∫outside(C)∣I0(x,y)−C2∣2(1−H(ϕ(x,y))dxdy总的能量函数也就可以写为:
F
(
ϕ
(
x
,
y
)
)
=
μ
∫
Ω
∣
∇
H
(
ϕ
(
x
,
y
)
)
∣
d
x
d
y
+
ν
∫
Ω
H
(
ϕ
(
x
,
y
)
d
x
d
y
+
λ
1
∫
Ω
∣
I
0
(
x
,
y
)
−
C
1
∣
2
H
(
ϕ
(
x
,
y
)
d
x
d
y
+
λ
2
∫
o
u
t
s
i
d
e
(
C
)
∣
I
0
(
x
,
y
)
−
C
2
∣
2
(
1
−
H
(
ϕ
(
x
,
y
)
)
d
x
d
y
F(\phi(x,y)) =\mu \int_\Omega{|\nabla H(\phi(x,y))|}dxdy + \nu \int_\Omega{H( \phi(x,y)}dxdy \\ + \lambda_1 \int_{\Omega}{|I_0(x,y)-C_1|^2 H( \phi(x,y)}\,{\rm d}x{\rm d}y + \\ \lambda_2 \int_{outside(C)}{|I_0(x,y)-C_2|^2 (1-H( \phi(x,y))}\,{\rm d}x{\rm d}y
F(ϕ(x,y))=μ∫Ω∣∇H(ϕ(x,y))∣dxdy+ν∫ΩH(ϕ(x,y)dxdy+λ1∫Ω∣I0(x,y)−C1∣2H(ϕ(x,y)dxdy+λ2∫outside(C)∣I0(x,y)−C2∣2(1−H(ϕ(x,y))dxdy其中
C
1
=
∫
Ω
I
(
x
,
y
)
H
(
ϕ
(
x
,
y
)
d
x
d
y
∫
Ω
H
(
ϕ
(
x
,
y
)
d
x
d
y
C_1 = \frac {\int_\Omega{I(x,y)H( \phi(x,y)}dxdy}{\int_\Omega{H( \phi(x,y)}dxdy}
C1=∫ΩH(ϕ(x,y)dxdy∫ΩI(x,y)H(ϕ(x,y)dxdy
C
2
=
∫
Ω
I
(
x
,
y
)
(
1
−
H
(
ϕ
(
x
,
y
)
)
d
x
d
y
∫
Ω
(
1
−
H
(
ϕ
(
x
,
y
)
)
d
x
d
y
C_2 = \frac {\int_\Omega{I(x,y)(1-H( \phi(x,y))}dxdy}{\int_\Omega{(1-H( \phi(x,y))}dxdy}
C2=∫Ω(1−H(ϕ(x,y))dxdy∫ΩI(x,y)(1−H(ϕ(x,y))dxdy,因为理论上来讲,Heaviside Fucntion(阶跃函数)是不存在的,所以我们选择了一个替代函数:
H
ϵ
(
z
)
=
1
2
(
1
+
2
π
a
r
c
t
a
n
(
z
ϵ
)
)
H_{\epsilon}(z) = \frac{1}{2}(1+\frac{2}{\pi}arctan(\frac{z}{\epsilon}))
Hϵ(z)=21(1+π2arctan(ϵz))
δ
ϵ
(
z
)
=
1
π
⋅
ϵ
ϵ
2
+
π
2
\delta_{\epsilon}(z) = \frac{1}{\pi} \cdot \frac{\epsilon}{\epsilon^2+\pi^2}
δϵ(z)=π1⋅ϵ2+π2ϵ,当
ϵ
\epsilon
ϵ的取值越小,替代函数也就越接近理想中的的阶跃函数。
至此,有关能量泛函的所有细节已经交代完毕,然后利用变分法和梯度下降流(梯度下降法求解能量泛函极值)获得能量泛函的偏微分方程:
∂
ϕ
∂
t
=
δ
ϵ
(
ϕ
)
[
μ
d
i
v
(
∇
ϕ
∣
∇
ϕ
∣
)
−
ν
−
λ
1
(
I
−
C
1
)
2
+
λ
2
(
I
−
C
2
)
2
]
\frac{\partial \phi}{\partial t} = \delta_{\epsilon}(\phi)[\mu div(\frac{\nabla\phi}{|\nabla\phi|})-\nu - \lambda_1(I-C_1)^2+\lambda_2(I-C_2)^2]
∂t∂ϕ=δϵ(ϕ)[μdiv(∣∇ϕ∣∇ϕ)−ν−λ1(I−C1)2+λ2(I−C2)2]
2.2离散化过程
在对上述偏微分方程进行离散化前,首先定义
h
h
h表示空间补步长,
Δ
t
\Delta t
Δt表示时间步长,那么我们就可以用
ϕ
i
,
j
n
=
ϕ
(
n
Δ
,
x
i
,
y
i
)
\phi_{i,j}^n=\phi(n\Delta,x_i,y_i)
ϕi,jn=ϕ(nΔ,xi,yi)作为
ϕ
(
t
,
x
,
y
)
\phi(t,x,y)
ϕ(t,x,y)的近似,然后定义如下差分式子:
Δ
−
x
ϕ
(
x
,
y
)
=
ϕ
i
,
j
−
ϕ
i
−
1
,
j
,
Δ
+
x
ϕ
(
x
,
y
)
=
ϕ
i
+
1
,
j
−
ϕ
i
,
j
\Delta_-^x\phi(x,y) = \phi_{i,j}-\phi_{i-1,j},\Delta_+^x\phi(x,y) = \phi_{i+1,j}-\phi_{i,j}
Δ−xϕ(x,y)=ϕi,j−ϕi−1,j,Δ+xϕ(x,y)=ϕi+1,j−ϕi,j
Δ
−
y
ϕ
(
x
,
y
)
=
ϕ
i
,
j
−
ϕ
i
,
j
−
1
,
Δ
+
x
ϕ
(
x
,
y
)
=
ϕ
i
,
j
+
1
−
ϕ
i
,
j
\Delta_-^y\phi(x,y) = \phi_{i,j}-\phi_{i,j-1},\Delta_+^x\phi(x,y) = \phi_{i,j+1}-\phi_{i,j}
Δ−yϕ(x,y)=ϕi,j−ϕi,j−1,Δ+xϕ(x,y)=ϕi,j+1−ϕi,j然后我们根据
C
1
C_1
C1和
C
2
C_2
C2的计算公式计算出对应值.
最后对应的离散化方程式就为(这可能是我打出的史上最长公式):
ϕ
i
,
j
n
+
1
−
ϕ
i
,
j
n
Δ
t
=
δ
n
(
ϕ
i
,
j
n
)
[
μ
h
2
Δ
−
x
⋅
(
Δ
+
x
ϕ
i
,
j
n
+
1
(
Δ
+
x
ϕ
i
,
j
n
)
2
/
(
h
2
)
+
(
ϕ
i
,
j
+
1
−
ϕ
i
,
j
−
1
)
2
/
(
2
h
)
2
)
+
μ
h
2
Δ
−
y
⋅
(
Δ
+
y
ϕ
i
,
j
n
+
1
(
Δ
+
y
ϕ
i
,
j
n
)
2
/
(
h
2
)
+
(
ϕ
i
+
1
,
i
−
ϕ
j
−
1
,
i
)
2
/
(
2
h
)
2
)
−
υ
−
λ
1
(
I
i
,
j
−
C
1
(
ϕ
n
)
)
2
+
λ
2
(
I
i
,
j
−
C
2
(
ϕ
n
)
)
2
]
\frac{\phi_{i,j}^{n+1}-\phi_{i,j}^n}{\Delta t} = \delta_n(\phi_{i,j}^n)[\frac{\mu}{h^2}\Delta_-^x \cdot(\frac{\Delta_+^x \phi_{i,j}^{n+1}}{\sqrt{(\Delta_+^x\phi_{i,j}^n)^2/(h^2) + (\phi_{i,j+1}-\phi_{i,j-1})^2/(2h)^2}}) \\ + \frac{\mu}{h^2}\Delta_-^y \cdot(\frac{\Delta_+^y \phi_{i,j}^{n+1}}{\sqrt{(\Delta_+^y\phi_{i,j}^n)^2/(h^2)+(\phi_{i+1,i}-\phi_{j-1,i})^2/(2h)^2}}) - \upsilon -\lambda_1(I_{i,j}-C_1(\phi^n))^2 + \lambda_2(I_{i,j}-C_2(\phi^n))^2 ]
Δtϕi,jn+1−ϕi,jn=δn(ϕi,jn)[h2μΔ−x⋅((Δ+xϕi,jn)2/(h2)+(ϕi,j+1−ϕi,j−1)2/(2h)2Δ+xϕi,jn+1)+h2μΔ−y⋅((Δ+yϕi,jn)2/(h2)+(ϕi+1,i−ϕj−1,i)2/(2h)2Δ+yϕi,jn+1)−υ−λ1(Ii,j−C1(ϕn))2+λ2(Ii,j−C2(ϕn))2]
2.3 代码实现
那么接下来就是进行代码实现啦,代码实现主要分为一下主要部分:
- 读入图片
Image = cv2.imread('1.bmp',1)
image = cv2.cvtColor(Image,cv2.COLOR_BGR2GRAY)
img = np.array(image,dtype='float64')
plt.imshow(img,cmap = 'gray')
print(img.shape)
- 初始化水平集函数
IniLSF = np.ones([img.shape[0],img.shape[1]],img.dtype)
IniLSF[40:80,40:80]=-1
IniLSF = -IniLSF
- 定义水平集算法
def CV(LSF,img,nu,mu,epison,step):
Drc = (epison/math.pi)/(epison*epison+LSF*LSF)
# Hea = 0.5*(1+(2/math.pi)*mat_math(LSF/epison,'atan'))
Hea = 0.5*(1+(2/math.pi)*np.arctan(LSF/epison))
Iy,Ix = np.gradient(LSF)##q4#
# s = mat_math(Ix*Ix+Iy*Iy,"sqrt")
s = np.sqrt(Ix*Ix+Iy*Iy)
Nx = Ix/(s+0.000001)
Ny = Iy/(s+0.000001)
Mxx,Nxx = np.gradient(Nx)
Nyy,Myy = np.gradient(Ny)
cur = Nxx + Nyy
Length = nu*Drc*cur
Area = mu*Drc
s1 = Hea*img
s2 = (1-Hea)*img
s3 = 1-Hea
C1 = s1.sum()/Hea.sum()
C2 = s2.sum()/s3.sum()
CVterm = Drc*(-1*(img-C1)*(img-C1)+1*(img-C2)*(img-C2))
LSF = LSF+step*(Length+Area+CVterm)
return LSF
- 定义关键参数,并迭代计算,
nu = 0.0001*255*255
mu = 1
num = 10
epison = 1
step = 0.1
LSF = IniLSF
for i in range(1,num):
LSF = CV(LSF,img,nu,mu,epison,step)
if i%2 == 0:
plt.contour(LSF,[0],linewidths = 3.0,linestyles = 'dotted',colors='r')
# plt.draw()
plt.show()
print("over")