3D LUT圖像處理


1. 介紹

在我們開始之前,先對必要的背景知識做一些簡單的鋪墊。LUT 是 Lookup Table 的縮寫,在圖像處理方面,LUT 可以用來完成類似濾鏡的效果,其原理本質上就是一個映射關系,輸入顏色 (r, g, b),通過 LUT 去查找,得到一個新的顏色 (R, G, B),則完成了一次映射操作。

LUT 又分為 1D LUT 和 3D LUT,1D LUT 是指 R, G, B 三個分量互相不影響,都是獨立映射,即存在 3 個映射 $f_1$, $f_2$, $f_3$,有如下關系存在。
$R=f_1(r), G=f_2(g), B=f_3(b)$
而 3D LUT 則是存在一個映射關系 $(R,G,B)=f(r,g,b)$。可以這樣想象一下方便理解,我們有一個三維的坐標系,長寬高的取值范圍都是 1,長寬高可以表示為 RGB 三個分量的值,而這個坐標系圍成的立方體中,填滿了某種混合顏色的塗料,並且固化在里面。那么任意顏色 [公式] 隨便取一個值,即可以在這個坐標系中找到這樣一個點,我們通過黑科技,把這個點對應的顏料取出來看一看,發現它的顏色是$color_2$。這就完成了一次映射,這個立方體,就是我們的映射關系。

2. cube 文件

知道了 LUT 的原理,那么應該如何表示呢?由於輸入顏色到輸出顏色的映射是任意的,比如,你完全可以將黑色映射成白色,白色映射成紅色,紅色映射成黑色類似這樣,所以它的功能比調節色相、飽和度、對比度、明暗更加強大。對於這樣任意的映射,我們只能采用枚舉法來枚舉,或者稱為采樣。

大多數情況,我們的顏色值的取值范圍都是 (0, 0, 0) 到 (255, 255, 255),如果要將這個映射完全存儲起來,我們需要 255 * 255 * 255 條顏色信息與之對應,那么這個數據量是龐大的,並不利於傳播與使用。因此我們使用采樣+插值的方法,僅使用 32 * 32 * 32 來對映射信息做采樣,在運行時,再做插值來還原中間缺失的信息。

Adobe 公司推出了 .cube 的文件格式,本質上是純文本文件,其 Specification 在這里:cube-lut-specification-1.0.pdf

下面展示該文件的一個片段:

#Created by: Adobe Photoshop Export Color Lookup Plugin
#Copyright: (C) Copyright 2017 RocketStock
TITLE "Sepia"

#LUT size
LUT_3D_SIZE 32

#data domain
DOMAIN_MIN 0.0 0.0 0.0
DOMAIN_MAX 1.0 1.0 1.0

#LUT data points
0.086273 0.086273 0.086273
0.098907 0.087311 0.086761
0.111694 0.088409 0.087250
0.124847 0.089630 0.087769
0.138275 0.090942 0.088348
0.151978 0.092285 0.088959
0.166077 0.093750 0.089600
0.180420 0.095245 0.090240
0.195160 0.096832 0.090912
0.210175 0.098541 0.091675
0.225311 0.100250 0.092377
0.240631 0.102081 0.093170
0.255951 0.103973 0.093994
0.271210 0.105927 0.094849
0.286560 0.107971 0.095734
0.301910 0.110077 0.096710
// 文件未完

值得注意的是,LUT_3D_SIZE 后面緊跟的數字為 N,是這個 LUT 的采樣大小,上述文件中為 32,也就是說,此文件記錄了 32 * 32 * 32 個顏色信息。

DOMAIN_MIN 指定了顏色空間的最小值,DOMAIN_MAX 指定了顏色空間的最大值,兩個加在一起決定了顏色空間的范圍。

再往下則由一堆數字構成,每行 3 個數字,分別表示了 RGB 的分量,每行是一個顏色值,此文件中,定義了 32 * 32 * 32 個顏色值(有這么多行數字)。

那么這堆數據如何使用呢?假設我們有任意顏色 [公式],首先我們要將顏色分量變換到[公式] 之間,然后,第$n$行就是我們要取的顏色,$n = r + N * g + N * N * b$。

3. 插值

回想一下顏色立方體的例子,在坐標空間中,任意一個點的坐標都是由三個 [0, 1] 之間的數構成的,顯然,它是連續的,但是當我們計算出映射的顏色 n 時,這個 n 是離散的,它只能是在 [0, 323232 -1] 之間的整數。

插值的計算方法是這樣的:

對於任意顏色$color(r,g,b)$,我們轉換到$0<r,g,b<1$之間時,都需要保留它們的上界和下界。比如 r = 0.21(我隨便亂寫的值),那么$r*31=6.51$,我們對 6.51 同時做向上和向下取整,得到兩個數,6 和 7。同樣的,我們對 green 和 blue 做一樣的處理,總共可以得到 6 個數,分別記為:$redH, redL, greenH, greenL, blueH, blueL$,H 表示上界,high 嘛,L 表示下界,low 嘛。

這樣,我們可以通過公式$n = r + N * g + N * N * b$,得到兩個 Index:
$indexH = redH + N * greenH + N * N * blueH$
$indexL = redL + N * greenL + N * N * blueL$

接着,使用這兩個 index,可以在 LUT 表里面找到對應的兩個顏色值記為 colorH 和 colorL:
$colorH = table[indexH]$
$colorL = table[indexL]$

這兩個顏色,就是距離我們的 $color(r,g,b)$ 最近的兩個離散的點,接下來,我們需要根據這兩個點做插值計算。

下圖表示:在任意$color(r,g,b)$時,我們只能得到離散的最靠近的$colorH$和$colorL$。
在這里插入圖片描述
如何做插值呢?兩個顏色相加直接除以2?當然這是一種方法,但未免太粗糙了,回想一下之前做的 r * 31 = 6.51,我們得到上界和下界 6 和 7,這里的意思不就是說,6.51 在 6 和 7 之間,小數部分(0.51)表示的傾向程度,如果是 6.21 ,說明這個數,更加傾向於 6,而 6.87 則是更加傾向於 7,因此我們可以用這個小數部分來做插值計算。

之前的步驟中,我們已經得到了歸一化到[0,31]的顏色分量和兩個顏色值:
$0<red, green, blue < 31$
$colorH = table[indexH]$
$colorL = table[indexL]$

我們現在定義一個計算表示取小數部分:
$小數部分=dec(number)=number-floor(number)$

接着,我們就可以對三個顏色分量做插值了:
$R_{result} = (colorH.red - colorL.red)dec(red)+colorL.red$
$G_{result} = (colorH.green - colorL.green)
dec(green)+colorL.green$
$B_{result} = (colorH.blue - colorL.blue)*dec(blue)+colorL.blue$

最終,我們就得到了映射后的顏色值了。

4. Python 實現

最終代碼實現於此,請自行下載查看:代碼

5. 最后


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM