ISP-Gamma

简介:个人学习分享,如有错误,欢迎批评指正。

一、Gamma的概念

1. 什么是 Gamma

最简单地说:Gamma 是一种非线性映射,用来把线性亮度信号转换成更适合显示、存储和人眼感知的亮度分布。

传感器输出的 RAW 信号,经过 black level、白平衡、去马赛克、CCM 等处理后,本质上仍然大致是线性光强空间的数据。

所谓"线性",就是:

  • 场景亮度翻 2 倍,信号数值也翻 2 倍
  • 物理光能和数值之间近似成正比

但人眼并不是线性感知亮度的。

人眼对暗部更敏感,对亮部没那么敏感。

同时,显示设备和图像编码也往往不直接使用线性亮度。

因此,ISP 里就需要一个模块,把"线性光强"压缩成"更符合视觉感知和显示特性"的非线性信号,这个过程就是 Gamma。

2. 为什么需要 Gamma

Gamma 之所以存在,主要有三个根本原因:

2.1 线性信号不适合直接显示

假设一个线性亮度值范围是:
L ∈ [ 0 , 1 ] L \in [0,1] L∈[0,1]

如果你直接把它送到显示端,图像通常会表现为:

  • 整体偏暗
  • 暗部层次不明显
  • 中低亮区域看起来"闷""死黑"

因为线性空间里,大量数值分布其实被"浪费"在高亮区域,而人眼对这些高亮差异没那么敏感。

Gamma 的本质就是:
重新分配码值,让暗部和中间调获得更多表达能力。

2.2 人眼对亮度的感知是非线性的

人眼感知亮度更接近对数型或幂函数型响应,而不是线性响应。

也就是说:

  • 从 0.01 到 0.02 的变化,你很容易感觉到
  • 从 0.81 到 0.82 的变化,你不太敏感

所以如果图像编码还用线性分配:

  • 暗部会不够细
  • 亮部反而分得太密

Gamma 就是在模仿这种视觉特性,使得有限 bit 数能更高效地分配给人眼更敏感的区域。

2.3 提高编码效率

这在数字图像和视频里很重要。

例如 8bit 图像总共只有 256 个等级。

如果你在线性空间里分配这 256 个等级,那么暗部可用等级很少,容易:

  • banding(色带)
  • 暗部断层
  • 细节丢失

而做了 Gamma 之后:

  • 暗部获得更多码值密度
  • 中间调更丰富
  • 整体主观效果更自然

所以 Gamma 还有一个很现实的作用:
用更少的 bit,保留更好的视觉层次。

3. Gamma 的数学形式

Gamma 最经典的表达形式是一个幂函数:
V o u t = V i n 1 / γ V_{out} = V_{in}^{1/\gamma} Vout=Vin1/γ

或者写成:
y = x 1 / γ y = x^{1/\gamma} y=x1/γ

其中:

  • x x x:输入的线性亮度值
  • y y y:输出的 Gamma 编码值
  • γ \gamma γ:Gamma 系数,常见如 2.2、2.4

3.1 为什么是 1 / γ 1/\gamma 1/γ

因为在图像编码里,通常做的是"Gamma 编码",也就是把线性值压成非线性值。

例如当 γ = 2.2 \gamma = 2.2 γ=2.2 时:
y = x 1 / 2.2 y = x^{1/2.2} y=x1/2.2

由于 (1/2.2 < 1),这个函数会把低亮度区域"抬起来"。

举个例子:

如果
x = 0.25 x=0.25 x=0.25


y = 0.25 1 / 2.2 ≈ 0.53 y=0.25^{1/2.2}\approx0.53 y=0.251/2.2≈0.53

也就是说,原来线性空间中 0.25 的亮度,经过 Gamma 后编码成约 0.53。

这正是"抬暗部"的体现。

3.2 反 Gamma

如果以后要恢复线性光强,比如用于物理计算、图像融合、神经网络训练等,则要做反变换:
x = y γ x = y^{\gamma} x=yγ

这就叫 inverse gamma 或 de-gamma。

4. Gamma 曲线长什么样

Gamma 曲线是一个向上凸的非线性曲线。

如果横轴是输入线性亮度 x x x,纵轴是输出编码值 y y y,那么:

  • 在暗部,曲线比较陡
  • 在亮部,曲线逐渐变平

这意味着:

  • 暗部小变化会被放大
  • 高亮部分变化被压缩

所以 Gamma 的视觉效果可以概括为:提升暗部可见性,压缩高亮码值密度。

