《A Closed-Form Solution to Natural Image Matting》【1】是作者:Anat Levin, Dani Lischinski, and Yair Weiss等人在2008年2月的一篇文章,它所用抠图的方法是文章《Semantic Soft Segmentation》(2018,来自MIT CSAIL的YAĞIZ AKSOY等人)的基础。
在文中,image matting(抠图)问题被归结为求解compositing equation:
(1)式中 是像素点,是已知的,未知数有: ,其中 表示透明度, 分别为前景颜色和背景颜色。
(1)是一个严重的欠约束的方程组:假如图像有N个像素点,则可以列出N个方程,但有3N个未知数。
为求解抠图所用模板—— ,它是矢量,每一个像素对应它的一个元素 ,如图1和图2:
图1、 原图
图2 、 模板
为求 模板,需要给公式(1)添加一些约束:
1、用户给出trimap(三区标注图,图中标签有三种:Foreground、Background、Unknown),或手动粗略(Scribbles)地指出前景与背景,Trimap实例如下图所示:
图3、 白色为前景区域、黑色为背景区域、灰色为未知区域
图4、Scribbles图,白色为前景,黑色为背景,中间部分为未知区域
2、假设:在一个小的窗口(window,用w表示)中,前景(FG)与背景(BG)是一个常数,分别为F和B,于是(1)式可以转换成为:
此处的约等于“ ”是因为假设了F和B是常数,由此引入的。
针对公式(2),该文章中有一段话是这样的:
为什么(2)式就 suggests 最小化这个代价函数(3)呢?
这是整篇文章的关键,是后续推导的基础,我的理解是这样的:
一、对于一幅特定的图,前景区域和后景区域是确定的
假设一个window是3*3矩阵,有四种情况:
CASE 1: 该w都在Trimap的前景或背景中,则F和B以及
都是已知的,这些像素可被认为是“约束点(Constraint Points)”,在前景中,
;若在背景中,
。
CASE 2: 在w中,既有B约束点,又有F约束点,则F和B已知,由(2)构成的方程组与未知的
的个数相同,可有唯一解。
CASE 3: 在w中,只有F约束点(或B约束点)和U点(Unknown Pixel),则未知变量数量比方程式数量多1,有无限多解。
CASE 4: 在w中,只有U点,则未知数数量比方程式数量多2,没有唯一解。
若我们的滑动窗从CASE _1和CASE_2开始移动,会出现CASE_3,然后出现CASE_4。
如果在CASE_3中,我们为window中任一个U点(像素i),设定
值,则CASE_3方程组有唯一解。当窗口从CASE_3进入CASE_4时,若前面CASE_3的点都已经确定,则此时CASE_4的window的F和B也已经确定(来自上一次滑动窗口),因而也可求出所有的点的
。如此F和B信息可以通过新的窗口传递下去,最后可计算出所有的点的透明度
值。
观察上述迭代过程,矢量 会在迭代过程中发生变化。这是因为我们在计算一个窗体 (以像素i为中心的window)的 时,认为 和 是常数,由上一个滑动窗继承过来,然而若滑动的路径不同,即使是同一点上算出的 也可能不同。随机游动,产生了随机序列 。
这是一个马尔可夫过程,滑动窗口随机游走,每滑动一次,计算一次
,用
表示。如果输入图片有唯一最优抠图(人能够将所要前景抠图出来),则存在收敛值,随机游动得到的
最后可以稳定下来。因为
所处空间是完备空间,因而存在柯西序列性质,则:
可根据公式(4)设计随机游走方案(
关键在于把
看作是柯西序列
),只要迭代步数足够多,前后两次迭代所得的矢量距离就会趋向0,此时所得矢量
便是最优
。这样的算法前人已经提出,也可取得较好的抠图效果,但不足之处是速度太慢,这是所有MonteCarlo方法都具有的。
透明度收敛,即前景与背景是确定的,反之亦然。
二、问题归结为求 二次型的凸优化
【1】文最突出的贡献是给出最优
解析解,不需要经多次迭代就可得到。思想是这样的:
既然矢量
存在最优,令像素
上的透明度最优值为
,根据公式(2)得到它的一个估计值:
,两者之间的距离定义为:
。
若滑动窗体的size为N*N(例如:N=3),一个窗体有N*N个像素,亦即可计算N*N个估计值,这些估计值与最优
之间距离的和是公式(5),随着随机游动的次数增加,最后会收敛,并达到最小值。因此,公式(5)可作为一个窗体透明度与它的最优值之间距离:
以每个像素为中心点作window,窗体与像素一一对应,因而图像 估计的透明度与最优透明度之间总距离可以表述为所有窗体距离的和:
公式(6)加上一个为平滑用的正则项( ),就得到了公式(3)。问题:
为什么(2)式就 suggests 最小化这个代价函数(3)呢?
得解。
3、求透明度的解析解
公式(3)我们重新抄一次,如下:
假设窗体 的size是3*3,其像素为 ,其前景和背景颜色为: ,根据公式(2)有:
令:
将(7)代入(3)有:
目标是求解能令(8)取最小值的 ,问题等价于两个连续的求最小值步骤:
(1)给定 ,求 的最小值:
(2)求
由公式(7)有:
将(9)代入(8)有:
公式(10)中,L被称为Laplacian Matrix, 是 最后一个元素后增添0后的扩展矢量,因而L是 去掉最后一行和一列的矩阵,它的每个元素是可确定的:
其中 表示window中像素个数, 表示窗体像素的平均值, 表示窗体像素的方差, 表示狄利克雷函数。
于是, 可以表示成为透明度矢量 的二次型,再加上限制点约束条件,问题最后转化成带有约束条件的凸优化问题:
(12)中 是对角矩阵,其对角线元素对应图中每个点,限制点(标注出是FG或BG的点)上为1值,而其他非限制点均为0。 是与 相同维度的列矢量,限制点上为相应 值,其余为0。由此,可定义Loss为:
Loss对 求偏导,并置零:
(14)给出了透明度 的解析解。
4、将该方法扩展至彩色图片
彩色图像
可以看成是三个单色 Channel 的合并(Concatenation),透明度在这三个Channel中保持一致,于是根据(1)有:
我们希望彩色图像透明度问题也能象单色图像透明度问题那样转化为求 二次型凸优化问题。方法之关键在于找到公式(5)那样的小窗体(little window)透明度的估计值与其最优值之间距离,即要从(15)中得到 的估计值。(虽然我们不知道最优值,但我们知道其存在,这个前面叙述有说明,若能构建出估计值,则二次型成矣。)由(15)得到:
(16)说明的是矢量 和矢量 是线性关系,这个约束太强:若两个矢量不是线性,则 只能为0。因而【1】引入了“两色线模型”(color line model),如图所示:
图5 “前景-背景”双色线性模型
该模型有如下几个近似:
- 在小窗体中,前景部分可以用一条在 空间的直线段表示;
- 线段可以由两个端点确定,其他部分由端点间的线性插值确定;
- 背景部分也可以用同样的方式表达。
因而有:
其中, 分别对应小窗体前景的两个颜色矢量, 分别对应小窗体背景的两个颜色矢量, 分别对应前景和背景插值斜率。代入(16)有:
令
(19)是一个线性方程组,若有解的话,要求 与其增广矩阵 的秩相等。此处,我没有讨论这个方程组是否一定有非平凡解,但若 满秩的话,则一定有解。放宽了约束条件后, 是 的第一行元素,因而有:
(20)式是矢量
的每一个元素的估计,参考上述分析(公式(6)),定义最优值与估算值距离:
与单色图像分析一致,问题可以转换为二次型凸优化,有:
后续计算方法与单色方法相同,参考公式(12)、(13)、(14)。
在GitHub上有实现代码: https://github.com/MarcoForte/closed-form-matting
以下摘抄一段以助理解:
def compute_laplacian(img, mask=None, eps=10**(-7), win_rad=1):
"""Computes Matting Laplacian for a given image.
Args:
img: 3-dim numpy matrix with input image
mask: mask of pixels for which Laplacian will be computed.
If not set Laplacian will be computed for all pixels.
eps: regularization parameter controlling alpha smoothness
from Eq. 12 of the original paper. Defaults to 1e-7.
win_rad: radius of window used to build Matting Laplacian (i.e.
radius of omega_k in Eq. 12).
Returns: sparse matrix holding Matting Laplacian.
"""
win_size = (win_rad * 2 + 1) ** 2
h, w, d = img.shape
# Number of window centre indices in h, w axes
c_h, c_w = h - 2 * win_rad, w - 2 * win_rad
win_diam = win_rad * 2 + 1
indsM = np.arange(h * w).reshape((h, w))
ravelImg = img.reshape(h * w, d)
win_inds = _rolling_block(indsM, block=(win_diam, win_diam))
win_inds = win_inds.reshape(c_h, c_w, win_size)
if mask is not None:
mask = cv2.dilate(
mask.astype(np.uint8),
np.ones((win_diam, win_diam), np.uint8)
).astype(np.bool)
win_mask = np.sum(mask.ravel()[win_inds], axis=2)
win_inds = win_inds[win_mask > 0, :]
else:
win_inds = win_inds.reshape(-1, win_size)
winI = ravelImg[win_inds]
win_mu = np.mean(winI, axis=1, keepdims=True)
win_var = np.einsum('...ji,...jk ->...ik', winI, winI) / win_size - np.einsum('...ji,...jk ->...ik', win_mu, win_mu)
inv = np.linalg.inv(win_var + (eps/win_size)*np.eye(3))
X = np.einsum('...ij,...jk->...ik', winI - win_mu, inv)
vals = np.eye(win_size) - (1.0/win_size)*(1 + np.einsum('...ij,...kj->...ik', X, winI - win_mu))
nz_indsCol = np.tile(win_inds, win_size).ravel()
nz_indsRow = np.repeat(win_inds, win_size).ravel()
nz_indsVal = vals.ravel()
L = scipy.sparse.coo_matrix((nz_indsVal, (nz_indsRow, nz_indsCol)), shape=(h*w, h*w))
return L
def closed_form_matting_with_prior(image, prior, prior_confidence, consts_map=None):
"""Applies closed form matting with prior alpha map to image.
Args:
image: 3-dim numpy matrix with input image.
prior: matrix of same width and height as input image holding apriori alpha map.
prior_confidence: matrix of the same shape as prior hodling confidence of prior alpha.
consts_map: binary mask of pixels that aren't expected to change due to high
prior confidence.
Returns: 2-dim matrix holding computed alpha map.
"""
assert image.shape[:2] == prior.shape, ('prior must be 2D matrix with height and width equal '
'to image.')
assert image.shape[:2] == prior_confidence.shape, ('prior_confidence must be 2D matrix with '
'height and width equal to image.')
assert (consts_map is not None) or image.shape[:2] == consts_map.shape, (
'consts_map must be 2D matrix with height and width equal to image.')
logging.info('Computing Matting Laplacian.')
laplacian = compute_laplacian(image, ~consts_map if consts_map is not None else None)
confidence = scipy.sparse.diags(prior_confidence.ravel())
logging.info('Solving for alpha.')
solution = scipy.sparse.linalg.spsolve(
laplacian + confidence,
prior.ravel() * prior_confidence.ravel()
)
alpha = np.minimum(np.maximum(solution.reshape(prior.shape), 0), 1)
return alpha
上述代码中的一些解释:
1、numpy.einsum(subscripts, *operands, out=None, dtype=None, order=’K’, casting=’safe’, optimize=False)
解释:
einsum全称为Einstein summation convention,是一种求和的范式,在很多基于多维张量的张量运算库,如numpy,tensorflow,pytorch中都有所应用。einsum可以用一种很简单的,统一的方式去表示很多多维张量的运算。详细的解释可以参考:
https://blog.csdn.net/LoseInVain/article/details/81143966
2、scipy.sparse.linalg.spsolve
scipy.sparse.linalg.spsolve(A, b, permc_spec=None, use_umfpack=True)
Solve the sparse linear system Ax=b, where b may be a vector or a matrix.
由上述代码可见函数 closed_form_matting_with_prior() 的求解过程与公式(14)是一致的。
参考文献:
【1】《A Closed-Form Solution to Natural Image Matting》