生成用于sRGB到CIELAB色彩空间转换的3DLUT(.3dl文件)

Generating a 3DLUT (.3dl file) for sRGB to CIELAB colorspace transformation

本文关键字:3DLUT 3dl 文件 转换 空间 用于 sRGB 色彩 CIELAB      更新时间:2023-10-16

我们的API中已经有一个高度优化的类来读取3D Lut(Nuke格式(文件并将转换应用于图像。因此,与其使用复杂的公式逐像素迭代并将 RGB 值转换为 Lab(RGB->XYZ->Lab( 值,我认为如果我生成一个查找表来为 RGB 到 LAB(或 XYZ 到 LAB(转换会更好。这可能吗?

我了解 3D LUT 如何用于从 RGB 到 RGB 的转换,但我对 RGB 到 Lab 感到困惑,因为 L、a 和 b 有不同的范围。有什么提示吗?

编辑:

你能解释一下LUT将如何工作吗?这里有一个解释:链接

例如,以下是我对RGB->RGB变换的3D LUT的理解:Nuke 3dl Lut 文件示例:

0    64   128   192   256   320   384   448   512   576   640   704   768   832   896   960  1023 
R, G, B 
0, 0, 0 
0, 0, 64 
0, 0, 128 
0, 0, 192 
0, 0, 256 
.
.
.
0, 64, 0
0, 64, 64
0, 64, 128
.
.

这里不是为源 10 位 RGB 值生成 1024*1024*1024 表,而是将每个 R、G 和 B 范围量化为 17 个值,生成一个 4913 行表。 第一行给出了可能的量化值(我认为这里只有长度和最大值很重要(。 现在假设,如果源 RGB 值为 (20, 20, 190(,则输出将是行 # 4 (0, 0, 192((使用一些插值技术(。这是对的吗?这个是针对 10 位源的,您可以通过将范围从 0 更改为 255 来生成一个 8 位源?

同样,您将如何进行sRGB->Lab转换?

假设您的源颜色空间是字节的三元组(RGB,每个 8 位(,并且两个颜色空间都存储在分别名为 SourceColorTargetColor 的结构中,并且您有一个如下所示的转换函数:

TargetColor convert(SourceColor color) {
    return ...
}

然后,您可以创建如下所示的表:

TargetColor table[256][256][256]; // 16M * sizeof(TargetColor) => put on heap!
for (int r, r < 256; ++r)
  for (int g, g < 256; ++g)
    for (int b, b < 256; ++b)
      table[r][g][b] = convert({r, g, b}); // (construct SourceColor from r,g,b)

然后,对于实际的图像转换,使用替代转换函数(我建议您编写一个图像转换类,该类在其构造函数中接受函数指针/std::function,因此易于交换(:

TargetColor convertUsingTable(SourceColor source) {
    return table[source.r][source.g][source.b];
}

请注意,空间消耗是16M * sizeof(TargetColor)的(假设 32 位Lab这将是64MBytes的(,所以表应该是堆分配的(如果你的类要存在于堆上,它可以存储在类中,但最好在构造函数中使用 new[] 分配它并将其存储在智能指针中(。

另一种方法是使用图形硬件,也称为"通用GPU计算"。有一些不同的工具可以做到这一点,例如OpenGL GLSL,OpenCL,CUDA,...与 CPU 解决方案相比,您应该获得大约 100 倍甚至更高的令人难以置信的加速。

最"兼容"的解决方案是将 OpenGL 与特殊的片段着色器一起使用,您可以使用它执行计算。这意味着:将您的输入图像作为纹理上传到 GPU,使用特殊的着色器程序将其渲染在(目标(帧缓冲区中,该程序将您的 RGB 数据转换为 Lab(或者它也可以使用查找表,但 GPU 上的大多数浮点计算都比表/纹理查找更快,所以我们不会在这里这样做(。

首先,将您的 RGB 到实验室转换功能移植到 GLSL。它应该适用于浮点数,因此如果您在原始转换中使用了整数值,请删除它们。OpenGL使用"钳位"值,即0.01.0之间的浮点值。它将看起来像这样:

vec3 rgbToLab(vec3 rgb) {
    vec3 lab = ...;
    return lab;
}

然后,编写着色器的其余部分,它将获取 (RGB( 纹理的像素,调用转换函数并将像素写入颜色输出变量中(不要忘记 alpha 通道(:

uniform sampler2D texture;
varying vec2 texCoord;
void main() {
    vec3 rgb = texture2D(texture, texCoord).rgb;
    gl_FragColor = vec4(lab, 1.0);
}

相应的顶点着色器应在填充整个屏幕(帧缓冲区(的目标四边形的左下角写入(0,0)值,在右上角texCoord写入(1,1)值。

最后,通过在

与图像大小相同的帧缓冲区上渲染,在应用程序中使用此着色器程序。渲染一个填充整个区域的四边形(无需设置任何变换,只需从 2D 顶点渲染一个四边形(-1,-1)(1,1) (。将统一值texture设置为您作为纹理上传的 RGB 图像。然后,从设备读回帧缓冲,它应该希望将您的图像包含在 Lab 色彩空间中。