Digital Imaging · 2021-11-24

【一点点色彩科学】如何放心使用N-Log – 尼康曲线那点事

最近修改于2021-11-24

本文包含以下内容

  1. 一些误区
  2. N-Log基本定义(曲线 色域特性)
  3. N-Log基本处理流程
  4. N-Log蓝色溢出问题
  5. Input Device Transform - IDT FOR NLOG

1.开始之前 - 一些误区

N-Log是尼康开发的色彩空间

注意!N-Log不是色彩空间。
要完整定义一个色彩空间,最基本需要两个要素,「Gamut」「Gamma」
Gamut是什么,其实就是我们在CIE1931色度图上看到的那种三角形,所以你就明白了,Gamut就可以理解为色域。
而Gamma,便是一条编码曲线,表示输入输出的一个1维关系。

N-Log能让尼康相机获得更大的宽容度

N-Log并不是一个性能指标,而是一种场景亮度如何对应编码值的关系

2.基本定义

考虑到很多本文章也会有很多基础用户阅读,因此有必要强调Log曲线本质,在N-Log 官方白皮书中的介绍是这样的

N-Log is designed to permit full use of sensor dynamic range in a professional movie workflow. The curve characteristics of N-Log balance shadows and highlights and are optimized for 10-bit movie recording.

值得注意的是,官方提到的是“permit full use of sensor dynamic range”,而不是“to get more dynamic range”,意思已经非常明确,即:N-Log的设计目的是为了最大化利用传感器动态范围。

但即便这样说,依然有大量用户以为Log画面就能让相机获得更多的动态范围,这是不严谨的。

由于曝光档位(Exposure)近似于线性光(Scene Linear)成对数关系,因此Log(L)的作用就是让整个画面的不同曝光区「相对平均」的分配到有限的码值中(例如0-1023)。

编码

将场景光转换为N-Log函数如下

OETF

if (y  0.328)
x = 650 * (y + 0.0075)^(1/3) 
else
x = 150*log(y)+619 //log() is based on Log-e
end

将N-Log编码转换为场景光函数如下

EOTF

if (x  452)
y = (x/650)^3 −0.0075 
else
y = exp[(x−619)/150] 
end
//y is reflectance

色域

三色基点与白点定义如下

WP0.31270.3290
R0.7080.292
G0.1700.797
B0.1310.046

是不是觉得,这个三色基坐标和某个现有的标准色域有点像呢?

对啦!就是ITU-R BT.2020!大名鼎鼎的Rec.2020色域,这一点在官方文档里也有说明:

The gamut for N-Log is same as the wide color gamut known as “ITU-R BT.2020”.

根据官方白皮书,N-Log与BT2020搭配使用(这里也证明了N-Log本身并不是一个色彩空间,而是一条Gamma曲线,这容易在用户知识不全面的情况下混淆)

白点

N-Log使用D65白点,这与其使用ITU-R BT.2020的色域相吻合,即可以理解为 Rec.2020 (D65)

长啥样?

N-Log跟其他log画面一样,也是长得灰灰的咯

3.N-Log基本处理流程

N-log与所有摄影机的Log一样,处理方式大致可以分为3种:

  1. 使用手工调色进行还原
  2. 使用匹配的LUT(查找表)进行还原
  3. 使用色彩管理系统(CMS)进行还原

使用手工调色进行还原

没啥可说的,干就完了,不就是手工Mapping吗?(胶片调色时代老师傅狂喜)

饱和度加上去,曲线拉拉对比度,大功告成!

使用曲线也可以直接完成将N-Log较灰的画面还原到不错的结果,用户可以完全自己决定画面的反差关系

使用匹配的LUT(查找表)进行还原

此方法为一键方法,直接能够获得正常结果,因此不赘述。

此处提供用于还原的33Point LUT。该LUT已经做过外观微调,以适配N-Log还原后的色彩特性,因此结果与CMS还原结果有一定差异。如果你需要快速将N-Log画面转换到一个干净通透的目标状态,这是一个不错的选择。但如果你有较高要求,例如想执行SR-DR的转换,则不推荐使用该LUT,因为该LUT并不是一个可逆选择

使用色彩管理系统进行还原

根据刚刚的定义,可以直接把N-Log素材的色域当作BT2020来使用,因此Mapping过程可以描述为:

Rec.2020/N-Log -> Rec.709 / BT.1886 (Gamma2.4 + OOTF)

在此过程中,需要留意定义域变化导致的信号超标问题。选择正确的Tone Mapping Method以及Gamut Mapping Method是有必要的。

对于CMS中没有N-Log定义的情况下,可以自己利用N-Log公式进行转换。

如果用户使用2065-1体系,可以使用本文末尾的IDT函数来实现转换。

4.祖传蓝色问题

在之前已经有大量网上的言论说明了N-log在拍摄一些颜色纯度高的蓝色灯光时候,会出现不利的「溢出」问题。简而言之,当蓝色灯光饱和度足够高的时候,明亮的蓝色渐变突然逆转,变成暗而纯的蓝色,此时如果用取色器,用户能够注意到这些区域几乎没有R,G 通道的信息。

N-Log画面,注意灯珠的颜色变化,灯珠的发光点应该是明亮的,但是此时却成为了暗蓝色

在我们的实验中,我们使用一个蓝色属性的照明环境,并逐渐增加其饱和度,可以留意一下画面的色彩信息是如何随着照明环境变化而改变的。同时可以注意每一张图像的照明环境对应的色品坐标(就是在CIE1931图上面的那个点点)

显然,当照明光的色品坐标靠近CIE1931xy(0.1494, 0.0802)时,我们的小电视上就已经出现了不利的反应。当我们把这个点放在CIE1931xy图上,正好能发现,它已经接近Rec2020的边缘区域。所以也不难怪出现问题咯!

很难的啦!

不仅如此,当直面高纯度蓝色发光源拍摄,画面中一些亮度较低的区域也容易出现问题,这在欠曝的N-Log画面中较为容易出现。

什么,你说尼康不能拍视频?

由于这个现象的存在,便出现了大量「键摄」开始说N-Log垃圾,或者直接说N-Log不适合拍视频。

讲个笑话,当我问及这些砖家为什么会出现这样的现象的时候,他们总给出惊人相似的答案:宽容度不够

事实上,蓝色溢出的问题,N-Log并不是主要元凶,这里我们需要强调一个新问题

N-Log并不是一个色彩空间

要完整定义一个色彩空间,最基本需要两个要素,「Gamut」「Gamma」

Gamut是什么,其实就是我们在CIE1931色度图上看到的那种三角形,所以你就明白了,Gamut就可以理解为色域。

而Gamma,便是一条编码曲线,表示输入输出的一个1维关系。

如果你能理解这个,那么n-log的溢出问题就能得到更多解释了

原来是Gamut的超标啊

之前已经提到过,N-Log使用Rec2020色彩空间,显然这个空间的大小是有限的,那么如果当现场光的色度坐标大于了这个空间,就会发生「超标」的问题。不仅如此,其他摄影机的素材在处理流程中也不可避免遇到这个问题,更广泛的说,当我们在Rec709 OR sRGB这些工作空间工作的时候,就必须要面对摄影机更大色域,到显示器的更小色域的转换问题。

所以要解决这个问题,就需要使用Mapping来将超出来的东西,往回收一收,以防在后续处理(计算)中出现更多的问题。

什么?你没有感觉到

倒也正常,因为现在的大部分图像处理软件,都在后台帮助用户完成了这些「复杂」的数学变换,用户基本感知不到。举个例子,当你用PS的Camera RAW打开一张RAW图像的时候,PS就自动完成了由摄影机色域(可能有ProPhotoRGB那么大)到显示色域(可能是sRGB)的转换。然而N-Log的素材“看起来“并没有帮助我们来做好这个过程。

那就开工呗

压缩色彩的方法有很多,最常见的就是使用饱和度曲线,以及硬Clip工具来实现,所以其实如果功夫到家,一根RGB曲线也能完成任务。

色域压缩算法(Gamut Compress)

下面以SMPTE2065-1的CMS流程中为例,演示建立于减色法(CMY+Achromatic)中色域压缩基本步骤,该算法已经有开源思路。

  • 读取输入图像
  • 转换图像到AP1 Linear色彩空间
  • 利用RGB重建Achromatic,计算出的消色通道为 ach
  • 计算减色系统中的色度信息
  • 对色度通道进行压缩
  • Achromatic与Chroma通道合并+逆运算计算RGB
  • 转换到SMPTE2065-1 (AP0 Linear)色彩空间
  • 过程结束

压缩核心算法

float compress(float dist, float lim, float thr, float pwr, bool invert)
{
    float comprDist;
    float scl;
    float nd;
    float p;

    if (dist  thr) {
        comprDist = dist; // No compression below threshold
    }
    else {
        // Calculate scale factor for y = 1 intersect
        scl = (lim - thr) / pow(pow((1.0 - thr) / (lim - thr), -pwr) - 1.0, 1.0 / pwr);

        // Normalize distance outside threshold by scale factor
        nd = (dist - thr) / scl;
        p = pow(nd, pwr);

        if (!invert) {
            comprDist = thr + scl * nd / (pow(1.0 + p, 1.0 / pwr)); // Compress
        }
        else {
            if (dist > (thr + scl)) {
                comprDist = dist; // Avoid singularity
            }
            else {
                comprDist = thr + scl * pow(-(p / (p - 1.0)), 1.0 / pwr); // Uncompress
            }
        }
    }

    return comprDist;
}