但注意,它并不一定等于 HDR 里的"压高光细节",这一点后面会和 tone mapping 区分。

5. Gamma 的核心作用到底是什么

Gamma 最核心的作用,可以归纳成三点:

  • 把线性信号变成感知更均匀的信号

    这是它最本质的作用。

  • 让显示效果正常

    如果你把线性 RGB 直接显示到一个默认按 sRGB/2.2 Gamma 理解的显示链路上,图像通常会太暗。

    Gamma 编码的目的之一,就是让输出更匹配显示系统的预期。

  • 为后续压缩和存储做准备

    JPEG、视频编码、显示输出,通常都不是线性空间直接存储,而是某种非线性编码空间。

    Gamma 使图像更适合这些标准。

6. sRGB Gamma 是什么

很多时候大家说"Gamma 2.2",但严格说,sRGB 并不是纯粹的单一幂函数。

sRGB 的传递函数是分段的:

当线性值较小时,采用线性段;

较大时采用近似幂函数段。

标准形式是:
C ′ = { 12.92 C , C ≤ 0.0031308 1.055 C 1 / 2.4 − 0.055 , C > 0.0031308 C' = \begin{cases} 12.92C, & C \le 0.0031308 \\ 1.055C^{1/2.4}-0.055, & C > 0.0031308 \end{cases} C′={12.92C,1.055C1/2.4−0.055,C≤0.0031308C>0.0031308

这里:

  • C C C:线性值
  • C ′ C' C′:编码值

所以很多工程里说"Gamma 2.2",其实常常只是一个近似说法。

真正标准输出若对接 sRGB,通常用的是这条分段 OETF。

二、Gamma 和亮度、对比度的关系

Gamma 会影响亮度和对比度,但它不是简单的亮度调节

1. Gamma 改的是"中间调分布"

假设你提高整体亮度,是所有像素统一加一个值;

而 Gamma 不是这样。

Gamma 主要影响:

  • 暗部提升多少
  • 中间调展开多少
  • 高亮压缩多少

所以它更像是:
一种按亮度位置分段作用的非线性对比度重分配。

2. 不同 Gamma 值的效果

  • Gamma 较大(例如 2.4)

    编码时 1 / γ 1/\gamma 1/γ 更小,暗部抬得更多,整体看起来更亮、更柔。

  • Gamma 较小(例如 1.8)

    暗部提升没那么明显,图像中间调相对更"硬"。

    但具体视觉效果还要结合 tone mapping 和 display chain。

三、Gamma 和 Tone Mapping 的区别

这个地方特别容易混。

1. Gamma 是"标准型非线性编码"

Gamma 的核心目的是:

  • 感知均匀化
  • 适配显示和编码
  • 非线性压缩码值分布

它通常是一条比较规则、平滑、全局性的曲线。

2. Tone Mapping 是"动态范围映射"

Tone Mapping 的核心目的是:

把大动态范围压到显示设备能承受的范围内,同时尽量保留观感。

它更关注:

  • 高光压缩
  • 暗部保留
  • 整体动态范围映射
  • 局部或全局视觉平衡

所以:

  • Gamma 更偏"编码特性"
  • Tone Mapping 更偏"动态范围重映射"

3. 两者的关系

在实际 ISP 中:

  • Tone Mapping 往往先处理全局亮度分布
  • Gamma 再做感知编码

也有一些实现把两者融合为一条 LUT,但概念上它们不是一回事。

四、Gamma 和 OETF / EOTF 的区别

这也是显示链路里必须搞清楚的。

1. OETF

OETF = Opto-Electronic Transfer Function

意思是:

从场景光信号到电子编码信号的变换

在相机/ISP 里,Gamma 编码常常就属于 OETF 这一类。

2. EOTF

EOTF = Electro-Optical Transfer Function

意思是:

从电子信号到显示光输出的变换

这是显示器端的事情。

3. 为什么历史上叫 Gamma

早期 CRT 显示器的光输出和输入电压之间天然就近似:
L ∝ V γ L \propto V^\gamma L∝Vγ

其中 γ \gamma γ 大约在 2.2 左右。

所以相机端常常做一个反向的幂函数预补偿:
V ∝ L 1 / 2.2 V \propto L^{1/2.2} V∝L1/2.2

这样整个链路组合起来就接近线性。

所以"Gamma"这个词既有历史背景,也有现代编码含义。

五、Gamma 和降噪的关系

这是手机 ISP 里非常实际的问题。

因为 Gamma 会提升暗部信号,所以也会提升暗部噪声。

因此:

  • 如果降噪太弱,Gamma 后暗部会脏
  • 如果降噪太强,Gamma 后暗部会糊

所以工程里常常是:

先在 RAW / RGB 线性域做好噪声控制,再进入 Gamma。

否则 Gamma 相当于一个"噪声放大镜"。

六、Gamma的值如何得到?

1. 先说结论:Gamma 的值通常不是直接"算出来"的,而是"由标准 + 目标 + 调试"共同确定的

如果你问的是最经典的单一 Gamma 参数,比如:

  • 1.8
  • 2.2
  • 2.4

那么它最早来自两方面:

  • 显示设备的物理特性
  • 人眼对亮度的非线性感知

如果你问的是手机 ISP 里真正用的 Gamma 曲线,那通常不是一个单独数字,而是:
一条 LUT 曲线,或者一条分段函数曲线

这条曲线往往是根据:

  • 目标输出标准(sRGB / Rec.709 / P3 / HDR)
  • 显示链路要求
  • 暗部层次需求
  • 噪声表现
  • 主观观感调试

最后确定出来的。

所以严格说:
理论上的 Gamma 值可以从显示和视觉模型得到;工程上的 Gamma 曲线通常通过标准约束和调试拟合得到。

2. Gamma 最早为什么会有一个"2.2"这样的值

2.1 来自显示器的物理响应

早期 CRT 显示器的亮度输出和输入电压之间,不是线性关系,而近似满足:
L ∝ V γ L \propto V^\gamma L∝Vγ

其中:

  • L L L:显示亮度
  • V V V:输入电压
  • γ \gamma γ:通常大约在 2.2 左右

也就是说,CRT 天生就带一个大约 2.2 的幂律响应。

如果相机端什么都不做,图像送到 CRT 上会过暗。

所以编码端就会做一个反向补偿:
V ∝ L 1 / 2.2 V \propto L^{1/2.2} V∝L1/2.2

这样两边合起来:
L d i s p l a y ∝ ( L s c e n e 1 / 2.2 ) 2.2 = L s c e n e L_{display} \propto (L_{scene}^{1/2.2})^{2.2} = L_{scene} Ldisplay∝(Lscene1/2.2)2.2=Lscene

整个链路就近似恢复线性。

所以最早的 Gamma=2.2,本质上是从显示器物理特性来的。

2.2 后来又和视觉感知相吻合

人眼对亮度的感知本来就不是线性的,而更接近对数型或幂律型。

所以 2.2 左右这样的非线性,不仅适合补偿 CRT,也刚好比较适合:

  • 暗部展开
  • 码值高效分配
  • 符合主观视觉

因此它后来就成为非常经典的标准值。

七、标准 Gamma 值是怎么定的

不同标准里,Gamma 的"值"并不完全一样。

1. sRGB

sRGB 不是单一纯幂函数,但可以近似看作 Gamma ≈ 2.2。

它的标准 OETF 是分段的:
C ′ = { 12.92 C , C ≤ 0.0031308 1.055 C 1 / 2.4 − 0.055 , C > 0.0031308 C' = \begin{cases} 12.92C, & C \le 0.0031308 \\ 1.055C^{1/2.4}-0.055, & C > 0.0031308 \end{cases} C′={12.92C,1.055C1/2.4−0.055,C≤0.0031308C>0.0031308

所以如果你问:

"sRGB 的 Gamma 值是多少?"

更准确地说应该是:

  • 不是严格单一 Gamma
  • 工程上常近似为 2.2
  • 严格实现要用分段函数

2. Rec.709

Rec.709 的 OETF 也是分段的,不是简单纯 2.2。

但工程里也常用近似 Gamma 概念去理解。

3. BT.1886

显示端常说的 BT.1886 更接近 Gamma 2.4 的显示 EOTF。

所以如果某系统面向视频/电视显示,Gamma 目标常比普通 PC/sRGB 更重一些。

4. Apple / 老系统

有些早期系统曾偏向 1.8 左右。

但现在主流图像和显示链路里,2.2 和 2.4 更常见。

5. 如果只用单个 Gamma 参数,它是怎么"确定"的

如果不谈复杂 LUT,而只谈一个幂函数:
y = x 1 / γ y = x^{1/\gamma} y=x1/γ

那么 γ \gamma γ 的选取,一般来自下面几个方面。

5.1 由目标显示标准决定

如果输出目标是:

  • sRGB 内容显示:一般参考 2.2 附近
  • 广播电视/暗室视频:更可能接近 2.4
  • 某些专用系统:按各自显示标准

也就是说:
Gamma 首先不是从图片本身算出来,而是从输出系统要求定下来的。

5.2 由主观亮度风格决定

即使在同一显示标准下,不同产品也可能会在中间调和暗部做微调。

例如:

  • 想让画面更通透:暗部可适当抬高
  • 想让画面更厚重:暗部压一点
  • 想让夜景更亮:中低亮可多抬一些
  • 想让噪声不明显:暗部不要抬过头

这时"等效 Gamma"就会发生变化。

5.3 由 bit 深和量化噪声决定

如果输出位深较低,比如 8bit,Gamma 的设计需要更注意暗部码值分配,否则容易 banding。

所以工程上 Gamma 不是只看亮不亮,还要看:

  • 暗部层次
  • banding
  • 量化误差
  • 噪声放大

6. 真实 ISP 里,Gamma 曲线通常怎么得到

重点来了。

真实 ISP 中,Gamma 往往不是一个单值,而是一条曲线。

这条曲线的确定,通常有三种来源:

  1. 直接采用标准曲线
  2. 在标准曲线基础上做调节
  3. 完全通过画质调试生成 LUT

6.1 直接采用标准曲线

最简单的情况是:

  • 目标输出是 sRGB
  • ISP 直接采用 sRGB OETF
  • 或采用近似 2.2 Gamma 曲线

这种情况下,Gamma "值"其实来自标准,不需要你自己拟合。

例如你知道要输出 sRGB,那么就直接用:
C ′ = { 12.92 C , C ≤ 0.0031308 1.055 C 1 / 2.4 − 0.055 , C > 0.0031308 C' = \begin{cases} 12.92C, & C \le 0.0031308 \\ 1.055C^{1/2.4}-0.055, & C > 0.0031308 \end{cases} C′={12.92C,1.055C1/2.4−0.055,C≤0.0031308C>0.0031308

这就是标准给定的。

6.2 基于标准曲线微调

这在手机 ISP 里非常常见。

做法是:

  • 先拿标准 Gamma / OETF 当基础
  • 再针对产品风格、噪声、亮度观感调整中低亮部分
  • 最终生成一条 LUT

例如:

  • 暗部稍微再抬一点,夜景更亮
  • 中间调压一点,画面对比更好
  • 高亮收一点,避免发白

这时候你已经不能简单说"Gamma=2.2",而是应该说:
这是一条以 2.2/sRGB 为基础调出来的 Gamma LUT

6.3 完全通过调试生成 LUT

在很多 ISP 芯片里,Gamma 模块就是一张查找表。

例如输入 10bit,输出 10bit:

  • 输入 0~1023
  • 输出也 0~1023
  • 中间每个点对应一个映射值

这张 LUT 的生成通常流程是:
第一步:确定目标观感

例如:

  • 暗部不能死黑
  • 中间调要通透
  • 高亮不要太平
  • 夜景不能把噪声抬炸

第二步:选一条初始曲线

通常来自:

  • sRGB
  • 2.2 Gamma
  • 2.4 Gamma
  • 上一代产品曲线

第三步:用灰阶图和真实场景调试

观察:

  • 灰阶过渡
  • banding
  • 人像脸部亮度
  • 夜景阴影
  • 高光过渡

第四步:修改控制点

在 LUT 上调几个关键控制点,比如:

  • 黑位附近
  • 10% 灰
  • 18% 灰
  • 50% 灰
  • 80% 灰
  • 高亮端

第五步:迭代确定最终曲线

所以从工程上讲,Gamma 的值往往是:

先有一条理论曲线,再通过主客观联合调试收敛出来。

7. Gamma 能不能通过实验"拟合出来"

可以,但要分清你拟合的是什么。

7.1 拟合显示器的 Gamma

如果你是测某个显示器的真实响应,可以做实验:

  • 给显示器输入一系列灰阶码值 V V V
  • 用亮度计测输出亮度 L L L
  • 拟合关系:
    L = k V γ L = kV^\gamma L=kVγ
    两边取对数:
    log ⁡ L = log ⁡ k + γ log ⁡ V \log L = \log k + \gamma \log V logL=logk+γlogV
    然后对 log ⁡ V \log V logV 和 log ⁡ L \log L logL 做线性回归,就能拟合出 γ \gamma γ。
    这是真正"通过数据拟合 Gamma 值"的经典方式。

7.2 拟合某条目标编码曲线的等效 Gamma

如果你手头已经有一条 LUT,也可以在某一段内拟合它的等效 Gamma。

比如你有若干输入输出点 ( x i , y i ) (x_i,y_i) (xi,yi),想找一个最接近的幂函数:
y = x 1 / γ y = x^{1/\gamma} y=x1/γ

同样可通过对数变换估计一个"等效 Gamma"。

但注意:
如果原曲线是分段的、带 toe/shoulder 的,那它并不真正对应一个单独的 Gamma 值。

这时只能说"某一区间等效 Gamma 约为多少"。

8. 在 ISP 里,Gamma 曲线具体是如何调出来的

这个部分最贴近工程。

8.1 先确定输入输出域

你要先明确:

  • 输入是线性 RGB 吗
  • 输出是要给 sRGB 吗
  • 输出 bit depth 是 8bit、10bit 还是 12bit
  • 后面还有没有 tone mapping / LTM / contrast enhancement

因为这些都会影响 Gamma 该长什么样。

8.2 选参考曲线

一般选:

  • sRGB 曲线
  • Gamma 2.2
  • Gamma 2.4
  • 上一代成熟产品曲线

作为初始值。

8.3 看灰阶

灰阶是调 Gamma 最重要的测试图之一。

你要看:

  • 黑位是否过死
  • 近黑是否有层次
  • 中灰是否自然
  • 高灰是否挤压
  • 是否有 banding

如果暗部断层,说明暗部展开不够。

如果整体发灰,说明暗部/中间调抬太多。

8.4 看自然图

只看灰阶不够,还要看:

  • 人像肤色
  • 室内中低照场景
  • 夜景
  • 逆光
  • 高亮天空

因为 Gamma 曲线在真实图里会同时影响:

  • 通透感
  • 层次感
  • 噪声显著度
  • 对比感

8.5 结合噪声调

Gamma 最大的工程矛盾之一是:

暗部抬得越多,暗部噪声也越容易暴露。

所以 Gamma 不是单独调的,而要和:

  • Bayer NR
  • YNR
  • chroma NR
  • sharpen
  • tone mapping

一起看。

夜景里尤其明显。

9. 一个简单的工程理解:Gamma 的"值"其实常常来自控制点

很多 ISP 里,Gamma 不是输入一个 2.2,而是输入一串点:

bash 复制代码
x:  0,   64,  128, 256, 512, 768, 1023
y:  0,  140, 220, 380, 650, 860, 1023

然后硬件做插值。

所以你最后得到的不是"一个 Gamma 值",而是一条曲线。

如果有人问"这条 Gamma 的值是多少",更准确的回答应该是:

  • 它不是单一 Gamma
  • 它是 LUT 曲线
  • 可以在某些区间拟合出等效 Gamma

理论上,Gamma 值主要由显示系统的传递特性和人眼视觉感知特性决定;工程上,ISP 中的 Gamma 通常以标准曲线为初始参考,再结合目标输出标准、灰阶测试、自然场景表现、噪声水平和主观画质调试,最终确定为一条 LUT 或分段非线性曲线。

八、总结

你可以这样理解:
Gamma 是 ISP 后段把线性亮度重新编码为感知更均匀亮度的一步,它本质上不是为了"让图更亮",而是为了让有限的输出码值更符合人眼感知和显示系统的需要。

这句话很重要。

因为很多初学者把 Gamma 当成"提亮模块",其实不准确。

它更准确的定位是:

  • 感知编码
  • 显示适配
  • 码值重分布

Gamma 是一种作用在线性图像信号上的非线性传递函数,通过压缩高亮、展开暗部和中间调,将物理线性光强映射到更符合人眼视觉特性和显示编码要求的亮度表示。

参考文献:
图像信号处理芯片设计原理----10 Gamma矫正


结~~~

相关推荐
机器学习之心2 小时前
HHO-LSBoost哈里斯鹰算法优化最小二乘提升多输入回归预测MATLAB代码
算法·matlab·回归·hho-lsboost
ballball~~2 小时前
ISP-Demosaic
图像处理·数码相机·算法
m0_730115112 小时前
C++中的装饰器模式实战
开发语言·c++·算法
我真会写代码2 小时前
深入浅出图像处理原理:OpenCV基础与YOLO实战,从入门到落地
图像处理·人工智能
m0_743470372 小时前
C++中的适配器模式
开发语言·c++·算法
英俊潇洒美少年2 小时前
react 18 的fiber算法
前端·算法·react.js
Oueii2 小时前
模板代码模块化设计
开发语言·c++·算法
m0_730115112 小时前
C++与Rust交互编程
开发语言·c++·算法
jay神2 小时前
基于 YOLOv8 的交通违规检测系统
人工智能·算法·yolo·目标检测·计算机视觉