最近修改于2021-11-24
本文包含以下内容
- 一些误区
- N-Log基本定义(曲线 色域特性)
- N-Log基本处理流程
- N-Log蓝色溢出问题
- 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

色域
三色基点与白点定义如下
WP | 0.3127 | 0.3290 |
R | 0.708 | 0.292 |
G | 0.170 | 0.797 |
B | 0.131 | 0.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种:
- 使用手工调色进行还原
- 使用匹配的LUT(查找表)进行还原
- 使用色彩管理系统(CMS)进行还原
使用手工调色进行还原
没啥可说的,干就完了,不就是手工Mapping吗?(胶片调色时代老师傅狂喜)
饱和度加上去,曲线拉拉对比度,大功告成!

使用匹配的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 通道的信息。

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