4、数学模型:二维增益模型、多项式拟合模型、网格插值模型

好,咱们接着聊。上一节我们搞清楚了镜头阴影是怎么来的,也知道了校正的核心就是给每个像素乘上一个增益系数。那问题来了——这个增益系数怎么算?怎么存?怎么用?

说白了,就是怎么用数学去描述“画面边缘暗、中间亮”这个现象。我这些年折腾下来,发现主流的数学模型就三种:二维增益模型、多项式拟合模型、网格插值模型。各有各的脾气,咱们一个一个说。

4.1 二维增益模型——最朴素的思路

二维增益模型,其实是最早被用在硬件里的方法。为什么?因为它简单,计算量小,适合在ISP流水线里硬跑。

它的核心思想是:假设镜头阴影是旋转对称的,也就是说,离画面中心越远,亮度衰减越厉害。那增益就只跟像素到中心的距离有关。

数学表达式长这样:

G(x, y) = 1 + k1 * r² + k2 * r⁴ + k3 * r⁶

其中 r = sqrt((x - cx)² + (y - cy)²),(cx, cy) 是图像中心坐标。k1、k2、k3 是拟合出来的系数。

关键点:这个模型只用了径向距离,不考虑角度。所以它只能处理完美的圆形阴影。如果你的镜头有偏心,或者模组装配有倾斜,那这个模型就不太够用了。

我记得有一次,客户反馈说画面左上角比右下角暗很多。我一开始用二维增益模型去调,怎么调都调不好。后来才发现,是镜头的光轴偏了。嗯,这时候就得换模型了。

我的经验:二维增益模型适合做快速原型验证。我一般先用它跑一遍,看看残差有多大。如果残差在5%以内,那就直接用这个模型,省事。如果超过5%,再考虑更复杂的模型。

4.2 多项式拟合模型——更灵活,也更费劲

多项式拟合模型,说白了就是把二维增益模型给“升级”了。它不再假设阴影是旋转对称的,而是允许增益在x方向和y方向上独立变化。

它的通用形式是:

G(x, y) = a00 + a10*x + a01*y + a20*x² + a11*x*y + a02*y² + ...

阶数越高,拟合精度越好,但计算量也越大。我一般用到3阶或4阶,再高就容易过拟合了。

你想想看,这个模型的好处是什么?它能捕捉到非对称的阴影。比如镜头装配有点歪,或者传感器表面有微小的倾斜,这些都能被多项式拟合出来。

注意:多项式拟合有个坑——边界效应。在图像边缘,多项式可能会“放飞自我”,算出一些离谱的增益值。我曾经遇到过,在图像角落增益突然飙到2.0以上,结果画面直接过曝。后来我加了边界约束,才把这个问题压下去。

实际项目中,我一般这样用:

  1. 先拍一张均匀光照下的灰卡图
  2. 提取每个像素的亮度值
  3. 用最小二乘法拟合多项式系数
  4. 检查残差分布,如果某个区域残差特别大,就增加局部权重

代码实现也不复杂,用numpy的polyfit或者scipy的curve_fit都能搞定。我贴一段伪代码,大家感受一下:

# 假设我们已经提取了每个像素的坐标和亮度值
# x, y 是归一化后的坐标,范围 [-1, 1]
# z 是目标增益值

# 构造多项式特征矩阵
def poly_features(x, y, order=3):
    features = []
    for i in range(order+1):
        for j in range(order+1-i):
            features.append(x**i * y**j)
    return np.column_stack(features)

# 最小二乘拟合
X = poly_features(x, y, order=3)
coeffs = np.linalg.lstsq(X, z, rcond=None)[0]

避坑指南:我曾经把坐标直接用了像素值(比如x从0到1920),结果拟合出来的系数数量级差了好几个量级,数值稳定性一塌糊涂。后来我养成了习惯——先把坐标归一化到[-1, 1]或者[0, 1]区间。这个小动作,能省掉你很多调试时间。

4.3 网格插值模型——精度之王,但吃资源

网格插值模型,是这三种模型里精度最高的。它的思路很简单:把图像分成网格,每个网格顶点存一个增益值,网格内部的像素用插值算出来。

为什么精度高?因为它不假设任何数学形式。阴影长什么样,它就存什么样。哪怕你的镜头阴影是个不规则形状,它也能完美还原。

网格的划分方式一般有两种:

网格类型 优点 缺点
均匀网格 实现简单,硬件友好 在阴影变化剧烈的区域,精度不够
非均匀网格 在边缘区域可以加密网格 实现复杂,需要额外的坐标映射

我个人习惯用均匀网格,网格大小取 16x16 或者 32x32。为什么?因为大多数ISP硬件都支持这个粒度,而且存储开销可控。一个1920x1080的图像,用32x32的网格,只需要存 33x33=1089 个增益值,每个值用16位定点数存,也就2KB出头。

插值方法也有讲究:

  • 双线性插值:最常用,计算量小,效果够用
  • 双三次插值:精度更高,但硬件实现成本高
  • 最近邻插值:千万别用,会有明显的块状效应

我的建议:如果你在做手机摄像头,用网格插值模型准没错。现在的ISP芯片基本都内置了LSC(Lens Shading Correction)模块,支持网格增益表。你只需要把标定好的网格数据烧进去就行。

但要注意,网格插值模型有个“软肋”——标定过程比较繁琐。你需要采集多张不同亮度的灰卡图,然后逐网格计算增益值。我记得有一次,为了标定一个广角镜头的阴影,我拍了200多张图,处理了一整天。

警告:网格插值模型对噪声非常敏感。如果标定用的灰卡图有噪声,那算出来的网格增益也会带噪声,最终校正出来的画面会出现“水波纹”。我的做法是:先对灰卡图做降噪处理,然后再计算增益。另外,网格增益表最好做一次平滑滤波,去掉高频抖动。

4.4 三种模型怎么选?

好了,三种模型都讲完了。你可能会问:那我到底该用哪个?

我根据项目经验,给个参考:

场景 推荐模型 理由
低成本方案,硬件资源紧张 二维增益模型 只需要3-4个系数,计算量极小
中端方案,需要处理非对称阴影 多项式拟合模型 灵活,精度够用,存储开销小
高端方案,追求极致画质 网格插值模型 精度最高,能处理任意形状的阴影

其实,在实际产品中,这三种模型并不是互斥的。我见过一些方案,先用多项式拟合做一个粗校正,再用网格插值做细调。这样既保证了精度,又降低了网格的存储量。

嗯,关于数学模型,今天就聊这么多。下一节我们讲讲标定流程——怎么从一张灰卡图,算出这些模型需要的参数。那才是真正考验工程能力的地方。