这篇博客介绍的是2001年的一篇文章Active Contours Without Edges,作者是Tony Chan。这篇文章介绍了一种改进的active contour图像分割或者说轮廓检测方法。作者首先分析了已有的active contour/snakes模型的缺点。作者认为在相关的模型中,能量函数中的图像能量基本上都是定义在图像梯度上的,通过梯度达到最大(即边缘)来让曲线演化停止。但是这种模型检测不到比较光滑的边缘。所以作者提出采用另一种停止曲线演化的策略:Munford-Shah分割技术。
作者通过一个简单的例子引入自己的模型。现在我们先来看一下这个例子,从而直观感受到作者定义的能量函数在取得极小值时,对应的曲线就是我们希望得到的轮廓。首先说明一下下面将要用到的数学符号:
Ω : R2 中的有界开子集 u0 :从 Ω→R 的一个映射,这里表示图片 C(s) :从 [0,1]→R2 的映射,表示曲线 ω : Ω 中的开集,曲线 C 是其边界,即C=∂ω假设给定一张图片 u0 ,这张图片比较特殊,由两块区域构成,其中区域一所有的像素点取值都为 ui0 ,区域二中所有的像素点取值都为 uo0 。现在我们希望得到区域一的轮廓。考虑下面的”fitting”项(称为fitting应该是因为这个函数控制着曲线最终拟合至物体边缘停止):
F(C)=∫inside(C)|u0(x,y)−c1|2dxdy+∫outside(C)|u0(x,y)−c2|2dxdy(1) 其中常数 c1 和 c2 分别是曲线C内部像素点的均值和C外部像素点的均值。对于刚才介绍的特殊的图片,可以看出使得这个能量函数值最小的曲线C就是我们希望得到的区域一的轮廓,此时能量值为0(可以自行验证曲线C不是区域一轮廓的其他几种情况,对应的能量值都大于0)。 作者提出的模型其实就是上面的能量函数,另外还加上了曲线的长度、曲线内区域的面积等正则项: F(C)=μ⋅Length(C)+ν⋅Area(inside(C))+λ1∫inside(C)|u0(x,y)−c1|2dxdy+λ2∫outside(C)|u0(x,y)−c2|2dxdy(2) 其中, μ≥0 , ν≥0 , λ1,λ2>0 。 作者把这个函数写成 F(c1,c2,C) ,但是由于 c1 和 c2 也是 C 的函数,所以这里写成F(C)。
作者在文章中提到能量函数(1)是Mumford-Shah的特例,感觉只是为了说明因为Mumford-Shah有极小值,所以(1)也有极小值而已。
现在怎么求解上面的极小值问题呢?作者把上面关于曲线C的能量函数写成关于水平集函数 ϕ 的能量函数(函数的函数——泛函),然后求解水平集函数。 要用 ϕ 替换公式(1)中的曲线 C ,需要引入两个函数,简称为H函数和Delta函数吧: H(z)={10z≥0z<0
δ0(z)=ddzH(z) 那么现在可以把公式(1)重新写成: F(c1,c2,ϕ)=μ∫Ωδ(ϕ(x,y))|∇ϕ(x,y)|dxdy+ν∫ΩH(ϕ(x,y))dxdy+λ1∫Ω|u0(x,y)−c1|2H(ϕ(x,y))dxdy+λ2∫Ω|u0(x,y)−c2|2(1−H(ϕ(x,y)))dxdy(3) c1 和 c2 现在通过 ϕ 求出: {c1(ϕ)=average(u0) in {ϕ≥0}c2(ϕ)=average(u0) in {ϕ<0}(4)针对泛函极值问题,可以得到 ϕ 的欧拉-拉格朗日公式,其实就是一个偏微分方程:
∂ϕ∂t=δϵ(ϕ)[μ⋅div(∇ϕ|∇ϕ|)−ν−λ1(u0−c1)2+λ2(u0−c2)2]=0ϕ(0,x,y)=ϕ0(x,y) in Ωδϵ(ϕ)|∇ϕ|∂ϕ∂n⃗ =0 on ∂Ω(5) 其中 n⃗ 是边界 ∂Ω 的外法线。 细心的读者是不是注意到Delta函数的下标从0变成 ϵ 了。作者在得到的欧拉-拉格朗日方程中,并没有用上面介绍的H函数以及对应的Delata函数,而是换成了下面的: Hϵ=12(1+2πarctan(zϵ)) δϵ=H′ϵ=ϵπ1z2+ϵ2 当 ϵ→0 时, 分别收敛到H和 δ0 。为什么要这样做呢?因为原来的 δ0 基本上处处为0,是没办法求上面的欧拉-拉格朗日方程的。而现在 δϵ 的支撑集是整个实数集 R ,所以不管初始曲线是什么样,都可以得到全局最优解;另外还可以检测到内部的轮廓。 (对于这个 δϵ 的物理意义理解的不是很透彻。)好了,到现在需要用到各种数学知识的理论部分已经结束了。现在需要做的就是想办法求解上面的偏微分方程。
这里采用迭代法求解上面的偏微分方程。首先需要对偏微分方程离散化。这里怎么离散化的就不介绍了。总之,经过重重障碍,现在得到水平集函数 ϕ 的迭代形式:
ϕn+1i,j−ϕni,jΔt=δh(ϕni,j)[μ curvatureij−ν−λ1(u0,i,j−c1)2+λ2(u0,i,j−c2)2](6)把上面所有的东西总结起来,其实就是下面简简单单的4个步骤(步骤4重新初始化是可选的,这里不介绍):
随机初始化 ϕ0=ϕ0,n=0 根据公式(4)计算两个均值 c1 和 c2 根据迭代公式(6)求解 ϕn+1 重新初始化 ϕ :因为这一步可用可不用,所以不介绍怎么重新初始化的检查是否收敛,是的话则停止;否则回到步骤2我上传了一份Python的实现代码:https://github.com/VictoriaW1/ACWE。以后还会对这份代码进行优化,比如在每次更新 ϕ 后做平滑处理。