【去马赛克专题】demosaic算法之基础插值

1. 基础插值简介

早期的demosaic算法多为常见的最近邻插值、双线性插值、双三次插值、三次B样条插值等,这类算法将R、G、B三通道分离,然后分别在单通道上对缺失通道像素位置插值处理,最后三通道再叠加回去。

简单插值算法容易实现并且考虑了空间相关性,但完全忽略了光谱相关性和边缘结构细节,重建结果经常存在颜色伪影、拉链效应和模糊等缺陷,仅适合重建较为平滑的图像类型。但是作为最基础的插值算法,是理解和设计新型算法的基础,对参考评估其他算法的性能具有广泛的意义。

一个示例,可以看到色差和色比在大部分区域是比较平坦的

另外的基础插值方法如色比恒定法和色差恒定法是基于预设模型,首先通过双线性插值得到G通道的重建结果,然后假设像素一定邻域范围内的色比和色差近似相等,继而重建R、B通道。

色差色比法的思想是尽量避开边缘区域的插值:在边缘地方插值容易出错,那就先在观测信息比较充足的G通道完成第一次插值,然后构造一个平坦的色差或色比空间Kr,Kb,进行接下来的插值。这类算法既考虑了空间相关性也考虑了光谱相关性,较单通道插值再叠加的方法在客观评价指标上有较大的提升,但是对于边缘区域没有引入额外的算法逻辑处理,没有从根本上解决插值时边缘两侧像素成分混叠的问题,因此并不会对颜色伪影、拉链效应等问题有明显改善。

demosaic原理相关内容可参照:从bayer到rgb:ISP中的demosaic技术 - 知乎 (zhihu.com)


2. 基础插值算法详解

2.1 最近邻插值

最近邻插值作为最简单的插值算法,将bayer格式图像的R、G、B对应的像素位置分离,分别在三个通道上对缺失值进行最近邻插值,然后将三通道叠加得到最终的RGB图像。

如上图,以RGGB的bayer排布格式为例,首先会按相同颜色把三通道拆离。这里以第三行第三列的像素举例,该点像素缺失B、G的值,因此需要在B和G通道的找到最相邻的整数坐标点填充缺失位置。当周围参考点的距离都相等时,则选择任意一个或者只选择一侧的像素数值作为插值数值。

这里G值统一选择左侧邻域,B值统一选择左上对角邻域进行插值。最近邻法速度最快,但图像会出现明显的块状效应,会在一定程度上损失空间对称性。

2.2 双线性插值

最近邻插值没有充分利用空间相关性去考虑到附近的几个像素点按权重分配,为了提高缺失值像素值的准确性产生了双线性插值算法。

还是以第三行第三列的像素位置为例,当前位置缺失G和B通道。根据双线性插值原理,G通道计算为最近邻域4点G值的均值,B通道为对角邻域4点B值的均值。这里发散到所有存在R通道值的像素位置(i,j),表达式为:

那如果当前位置只有G通道像素值,缺失R和B的像素值呢?通过图例不难发现在G值3X3邻域只存在2个已知R值和2个已知B值,而且在奇数行偶数列的Gr和偶数行奇数列的Gb的周围分布是不同的,因此缺失R和B的插值公式表示为最近2个值得平均:

最后已知B像素缺失R和G通道位置,插值公式是和最开始提到的已知R像素缺失B和G通道类似,因为R和B通道像素分布是一致的:

归纳总结发现,上述步骤其实是通过两个3X3卷积核对各通道进行低通滤波,因此较最近邻插值算法会导致图像出现轻微的模糊:

此外,一些高阶多项式的插值方法如双三次插值(Bicubic)(也叫立方卷积插值),相较于双线性插值是一种采样点更多并且更复杂的插值方式。该算法利用待采样点周围16个点的灰度值作三次插值,不仅考虑到4个直接相邻点的灰度影响,而且考虑到各邻点间灰度值变化率的影响,它能创造出比双线性插值更平滑的图像边缘。另外如双三次B样条插值等方法是以三次B样条为基函数的双三次插值,由于时间开销较大同时原理比较类似,此处不多做详细说明。

