012、RAW域预处理:黑电平校正、坏点校正与镜头阴影校正的算法与标定实战
2026/7/12 14:44:29 网站建设 项目流程

012、RAW域预处理:黑电平校正、坏点校正与镜头阴影校正的算法与标定实战

一个让我半夜被电话叫醒的bug

2018年某款旗舰机项目,夜拍模式刚调通,产线突然报批量黑屏——不是全黑,是那种暗部发紫、边缘发绿、中间还有几个死像素点的“五彩斑斓黑”。产线经理在电话里吼:“你们Camera团队到底行不行?”我盯着log看了半小时,最后发现是RAW域预处理三个模块的流水线顺序搞反了:坏点校正跑在黑电平校正之前,导致暗电流被当成坏点给抹了,镜头阴影校正又拿错误的数据去算增益,整个链条崩了。

从那以后,我养成了一个习惯:RAW域预处理,顺序就是命。黑电平校正(BLC)→ 坏点校正(DPC)→ 镜头阴影校正(LSC),这个顺序谁动谁出事。

黑电平校正:你以为减个值就完事了?

很多新手拿到RAW图,第一反应是“黑电平不就是减去一个固定值吗?”然后对着全黑帧算个均值,代码里写一句pixel -= black_level就交差了。这种写法在实验室里能跑,一到量产就翻车。

真实场景下的黑电平有多野?

  • 温度漂移:Sensor温度从-20℃升到60℃,暗电流能翻3-5倍。你冬天标定的黑电平,夏天用就是错的。
  • 增益耦合:模拟增益每增加一倍,黑电平会跟着往上跳。我见过某颗Sensor在16x增益下,黑电平比1x时高了12个DN(12-bit RAW)。
  • 列/行噪声:CMOS的读出电路天生有列方向噪声,每列的黑电平可能差2-3个DN。你取全图均值去减,每列都欠校正。

实战中怎么干?

我们团队的做法是:产线标定时,对每个增益档位、每个温度点,采集全黑帧,然后按列计算黑电平。代码里维护一张black_level_table[gain_idx][column]的表,运行时根据当前增益和温度插值。

// 别这样写:pixel -= global_black_level;// 这里踩过坑:全局减会导致列噪声放大,暗部出现竖条纹// 正确的做法:按列校正uint16_tblack_level_col=black_level_table[gain_idx][col];pixel=(pixel>black_level_col)?(pixel-black_level_col):0;// 注意:减完要钳位到0,否则负值会卷成65535,画面出现白色噪点

还有一个容易被忽略的点:OB(Optical Black)区域。很多Sensor在像素阵列边缘有被遮挡的暗像素,可以用来实时估算黑电平。但OB区域本身也有噪声,直接取均值会引入随机抖动。我们一般用中值滤波后再取均值,或者用IIR低通滤波做时间域平滑。

坏点校正:别把星星当成坏点抹了

坏点校正的坑,比黑电平还多。最经典的翻车案例:夜拍星空模式,算法把星星当成坏点给抹了,用户投诉“你们手机拍不出银河”。

坏点分两类:

  1. 静态坏点:出厂就有,位置固定,产线标定一次就行。
  2. 动态坏点:随温度、增益、曝光时间变化。高温下Sensor的暗电流噪声会超过阈值,被误判为坏点。

标定环节的实操经验

产线标定静态坏点时,我们拍三张不同亮度的灰阶卡:暗场(0.1 lux)、中灰(18%反射率)、亮场(90%反射率)。对每个像素,如果它在三张图里都偏离邻域均值超过阈值,才标记为坏点。这样能过滤掉随机噪声导致的误判。

// 坏点检测:基于邻域统计// 这里踩过坑:用3x3窗口太小,容易把纹理边缘误判为坏点// 建议用5x5窗口,排除中心像素后计算均值和标准差uint16_tneighbors[24];// 5x5窗口去掉中心intcount=0;for(intdy=-2;dy<=2;dy++){for(intdx=-2;dx<=2;dx++){if(dx==0&&dy==0)continue;neighbors[count++]=image[y+dy][x+dx];}}floatmean=calculate_mean(neighbors,24);floatstd=calculate_std(neighbors,24,mean);// 动态阈值:根据增益和ISO调整floatthreshold=base_threshold*(1.0+gain*0.1);if(fabs(pixel-mean)>threshold*std){// 标记为坏点,用中值替换pixel=median_filter(neighbors,24);}

