从JPEG图像中提取亮度的正确方法是什么(伽马校正等)?

3
简而言之:我无法从JPEG照片的RGB值中提取有意义的光强度,并且尝试考虑伽马校正或sRGB只会使情况变得更糟。
我正在做一个玩具项目,涉及处理一堆使用间隔计拍摄的照片图像。基本上,我想将它们制作成时间流逝视频,并进行一些修正,使得剪辑更整洁。我使用佳能数码单反相机。
我需要一个函数,可以给定一个JPEG文件,找出“平均场景亮度”。结果应该是一个简单的数字;不需要用任何绝对光度单位来表达,我只做相对比较。因此,例如,你拍了一张房间的照片,函数返回“5.0”。然后你在灯光旁边加了第二个完全相同类型的灯泡,再次拍摄。现在该函数应该给你“10.0”。
因此,我目前的这个函数实现结合了几个方面:ISO速度、快门速度、光圈(从EXIF中提取)和平均图像亮度。显然,Exif信息更重要,因为在自动模式下,相机会尝试使用这样那样的设置,所以图像亮度围绕中灰点左右。 然而,ISO/快门/光圈设置的分辨率都不到1/3档,因此检测图像亮度对于“微调”非常重要。
当我进行此操作时,我得到了一些明显错误的结果,我挖掘得越深,就越感到困惑。因此最终我设置了一个“几乎严肃”的实验:

测试设置: 一个房间里的简单墙壁,使用白炽灯照明,光线比较均匀。 使用两台相机进行比较:50毫米定焦镜头的5D和35毫米定焦镜头的350D。 离墙距离:约3米。 所有照片都以1/10秒快门速度拍摄。 相机设置:手动,“忠实模式”(无增强、无饱和度或对比度提升),钨丝白平衡,无自定义功能,JPEG-Fine,sRGB色彩空间。镜头没有滤镜。照明不变,只改变ISO和光圈设置。 以下是我得到的结果:

     Avg   Spd   ISO  Aperture
1. 0.3507, 0.10, 100, f/2.8
2. 0.5382, 0.10, 200, f/2.8
3. 0.3557, 0.10, 200, f/4.0
4. 0.2709, 0.10, 200, f/5.0
5. 0.2118, 0.10, 200, f/5.6
6. 0.1718, 0.10, 200, f/6.3
7. 0.1459, 0.10, 200, f/7.1
8. 0.1112, 0.10, 200, f/8.0
9. 0.0883, 0.10, 200, f/9.0

第一列是整个图像的平均像素值(直接从JPEG中获取),通过将(R+G+B)/3转换为灰度值。颜色在[0..1]范围内进行归一化,通过将[0..255]范围除以255来实现。因此,在1)和2)之间,我仅更改ISO设置,图像应该变亮两倍,但平均像素值仅增加53%(没有任何过曝区域)。
2..3: 光圈减小一档,图像应该变暗一半,因此1)和3)相同(额外的亮度可能是由于减少了晕影)。
3..5: 再次减小一档,因此5)应该比3)暗一半。
5..8: 同样,应该减半(虽然基本上没问题)。
所有这些都非常奇怪。顺便说一句,两台相机的结果是一致的,这表明这不仅仅是特定型号的奇特之处。

这是没有应用任何伽马校正的情况。JPEG读取代码使用C++编写,基本上遵循了IJG示例代码(djpeg实用程序)。现在,JPEG保存了经过伽马校正的值,因此像素值应被视为sRGB颜色空间中的值(获取源像素,转换为[0..1],并应用sRGB->linear RGB transform。让我们尝试一下:

     Avg   Spd   ISO  Aperture
1. 0.1140, 0.10, 100, f/2.8
2. 0.2746, 0.10, 200, f/2.8
3. 0.1175, 0.10, 200, f/4.0
4. 0.0682, 0.10, 200, f/5.0
5. 0.0424, 0.10, 200, f/5.6
6. 0.0287, 0.10, 200, f/6.3
7. 0.0213, 0.10, 200, f/7.1
8. 0.0133, 0.10, 200, f/8.0
9. 0.0092, 0.10, 200, f/9.0

我还尝试了“普通”的伽马校正(伽马=2.2),结果与sRGB校正情况非常相似。
所以我非常困惑。 有人能解释一下从相机JPEG中提取的RGB强度应该如何真正解释吗?因为我已经没有任何想法了 :)

我怀疑相机传感器不完全是线性的。 另外,看起来你想要一个线性的亮度值..每个像素覆盖了一个区域,所以也许尝试一下取 luminosity 值的平方根。 你还可以尝试转换为 HSB 并使用其中的亮度值。 这似乎很有用: http://www.kweii.com/site/color_theory/2007_LV/BrightnessCalculation.pdf - Pete
@Pete,感谢你的指点。是的,我确实需要一个线性亮度值。 然而,HSB方法和RGB向量长度方法确定亮度并不适用于我的测试照片;它们几乎是灰色的,因此无论使用哪种方法,亮度测量都是微不足道的。平方根的想法也没有帮助,我还认为这不是物理上正确的...亮度加倍,图像传感器中的光子应该增加一倍。传感器本身是固有线性的 - anrieff
最后一个链接还提到了数字传输函数,这可能是困扰我的问题。如果我发现了任何重大发现,我将进一步研究并发布更新。 - anrieff
1个回答

9

因此,当我继续阅读时,这个谜团逐渐揭开。

尽管相机的传感器在理论上能够测量线性光强度,但它显然并未这样做,而是模仿了照相胶片的行为,后者具有众所周知的非线性响应(例如,请参见此处,图3)。 因此,数码单反的响应曲线远非线性,而更像这样:

数码相机曝光响应

因此,如果没有精确的校准,从像素值中获取场景绝对亮度是不切实际的。

然而,我只想对照片进行亮度调整,大致正确的亮度估计就足够了,因此我开始重构我的相机(佳能350D)的传输函数:

佳能350D传感器的对数线性响应曲线

白色数据点对应于各种光圈值(f / 22、f / 20、f / 18、f / 16等,以1/3档为增量)下的不同曝光。与上一个图表一样,X轴是入射亮度的对数,而Y轴是像素值(经Gamma校正后)的线性值。假设该图表在单位正方形内,我还通过五阶多项式计算出了近似拟合曲线:

(((((- 6.76219 * x) + 12.0459) * x - 5.8683) * x + 1.72338) * x - 0.148753) * x + 0.0105364;

对于列表[0.05,1]中的每一个x:

所以,如果你拥有原始(“真实”)亮度,获取像素值将如下:

  1. 获取输入亮度的对数,并线性转换它,使您在[0..1]间隔内具有7¾个曝光档次,
  2. 应用上述多项式(不过,您需要对[0..0.05)区间进行特殊处理)。
  3. 应用sRGB压缩。

称此变换为T,并且我应用程序中的整个工作流程现在如下所示:

  1. 使用T-1处理输入JPEG图像,
  2. 结果值以浮点格式保留,并被视为线性RGB值,
  3. 在保存结果回JPEG之前应用T

我认为如果您使用原始未经处理的数据,应该能够获得线性值。CCD是人类已知最线性的设备(http://en.wikipedia.org/wiki/Charge-coupled_device);相机制造商将这种曲线应用于图像,使您最终获得的图像看起来更加令人愉悦。我建议您尝试使用原始数据,看看是否有区别--您不应该必须处理这些问题。 - mmr
我不这么认为。这个人 显然在处理 RAW 数据,而同样的转换曲线也在那里明显存在。虽然我自己没有尝试过这个方法。此外,RAW 文件占用更多的空间,而我想要处理成千上万张照片(延时摄影),所以存储空间需求确实是一个问题。最后,RAW 文件是特定于供应商的,需要专门的库来读取它们,并且处理可能会很麻烦(我不想实现一个完全成熟的 YetAnotherRAWProcessingApp):) - anrieff
@anrieff-- 我只是提醒你不要删除太多信息。RAW 中有很多信息在 jpeg 转换中会丢失,这种损失可能会严重影响你所做的任何事情。自动转换为 jpg 是你的过程中另一个错误的来源;修改转换参数将产生不同的结果。我想问题是差异是否显著。虽然我不知道商业相机不是线性的,但感谢提供链接。科学探测器最好是线性的,否则很多科学都是错误的。 - mmr
你在这里的方法肯定是正确的。传输曲线完全是由相机制造商或原始处理软件的心血来潮而任意确定的,因此测量它非常关键。原始数据可能是线性的,但由于你不能在经过转换之前使用它,所以这一点是无关紧要的。 - Mark Ransom

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接