2.3 色比恒定法

事实上不同的颜色通道之间具有很强的关联,色比恒定法在最近邻插值、双线性插值的基础上额外考虑光谱相关性,认为对于采样点(i,j)和邻域(m,n),以R通道缺失值的重建为例:

意思就是说在一个小的像素范围内,它们的R/G和B/G的比值是基本相等的。接下来描述如何通过这个假设完成所有缺失像素的插值:

(1)首先用2.2提到的双线性插值得到G通道所有缺失像素。

(2)R、B两通道也会进行双线性插值作为初步估计值,然后对所有像素位置分别计算kr = R/G和kb = B/G。

(3)由于G通道和色比在(1)和(2)已经全部得到,那么缺失像素位置的R/G和B/G为四邻域的色比平均:

实际这里第二步建立了色比平滑空间,再进行第三步插值,并不是已知求已知

可以看到尽管利用了光谱相关性,但是基于常色调的插值方法本质还是双线性插值,该类方法在任何邻域中都认为色彩值很接近并且色调变化平缓,因此在平缓区域的重建有着较好的结果而在边缘部分效果仍然不佳。另外色比法作为除法还需要考虑分母为零的情况,因此使用这类插值算法时必须仔细分析图像的相关结构,以求达到理想效果。

2.4 色差恒定法

与色比恒定法类似,色差恒定法认为对于采样点(i,j)和邻域(m,n),以R通道缺失值的重建为例有:

以经典的Kr-Kb色差插值法为例,重建流程如下:

(1) 双线性插值得到G通道的重建结果,R、B两通道也会进行双线性插值作为初步估计值。

(2) 定义色差分量Kr=G-R、Kb=G-B,得到全图的色差分量Kr、Kb,考虑到色差是一个缓慢变化的成分,接着进行步骤(3)和(4)。

(3) 对于Gr、Gb通道上的R、B分量重建,采用四邻域的色差加权:

(4) 对于B通道上的R分量重建(R通道上的B分量重建),采用对角邻域的色差加权:

色差较色比法有两点优势:第一,色差的运算简单,更容易实现。第二, 色比在G通道接近0时误差较大,色差不存在这类问题。因此,绝大多数颜色插值算法中使用了色差。


3.代码仿真

实验采用kodak数据集:True Color Kodak Images

从GT(rgb)生成mosaic方式为rgb图像每个像素按照bayer排布分别采样r、g、b对应的值(随手写的python代码以方便理解和debug为主,能跑。。):

def makemosaic(rgb_image, pattern):
    w, h, z = rgb_image.shape
    R = np.zeros((w, h))
    GR = np.zeros((w, h))
    GB = np.zeros((w, h))
    B = np.zeros((w, h))

    if (pattern == "RGGB"):
        R[::2, ::2] = rgb_image[::2, ::2, 0]
        GR[::2, 1::2] = rgb_image[::2, 1::2, 1]
        GB[1::2, ::2] = rgb_image[1::2, ::2, 1]
        B[1::2, 1::2] = rgb_image[1::2, 1::2, 2]
    #其余pattern感兴趣可以补充一下
    mosaic_image = R + GR + GB + B
    return mosaic_image

最近邻插值

#这里计算三通道mask,方便后面插值前把mosaic图三通道拆开
def masks_Bayer(im, pattern):
    w, h = im.shape
    R = np.zeros((w, h))
    GR = np.zeros((w, h))
    GB = np.zeros((w, h))
    B = np.zeros((w, h))

    if (pattern == "RGGB"):
        R[::2, ::2] = 1
        GR[::2, 1::2] = 1
        GB[1::2, ::2] = 1
        B[1::2, 1::2] = 1

    R_m = R
    G_m = GB+GR
    B_m = B
    return R_m, G_m, B_m

#最近邻
def nearest(img, pattern):
    img = img.astype(np.float64)
    R_m, G_m, B_m = masks_Bayer(img, pattern)
    R_m = img * R_m
    G_m = img * G_m
    B_m = img * B_m
    h, w = img.shape
    R = np.zeros((h,w))
    G = np.zeros((h,w))
    B = np.zeros((h,w))
    for i in range(1,h-1):
        for j in range(1,w-1):
            if ((i % 2 == 0) & (j % 2 == 0)):
                G[i,j] = G_m[i-1,j]
                B[i,j] = B_m[i+1,j+1]
                R[i,j] = R_m[i,j]
            elif ((i % 2 == 1) & (j % 2 == 1)):
                G[i,j] = G_m[i-1,j]
                R[i,j] = R_m[i-1,j-1]
                B[i,j] = B_m[i,j]
            elif ((i % 2 == 0) & (j % 2 == 1)):
                R[i,j] = R_m[i,j-1]
                B[i,j] = B_m[i-1,j]
                G[i,j] = G_m[i,j]
            else:
                R[i,j] = R_m[i-1,j]
                B[i,j] = B_m[i,j-1]
                G[i,j] = G_m[i,j]

    result_img = np.zeros((h, w, 3))

    result_img[:, :, 0] = R
    result_img[:, :, 1] = G
    result_img[:, :, 2] = B

    return result_img

双线性插值

#卷积形式的双线性
def bilinnearcov(img, pattern):
    img = img.astype(np.float64)
    R_m, G_m, B_m = masks_Bayer(img, pattern)

    H_G = np.array(
        [[0, 1, 0],
         [1, 4, 1],
         [0, 1, 0]]) / 4  # yapf: disable

    H_RB = np.array(
        [[1, 2, 1],
         [2, 4, 2],
         [1, 2, 1]]) / 4  # yapf: disable

    R = signal.convolve(img * R_m, H_RB, 'same')
    G = signal.convolve(img * G_m, H_G, 'same')
    B = signal.convolve(img * B_m, H_RB, 'same')

    h, w = img.shape
    result_img = np.zeros((h, w, 3))

    result_img[:, :, 0] = R
    result_img[:, :, 1] = G
    result_img[:, :, 2] = B

    return result_img

色差法

def colordiff(img, pattern):
    img = img.astype(np.float64)
    R_m, G_m, B_m = masks_Bayer(img, pattern)
    R_m = img * R_m
    G_m = img * G_m
    B_m = img * B_m
    h, w = img.shape
    Kr = np.zeros((h,w))
    Kb = np.zeros((h,w))
    R = np.zeros((h,w))
    G = np.zeros((h,w))
    B = np.zeros((h,w))
#边界懒得处理了,先插值G通道
    for i in range(2,h-2):
        for j in range(2,w-2):
            if ((i % 2 == 0) & (j % 2 == 0)):
                G[i,j] = (G_m[i-1,j]+G_m[i+1,j]+ G_m[i,j-1]+G_m[i,j+1])/4
            elif ((i % 2 == 1) & (j % 2 == 1)):
                G[i,j] = (G_m[i-1,j]+G_m[i+1,j]+ G_m[i,j-1]+G_m[i,j+1])/4
            elif ((i % 2 == 0) & (j % 2 == 1)):
                G[i,j] = G_m[i,j]
            else:
                G[i,j] = G_m[i,j]

