4. RAW域去马赛克:传统插值算法与深度学习

去马赛克,说白了就是把拜耳阵列上缺失的RGB信息补全。每个像素只记录一种颜色,另外两种颜色要靠周围像素猜出来。这个“猜”的过程,就是去马赛克的核心。

我刚开始做ISP时,觉得这步很简单。不就是插值嘛,跟图像缩放差不多。后来踩了不少坑才发现——去马赛克做不好,后面所有处理都是白搭。边缘锯齿、伪色、摩尔纹,这些 artifacts 一旦在RAW域产生,后面的降噪、锐化只会把它们放大。

核心认知:去马赛克不是单纯的插值问题,而是结构重建问题。你不仅要补全颜色,还要保持边缘锐度、抑制伪影、保留纹理细节。

4.1 传统插值算法

传统方法我分成两类讲:一类是“无脑平均”,一类是“带点脑子”。

4.1.1 双线性插值

这是最基础的方法。每个像素缺失的两个颜色通道,直接用相邻同色像素的平均值填充。

举个例子,对于G通道(绿色像素占一半),R和B位置的G值怎么算?

// 以R像素位置为例,插值G通道
G_at_R = (G_left + G_right + G_up + G_down) / 4

// 对于B像素位置,插值R通道
// 需要取对角线方向的R像素平均
R_at_B = (R_tl + R_tr + R_bl + R_br) / 4

双线性插值的优点是简单、快。缺点是——它假设图像是平滑的。遇到边缘就完蛋。

我曾经踩过的坑:在一条黑白分明的斜边上,双线性插值会把边缘两侧的颜色混在一起,产生明显的“锯齿”和“伪色”。尤其是红色和蓝色通道,因为采样率低,伪色更严重。

4.1.2 边缘导向插值

既然双线性在边缘处表现差,那我们就先判断边缘方向,再沿着边缘方向插值。这就是边缘导向插值的核心思想。

我习惯用这种方法:先计算水平和垂直方向的梯度,选择梯度小的方向进行插值。

// 计算水平和垂直梯度
grad_h = |G_left - G_right|
grad_v = |G_up - G_down|

// 选择梯度小的方向插值
if (grad_h < grad_v) {
    // 水平方向更平滑,用水平邻居
    G_at_R = (G_left + G_right) / 2
} else if (grad_v < grad_h) {
    // 垂直方向更平滑,用垂直邻居
    G_at_R = (G_up + G_down) / 2
} else {
    // 两个方向差不多,用四个邻居平均
    G_at_R = (G_left + G_right + G_up + G_down) / 4
}

这个方法比双线性好很多。边缘处的锯齿明显减少。但问题在于——梯度计算本身受噪声影响很大。我在项目中遇到过,暗光场景下梯度方向判断错误率超过30%。

算法 PSNR提升 边缘质量 计算量 噪声敏感度
双线性插值 基准 差(锯齿明显) 低 低
边缘导向插值 +2~3dB 中(边缘保持较好) 中 高
自适应插值 +3~5dB 良(纹理保留好) 中高 中
我的建议:如果算力有限,边缘导向插值是性价比最高的选择。但一定要配合预处理降噪,否则梯度方向会乱跳。

4.2 深度学习去马赛克

传统方法做到极致也就那样了。真正让去马赛克质量上一个大台阶的,是深度学习。

我第一次用CNN做去马赛克时,效果让我很惊讶。边缘几乎看不到锯齿,伪色也少了很多。但训练过程很痛苦——数据准备、损失函数设计、推理速度优化,每一步都是坑。

4.2.1 网络结构设计

去马赛克网络的核心挑战是:输入是单通道的拜耳模式,输出是三通道的RGB。这中间需要一个“通道扩展”的过程。

我常用的结构是:

  1. 输入层:将拜耳模式重排为4通道(R、G1、G2、B)
  2. 特征提取:3~5个卷积块,每个块包含Conv+ReLU+BN
  3. 残差连接:学习高频残差,而不是直接预测RGB值
  4. 输出层:1x1卷积将特征映射到3通道RGB
# 伪代码:去马赛克网络核心结构
def demosaic_net(bayer_input):
    # 重排:将拜耳模式转为4通道
    x = pack_bayer(bayer_input)  # [H, W] -> [H/2, W/2, 4]
    
    # 特征提取
    x = conv3x3(x, 64) + relu
    x = conv3x3(x, 64) + relu
    x = conv3x3(x, 64) + relu
    
    # 残差学习
    residual = conv3x3(x, 4)
    x = x + residual
    
    # 输出RGB
    rgb = conv1x1(x, 3)
    return rgb
关键技巧:不要直接预测RGB值,而是预测残差。这样网络只需要学习“修正量”,收敛更快,效果也更好。

4.2.2 损失函数选择

我试过很多损失函数。L1损失比L2损失好——L2会过度惩罚大误差,导致结果过于平滑。但光用L1还不够。

我个人习惯用组合损失:

  • L1损失:保证像素级精度
  • 感知损失:用预训练VGG提取特征,比较特征空间的差异
  • 梯度损失:保持边缘锐度
loss = lambda_l1 * L1_loss + lambda_perc * Perceptual_loss + lambda_grad * Gradient_loss

权重怎么设?我一般设 lambda_l1=1.0, lambda_perc=0.1, lambda_grad=0.5。你可以根据场景调整。

4.3 伪影抑制

去马赛克做完了,但问题还没结束。伪影——这是所有去马赛克算法都绕不开的坎。

4.3.1 常见伪影类型

伪影类型 表现 产生原因
锯齿(Zipper) 边缘呈锯齿状 插值方向判断错误
伪色(False Color) 边缘出现彩色条纹 RGB通道插值不一致
摩尔纹(Moiré) 高频纹理区域出现波纹 采样率不足导致混叠
模糊(Blur) 纹理细节丢失 过度平滑

4.3.2 后处理抑制方法

我常用的伪影抑制策略有三个:

  1. 边缘导向滤波:在去马赛克后,用边缘保持滤波器(如双边滤波)处理,减少锯齿
  2. 颜色校正:对伪色区域做色度平滑,保持亮度通道不变
  3. 自适应阈值:检测高频区域,对摩尔纹严重的区域做局部平滑
注意:后处理是双刃剑。抑制伪影的同时,也会损失真实细节。我建议用“检测-处理”的策略——只对伪影区域做处理,保留正常区域。

4.4 知识体系总览

下面这张图总结了去马赛克的核心流程和关键技术点:

RAW域去马赛克知识体系 拜耳RAW输入 传统插值算法 深度学习去马赛克 伪影抑制技术 双线性插值 边缘导向插值 CNN结构设计 损失函数优化 锯齿抑制 伪色消除 高质量RGB输出 💡 工程实践建议 • 算力充足时:优先选择深度学习方案,质量上限高 • 算力受限时:边缘导向插值 + 后处理伪影抑制,性价比最优 • 无论哪种方案,预处理降噪都是必须的

4.5 工程实践要点

最后分享几个我在项目中积累的经验:

  • 数据增强很重要:训练深度学习模型时,对RAW数据做随机裁剪、旋转、翻转,能显著提升泛化能力
  • 量化感知训练:如果最终要部署到移动端,建议在训练时就加入量化模拟,避免推理时精度下降
  • 多帧融合:去马赛克和多帧降噪可以联合优化。我试过把多帧信息引入去马赛克网络,PSNR提升了1.5dB
我的习惯:每次调完去马赛克参数,我都会用一张包含精细纹理的测试图跑一遍。如果纹理区域出现模糊或伪色,说明参数还需要调整。别只看PSNR,主观质量才是最终标准。