EVMeter-理论篇

鱼一直对单反的自动测光原理很感兴趣,可没学过光学和光电传感器相关知识的我一直都不知道怎样实现它。
直到我看到了一个 老外做的测光表

其实很简单,只需要一个普通的光强传感器(精度越高越好,动态范围越大越好)
难的部分(也是困扰我的部分)是计算的算法,好在查wiki可以查到,还以为是什么相机厂商的黑科技呢(23333

EV-Exposure Value

曝光值代表能够给出同样曝光的所有相机光圈和快门的组合。这一概念是在一九五零年代在德国发展起来的,被试图用以简化在等价的拍摄参数之间进行选择的过程。曝光值同样也可以表示曝光刻度上的一个级差,1EV对应于两倍的曝光比例并通常被称为“一挡”

曝光值最早是使用符号Ev来表示,ISO标准中(不是感光的那个ISO,是国际标准化组织)延续了这一标记法,但在其他地方EV这个缩写更常见~

Define

曝光值是一个以2为底的对数刻度系统:

E_v = \log_2 {N^2 \over t}

其中N是光圈(F value),t是曝光时间(快门),单位秒(s).

EV & Light(Lux)

在给定照明条件下所采用的F值与曝光时间由下式给出:

{N^2 \over t} = {LS \over K}

其中:

  • N是相对光圈(F值)
  • t是曝光时间,单位秒(s)
  • L是场景平均辉度(就是平均亮度)
  • S是ISO指数
  • K是反射式测光表校正常数

代入等式右边,得到曝光值:

E_v = \log_2{LS \over K}

同样也可以由入射式测光结果计算拍摄参数:

{N^2 \over t} = {ES \over C}

其中:

  • E是照度
  • C是入射式测光表校正常数
    当然也有:
E_v = log_2{ES \over C}

用EV作为照度与亮度的量度

好绕口的标题.jpg
在给定ISO和测光表校正常数(K或C)之后,EV与亮度(或照度)之间就建立了直接的关联。

严格地说,曝光值并不是亮度或照度的量度:事实上,一个对应于某一亮度(或照度)的曝光值是用以指示照相机(已知其感光度)来获取所谓的正确曝光用的。然而,摄影器材生产商在实践中常常用感光度100时的曝光值来表达亮度,如在说明测光范围或对焦灵敏度时。这一做法由来已久。标准的做法是同时声明所用测光表校正常数和对应的感光度,但很少有厂商会这么做。

不同厂商所选用的反射式测光表校正常数之间有微小的差别。一个普遍的选择是12.5(佳能,尼康和世光)。在感光度100时,曝光值与亮度的关系为:

L = 2^{EV-3}

该式的推导如下:
已知

EV = log_2{N^2 \over t}
{N^2 \over t} = {LS \over K}
\Longrightarrow
L = 2^{EV}*{K \over S}
 while K=12.5,S=100,
\Longrightarrow L = 2^{EV}*0.125=2^{EV-3}

入射式测光表的情况比反射式测光表要复杂得多,因为校正常数C与传感器类型有关。有两种传感器很常见:平面型(余弦响应)和半球型(心形响应)。用平面型传感器测量照度时,C 的典型值是250,而照度的单位是勒克斯(lux)。在 C = 250时,照度与感光度100下的曝光值的关系为:

E = 2.5*2^{EV}

推导如下:
已知

EV = log_2{N^2 \over t}
{N^2 \over t} = {ES \over C}
\Longrightarrow
E = 2^{EV}*{C \over S}
while C=250,S=100,
\Longrightarrow L = 2.5*2^{EV}

尽管(用平面型传感器)测量得到的照度可以用以指示对于一个平面物体的曝光,这一办法对于通常的场景却不是那么有用,因为这些场景中的许多元素都不是平的,其相对于相机的朝向也各不相同。半球型传感器在测量影像曝光时更为实用。对于半球型传感器,C值通常在320(美能达)和340(世光)之间。如果对“照度”的定义不那么严格的话,半球型传感器所测得的结果可以说是指示了“场景照度”。

下表显示了该关系式下不同曝光值所对应的亮度值.用这个式子,一个以曝光值为示数的反射式测光表可以用来测量亮度

Table

曝光值与亮度(感光度=100,K=12.5)和照度(感光度=100,C=250)

EV 亮度(cd/m^2) 照度(lx) w分割行w EV 亮度(cd/m^2) 照度(lx)
-4 0.008 0.156 www 7 16 320
-3 0.016 0.313 www 8 32 640
-2 0.031 0.625 www 9 64 1280
-1 0.063 1.25 www 10 128 2560
0 0.125 2.5 www 11 256 5120
1 0.250 5 www 12 512 10240
2 0.5 10 www 13 1024 20480
3 1 20 www 14 2048 40960
4 2 40 www 15 4096 81920
5 4 80 www 16 8192 163840
6 8 160 www of! of! overflow!

对于不同的ISO(这次是感光度了)的修正

以上给出的所有数据都是在ISO=100的条件下测得的。
如果你了解过ISO和曝光补偿,那么你应该知道该怎么做了~

对于某一感光度S,应当以这一感光度高出(低于)ISO100的曝光挡数进行增减,用公式表示为:

EV_S = EV_{100} + log_2{S \over 100}.

比如,ISO400比ISO100高出2档:

EV_{400} = EV_{100} + log_2{400 \over 100}
 = EV_{100} + 2.

对于更低的感光度,以这一感光度低于ISO100的曝光挡数降低曝光值(增加曝光)。比如ISO50比ISO100低了一挡。

EV_{50} = EV_{100} + log_2{50 \over 100}
= EV_{100} - 1.

使用EV表示的曝光补偿

许多现代照相机都允许设置曝光补偿,并通常用EV这个词来表示。在这种情况下, EV指的是相机测光数据减去实际曝光值的差。 比如+1EV的曝光补偿意味着增加一档曝光,不管是通过延长曝光时间还是更小的f值。

在这里,EV的含义和先前刻度系统中的EV(曝光值)恰好是相反的。增加曝光对应于减少曝光值(-EV),因此+1EV的曝光补偿意味着更小的EV(曝光值);反过来,-1EV的曝光补偿的结果是更大的EV。例如,测得某一比中性灰更亮的物体的EV为16,并设定+1EV的曝光补偿以修正曝光,那么最终的拍摄参数对应于EV15(而不是17)。

再进一步!

我们使用相机M档进行拍摄的操作一般是——

  1. 指定ISO、光圈、快门
  2. 看一下曝光补偿的提示,是欠曝了还是过曝了,适当调整拍摄参数
  3. Shoot!

这个流程应该适用于现在主流的数码相机。。

那么我们也可以根据这个流程来设计测光表的算法:

  • 输入3个参数
  • 根据传感器获取的光照度和指定的ISO来计算被拍摄场景的EV值,记作EVr(Real)
  • 根据3个参数算出EV值,记作EVt(theoretical)
  • 两者做差,得到曝光补偿

当然,也可以更直接一些:

  • 输入2个参数
  • 根据公式直接定出第三个参数,此时的EVr与EVt理论上来说完全相等
  • 但是由于参数刻度非线性,不可能完全相等。

或者把这个流程任意调换顺序,反正是4个参数,知3求1.都没问题的~

由此,我们整理公式可以得到:

给定ISO、光圈值,计算快门时间:

t = {N^2 \over S*E} * C

给定光圈值、快门时间,计算ISO:

S = { { N^2 * E } \over t} * {1 \over C}

给定快门时间、ISO,计算光圈F值:

N = \sqrt {S*E*t \over C}

式中,各参数含义如下:

  • t-快门时间,单位秒(s)
  • N-光圈F值,典型的如f2.8、f4、f5.6……
  • S-ISO值,典型的如100、200、320、400、800、1600……
  • E-入射式测光传感器得到的照度值,单位lux(勒克斯),由传感器给出
  • C-平面型传感器测量照度时的校正常数,默认250,需要根据硬件结构等不定因素适当调整

至此,我们的理论部分就算作结束了~接下来就是激动人心的画板子写代码time了!(orz)

Codeworks

今天我调了半天,写出了初代曝光参数计算算法,数据结构和C没学好(我太菜了),所以并不会做优化。。。在x86平台上使用vscode编写,g++编译调试,测试初步通过。

8DRkEd.png

测试数据

第一步我们需要获取标准的ISO/曝光时间/光圈数据表,很简单,打开相机装上AF镜头,开始一个一个调参数…

uint16_t ISO[24] = {
        50, 100, 125, 160, 200, 250, 320, 400,
        500, 640, 800, 1000, 1250, 1600, 2000, 2500,
        3200, 4000, 5000, 6400, 8000, 10000, 12800,25600
};

double APERTURE[21] = {
        1.4, 1.8, 2.0, 2.8, 3.5, 4.0, 4.5, 5.0,
        5.6, 6.3, 7.1, 8.0, 9.0, 10.0, 11.0, 13.0,
        14.0, 16.0, 18.0, 20.0, 22.0
};

uint16_t SHUTTER[] = {
        /* values at the beginning should be used as 1/x */
        4000, 3200, 2500, 2000, 1600, 1250, 1000,
        800, 640, 500, 400, 320, 250, 200, 160, 125,
        100, 80, 60, 50, 40, 30, 25, 20, 15, 13, 10, 8, 6, 5, 4,
        /* values below represents : 0.3s, 0.4s, 0.5s, 0.6s, 0.8s, 1s */
        3, 4, 5, 6, 8, 1
};

double SHUTTER_F[] = {
        0.00025, 0.0003125, 0.0004, 0.0005, 0.000625, 0.0008, 0.001,
        0.00125, 0.0015625, 0.002, 0.0025, 0.003125, 0.004, 0.005, 0.00625, 0.008,
        0.01, 0.0125, 0.0166667, 0.02, 0.025, 0.0333333, 0.04, 0.05, 0.0666667, 0.0769231, 0.1, 0.125, 0.1666667, 0.2, 0.25,
        /* values below represents : 0.3s, 0.4s, 0.5s, 0.6s, 0.8s, 1s */
        0.3, 0.4, 0.5, 0.6 ,0.8, 1.0

double lx_test[11] = {0.156, 0.625, 2.5, 10, 40, 160, 640, 2560, 5120, 20480, 81920};

#define SHUTTER_F_SIZE sizeof(SHUTTER_F)/sizeof(SHUTTER_F[0])
#define APERTURE_SIZE sizeof(APERTURE)/sizeof(APERTURE[0])
#define ISO_SIZE sizeof(ISO)/sizeof(ISO[0])

为了简化计算过程,我直接根据分数时间计算出了小数,单独列了一个表。
最后的lx_test是我们后面测试代码的时候要用的。

根据ISO/Aperture/Lux计算时间

我们从这个功能入手,来分析一下这个简单的算法。。
整个算法的核心是我们上一章里推出的公式:

t = {N^2 \over S*E} * C

接下来构造一个函数,留好数据接口就ok啦~

uint16_t calcTime(uint16_t iso, double aperture, float lux)
{
    double time;
    uint8_t i = 0, index = 0;
    double previous_t, next_t;
    /* Calculation */
    time = (aperture * aperture) * C_CONSTANT / (lux * (double)iso);
    /* Skip the value below, and prevent from overflow */
    while (time > SHUTTER_F[i++])
        if (i >= SHUTTER_F_SIZE-1)
            return SHUTTER[SHUTTER_F_SIZE-1];

    i -= 1;
    /* avoid overflow */
    if (i == 0)
        return SHUTTER[0];
    previous_t = SHUTTER_F[i-1];
    next_t = SHUTTER_F[i];
    /* Get the Closest Standard value */
    index = (fabs(time-previous_t) > fabs(time-next_t)) ? i : i-1;
    return SHUTTER[index];
}

注意这里返回的是16位长无符号整数,因为我们最后要显示的是1/x秒,而不是长达5、6位的小数。。。

分析完这个,那么其他的算法对我们来说应该都是小case了。。。除后面有一个需要开根号取对数的操作有点绕,别的都ok

要注意的是,在计算光圈的最近标准值时,index那里的逻辑需要反过来,因为iso和shutter都是从小到大排序建表的, 而光圈数值是这样建的,可物理意义是反过来的

Test!

以下是几个测试结果,从结果初步来看,效果不错~

算ISO

8DRiHH.png

算快门时间

8DRPDe.png

算光圈

8DRCuD.png

算曝光补偿

8DRpjO.png

下一站!

当然是激动人心的实践环节~
请看下集~

Reference

Wikipedia-EV
StackExchange
StackOverflow

发表回复