【计算机图形学】Colorization Using Optimization给灰度图进行着色上色

Colorization Using Optimization

概述:算法利用优化的方法对灰度图进行着色,用户只需简单操作就行,无需对ROI区域进行精确分割。
【计算机图形学】Colorization Using Optimization给灰度图进行着色上色_第1张图片

文章目录

  • Colorization Using Optimization
    • 1. 预备知识
    • 2. 算法原理
    • 3. 算法实现
      • 3.1 开发环境
      • 3.2 实现过程
    • 4. 心得体会
    • 5. 参考文献

1. 预备知识

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.2990.1690.50.5870.3310.4190.1140.50.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 = 1110.000930.34371.772161.4016870.714170.00099 YU128V128

2. 算法原理

算法利用优化的方法对灰度图进行着色。具体而言,在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)sN(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)sN(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}}} wrse2σ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 sN(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,sN(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的值。

3. 算法实现

3.1 开发环境

电脑:Ubuntu18.04,cpu:1.8GHz ×8,内存 :8G
环境:Python,主要用scipy.sparse库的csc函数来构建稀疏矩阵,用linalg.spsolve库来求解线性方程。

3.2 实现过程

算法输入是一张灰度图,以及一张对其进行简单上色的草图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)

【计算机图形学】Colorization Using Optimization给灰度图进行着色上色_第2张图片

  1. 学会用类和方法实现。在上次的任务中,需要定义三个函数然后重复写输入输出,这样比较麻烦。但用类和方法实现的话,对方法输出的变量加上self,就可以在下个方法中直接使用,更加简洁。
  2. sketch很重要。图片最好用bmp格式,而且图片质量与上色的位置和色彩强度很有关系。
  3. 用csc或者lil配合spsolve求解线性方程,速度比较快。

4. 心得体会

  • sketch很重要。图片最好用bmp格式,而且图片质量与上色的位置和色彩强度很有关系。
  • 用csc或者lil配合spsolve求解线性方程,速度比较快。

5. 参考文献

[1] Colorization using optimization

[2] 读源码学算法之Colorization
[3] 机器学习:Colorization using Optimization
[4] Colorization_using_Optimization code
[5] Colorization
[6] Colorization_using_Optimization code2
[7] ColorizationUsingOptimization

你可能感兴趣的:(计算机图形学,传统算法,算法,人工智能,图形渲染,图像处理)