查看全课大纲(第 21 / 93 节)
- 1.计算机视觉概述
- 2.计算机视觉应用场景
- 3.困难与挑战
- 4.开源库介绍
- 5.图像显示与存储
- 6.CLAHE
- 7.腐蚀膨胀与开闭运算
- 8.滤波与边缘填充
- 9.各种滤波
- 10.图像金字塔
- 11.傅里叶变换
- 12.短时傅里叶变换与小波变换
- 13.颜色特征与颜色直方图
- 14.几何特征_边缘
- 15.几何特征-角点与斑点
- 16.局部特征-SIFT特征描述子
- 17.局部特征-SURF特征描述子
- 18.局部特征-ORB特征描述子
- 19.其他特征描述子
- 20.传统图像分割技术
- 21.GraphCut
- 22.GrabCut
- 23.人脸检测的传统方法
- 24.行人检测的传统方法HOG+SVM
- 25.行人检测DPM
- 26.人工神经网络的架构
- 27.目标函数与梯度下降
- 28.误差反向传播
- 29.可手算的神经网络
- 30.改进目标函数与克服梯度消散
- 31.神经网络之传统神经网络与深度网络的差异
- 32.各种梯度下降算法
- 33.如何选择合适的梯度下降算法
- 34.避免过适应
- 35.CNN的架构
- 36.CNN的误差反向传播_卷积层
- 37.CNN的误差反向传播_池化层
- 38.分类所用的数据集
- 39.Alexnet详解
- 40.VGG详解
- 41.GoogLeNet_v1
- 42.BatchNormalization
- 43.GoogLeNet_v3v4
- 44.ResNet
- 45.ResNeXt
- 46.CNN设计准则
- 47.图像检索与推土机距离
- 48.基于颜色相似性
- 49.基于形状相似性
- 50.基于纹理相似性
- 51.基于局部特征相似性
- 52.DL的二值哈希编码
- 53.高维空间中检索KDTree
- 54.高维空间中检索LSH
- 55.目标识别任务综述
- 56.RCNN
- 57.SPP-Net
- 58.Fast R-CNN
- 59.Faster R-CNN
- 60.R-CNN系列模型回顾
- 61.R-FCN的设计思路
- 62.R-FCN的实现
- 63.YOLOv1
- 64.YOLOv2_9000
- 65.YOLOv3
- 66.图像分割应用场景与数据集
- 67.上采样的实现方式
- 68.FCN网络架构的由来
- 69.FCN构建细节
- 70.DeepLabv1
- 71.CRF
- 72.DeepLabv2
- 73.DeepLabv3
- 74.特定的医学影像分割任务
- 75.U-Net
- 76.3D U-Net
- 77.V-Net
- 78.DenseNet
- 79.病理切片分析任务及数据集
- 80.病理切片分析
- 81.图说任务及其传统处理方法
- 82.深度语言模型与RNN
- 83.LSTM及其变种
- 84.NIC模型
- 85.SAT模型
- 86.图说的性能指标
- 87.判别模型与生成模型
- 88.KL散度与JS散度
- 89.VAE
- 90.GAN的基本原理
- 91.DCGAN与CGAN
- 92.Wasserstein GAN
- 93.SRGAN
GraphCut
小象实战讲义 · 基于深度学习的计算机视觉
在图像分割领域,我们已学习了基于阈值、边缘和区域的方法。本节将介绍一种基于图论的经典分割算法——GraphCut。它将图像分割问题巧妙地转化为图论中的最小割问题,通过定义能量函数并求解其最小值,实现前景与背景的分离。这种方法在深度学习兴起前,曾是交互式分割的重要工具,其思想对后续算法仍有深远影响。
💡 核心导读
- 图论映射:将图像像素映射为带权无向图的顶点,像素间的相似度作为边的权重。
- 能量最小化:分割问题转化为求解能量函数的最小值,能量函数由区域项和边界项构成。
- 交互式分割:算法需要用户提供少量前景和背景的“种子点”作为先验知识。
- 最小割求解:通过求解图的最小割,将图划分为前景和背景两个子集,完成分割。
从图像到图:问题的转化
GraphCut 是一种基于图论的图像分割方法。其核心思想是将图像分割问题转化为一个图论中的最小割问题。
首先,将待分割的图像映射为一个带权重的无向图 G = (V, E)。其中:
- 顶点 (V):图中的每个节点对应图像中的一个像素。
- 边 (E):图中连接顶点的边分为两类:
- 邻域边:连接相邻的像素顶点(例如,采用4邻域或8邻域连接)。每条边上的权重
w(p, q)代表了相邻像素p和q之间的非负相似度。相似度可以根据像素的灰度、颜色或纹理特征来计算,越相似则权重越大。 - 终端边:除了像素顶点,图中还有两个特殊的终端顶点,分别代表前景(Source, S) 和背景(Sink, T)。每个像素顶点
p都分别与S和T相连,形成两条边。
- 邻域边:连接相邻的像素顶点(例如,采用4邻域或8邻域连接)。每条边上的权重
这样,一张图像就被构造成了一个包含两类顶点和两类边的复杂网络。
所谓“割”,是指一个边的集合 C,如果将这些边从图中移除,会导致整个图被分割成两个互不连通的子集:一个包含 S,另一个包含 T。一个“割”的代价是这个集合中所有边权重之和。GraphCut 的目标就是找到所有可能的割中,代价最小的那个,即“最小割”。根据最大流最小割定理,最小割问题可以通过最大流算法高效求解。
找到最小割后,与 S 相连的像素被标记为前景,与 T 相连的像素被标记为背景,从而完成分割。下图直观展示了这一映射与分割过程:
图像像素平面: 对应的图结构:
┌───┬───┬───┐ S (前景源点)
│ p │ q │ │ ↑ ↑ ↑ ...
├───┼───┼───┤ │ │ │
│ │ │ │ p──q── ... (邻域边)
└───┴───┴───┘ ↓ ↓ ↓ ...
T (背景汇点)
一个可能的“割”(虚线):
S ----////---- p ---- q ---- ... (割断了S-p和p-q边)
C
T ------------ p ---- q ---- ... (p与T仍相连)
割断后,p与S断开,与T相连 => p被分为背景。能量函数的定义
如何将我们对“好的分割”的期望,用数学语言描述并融入到最小割的框架中呢?GraphCut 使用了一个基于能量最小化的模型。一个好的分割对应一个能量较低的状态。
能量函数 E(A) 由两部分组成:
其中 A 代表一种分割方案(即每个像素的标签分配),λ 是一个平衡两项权重的系数。
区域项 R(A):数据拟合惩罚
区域项 R(A) 衡量的是单个像素被赋予某个标签(前景或背景)的代价。它的思想是:一个像素的颜色如果更接近已知的前景颜色分布,那么它被分为前景的代价就应该小;反之,被分为背景的代价就大。
设 p 为某个像素,lp 为其标签(0 代表背景,1 代表前景)。区域项通常定义为:
这里 I_p 是像素 p 的颜色值。Pr(I_p | model) 表示该颜色值属于某个颜色模型的概率。概率越高,负对数就越小,惩罚也就越小。因此,算法会倾向于将像素分配给其颜色概率更高的那个类别。
在实际的交互式分割中,前景和背景的颜色模型(例如,颜色直方图或高斯混合模型)是由用户提供的“种子点”来初始化的。
边界项 B(A):分割边界平滑惩罚
边界项 B(A) 衡量的是相邻像素之间标签不一致的代价。它的目标是鼓励分割边界沿着图像中颜色、纹理等特征变化剧烈的地方通过,从而使分割结果在视觉上更平滑、更合理。
边界项通常定义为:
其中:
N是所有相邻像素对的集合。δ(·)是指示函数,仅当相邻像素p和q被赋予不同标签时,惩罚项才被激活。B_{p,q}是惩罚强度,它取决于像素p和q的相似度。(I_p - I_q)^2:像素间的颜色差异。差异越小(越相似),exp值越大,惩罚强度B越大。这意味着分割边界穿过了颜色均匀的区域,这是不希望的,因此要施以重罚。dist(p, q):像素间的空间距离(通常为1)。距离越近,惩罚强度越大。σ是一个常数,用于控制颜色差异的敏感度。
边界项的直观理解:如果两个相邻像素颜色非常接近,我们期望它们属于同一物体(前景或背景),因此给它们分配不同标签的代价很高。如果它们颜色差异很大,那它们很可能处于物体的边缘,此时分配不同标签的代价就很小。
权重的计算与算法流程
综合以上,我们可以总结出图中各类边的权重赋值规则。设 K 为一个极大的数(如所有边权重之和加1),代表“绝对不可切断”。
| 边的类型 | 连接顶点 | 权重 w | 含义 |
|---|---|---|---|
| 邻域边 | {p, q} (相邻像素) | B_{p,q} | 惩罚分割边界穿过均匀区域。颜色越相似,权重越大。 |
| 终端边 | {p, S} (像素-前景) | K | 如果 p 是用户指定的前景种子点,此边不可断。 |
0 | 如果 p 是用户指定的背景种子点,此边应断开。 | ||
λ · R_p(0) | 如果 p 是未知点,权重反映它属于背景的惩罚。R_p(0)小则权重小,易切断(归为背景)。 | ||
| 终端边 | {p, T} (像素-背景) | 0 | 如果 p 是用户指定的前景种子点,此边应断开。 |
K | 如果 p 是用户指定的背景种子点,此边不可断。 | ||
λ · R_p(1) | 如果 p 是未知点,权重反映它属于前景的惩罚。R_p(1)小则权重小,易切断(归为前景)。 |
基于上述权重构建好图之后,GraphCut 的标准算法流程如下:
- 用户交互:用户在图像上标记少量绝对属于前景和背景的种子点。
- 模型初始化:根据种子点的颜色,建立前景和背景的颜色概率模型(如直方图)。
- 建图:根据当前颜色模型和上述规则,为图中所有边计算权重。
- 求解最小割:在构建的图上运行最大流/最小割算法(如Boykov-Kolmogorov算法)。
- 分配标签:根据最小割的结果,将所有与
S连通的像素标记为前景,其余标记为背景。 - (可选)迭代优化:在更高级的变体(如GrabCut)中,会用新的分割结果更新颜色模型,然后回到步骤3迭代优化,直到能量函数收敛。
下面,我们用一个极度简化的数值例子来演示权重的计算思想。假设我们有一张2x2的灰度图像,用户指定了左上角为前景(S),右下角为背景(T)。
import numpy as np
# 定义一个极大的数 K
K = 1e6
# 假设的2x2图像灰度值
image = np.array([[100, 120],
[110, 50]], dtype=np.float32)
# 用户交互:种子点 (行, 列)
foreground_seed = (0, 0) # 左上角,值100
background_seed = (1, 1) # 右下角,值50
# 简化计算:区域项 R(p) 用灰度值到种子点灰度差的负指数模拟概率
# 前景模型“中心”假设为100,背景模型“中心”假设为50
def simple_likelihood(pixel_val, model_center):
# 简化:相似度与差值的负指数成正比
diff = abs(pixel_val - model_center)
return np.exp(-diff / 20.0) # 20是随意选的尺度参数
# 计算每个像素属于前景(F)和背景(B)的“概率”(未归一化)
prob_fg = np.zeros_like(image)
prob_bg = np.zeros_like(image)
for i in range(image.shape[0]):
for j in range(image.shape[1]):
prob_fg[i, j] = simple_likelihood(image[i, j], 100) # 前景中心100
prob_bg[i, j] = simple_likelihood(image[i, j], 50) # 背景中心50
print("像素灰度矩阵:")
print(image)
print("\n像素属于前景的‘似然’值 (基于种子点(100)):")
print(prob_fg)
print("\n像素属于背景的‘似然’值 (基于种子点(50)):")
print(prob_bg)
# 计算区域项惩罚 R(p) = -ln(probability),避免log(0),加小常数
epsilon = 1e-10
R_fg = -np.log(prob_fg + epsilon) # Rp(1)
R_bg = -np.log(prob_bg + epsilon) # Rp(0)
print("\n区域项惩罚 R_fg (标签为前景的代价):")
print(R_fg)
print("\n区域项惩罚 R_bg (标签为背景的代价):")
print(R_bg)
# 设定平衡参数 λ
lambda_val = 1.0
# 根据规则计算权重(仅示意几个关键边)
print("\n--- 关键边权重计算示意 ---")
# 1. 种子点相关的终端边
print(f"边 [前景种子(0,0) -> S]: w = {K} (极大,不可割)")
print(f"边 [前景种子(0,0) -> T]: w = {0} (应割断)")
print(f"边 [背景种子(1,1) -> S]: w = {0} (应割断)")
print(f"边 [背景种子(1,1) -> T]: w = {K} (极大,不可割)")
# 2. 未知点 (0,1) 的终端边
p = (0, 1)
R_fg_val = R_fg[p]
R_bg_val = R_bg[p]
print(f"\n未知像素 (0,1) 灰度={image[p]}:")
print(f" 边 [p -> S]: w = λ * R_bg(p) = {lambda_val} * {R_bg_val:.4f} = {lambda_val * R_bg_val:.4f}")
print(f" 边 [p -> T]: w = λ * R_fg(p) = {lambda_val} * {R_fg_val:.4f} = {lambda_val * R_fg_val:.4f}")
if R_fg_val < R_bg_val:
print(f" 分析:R_fg({R_fg_val:.4f}) < R_bg({R_bg_val:.4f}),说明该像素更可能属于前景。")
print(f" 因此边[p->T]权重({lambda_val * R_fg_val:.4f})较小,在最小割中更可能被切断,从而使p连接S(前景)。")
else:
print(f" 分析:R_bg({R_bg_val:.4f}) < R_fg({R_fg_val:.4f}),说明该像素更可能属于背景。")
print(f" 因此边[p->S]权重({lambda_val * R_bg_val:.4f})较小,在最小割中更可能被切断,从而使p连接T(背景)。")
# 3. 邻域边权重 B{p,q} 计算示意 (以(0,0)和(0,1)为例)
p_val = image[0,0]
q_val = image[0,1]
color_diff = abs(p_val - q_val)
sigma = 30.0 # 假设参数
B_pq = np.exp(- (color_diff ** 2) / (2 * sigma ** 2))
print(f"\n邻域边 [(0,0)-{p_val} <-> (0,1)-{q_val}]:")
print(f" 颜色差 = {color_diff}")
print(f" 边界项权重 B = exp(-({color_diff}^2)/(2*{sigma}^2)) = {B_pq:.4f}")
print(f" 意义:若两像素最终标签不同,割断此边将付出 {B_pq:.4f} 的代价。")像素灰度矩阵:
[[100. 120.]
[110. 50.]]
像素属于前景的‘似然’值 (基于种子点(100)):
[[1. 0.36787942]
[0.60653067 0.08208501]]
像素属于背景的‘似然’值 (基于种子点(50)):
[[0.08208501 0.03019738]
[0.04978707 1. ]]
区域项惩罚 R_fg (标签为前景的代价):
[[-0. 1. ]
[ 0.5 2.5]]
区域项惩罚 R_bg (标签为背景的代价):
[[ 2.5 3.5]
[ 3. -0. ]]
--- 关键边权重计算示意 ---
边 [前景种子(0,0) -> S]: w = 1000000.0 (极大,不可割)
边 [前景种子(0,0) -> T]: w = 0 (应割断)
边 [背景种子(1,1) -> S]: w = 0 (应割断)
边 [背景种子(1,1) -> T]: w = 1000000.0 (极大,不可割)
未知像素 (0,1) 灰度=120.0:
边 [p -> S]: w = λ * R_bg(p) = 1.0 * 3.5000 = 3.5000
边 [p -> T]: w = λ * R_fg(p) = 1.0 * 1.0000 = 1.0000
分析:R_fg(1.0000) < R_bg(3.5000),说明该像素更可能属于前景。
因此边[p->T]权重(1.0000)较小,在最小割中更可能被切断,从而使p连接S(前景)。
邻域边 [(0,0)-100.0 <-> (0,1)-120.0]:
颜色差 = 20.0
边界项权重 B = exp(-(20.0^2)/(2*30.0^2)) = 0.8007
意义:若两像素最终标签不同,割断此边将付出 0.8007 的代价。通过这个例子可以看到,权重的计算如何将我们对颜色相似性、用户交互先验的期望编码到图结构中,最终的最小割求解过程就是在所有可能的“分割方案”中,找到总权重代价最小的那一个。
📝 动手练一练
- 概念辨析:GraphCut 能量函数中的“区域项”和“边界项”分别主要影响分割结果的哪些方面?如果一幅图像的前景和背景颜色区分非常明显,但各自内部纹理复杂,你认为应该调高
λ来侧重区域项,还是调低λ来侧重边界项?为什么?
👉 点击查看参考答案
区域项主要影响单个像素的分类决策,它迫使像素的分类结果符合其颜色特征所归属的前景/背景概率模型。边界项主要影响分割边界的平滑程度,它鼓励分割边界沿着图像中颜色、纹理等特征发生突变的区域通过,避免边界穿过均匀区域。
在所述场景下(前景背景颜色区分明显,内部纹理复杂),区域项可以非常可靠地将像素按颜色分开。复杂的内部纹理会削弱基于颜色差异的边界线索,使边界项难以正确引导分割,甚至把物体内部误当作边界。因此,应该调高 λ,更加侧重区域项,让颜色信息起主导作用,从而获得更准确的分割。
- 权重计算:假设在构建图时,对于用户明确标记为背景的种子像素
p,它连接到源点S(前景)的边权重被设置为0,而连接到汇点T(背景)的边权重被设置为一个极大的数K。请从最小割的角度解释,为什么这样设置能保证该像素在最终分割结果中一定被标记为背景?
👉 点击查看参考答案
在最小割问题中,我们需要割断一些边,使得 S 和 T 不连通,并且割断的边总权重最小。
对于背景种子像素 p:
- 边
{p, S}权重为0。割断这条边的代价为0。 - 边
{p, T}权重为K(极大)。割断这条边的代价非常大。
为了保证 S 和 T 不连通,像素 p 不能同时与 S 和 T 都有通路。有两种割法:
- 割断
{p, S}(代价0),保留{p, T}。这样p与T连通,属于背景子集。 - 割断
{p, T}(代价K),保留{p, S}。这样p与S连通,属于前景子集。
由于最小割追求总代价最小,方案1的代价(0)远小于方案2的代价(K)。因此,算法必然会选择方案1,即该像素 p 最终一定被划分到包含 T 的背景子集中。这样就用极大的权重 K 强制实现了用户先验。
本章小结
本节我们深入探讨了基于图论的经典分割算法——GraphCut。它将图像分割转化为图的最小割问题,通过精心设计的能量函数(区域项与边界项)来形式化我们对“好分割”的定义,并利用用户提供的少量交互信息作为先验,最终通过最大流/最小割算法求得全局能量最优的分割结果。
GraphCut 及其改进算法 GrabCut 在深度学习普及之前,是交互式图像分割领域的标杆。其核心的“能量最小化”思想和将视觉问题转化为图优化问题的思路,至今仍在许多计算机视觉任务中有所体现。
📋 行动清单
- 理解映射关系:能够清晰复述如何将一幅图像及其像素关系映射为带权无向图,并说明两类顶点和两类边的作用。
- 掌握能量函数:能够写出 GraphCut 的能量函数公式,并解释区域项和边界项每一项的物理意义及计算方法。
- 分析权重设置:给定一个简单的图像和用户交互示例,能够推导出图中关键边(种子点终端边、未知点终端边、邻域边)的权重设置逻辑。
—— 小象教研组