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。这中间需要一个“通道扩展”的过程。
我常用的结构是:
- 输入层:将拜耳模式重排为4通道(R、G1、G2、B)
- 特征提取:3~5个卷积块,每个块包含Conv+ReLU+BN
- 残差连接:学习高频残差,而不是直接预测RGB值
- 输出层: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
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 后处理抑制方法
我常用的伪影抑制策略有三个:
- 边缘导向滤波:在去马赛克后,用边缘保持滤波器(如双边滤波)处理,减少锯齿
- 颜色校正:对伪色区域做色度平滑,保持亮度通道不变
- 自适应阈值:检测高频区域,对摩尔纹严重的区域做局部平滑
4.4 知识体系总览
下面这张图总结了去马赛克的核心流程和关键技术点:
4.5 工程实践要点
最后分享几个我在项目中积累的经验:
- 数据增强很重要:训练深度学习模型时,对RAW数据做随机裁剪、旋转、翻转,能显著提升泛化能力
- 量化感知训练:如果最终要部署到移动端,建议在训练时就加入量化模拟,避免推理时精度下降
- 多帧融合:去马赛克和多帧降噪可以联合优化。我试过把多帧信息引入去马赛克网络,PSNR提升了1.5dB