#插值色差维度Kr,Kb
    for i in range(2,h-2):
        for j in range(2,w-2):
            if ((i % 2 == 0) & (j % 2 == 0)):
                Kr[i,j] = G[i,j]-R_m[i,j]
                Kb[i, j] = G[i,j]-(B_m[i-1,j-1]+B_m[i-1,j+1]+B_m[i+1,j-1]+B_m[i+1,j+1])/4
            if ((i % 2 == 1) & (j % 2 == 1)):
                Kb[i,j] = G[i,j]-B_m[i,j]
                Kr[i, j] = G[i,j]-(R_m[i-1,j-1]+R_m[i-1,j+1]+R_m[i+1,j-1]+R_m[i+1,j+1])/4
            if ((i % 2 == 0) & (j % 2 == 1)):
                Kr[i, j] = G[i, j] - (R_m[i, j-1]+R_m[i, j+1])/2
                Kb[i, j] = G[i, j] - (B_m[i-1, j]+B_m[i+1, j])/2
            if ((i % 2 == 1) & (j % 2 == 0)):
                Kr[i, j] = G[i, j] - (R_m[i-1, j]+R_m[i+1, j])/2
                Kb[i, j] = G[i, j] - (B_m[i, j-1]+B_m[i, j+1])/2

#通过色差换算缺失R、B值
    for i in range(2,h-2):
        for j in range(2,w-2):
            if ((i % 2 == 0) & (j % 2 == 0)):
                R[i, j] = R_m[i, j]
                G[i,j] = R_m[i,j]+(Kr[i-1,j]+Kr[i+1,j]+Kr[i,j-1]+Kr[i,j+1])/4
                B[i, j] = G[i, j] - (Kb[i-1,j]+Kb[i+1,j]+Kb[i,j-1]+Kb[i,j+1])/4
            if ((i % 2 == 1) & (j % 2 == 1)):
                B[i, j] = B_m[i, j]
                G[i,j] = B_m[i,j]+(Kb[i-1,j]+Kb[i+1,j]+Kb[i,j-1]+Kb[i,j+1])/4
                R[i, j] = G[i, j] - (Kr[i-1,j]+Kr[i+1,j]+Kr[i,j-1]+Kr[i,j+1])/4
            if ((i % 2 == 0) & (j % 2 == 1)):
                G[i, j] = G_m[i, j]
                R[i, j] = G[i, j] - (Kr[i - 1, j] + Kr[i + 1, j] + Kr[i, j - 1] + Kr[i, j + 1]) / 4
                B[i, j] = G[i, j] - (Kb[i - 1, j] + Kb[i + 1, j] + Kb[i, j - 1] + Kb[i, j + 1]) / 4
            if ((i % 2 == 1) & (j % 2 == 0)):
                G[i, j] = G_m[i, j]
                R[i, j] = G[i, j] - (Kr[i - 1, j] + Kr[i + 1, j] + Kr[i, j - 1] + Kr[i, j + 1]) / 4
                B[i, j] = G[i, j] - (Kb[i - 1, j] + Kb[i + 1, j] + Kb[i, j - 1] + Kb[i, j + 1]) / 4
    result_img = np.zeros((h, w, 3))
    result_img[:, :, 0] = R
    result_img[:, :, 1] = G
    result_img[:, :, 2] = B
    del R_m, G_m, B_m
    return result_img

色比法

