一、论文介绍
Poisson Image Editing这篇论文使用泊松方程,实现了一种图像融合算法,将一张图像的某个区域无缝地复制到另一张图像中。如下图所示。
可以看到cloning是经过简单复制得到的图像,从源图像中抠出的区域并不能与目标图像很好地融合。右图是使用本文中提出的算法,经过无缝复制后,从源图像的抠出的区域可以无缝地导入目标图像中。
二、一些先验知识
1.泊松方程
泊松方程是泊松在物理学领域提出的一个偏微分方程,其形式为:
△
f
=
Ω
\bigtriangleup f=\Omega
△f=Ω
其中
△
\bigtriangleup
△表示二阶微分即拉普拉斯算子,
Ω
\Omega
Ω为已知量。
对于泊松方程的求解,论文中提到了给定狄利克雷边界条件的泊松方程解法,即给定函数
f
f
f在边界处的实际值。
2.Laplace算子
在二维图像中,图像函数
f
f
f是离散函数,拉普拉斯算子为如下卷积核形式:
[
0
1
0
1
−
4
1
0
1
0
]
\begin{bmatrix} 0& 1& 0\\ 1& -4& 1\\ 0& 1& 0 \end{bmatrix}
⎣⎡0101−41010⎦⎤
比如要计算
(
i
,
j
)
(i,j)
(i,j)处的像素点的二阶微分:
△
f
(
i
,
j
)
=
[
0
1
0
1
−
4
1
0
1
0
]
.
[
f
(
i
−
1
,
j
−
1
)
f
(
i
−
1
,
j
)
f
(
i
−
1
,
j
+
1
)
f
(
i
,
j
−
1
)
f
(
i
,
j
)
f
(
i
,
j
+
1
)
f
(
i
+
1
,
j
−
1
)
f
(
i
+
1
,
j
)
f
(
i
+
1
,
j
+
1
)
]
\bigtriangleup f(i,j)=\begin{bmatrix} 0& 1& 0\\ 1& -4& 1\\ 0& 1& 0 \end{bmatrix}.\begin{bmatrix} f(i-1,j-1)& f(i-1,j)& f(i-1,j+1)\\ f(i,j-1)& f(i,j)& f(i,j+1)\\ f(i+1,j-1)& f(i+1,j)& f(i+1,j+1) \end{bmatrix}
△f(i,j)=⎣⎡0101−41010⎦⎤.⎣⎡f(i−1,j−1)f(i,j−1)f(i+1,j−1)f(i−1,j)f(i,j)f(i+1,j)f(i−1,j+1)f(i,j+1)f(i+1,j+1)⎦⎤
三、算法原理
算法的目的是将一张图像的某一个区域与另一张图像更好地融合,当两张图像梯度(图像灰度的变化率)越接近,则说明两张图像融合得越好。即最小化如下公式:
m
i
n
∬
∣
▽
f
−
v
∣
2
min\iint \left | \bigtriangledown f-v \right | ^{2}
min∬∣▽f−v∣2
其中
v
v
v是源图像的梯度,
▽
f
\bigtriangledown f
▽f是目标图像的梯度。
要使得该公式最小,即
△
f
=
▽
v
\bigtriangleup f=\bigtriangledown v
△f=▽v
如上图所示,将源图像中脸部区域复制到目标图像中,经过简单的复制,得到右图所示的cloing图像,这个过程可以抽象为如下图所示的模型。
图中,
g
g
g是源图像圈出区域的图像函数,
v
v
v是设置的与源图像有关的梯度向量场,
v
=
▽
g
v=\bigtriangledown g
v=▽g
则
▽
v
=
△
f
\bigtriangledown v=\bigtriangleup f
▽v=△f.
Ω
\Omega
Ω是从源图像中抠出的部分区域,
f
f
f是该区域像素的像素值;
S
S
S是目标图像中除了
Ω
\Omega
Ω区域外的部分,
f
∗
f*
f∗是该区域像素的像素值。
∂
Ω
\partial \Omega
∂Ω是
Ω
\Omega
Ω区域的边界,边界像素值是对应的目标图像该处的像素值:
f
∣
∂
Ω
=
f
∣
∂
Ω
∗
f_{\mid \partial \Omega}=f_{\mid \partial \Omega}^{*}
f∣∂Ω=f∣∂Ω∗
这样就构造了一个具有狄利克雷边界条件的泊松方程: △ f = △ g \bigtriangleup f=\bigtriangleup g △f=△g w i t h with with f ∣ ∂ Ω = f ∣ ∂ Ω ∗ f_{\mid \partial \Omega}=f_{\mid \partial \Omega}^{*} f∣∂Ω=f∣∂Ω∗
四、一个例子
如下图所示,左图表示
4
∗
4
4*4
4∗4的源图像,右图是
4
∗
4
4*4
4∗4的目标图像,将左图中矩形框框住的区域复制到右图白色区域中,源图像
g
1
,
.
.
.
,
g
16
g_{1},...,g_{16}
g1,...,g16已知,目标图像中
f
1
,
.
.
.
,
f
16
f_{1},...,f_{16}
f1,...,f16已知,要求的是
x
1
,
x
2
,
x
3
,
x
4
x_{1},x_{2},x_{3},x_{4}
x1,x2,x3,x4
根据以上列出的泊松方程列出如下式子:
−
4
x
1
+
x
2
+
x
3
+
f
2
+
f
5
=
−
4
g
6
+
g
2
+
g
5
+
g
7
+
g
10
-4 x_{1}+x_{2}+x_{3}+f_{2}+f_{5}=-4 g_{6}+g_{2}+g_{5}+g_{7}+g_{10}
−4x1+x2+x3+f2+f5=−4g6+g2+g5+g7+g10
−
4
x
2
+
x
1
+
x
4
+
f
3
+
f
8
=
−
4
g
7
+
g
3
+
g
6
+
g
8
+
g
11
-4 x_{2}+x_{1}+x_{4}+f_{3}+f_{8}=-4 g_{7}+g_{3}+g_{6}+g_{8}+g_{11}
−4x2+x1+x4+f3+f8=−4g7+g3+g6+g8+g11
−
4
x
3
+
x
1
+
x
4
+
f
9
+
f
14
=
−
4
g
10
+
g
6
+
g
9
+
g
11
+
g
14
-4 x_{3}+x_{1}+x_{4}+f_{9}+f_{14}=-4 g_{10}+g_{6}+g_{9}+g_{11}+g_{14}
−4x3+x1+x4+f9+f14=−4g10+g6+g9+g11+g14
−
4
x
4
+
x
2
+
x
3
+
f
12
+
f
15
=
−
4
g
11
+
g
7
+
g
10
+
g
12
+
g
15
-4 x_{4}+x_{2}+x_{3}+f_{12}+f_{15}=-4 g_{11}+g_{7}+g_{10}+g_{12}+g_{15}
−4x4+x2+x3+f12+f15=−4g11+g7+g10+g12+g15
经过简单移项可写成以下矩阵形式:
[
−
4
1
1
0
1
−
4
0
1
1
0
−
4
1
0
1
1
−
4
]
[
x
1
x
2
x
3
x
4
]
=
[
−
4
g
6
+
g
2
+
g
5
+
g
7
+
g
10
−
f
2
−
f
5
−
4
g
7
+
g
3
+
g
6
+
g
8
+
g
11
−
f
3
−
f
8
−
4
g
10
+
g
6
+
g
9
+
g
11
+
g
14
−
f
9
−
f
14
−
4
g
11
+
g
7
+
g
10
+
g
12
+
g
15
−
f
12
−
f
15
]
\left[\begin{array}{cccc} -4 & 1 & 1 & 0 \\ 1 & -4 & 0 & 1 \\ 1 & 0 & -4 & 1 \\ 0 & 1 & 1 & -4 \end{array}\right]\left[\begin{array}{l} x_{1} \\ x_{2} \\ x_{3} \\ x_{4} \end{array}\right]=\left[\begin{array}{c} -4 g_{6}+g_{2}+g_{5}+g_{7}+g_{10}-f_{2}-f_{5} \\ -4 g_{7}+g_{3}+g_{6}+g_{8}+g_{11}-f_{3}-f_{8} \\ -4 g_{10}+g_{6}+g_{9}+g_{11}+g_{14}-f_{9}-f_{14} \\ -4 g_{11}+g_{7}+g_{10}+g_{12}+g_{15}-f_{12}-f_{15} \end{array}\right]
⎣⎢⎢⎡−41101−40110−41011−4⎦⎥⎥⎤⎣⎢⎢⎡x1x2x3x4⎦⎥⎥⎤=⎣⎢⎢⎡−4g6+g2+g5+g7+g10−f2−f5−4g7+g3+g6+g8+g11−f3−f8−4g10+g6+g9+g11+g14−f9−f14−4g11+g7+g10+g12+g15−f12−f15⎦⎥⎥⎤
4个方程式解4个未知数,可得到唯一解。
五、具体实现
代码核心在于构建线性系统 A x = b Ax=b Ax=b
int o_start_r = 32, o_start_c = 14, d_start_r = 76, d_start_c = 550;
int h = 64, w = 200;
void BuildSystem() {
SparseMatrix<double> A(h * w, h * w);
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
int index = i * w + j;
A.coeffRef(index, index) = -4;
if (i != 0) A.coeffRef(index, (i - 1) * w + j) = 1;
if (i != h - 1) A.coeffRef(index, (i + 1) * w + j) = 1;
if (j != 0) A.coeffRef(index, i * w + j - 1) = 1;
if (j != w - 1) A.coeffRef(index, i * w + j + 1) = 1;
}
}
Image o_img("./data/original.jpg");
Image d_img("./data/destination.png");
VectorXd R(h * w); VectorXd G(h * w); VectorXd B(h * w);
R.setZero(); G.setZero(); B.setZero();
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
int o_r = o_start_r + i, o_c = o_start_c + j;
int d_r = d_start_r + i, d_c = d_start_c + j;
Vec3d div_RGB = o_img.GetDivAt(o_r, o_c);
if (j == 0)
div_RGB -= d_img.colors[d_r][d_c - 1];
if (j == w - 1)
div_RGB -= d_img.colors[d_r][d_c + 1];
if (i == 0)
div_RGB -= d_img.colors[d_r - 1][d_c];
if (i == h - 1)
div_RGB -= d_img.colors[d_r + 1][d_c];
R(i * w + j) = div_RGB[0];
G(i * w + j) = div_RGB[1];
B(i * w + j) = div_RGB[2];
}
}
SparseLU<Eigen::SparseMatrix<double>, Eigen::COLAMDOrdering<int> > solver;
solver.compute(A);
VectorXd r = solver.solve(R);
VectorXd g = solver.solve(G);
VectorXd b = solver.solve(B);
for (int i = 0; i < h * w; i++) {
int r_ = d_start_r + i / w;
int c_ = d_start_c + i % w;
//保证RGB值在0~255
double new_r = min(max(r(i), 0.0), 255.0);
double new_g = min(max(g(i), 0.0), 255.0);
double new_b = min(max(b(i), 0.0), 255.0);
d_img.colors[r_][c_] = Vec3d(new_r, new_g, new_b);
}
d_img.Save("./data/result.jpg");
waitKey(0);
}
int main() {
BuildSystem();
return 0;
}
六、效果图