动态坏点怎么防?

我们加了一个“温度感知”模块:当Sensor温度超过50℃时,自动放宽坏点检测的阈值,避免把热噪声当成坏点。同时,对检测到的坏点做时间域一致性检查——如果同一个位置连续5帧都被标记为坏点,才真正执行替换。这样能避免单帧随机噪声导致的误判。

镜头阴影校正:LSC不是简单的乘个系数

镜头阴影校正(Lens Shading Correction)是RAW域预处理里最吃算力的模块,也是最容易出“阴阳脸”的环节。

LSC的物理本质:镜头边缘的光照强度比中心低,同时CMOS的像素响应在边缘也有角度依赖性。所以LSC需要同时补偿光照衰减(cos^4 law)和像素响应角度(microlens shading)。

标定环节的坑

产线标定LSC时,通常拍一张均匀光源下的灰卡。但均匀光源本身就不均匀——积分球的光均匀性通常只有±2%,这2%的误差会直接映射到LSC系数里。我们试过用多张不同角度的灰卡做平均,但效果有限。

后来换了个思路:用多项式拟合代替逐点查表。对每个颜色通道(R/Gr/Gb/B),用二阶或三阶多项式拟合径向衰减曲线。这样既能平滑掉标定噪声,又能减少存储空间。

// 别这样写:直接查表,256x256的表格占用256KB内存// 这里踩过坑:查表法在边缘区域有量化误差,导致出现同心圆条纹// 推荐做法:多项式拟合// 假设中心在 (cx, cy),计算归一化半径 rfloatdx=(x-cx)/cx;floatdy=(y-cy)/cy;floatr2=dx*dx+dy*dy;floatr4=r2*r2;// 三阶多项式:gain = 1 + a*r2 + b*r4floatgain=1.0+coeff_a*r2+coeff_b*r4;// 注意:增益不能无限大,边缘增益通常不超过4xif(gain>4.0f)gain=4.0f;pixel=(uint16_t)(pixel*gain+0.5f);// 这里踩过坑:浮点运算后要四舍五入,否则会有偏色

多通道对齐的玄学

LSC最头疼的是R/Gr/Gb/B四个通道的增益曲线不一致。如果Gr和Gb的增益差超过1%,画面就会出现“棋盘格”纹理。我们曾经因为LSC系数没对齐,被客户投诉“你们手机拍白色墙壁有彩色格子”。

解决方案:标定时对每个通道独立拟合,然后强制Gr和Gb的增益差小于0.5%。具体做法是,在拟合完成后,对Gr和Gb的系数做加权平均,让两者趋近。

三个模块的流水线协同

回到开头那个bug。正确的流水线顺序是:

  1. 黑电平校正:先减掉暗电流,让数据归零。
  2. 坏点校正:在干净的数据上检测坏点,避免暗电流被当成坏点。
  3. 镜头阴影校正:最后做增益补偿,因为坏点校正已经修复了异常像素,不会把坏点增益放大。

一个容易被忽略的细节:黑电平校正后的数据可能有负值(如果OB区域噪声导致减过头),坏点校正模块必须处理负值。我们会在BLC之后加一个钳位,但钳位会丢失信息。更好的做法是:BLC输出带符号的16位数据,DPC和LSC都支持有符号运算,最后再钳位到无符号16位。

个人经验性建议

  1. 标定数据要带温度标签:产线标定时,同时记录Sensor温度。后期如果发现黑电平漂移,可以回溯是温度问题还是增益问题。
  2. 预留调试接口:在固件里留一个寄存器,可以动态调整坏点检测阈值。量产后的客诉,很多时候靠这个接口救场。
  3. 别迷信“自适应算法”:有些论文鼓吹全自动坏点检测,不用标定。我在三个项目上试过,全翻车了。产线标定+自适应微调,才是工程上靠谱的方案。
  4. LSC的增益上限要设死:边缘增益超过4x时,信噪比已经崩了,不如直接裁掉边缘像素。有些手机厂商的“超广角”模式,其实就是把LSC增益过大的区域裁掉。
  5. 调试时用伪彩色图:把LSC增益映射成伪彩色,一眼就能看出哪个通道没对齐。比盯着灰度图数像素高效得多。

最后说一句:RAW域预处理是ISP pipeline的基石,这里出问题,后面所有模块(去马赛克、降噪、锐化)都会跟着错。花80%的时间把这20%的环节做扎实,比后面花200%的时间去修bug划算得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询