STEP1 读取输入图像

float ap0[3] = {rIn, gIn, bIn};

STEP2 转换图像到AP1 Linear色彩空间

float linAP1[3] = mult_f3_ap0_ap1(ap0);//请注意这是个抽象方法

STEP3 利用RGB重建Achromatic,计算出的消色通道为 ach

floatfloat ach = max_f3(linAP1)

STEP4 计算减色系统中的色度信息

float dist[3];
    if (ach == 0.0) {
        dist[0] = 0.0;
        dist[1] = 0.0;
        dist[2] = 0.0;
    }
    else {
        dist[0] = (ach - linAP1[0]) / fabs(ach);
        dist[1] = (ach - linAP1[1]) / fabs(ach);
        dist[2] = (ach - linAP1[2]) / fabs(ach);
    }

STEP5 Mapping大法

// Compress distance with parameterized shaper function
    float comprDist[3] = {
        compress(dist[0], LIM_CYAN, THR_CYAN, PWR, invert),
        compress(dist[1], LIM_MAGENTA, THR_MAGENTA, PWR, invert),
        compress(dist[2], LIM_YELLOW, THR_YELLOW, PWR, invert)
    };

STEP6 通道合并 重建新的RGB通道

float comprLinAP1[3] = {
        ach - comprDist[0] * fabs(ach),
        ach - comprDist[1] * fabs(ach),
        ach - comprDist[2] * fabs(ach)
    };

STEP7 自己完成后续步骤吧

(写不动了)

哦对了 忘了给出Default了


/* --- Gamut Compress Parameters --- */
// Distance from achromatic which will be compressed to the gamut boundary
// Values calculated to encompass the encoding gamuts of common digital cinema cameras
const float LIM_CYAN =  1.147;
const float LIM_MAGENTA = 1.264;
const float LIM_YELLOW = 1.312;

// Percentage of the core gamut to protect
// Values calculated to protect all the colors of the ColorChecker Classic 24 as given by
// ISO 17321-1 and Ohta (1997)
const float THR_CYAN = 0.815;
const float THR_MAGENTA = 0.803;
const float THR_YELLOW = 0.880;

// Aggressiveness of the compression curve
const float PWR = 1.2;

效果呈现

下列对比图展示了该方法在抑制色彩溢出方面的效果。

左侧一排分别为低饱和度、中饱和度、高饱和度照明条件,以及拍摄纯发光物体的画面;右侧为对应的使用压缩算法了的结果。

能够注意到该方法能够有效的在部分通道信息已经丢失的情况下,较好的重建亮度信息,并有效抑制超标的颜色。

最重要的一点是,该方法能够有效保持画面中的整体饱和度,以及除了「溢出」之外的其他画面颜色。留意画面中的CIE图和色板,除了蓝色以外的颜色基本不被影响。

还要干啥?当然是继续调色了呗!

做完这一步,调色师就可以和处理其他正常画面一样,展开常规调色了!

5.IDT

提供给有一定基础的用户使用


__DEVICE__ inline float Log_to_linear(float inv)
{
float outv;
if (inv > (452.0f/1023.0f) ) { outv = _powf(2.7182818284f, (1023.0f/150.0f * inv - 619.0f/150.0f)); } else { outv = _powf( inv / (650.0f/1023.0f), 3.0f ) - 0.0075f; }
return outv;
}

__DEVICE__ float3 transform(int p_Width, int p_Height, int p_X, int p_Y, float p_R, float p_G, float p_B)
{
const float mtx[9] = {0.6788911506598102f,0.15886842237789234f,0.16224042703562752f, 0.04557083087232135f,0.8607127720474108f,0.0937163970408747f, -0.0004857103518124508f,0.025060195735059528f,0.9754255145687619f};

float r1 = Log_to_linear(p_R);
float g1 = Log_to_linear(p_G);
float b1 = Log_to_linear(p_B);
float r2 = r1 * mtx[0] + g1 * mtx[1] + b1 * mtx[2];
float g2 = r1 * mtx[3] + g1 * mtx[4] + b1 * mtx[5];
float b2 = r1 * mtx[6] + g1 * mtx[7] + b1 * mtx[8];

return make_float3(r2, g2, b2);
}