图像分割是计算机视觉领域中一个长期存在的有趣问题,它涉及到将图像划分为可以直接促进一系列计算机视觉问题的区域,前提是这些分割是可靠且高效计算的。例如,图像识别可以利用分割结果来解决诸如图形-背景分离和部分识别等问题。
Felzenszwalb和Huttenlocher在他们的论文《Efficient Graph-Based Image Segmentation》中提出了一种解决上述分割问题的方法,通常被称为Felzenszwalb算法。他们的目标是开发一种广泛有用的图像分割计算方法,类似于其他低级技术如边缘检测在一系列计算机视觉任务中的使用。
他们认为一个好的分割方法应该具备以下属性:分割应该捕捉视觉上重要的分组或区域,这些区域通常反映了图像的全局方面;方法应该是高效的,意味着它应该在图像像素数量的几乎线性时间内运行。尽管许多方法在图像分割方面取得了相当大的进步,但大多数方法都很慢,并且没有考虑到对象可能具有强度不变性,因此会错误地分割该区域。
为了克服以前方法所面临的问题,Felzenszwalb和Huttenlocher采取了基于图的方法进行分割。他们将问题表述如下:设G = (V, E)是一个无向图,其中顶点vi ∈ V是待分割的元素集合,边(vi, vj) ∈ E对应于相邻顶点对。每条边(vi, vj) ∈ E都有一个相应的权重w((vi, vj)),这是一个非负的度量,表示相邻元素vi和vj之间的差异。
在图像分割的情况下,V中的元素是像素,边的权重是连接该边的两个像素之间的差异度量(例如,强度差异、颜色、运动、位置或某些其他局部属性)。S是图G的一个分割,使得G' = (V, E'),其中E' ⊂ E。S将G划分为G',使其包含不同的组件C。
在构建图像的图时,考虑了两种方法:网格图和最近邻图。网格图中每个像素只与周围的邻居(总共8个其他单元格)连接。边的权重是像素强度值之间的绝对差异。最近邻图中每个像素是特征空间中的一个点(x, y, r, g, b),其中(x, y)是像素位置,(r, g, b)是RGB中的颜色值。权重是两个像素特征向量之间的欧几里得距离。
在了解一个好的图分割的标准之前,最好先了解一些概念:内部差异、组件间差异和最小内部差异。内部差异是组件C的最大边权重,即使移除了所有权重小于Int(C)的边,组件'C'仍然可以保持连接。组件间差异是连接组件C1中的节点vi到组件C2中的节点vj的边的最小权重。最小内部差异是组件C1和C2的内部差异,由因子k控制。
为了判断分割的质量,需要对两个给定区域进行良好的成对区域比较。只有当这个谓词为True时,才认为两个组件是独立的。否则,分割可能太细,组件应该合并。
已经定义了上述条件中的各个术语,让更好地理解它们。组件间差异是连接组件C1中的节点vi到组件C2中的节点vj的边的最小权重。最小内部差异是组件C1和C2的内部差异,由因子k控制。这个因子k在MInt的定义中将内部差异和最小内部差异的理解分开。正如之前看到的τ(C),设置了组件内部节点需要与组件不同阈值。知道τ(C)=k/|C|,这里,常数k的属性如下:如果k很大,它会导致对较大对象的偏好;k不设置组件的最小大小。
现在考虑这个,可以在相同的上下文中理解最小内部差异(MInt(C1,C2))和组件间差异(Dif(C1,C2))。只有当组件间差异大于两个组件的最小内部差异时,两个组件才能被划分为两个不同的组件。
分割算法及其工作原理。已经看到并理解了谓词,这是算法的核心。给定一个输入图G = (V, E),有n个顶点和m条边。输出是将V分割成组件S = (C1, ..., Cr)。算法如下:虽然算法一开始可能看起来令人生畏,但它基本上做的是谓词想要的:算法遵循自下而上的程序。给定G=(V,E)和|V|=n,|E|=m。其中|V|是顶点(像素)的数量,|E|是边的数量。边按权重从小到大排序,标记为e1, e2, ..., em。最初,每个像素都有自己的组件,所以从n个组件开始。对于k=1,...,m:在步骤k的分割快照表示为Sk。取第k条边的顺序,ek=(vi, vj)。如果vi和vj属于同一个组件,则什么都不做,因此Sk=Sk-1。如果vi和vj属于两个不同的组件Ci_k-1和Cj_k-1,如在S_k-1的分割中,想合并它们,如果w(vi,vj) ≤ MInt(Ci_k-1,Cj_k-1);否则什么都不做。整个分割过程可以如下可视化:
这个分割算法有许多属性,其中最重要的属性是:对于任何(有限)图G = (V, E),存在某种分割S,既不粗糙也不细腻。有关更多属性和证明,可以查看原始论文。这个算法非常容易实现,因为它在skimage库中定义。
# 导入所需库
import skimage.segmentation
from matplotlib import pyplot as plt
# 读取图像
img = plt.imread("/content/image1.jpg")
# 执行分割
# 第一个k=50
# 第二个k=1000
res1 = skimage.segmentation.felzenszwalb(img, scale=50)
res2 = skimage.segmentation.felzenszwalb(img, scale=1000)
# 打印结果
fig = plt.figure(figsize=(12, 5))
ax1 = fig.add_subplot(121)
ax2 = fig.add_subplot(122)
ax1.imshow(res1); ax1.set_xlabel("k=50")
ax2.imshow(res2); ax2.set_xlabel("k=1000")
fig.suptitle("基于图的图像分割")
plt.tight_layout()
结果表明,k值较低会导致分割较粗糙,而k值较大则会导致选择较大的对象,因此分割更细腻。这可以从第二张图片中正确分割的球体中看出,但在第一张图片中没有。还可以标记边界,如果需要的话:
from skimage.data import camera
from skimage.util import img_as_float
from skimage.segmentation import mark_boundaries
img = img_as_float(camera()[::2, ::2])
res3 = skimage.segmentation.felzenszwalb(img, scale=100)
res4 = skimage.segmentation.felzenszwalb(img, scale=500)
fig = plt.figure(figsize=(12, 5))
ax1 = fig.add_subplot(121)
ax2 = fig.add_subplot(122)
ax1.imshow(mark_boundaries(img, res3)); ax1.set_xlabel("With k=100")
ax2.imshow(mark_boundaries(img, res4)); ax2.set_xlabel("With k=500")
fig.suptitle("绘制边界")
原始图像和结果表明,不同的k值对分割结果有显著影响。