Colorization Using Optimization这篇论文中介绍了一种简单而有效的灰度图像着色算法:在 Y U V YUV YUV色彩空间中,对灰度图像进行简单着色,再求解其他未知的像素点,填充到目标图像中得到彩色图像。
Y U V YUV YUV色彩空间中, Y Y Y表示图像的灰度, U 、 V U、V U、V表示图像的色度。灰度图像在 Y U V YUV YUV色彩空间中 Y Y Y值已知而 U 、 V U、V U、V值为 0 0 0,经过简单着色后的图像 Y Y Y值已知且部分 U 、 V U、V U、V值未知,最后得到的目标图像 Y 、 U 、 V Y、U、V Y、U、V值均已知。
RGB格式转化为YUV格式:
Y = 0.299 × R + 0.587 × G + 0.114 × B Y = 0.299 \times R + 0.587 \times G + 0.114 \times B Y=0.299×R+0.587×G+0.114×B
U = − 0.147 × R − 0.289 × G + 0.436 × B U = -0.147 \times R - 0.289 \times G + 0.436 \times B U=−0.147×R−0.289×G+0.436×B
V = 0.615 × R − 0.515 × G − 0.100 × B V = 0.615 \times R - 0.515 \times G - 0.100 \times B V=0.615×R−0.515×G−0.100×B
YUV格式转化为RGB格式:
R = Y + 1.140 × V R = Y + 1.140 \times V R=Y+1.140×V
G = Y − 0.395 × U − 0.581 × V G = Y - 0.395 \times U - 0.581 \times V G=Y−0.395×U−0.581×V
B = Y + 2.032 × U B = Y + 2.032 \times U B=Y+2.032×U
算法的输入:灰度图像和简单着色后的图像
算法的输出:彩色图像
算法基于一个前提: Y U V YUV YUV色彩空间下, Y Y Y值相似的相邻像素点,其 U V UV UV值也应该相似。
如何来描述这种相似呢?论文中用到了成本函数 J ( U ) J(U) J(U)和 J ( V ) J(V) J(V):
J ( U ) = ∑ r ( U r − ∑ s ∈ N ( r ) w r s U s ) 2 J(U)=\sum_{r}(U_{r}-\sum_{s\in N(r)}w_{rs}U_{s})^2 J(U)=r∑(Ur−s∈N(r)∑wrsUs)2
J ( V ) = ∑ r ( V r − ∑ s ∈ N ( r ) w r s V s ) 2 J(V)=\sum_{r}(V_{r}-\sum_{s\in N(r)}w_{rs}V_{s})^2 J(V)=r∑(Vr−s∈N(r)∑wrsVs)2
这样就把问题转化为最小化成本函数的优化问题。直观来看,当括号中的内容等于 0 0 0时,成本函数能取得最小值 0 0 0。
U r − ∑ s ∈ N ( r ) w r s U s = 0 U_{r}- \sum_{s\in N(r)} w_{rs}U_{s}=0 Ur−s∈N(r)∑wrsUs=0
V r − ∑ s ∈ N ( r ) w r s V s = 0 V_{r}- \sum_{s\in N(r)} w_{rs}V_{s}=0 Vr−s∈N(r)∑wrsVs=0
其中权重函数可以通过下式计算(每一个像素点与其邻域像素点的权重值需要有归一化的过程)。
W r s ∝ e − ( Y r − Y s ) 2 2 σ r 2 W_{rs}\propto e^{-\frac{(Y_{r}-Y_{s})^2}{2\sigma _{r}^2 } } Wrs∝e−2σr2(Yr−Ys)2
用一个例子来描述算法的大致过程,如图所示是一个 3 ∗ 3 3*3 3∗3的图像, 2 、 6 、 7 2、6、7 2、6、7处已着色,我们要做的工作就是求解出其他每一个未知的 U V UV UV值。
用 x 1 x_{1} x1, x 2 x_{2} x2, x 3 x_{3} x3, x 4 x_{4} x4, x 5 x_{5} x5, x 6 x_{6} x6, x 7 x_{7} x7, x 8 x_{8} x8, x 9 x_{9} x9来表示每一处要求解的U值。
对于第一个像素点,遍历其邻域坐标计算出权重 w 12 w_{12} w12, w 14 w_{14} w14, w 15 w_{15} w15,有: x 1 − w 12 x 2 − w 14 x 4 − w 15 x 5 = 0 x_{1}-w_{12}x_{2}-w_{14}x_{4}-w_{15}x_{5}=0 x1−w12x2−w14x4−w15x5=0
对于第二个像素点,该像素点已知,则有: x 2 = u 2 x_{2}=u_{2} x2=u2
对于第三个像素点,遍历其邻域坐标计算出权重 w 32 w_{32} w32, w 35 w_{35} w35, w 36 w_{36} w36,有: x 3 − w 32 x 2 − w 35 x 5 − w 36 x 6 = 0 x_{3}-w_{32}x_{2}-w_{35}x_{5}-w_{36}x_{6}=0 x3−w32x2−w35x5−w36x6=0
对于第四个像素点,遍历其邻域坐标计算出权重 w 41 w_{41} w41, w 42 w_{42} w42, w 45 w_{45} w45, w 47 w_{47} w47, w 48 w_{48} w48,有: x 4 − w 41 x 1 − w 42 x 2 − w 45 x 5 − w 47 x 7 − w 48 x 8 = 0 x_{4}-w_{41}x_{1}-w_{42}x_{2}-w_{45}x_{5}-w_{47}x_{7}-w_{48}x_{8}=0 x4−w41x1−w42x2−w45x5−w47x7−w48x8=0
对于第五个像素点,遍历其邻域坐标计算出权重 w 51 w_{51} w51, w 52 w_{52} w52, w 53 w_{53} w53, w 54 w_{54} w54, w 56 w_{56} w56, w 57 w_{57} w57, w 58 w_{58} w58, w 58 w_{58} w58有: x 5 − w 51 x 1 − w 52 x 2 − w 53 x 3 − w 54 x 4 − w 56 x 6 − w 57 x 7 − w 58 x 8 − w 59 x 9 = 0 x_{5}-w_{51}x_{1}-w_{52}x_{2}-w_{53}x_{3}-w_{54}x_{4}-w_{56}x_{6}-w_{57}x_{7}-w_{58}x_{8}-w_{59}x_{9}=0 x5−w51x1−w52x2−w53x3−w54x4−w56x6−w57x7−w58x8−w59x9=0
对于第六个像素点,该像素点已知,则有: x 6 = u 6 x_{6}=u_{6} x6=u6
对于第七个像素点,该像素点已知,则有: x 7 = u 7 x_{7}=u_{7} x7=u7
对于第八个像素点,遍历其邻域坐标计算出权重 w 84 w_{84} w84, w 85 w_{85} w85, w 86 w_{86} w86, w 87 w_{87} w87, w 89 w_{89} w89,有: x 8 − w 84 x 4 − w 85 x 5 − w 86 x 6 − w 87 x 7 − w 89 x 9 = 0 x_{8}-w_{84}x_{4}-w_{85}x_{5}-w_{86}x_{6}-w_{87}x_{7}-w_{89}x_{9}=0 x8−w84x4−w85x5−w86x6−w87x7−w89x9=0
对于第九个像素点,遍历其邻域坐标计算出权重 w 95 w_{95} w95, w 96 w_{96} w96, w 98 w_{98} w98,有: x 9 − w 95 x 5 − w 96 x 6 − w 98 x 8 = 0 x_{9}-w_{95}x_{5}-w_{96}x_{6}-w_{98}x_{8}=0 x9−w95x5−w96x6−w98x8=0
联立以上方程组,移项并写成矩阵形式:
( 1 0 0 − w 14 − w 15 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 − w 35 0 0 0 0 − w 41 0 0 1 − w 45 0 0 − w 48 0 − w 51 0 − w 53 − w 54 1 0 0 − w 58 − w 59 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 − w 84 − w 85 0 0 1 − w 89 0 0 0 0 − w 95 0 0 − w 98 1 ) ( x 1 x 2 x 3 x 4 x 5 x 6 x 7 x 8 x 9 ) = ( w 12 u 2 u 2 w 32 u 2 + w 36 u 6 w 42 u 2 + w 47 u 7 w 52 u 2 + w 57 u 7 u 6 u 7 w 86 u 6 + w 87 u 7 w 9 6 u 6 ) \begin{pmatrix} 1 & 0 & 0 & -w_{14} & -w_{15} & 0 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 1 & 0 & -w_{35} & 0 & 0 & 0 & 0 \\ -w_{41} & 0 & 0 & 1 & -w_{45} & 0 & 0 & -w_{48} & 0 \\ -w_{51} & 0 & -w_{53} & -w_{54} & 1 & 0 & 0 & -w_{58} & -w_{59} \\ 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & -w_{84} & -w_{85} & 0 & 0 & 1 & -w_{89} \\ 0 & 0 & 0 & 0 & -w_{95} & 0 & 0 & -w_{98} & 1 \end{pmatrix}\begin{pmatrix} x_{1} \\ x_{2} \\ x_{3} \\ x_{4} \\ x_{5} \\ x_{6} \\ x_{7} \\ x_{8} \\ x_{9} \end{pmatrix}=\begin{pmatrix} w_{12}u_{2} \\u_{2} \\w_{32}u_{2}+w_{36}u_{6} \\w_{42}u_{2}+w_{47}u_{7} \\w_{52}u_{2}+w_{57}u_{7} \\u_{6} \\u_{7} \\w_{86}u_{6}+w_{87}u_{7} \\w_{9{\tiny } 6}u_{6} \end{pmatrix} ⎝⎜⎜⎜⎜⎜⎜⎜⎜⎜⎜⎜⎜⎛100−w41−w5100000100000000010−w530000−w14001−w5400−w840−w150−w35−w45100−w85−w95000001000000000100000−w48−w58001−w980000−w5900−w891⎠⎟⎟⎟⎟⎟⎟⎟⎟⎟⎟⎟⎟⎞⎝⎜⎜⎜⎜⎜⎜⎜⎜⎜⎜⎜⎜⎛x1x2x3x4x5x6x7x8x9⎠⎟⎟⎟⎟⎟⎟⎟⎟⎟⎟⎟⎟⎞=⎝⎜⎜⎜⎜⎜⎜⎜⎜⎜⎜⎜⎜⎛w12u2u2w32u2+w36u6w42u2+w47u7w52u2+w57u7u6u7w86u6+w87u7w96u6⎠⎟⎟⎟⎟⎟⎟⎟⎟⎟⎟⎟⎟⎞
接下来的问题就是求解以上线性方程组,得到其他未知像素点的 U U U值( V V V值的求解同理)。
使用VS+opencv+eigen来实现该算法。通过以上的例子可以知道,算法的主要工作是构建一个线性系统( W x = u , W x = v Wx=u,Wx=v Wx=u,Wx=v)。
class Colorization {
private:
Image gray_img; //输入灰度图
Image sketch_img; //部分着色图
Mat mask; //差异图像
Image dest_img; //输出彩色图
SparseMatrix<double> W; //稀疏矩阵 D-W
VectorXd u; //列向量B
VectorXd v;
Eigen::SparseLU<Eigen::SparseMatrix<double>, Eigen::COLAMDOrdering<int> > solver; //线性方程组求解
public:
Colorization() {}
//构造函数
Colorization(Image& g_img, Image& s_img, Mat m_img, Image& d_img) {
gray_img = g_img;
sketch_img = s_img;
mask = m_img;
dest_img = d_img;
}
void GetColorfulImage() {
W.resize(sketch_img.h * sketch_img.w, sketch_img.h * sketch_img.w);
W.setIdentity();
double sigma = 10000;
//double mean = 0.0;//均值
//double sum = 0.0;
//for (int i = 0; i < sketch_img.h; i++) {
// for (int j = 0; j < sketch_img.w; j++) {
// double y = sketch_img.yuv_colors[i][j][0];
// sum += y;
// }
//}
//mean = sum / (sketch_img.h * sketch_img.w);
//double sigma = 0.0;//方差
//for (int i = 0; i < sketch_img.h; i++) {
// for (int j = 0; j < sketch_img.w; j++) {
// sigma = sigma + (sketch_img.yuv_colors[i][j][0] - mean) * (sketch_img.yuv_colors[i][j][0] - mean);
// }
//}
//sigma = sigma / (sketch_img.h * sketch_img.w);
cout << "build laplacian matrix..." << endl;
//计算权重矩阵(有颜色的像素点直接跳过不用计算它与邻域的权重)
//#pragma omp parallel for
for (int i = 0; i < gray_img.h; i++) {
for (int j = 0; j < gray_img.w; j++) {
int index = i * mask.cols + j;
double data = mask.data[index];
if (data == 255) continue;//如果像素点已知
int center_index = i * gray_img.w + j;//像素点在矩阵中的行值
double Y_ij = gray_img.yuv_colors[i][j][0];//像素点的Y值
vector<int> neighbors = gray_img.GetNeighborsOf(i,j);//获取像素点邻域索引
vector<double> weights;//记录权重
clock_t t1 = clock();
double w_sum = 0;//所有邻域像素点与中心像素点的权重值之和
for (int k = 0; k < neighbors.size(); k++) {
int neighbor_index = neighbors[k];
int x = neighbor_index / gray_img.w;//计算邻域像素点在图像中的行、列值
int y = neighbor_index % gray_img.w;
double Y_ij_neighbor = gray_img.yuv_colors[x][y][0];//邻域像素点
double w = exp(-pow(Y_ij - Y_ij_neighbor, 2) / (2 * sigma ));
weights.push_back(w);
w_sum += w;
}
clock_t t2 = clock();
//权重值归一化
for (int k = 0; k < neighbors.size(); k++) {
int neighbor_index = neighbors[k];
double neighbor_w = weights[k] / w_sum;
W.coeffRef(center_index, neighbor_index) = - neighbor_w; //这里耗时间, 没什么好办法。。。那就等它执行?嗯,一行要1s
}
clock_t t3 = clock();
//cout << i << "," << j << ", time 1: " << t2 - t1 << "; time 2: " << t3 - t2 << endl;
}
}
cout << "build right hand matrix U and V" << endl;
//构造U、V
VectorXd u; u.resize(sketch_img.h * sketch_img.w); u.setZero();
VectorXd v; v.resize(sketch_img.h * sketch_img.w); v.setZero();
for (int i = 0; i < sketch_img.h; i++) {
for (int j = 0; j < sketch_img.w; j++) {
int index = i * mask.cols + j;
double data = mask.data[index];
if (data == 255) {
u(i * sketch_img.w + j) = sketch_img.yuv_colors[i][j][1];
v(i * sketch_img.w + j) = sketch_img.yuv_colors[i][j][2];
}
else {
vector<int> ij_neighbor = sketch_img.GetNeighborsOf(i, j);
for (int k = 0; k < ij_neighbor.size(); k++) {
int ij_neighbor_index = ij_neighbor[k];
int x = ij_neighbor_index / sketch_img.w;
int y = ij_neighbor_index % sketch_img.w;
double u_tmp = 0;
double v_tmp = 0;
double neighbor_data = mask.data[ij_neighbor_index];
if (neighbor_data == 255) { //if neighbor is known
u_tmp = -W.coeffRef(i * sketch_img.w + j, ij_neighbor[k]) * sketch_img.yuv_colors[x][y][1];
v_tmp = -W.coeffRef(i * sketch_img.w + j, ij_neighbor[k]) * sketch_img.yuv_colors[x][y][2];
u(i * sketch_img.w + j) += u_tmp;
v(i * sketch_img.w + j) += v_tmp;
W.coeffRef(i * sketch_img.w + j, ij_neighbor[k]) = 0;
}
}
}
}
}
cout << "solve linear equations..." << endl;
solver.compute(W);
VectorXd U = solver.solve(u);
VectorXd V = solver.solve(v);
//TODO:操作dest-img的YUV通道数据
dest_img.ConvertRGB2YUV();
for (int i = 0; i < sketch_img.h; i++)
for (int j = 0; j < sketch_img.w; j++) {
dest_img.yuv_colors[i][j][0] = gray_img.yuv_colors[i][j][0];
dest_img.yuv_colors[i][j][1] = U(i * sketch_img.w + j);
dest_img.yuv_colors[i][j][2] = V(i * sketch_img.w + j);
}
dest_img.ConvertYUV2RGB();
}
//保存结果
void SaveResults(string path) {
dest_img.Save(path);
}
};