def colorratio(img, pattern):
    img = img.astype(np.float64)
    R_m, G_m, B_m = masks_Bayer(img, pattern)
    R_m = img * R_m
    G_m = img * G_m
    B_m = img * B_m
    h, w = img.shape
    Kr = np.zeros((h,w))
    Kb = np.zeros((h,w))
    R = np.zeros((h,w))
    G = np.zeros((h,w))
    B = np.zeros((h,w))
    for i in range(2,h-2):
        for j in range(2,w-2):
            if ((i % 2 == 0) & (j % 2 == 0)):
                G[i,j] = (G_m[i-1,j]+G_m[i+1,j]+ G_m[i,j-1]+G_m[i,j+1])/4
            elif ((i % 2 == 1) & (j % 2 == 1)):
                G[i,j] = (G_m[i-1,j]+G_m[i+1,j]+ G_m[i,j-1]+G_m[i,j+1])/4
            elif ((i % 2 == 0) & (j % 2 == 1)):
                G[i,j] = G_m[i,j]
            else:
                G[i,j] = G_m[i,j]

    for i in range(2,h-2):
        for j in range(2,w-2):
            if(G[i,j]<2):
                G[i,j]=1

    for i in range(2,h-2):
        for j in range(2,w-2):
            if ((i % 2 == 0) & (j % 2 == 0)):
                Kr[i,j] = R_m[i,j]/G[i,j]
                Kb[i, j] = (B_m[i-1,j-1]+B_m[i-1,j+1]+B_m[i+1,j-1]+B_m[i+1,j+1])/(4*G[i,j])
            if ((i % 2 == 1) & (j % 2 == 1)):
                Kb[i,j] = B_m[i,j]/G[i,j]
                Kr[i, j] = (R_m[i-1,j-1]+R_m[i-1,j+1]+R_m[i+1,j-1]+R_m[i+1,j+1])/(4*G[i,j])
            if ((i % 2 == 0) & (j % 2 == 1)):
                Kr[i, j] = (R_m[i, j-1]+R_m[i, j+1])/(2*G[i,j])
                Kb[i, j] = (B_m[i-1, j]+B_m[i+1, j])/(2*G[i,j])
            if ((i % 2 == 1) & (j % 2 == 0)):
                Kr[i, j] = (R_m[i-1, j]+R_m[i+1, j])/(2*G[i,j])
                Kb[i, j] = (B_m[i, j-1]+B_m[i, j+1])/(2*G[i,j])

    for i in range(2,h-2):
        for j in range(2,w-2):
            if ((i % 2 == 0) & (j % 2 == 0)):
                R[i, j] = R_m[i, j]
                G[i,j] = R_m[i,j]/((Kr[i-1,j]+Kr[i+1,j]+Kr[i,j-1]+Kr[i,j+1])/4)
                B[i, j] = G[i, j]* (Kb[i-1,j]+Kb[i+1,j]+Kb[i,j-1]+Kb[i,j+1])/4
            if ((i % 2 == 1) & (j % 2 == 1)):
                B[i, j] = B_m[i, j]
                G[i,j] = B_m[i,j]/((Kb[i-1,j]+Kb[i+1,j]+Kb[i,j-1]+Kb[i,j+1])/4)
                R[i, j] = G[i, j] * (Kr[i-1,j]+Kr[i+1,j]+Kr[i,j-1]+Kr[i,j+1])/4
            if ((i % 2 == 0) & (j % 2 == 1)):
                G[i, j] = G_m[i, j]
                R[i, j] = G[i, j] * (Kr[i - 1, j] + Kr[i + 1, j] + Kr[i, j - 1] + Kr[i, j + 1]) / 4
                B[i, j] = G[i, j] * (Kb[i - 1, j] + Kb[i + 1, j] + Kb[i, j - 1] + Kb[i, j + 1]) / 4
            if ((i % 2 == 1) & (j % 2 == 0)):
                G[i, j] = G_m[i, j]
                R[i, j] = G[i, j] * (Kr[i - 1, j] + Kr[i + 1, j] + Kr[i, j - 1] + Kr[i, j + 1]) / 4
                B[i, j] = G[i, j] * (Kb[i - 1, j] + Kb[i + 1, j] + Kb[i, j - 1] + Kb[i, j + 1]) / 4
    result_img = np.zeros((h, w, 3))
    result_img[:, :, 0] = R
    result_img[:, :, 1] = G
    result_img[:, :, 2] = B
    del R_m, G_m, B_m
    return result_img

结果对比

测试图:Kodak18CPSNRMAE
最近邻插值24.716.57
双线性插值28.014.82
色比恒定法31.653.42
色差恒定法(Kr-Kb)32.923.10

对比4种算法的结果,bilinear的的结果有轻微的模糊现象,nearest的伪彩色现象最为严重,拉链效应方面色差和色比法有轻微的改善,但整体仍然很严重。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值