LUT 滤镜究竟是啥玩意儿

4,913 阅读4分钟

位图

位图是一个像素数组。

将一张位图放大,
可以看到一个一个小格子,
每个小格子就是一个像素:

extendBitmap
每个像素都由 8 * 4 bit 的 RBGA 数据组成(带 alpha 通道)。
把图片恢复到初始尺寸:
lena512_weibo
组成这么一张 512 * 512 px 位图的像素数据量:

512 * 512 * 4 * 8 = 6291456 bit ≈ 768 kb

在图片传输之前我们会将位图进行压缩,
常见的压缩格式有 PNG & JPEG,这里不做深入。
还是刚刚的 512 * 512 的 lena 图,

1570444867668
经过 JPG 100% 质量压缩之后,大小仅为 154kb。

事实上,解压后的图片大小跟原始文件大小之间没有任何关系,
只与图片的像素数量有关。

顺带一提,简介中的预览效果,
是自带的 JPG Decoder 对图片解压后的位图效果,
压缩文件不经过解压是无法预览的。

滤镜

滤镜是对像素数组进行的操作,
大致可以分为以下几类:

  • 逐像素操作,包括调整图片的亮度、对比、饱和度、色调、灰度、分离 RGB 通道等;
  • 像素卷积变换,包括边缘检测、浮雕化、模糊、锐化等;
  • 仿射矩阵变换,包括缩放、旋转、倾斜、扭曲、液化等;
  • 图像合成;

其中最简单的滤镜是进行逐像素操作。
滤镜设计师经常会调制一个叫做色表的图片:

originalLUT
在客户端有相关框架可以将它应用成滤镜,
这张色表图到底是啥东西呢?

LUT

LUT 全称 LookUpTable,也称为颜色查找表,
它代表输入的像素数组跟输出的像素数组的映射关系。
比如一个像素的颜色值分别是 R1 G1 B1,
经过一次 LUT 操作:


LUT(R1) = R2
LUT(G1) = G2
LUT(B1) = B2

这个像素就从一个颜色指定变成了另一个颜色。

由于组成像素的 RGB 颜色值范围始终在 0 ~ 255,
所以可以预先保存好所有映射关系,
输入原图之后按坐标匹配,可降低计算成本。

3D LUT

将 RGB 分别作为一个 XYZ 建立三维坐标系,
一个 3D LUT 就相当于将一个坐标系映射到
另一个由 R1G1B1 构建的三维坐标系上:

3d-LUT-Trans
怎么用最少的数据记录这层映射关系呢?
有机智的同学做了两件事情:

  • 去除原生坐标系,由坐标来代表初始色值;
  • 将三维坐标记载的颜色数据降维成二维数组;

第一件事比较好理解就是设定颜色默认值,
第二件事,二维数组有没有很耳熟,
没错,位图就是二维数组。

3DLUTToArray
于是,滤镜中使用的色表图就生成了!
其本质是一个记录 RBG 三维颜色数据的映射表。
在传输的时候也可以打包成 .cube 文件,
同理,位图的像素数据可以预览,设计师可以很直观做视觉比较,
.cube 打包的数据文件未经解压不可预览,
其实是一个东西。

应用中使用的色表图,
其实是设计师将滤镜效果应用到原生 RBG 坐标系,
三维坐标变换后由新的 RBG 数组生成的位图。

关于 LUT 文件

这个 LUT 文件生成出来了,怎么使用呢?
这时候有同学就回答了,
前面说了,客户端有相关框架可以应用滤镜。

iOS 中与图像处理有关的框架:
CoreImage,Metal,OpenGL-ES,第三方框架 GPUImage 等,
它们都可以实现 LUT 映射。

所以,具体是怎么做的呢?

细心观察,
色表图每行每列各有 8 个小格,共64格。
我们将 512 * 512 px 的色表图放大:

selectRGB
每个小格有 64 * 64 个像素,
每个像素有一个 RGB 值,总数据量是:

64 * 64 * 64

按照之前说法,一个 RGB 三维坐标系携带的数据量应该是:

256 * 256 * 256

应该是匹配不了的。
其实是因为使用原坐标轴成本太高,
所以对每 4 个 RGB 进行了一次差值减少数据量。

使用 LUT 文件

对输入图片进行滤镜处理时,
先以颜色 (RGB) 的 B 值进行索引,
找到所属于的小格,

useLUT
然后根据 R 和 G 值在对应小格中定位到映射的目标像素,
读取对应的 RBG 值,应用在对应的目标像素上。

3D LUT Creator

3DLC 是一款专门用来编辑 LUT 文件的强大工具。
-待续-