概述:算法利用优化的方法对灰度图进行着色,用户只需简单操作就行,无需对ROI区域进行精确分割。
YUV格式
与我们熟知的RGB通道类似,YUV也是一种颜色编码方法,主要用于电视系统以及模拟视频领域,它将描述灰度值的亮度信息(Y)和描述色彩饱和度的色彩信息(UV)分离,没有UV信息一样可以显示完整的图像,只不过是黑白图像。
两者转化公式如下。
[ Y U V ] = [ 0.299 0.587 0.114 − 0.169 − 0.331 0.5 0.5 − 0.419 − 0.081 ] [ R G B ] + [ 0 128 128 ] \left[\begin{array}{l} Y \\ U \\ V \end{array}\right] = \left[\begin{array}{ccc} 0.299 & 0.587 & 0.114 \\ -0.169 & -0.331 & 0.5 \\ 0.5 & -0.419 & -0.081 \end{array}\right]\left[\begin{array}{l} R \\ G \\ B \end{array}\right]+\left[\begin{array}{c} 0 \\ 128 \\ 128 \end{array}\right] ⎣ ⎡YUV⎦ ⎤=⎣ ⎡0.299−0.1690.50.587−0.331−0.4190.1140.5−0.081⎦ ⎤⎣ ⎡RGB⎦ ⎤+⎣ ⎡0128128⎦ ⎤
[ R G B ] = [ 1 − 0.00093 1.401687 1 − 0.3437 − 0.71417 1 1.77216 0.00099 ] [ Y U − 128 V − 128 ] \left[\begin{array}{l} R \\ G \\ B \end{array}\right]=\left[\begin{array}{ccc} 1 & -0.00093 & 1.401687 \\ 1 & -0.3437 & -0.71417 \\ 1 & 1.77216 & 0.00099 \end{array}\right]\left[\begin{array}{c} Y \\ U-128 \\ V-128 \end{array}\right] ⎣ ⎡RGB⎦ ⎤=⎣ ⎡111−0.00093−0.34371.772161.401687−0.714170.00099⎦ ⎤⎣ ⎡YU−128V−128⎦ ⎤
算法利用优化的方法对灰度图进行着色。具体而言,在YUV颜色空间中,首先人为对灰度图进行简单着色,然后利用优化的方法求解其他未着色的像素点的颜色值,最后对这个像素点进行填充。
算法假设在3*3邻域的点中,灰度Y差距小的点,它们的色彩差距也会小。因此我们可以将这个问题转化成一个最小化 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_{\mathbf{r}}\left(U(\mathbf{r})-\sum_{\mathbf{s} \in N(\mathbf{r})} w_{\mathbf{r s}} U(\mathbf{s})\right)^{2} J(U)=r∑⎝ ⎛U(r)−s∈N(r)∑wrsU(s)⎠ ⎞2
J ( V ) = ∑ r ( V ( r ) − ∑ s ∈ N ( r ) w r s V ( s ) ) 2 J(V)=\sum_{\mathbf{r}}\left(V(\mathbf{r})-\sum_{\mathbf{s} \in N(\mathbf{r})} w_{\mathbf{r s}} V(\mathbf{s})\right)^{2} J(V)=r∑⎝ ⎛V(r)−s∈N(r)∑wrsV(s)⎠ ⎞2
这里,r是3*3的像素点邻域的中心点,s是r的相邻点(8个方向), w r s w_{r s} wrs是像素点r和s根据Y通道计算的相似度, w r s w_{r s} wrs计算公式如下,
w r s ∝ e − ( Y ( r ) − Y ( s ) ) 2 2 σ r 2 w_{r s} \propto e^{-\frac{(Y(r)-Y(s))^{2}}{2 \sigma_{r}^{2}}} wrs∝e−2σr2(Y(r)−Y(s))2
并且权重加和等于1,即 ∑ s ∈ N ( r ) W r s = 1 \sum_{\mathrm{s} \in \mathrm{N}(\mathrm{r})} \mathrm{W}_{\mathbf{r s}} = 1 ∑s∈N(r)Wrs=1
优化问题的最小值在 J ′ ( U ) = 0 , J ′ ( V ) = 0 J^{\prime}(U)=0,J^{\prime}(V)=0 J′(U)=0,J′(V)=0时取到,也就是解下面的方程:
W r , s = { 1 , r = s − w r s , r ≠ s , s ∈ N ( r ) 0 , otherwise W_{r, s}=\left\{\begin{array}{cc} 1 ,& r=s \\ -w_{r s}, & r \neq s, s \in N(r)\\ 0, & \text { otherwise } \end{array}\right. Wr,s=⎩ ⎨ ⎧1,−wrs,0,r=sr=s,s∈N(r) otherwise
W U = b 1 W U=b_{1} WU=b1
W V = b 2 W V=b_{2} WV=b2
这里的 b 1 , b 2 b1,b2 b1,b2指的是简单着色草图中上色像素点的 U , V U,V U,V值。
因为W矩阵只在两个点相邻的情况下才可能取非零的值,每个点的相邻点很少,所以它是一个稀疏的矩阵,可以用类似求解poisson image editing的方法来求解现在的方程来得到图片每个像素点的U,V值。最后将YUV通道的值转回RGB就可以得到一张彩色图。
文献[2]中有非常生动具体的例子解释如何求解未着色部分的U或者V的值。
电脑:Ubuntu18.04,cpu:1.8GHz ×8,内存 :8G
环境:Python,主要用scipy.sparse库的csc函数来构建稀疏矩阵,用linalg.spsolve库来求解线性方程。
算法输入是一张灰度图,以及一张对其进行简单上色的草图sketch。
算法输出这张图片的彩色图。
第一步,先对在RGB颜色空间的输入图片转换成YUV颜色空间。
第二步,找到简单上色后的区域,并记录其坐标。
第三步,记录整张图片的每个像素的横纵坐标索引,并通过第二步找到的坐标,计算其上色的坐标及其邻域的权重。因为权重矩阵 W W W非常大并且稀疏,所以用Compressed Sparse Row matrix(CSR)进行存储 W W W。
第四步,找到sketch图上色区域并把U,V通道的值赋给向量 b b b。
第五步,用scipy.sparse.linalg.spsolve求解线性方程组 W X = b WX=b WX=b,得到图片所有像素点的U,V通道的值,结合灰度图的Y通道的值组成YUV格式的图片,最后将其转化为RGB格式图片。
class ColorizationUsingOptimization():
def __init__(self, ori_img, skt_img):
self.ori_img = cv2.imread(ori_img).astype(np.float32) / 255
self.skt_img = cv2.imread(skt_img).astype(np.float32) / 255
# rgb2yuv
self.ori_yuv = cv2.cvtColor(self.ori_img, cv2.COLOR_BGR2YUV)
self.skt_yuv = cv2.cvtColor(self.skt_img, cv2.COLOR_BGR2YUV)
# get image size
self.width = self.ori_img.shape[0]
self.height = self.ori_img.shape[1]
self.img_size = self.width * self.height
# separate y u v
self.y_ori = self.ori_yuv[:, :, 0]
self.u_skt = self.skt_yuv[:, :, 1].reshape(self.img_size)
self.v_skt = self.skt_yuv[:, :, 2].reshape(self.img_size)
# get sketched pixels position
self.get_skt_pos()
# build weight matrix and b
self.build_weight_b()
# solve WX = b1 and colorization
self.color()
def get_skt_pos(self):
assert self.ori_img.shape == self.skt_img.shape, "not the same image size"
self.skt_pos = np.zeros((self.ori_img.shape[0], self.ori_img.shape[1]))
for i in range(self.skt_pos.shape[0]):
for j in range(self.skt_pos.shape[1]):
if (self.ori_img[i][j][0] != self.skt_img[i][j][0]):
self.skt_pos[i][j] = 1
def build_weight_b(self):
weight_data = []
row_inds = []
col_inds = []
# construct weight matrix
for w in range(self.width):
for h in range(self.height):
if self.skt_pos[w][h] == 0:
neighbor_value = []
for i in range(w - 1, h + 2):
for j in range(h - 1, h + 2):
if (0 <= i and i < self.width - 1 and 0 <= j and j < self.height - 1):
if (w != i) | (h != j):
neighbor_value.append(self.y_ori[w, h])
row_inds.append(w * self.height + h)
col_inds.append(i * self.height + j)
sigma = np.var(np.append(neighbor_value, self.y_ori[w, h]))
if sigma < 1e-6:
sigma = 1e-6
w_rs = np.exp(- np.power(neighbor_value - self.y_ori[w][h], 2) / sigma)
w_rs = - w_rs / np.sum(w_rs)
for item in w_rs:
weight_data.append(item)
weight_data.append(1)
row_inds.append(w * self.height + h)
col_inds.append(w * self.height + h)
self.W = scipy.sparse.csc_matrix((weight_data, (row_inds, col_inds)), shape=(self.img_size, self.img_size))
# construct b
self.b_u = np.zeros(self.img_size)
self.b_v = np.zeros(self.img_size)
# skt_pos_vec is the indix of nonzero element
skt_pos_vec = np.nonzero(self.skt_pos.reshape(self.img_size))
self.b_u[skt_pos_vec] = self.u_skt[skt_pos_vec]
self.b_v[skt_pos_vec] = self.v_skt[skt_pos_vec]
def color(self):
u_res = scipy.sparse.linalg.spsolve(self.W, self.b_u).reshape((self.width, self.height))
v_res = scipy.sparse.linalg.spsolve(self.W, self.b_v).reshape((self.width, self.height))
yuv_res = np.dstack((self.y_ori.astype(np.float32), u_res.astype(np.float32), v_res.astype(np.float32)))
rgb_res = cv2.cvtColor(yuv_res, cv2.COLOR_YUV2RGB)
[1] Colorization using optimization
[2] 读源码学算法之Colorization
[3] 机器学习:Colorization using Optimization
[4] Colorization_using_Optimization code
[5] Colorization
[6] Colorization_using_Optimization code2
[7] ColorizationUsingOptimization