4、RAW域降噪基础:从Bayer到噪声特性
各位同学,欢迎来到RAW域降噪的第一课。说实话,很多做ISP的工程师在RAW域待的时间并不长,因为大部分算法都在YUV域或者RGB域处理。但我个人的经验是——降噪这件事,越早做效果越好。RAW域就是最早的战场。
这一章我们不讲复杂的滤波公式,先把RAW域的底子打牢。你只有搞懂了Bayer Pattern、黑电平校正、LSC这些前置模块对噪声的影响,才能真正理解为什么RAW域降噪这么特殊。
4.1 RAW图格式:Bayer Pattern
传感器输出的原始数据,说白了就是一个马赛克图像。每个像素只记录一种颜色——R、G或者B。这就是经典的Bayer Pattern。
最常见的格式是RGGB,也就是左上角是R,然后G、G、B交替排列。为什么G有两个?因为人眼对绿色最敏感,传感器就多采了一半的绿色信息。
| Bayer类型 | 排列方式 | 常见场景 |
|---|---|---|
| RGGB | 第一行 R G R G... 第二行 G B G B... |
最主流,高通、索尼传感器 |
| BGGR | 第一行 B G B G... 第二行 G R G R... |
部分三星传感器 |
| GRBG | 第一行 G R G R... 第二行 B G B G... |
少数OmniVision方案 |
我在项目里遇到过最坑的事——拿到一个RAW图,默认当成RGGB去解,结果颜色全乱套了。后来才意识到,那个传感器用的是BGGR。所以第一件事:确认Bayer顺序,别想当然。
4.2 黑电平校正(BLC)对噪声的影响
黑电平校正,英文叫Black Level Correction。为什么要做这个?因为传感器在完全无光的情况下,输出的也不是0,而是一个偏置值。这个偏置就是黑电平。
BLC的公式很简单:
I_corrected = I_raw - black_level
但这里有个坑——黑电平本身也是有噪声的。你减去一个带噪声的偏置,相当于把噪声也带进来了。
我记得有一次调试夜景模式,发现暗部噪声特别大。排查了半天,最后发现是BLC的black_level估计不准,导致暗部被过度拉伸,噪声放大了好几倍。
BLC对噪声的影响主要体现在两点:
- 偏置误差放大噪声:black_level偏大,暗部被抬升,噪声方差跟着变大
- 截断效应:减去黑电平后,小于0的值被截成0,丢失了暗部细节
4.3 LSC对噪声的影响
Lens Shading Correction,镜头阴影校正。说白了就是镜头边缘进光量少,图像四周偏暗,需要把四周像素乘上一个增益系数来补偿。
LSC的公式:
I_corrected(x,y) = I_raw(x,y) * gain(x,y)
问题来了——增益是乘性的。边缘区域的增益可能达到2倍甚至3倍。噪声也跟着被放大了。
你想想看,中心区域增益是1.0,噪声不变。边缘区域增益是2.0,噪声方差直接变成4倍(因为噪声方差与增益平方成正比)。这就是为什么很多手机拍出来的照片,四周噪点明显比中心多。
| 区域 | LSC增益 | 噪声放大倍数 | 实际影响 |
|---|---|---|---|
| 中心 | 1.0x | 1x | 噪声正常 |
| 中间环 | 1.5x | 2.25x | 噪声开始明显 |
| 边缘 | 2.0x | 4x | 噪声爆炸 |
4.4 RAW域噪声特性分析
好了,前面铺垫了这么多,现在进入核心——RAW域的噪声到底长什么样?
简单说,RAW域的噪声是信号相关的。什么意思?就是亮的地方噪声大,暗的地方噪声小。这跟YUV域不一样,YUV域经过gamma校正后,噪声特性已经被扭曲了。
RAW域噪声模型通常用泊松-高斯混合模型来描述:
I_noisy = I_clean + n_shot(I_clean) + n_read
其中:
n_shot ~ Poisson(λ = I_clean) → 散粒噪声,与信号相关
n_read ~ Gaussian(0, σ_read) → 读出噪声,与信号无关
散粒噪声是光子计数的统计特性决定的。光子到达传感器是随机的,服从泊松分布。信号越强,光子数越多,噪声方差也越大。但注意——信噪比是随着信号增强而提高的,因为SNR = I / sqrt(I) = sqrt(I)。
读出噪声则是传感器电路引入的,跟信号大小没关系。在暗部,读出噪声占主导;在亮部,散粒噪声占主导。
还有一个容易被忽略的点——Bayer通道的噪声特性不同。G通道的量子效率通常比R和B高,所以G通道的散粒噪声系数K会小一些。这意味着G通道的信噪比天生就比R/B好。降噪时,R/B通道需要更强的处理。
下面这张图总结了RAW域噪声的完整链路:
4.5 噪声参数标定:工程落地第一步
理论说完了,来点实际的。你在项目里怎么拿到噪声参数?
我常用的方法是——拍灰卡。具体步骤:
- 用均匀光源照射灰卡,拍多张RAW图
- 取多帧的均值作为clean信号
- 计算每帧与均值的差值,得到噪声残差
- 对不同亮度区域统计噪声方差
- 拟合方差-信号曲线,得到σ_read和K
# 伪代码示例:噪声参数标定
def calibrate_noise(raw_images):
# raw_images: list of RAW frames
mean_img = np.mean(raw_images, axis=0)
residual = raw_images - mean_img
# 按信号强度分bin
bins = [0, 256, 512, 1024, 2048, 4096]
noise_var = []
signal_level = []
for i in range(len(bins)-1):
mask = (mean_img >= bins[i]) & (mean_img < bins[i+1])
noise_var.append(np.var(residual[mask]))
signal_level.append((bins[i] + bins[i+1]) / 2)
# 拟合: noise_var = K * signal + σ_read^2
# 用最小二乘法
K, sigma_read_sq = np.polyfit(signal_level, noise_var, 1)
return K, np.sqrt(sigma_read_sq)
好了,这一章的内容就是这些。RAW域的噪声特性,说白了就是三个关键词:信号相关、通道差异、空间非均匀。搞懂这三点,你后面设计降噪算法就有了方向。