LUTで遊んでみる | Nature | Photography | Music | Art

Nature | Photography | Music | Art

日々好奇心の趣くまま

サイト内の写真の使用ならびに無断転用を禁じます。

前回の続きで、この本の内容にインスパイアされたもの。
 

DaVinci Resolve カラーグレーディングBOOK

 

この本の内容で最も可能性を感じたものの一つがLUT(Look Up Table)を用いたポストプロダクション。
そもそもLUTはLogやRAWで撮影されたノッペリした映像を人間の眼の特性に合うように戻すのに使われるのだが、更にそれを超えてよりアグレッシブに色目を弄るのにも使うことができる。
さらにはLUTそのものを販売するビジネスまで成り立っているほど奥が深いものらしい。

"Look Up Table"という言葉は語られる分野によって別の意味を持つが、ここで取り上げている写真・映像界隈での定義に関しては以下が詳しい。
(元々は富士フィルムのサイトにあったものだが、部門がM&AによってWOWOWに移行したみたい。)
 

https://is.wowowent.co.jp/technology/lutindex.php

 

ついでに今回は関係ないが、同じサイトにあるLOGの解説も必見。
 

https://is.wowowent.co.jp/technology/logindex.php

 

要はここでいうLUTというのはあるRGB空間から別のRGB空間への非線形写像と考えればすっきりする。

LUTの設定値はファイルとして提供され、ソフト間の互換性を持たせるためにそのフォーマットの規格がいくつか決まっている。
その中でも最もよく使われているのがCUBEといわれるフォーマットで、その仕様は以下でDLできる。
 

http://wwwimages.adobe.com/www.adobe.com/content/dam/acom/en/products/speedgrade/cc/pdfs/cube-lut-specification-1.0.pdf

 

ただし、手持ちの画像・映像ソフトで試した限りは仕様すべてをサポートしているわけではないようで、Webで拾えるCUBEファイルを一通り見たところ以下のようなフォーマットが一般的だった。例としてPanasonic謹製のVlog->V709のcubeより。
 

 


# Vlog_to_V709_forV35_ver100.cube by panasonic

LUT_3D_SIZE 33

0.015625 0.015625 0.015625
0.028320 0.011719 0.013184
0.040771 0.008057 0.010742
0.054688 0.003906 0.008301
0.072021 0.000000 0.004883
.
. (33 * 33 * 33 = 35937個のテーブル)
.
.
0.996825 1.000000 1.000000
0.998535 1.000000 1.000000
1.000000 1.000000 1.000000


 

簡単に解説すると。

#はコメント

デフォルトではRGBのレンジは0.0~1.0となる。モノによっては以下のようにレンジを指定しているLUTもある。

LUT_3D_INPUT_RANGE 0.092 1.0196

LUT_3D_SIZEはRGB各テーブルの分割数。要は上の場合だと0.0~1.0を(33-1)等分割してそこ場所の対応値を格納してあり、そこにピッタリ嵌らないRGB値は近隣の対応値から補間することになる。
LUT_3D_SIZEの値は33とか65とか二進数で切れのよい数字+1となっている場合が多い。例えば領域を4分割するとその端点の数は5になるのと同じ理屈だと思われる。

上の例では割愛されているが、

TITLE   HOGE

のようにLUTに名前(?)をつけてあるものもある。

CUBE形式は大きく1D LUTと3D LUTに分かれるが、上は3D LUTの例。
3D LUTの方が自由度が2次元も増えるため処理が重くなるが、色を弄れる可能性が大幅に広がる。
世の中に出回っているものも3D LUTのものが多く、今回ネタにするのも3D LUTについて。

LUTををいろいろハッキングするべく、CUBEファイルをいろいろな条件で生成できるスニペットを作ってみた。
Calc関数内部を弄ればもっと複雑なLUTも作れるはず。
Python3.4以上とNumpyが必要。
 

 


import random
import enum
import numpy as np

class LutMode(enum.Enum):
    ModeNoChange    = 1
    ModeGamma       = 2
    ModeContrast    = 3
    ModePlaneSwap0  = 4
    ModePlaneSwap1  = 5
    ModePlaneSwap2  = 6
    ModeBW          = 7
    ModeRandom      = 8

sMode           = LutMode.ModeGamma
sTitle          = "\"Generated by make_cube\""
sLut_3d_size    = 33
sVal_max        = 1.0
sVal_min        = 0.0
sGammaFactor    = 2.2
sContrastFactor = 2.0
sContrastPoint  = 0.5
sDiv            = float(sVal_max - sVal_min) / float(sLut_3d_size - 1)

def gamma(index):
    ret = (sDiv * float(index)) ** (1.0 / sGammaFactor)
    return ret
    
def contrast(x,factor,point):    
    if x < point:
        y = point*(x/point) ** factor
    else:
        y = 1. - ((1.-point)*((1.-x)/(1.-point)) ** factor)
    return y

def calc(r,g,b,r_index,g_index,b_index,mode):
    if mode == LutMode.ModeGamma:
        r_new   = r * gamma(r_index)
        g_new   = g * gamma(g_index)
        b_new   = b * gamma(b_index)
    elif mode == LutMode.ModeContrast:
        r_new   = contrast(r,sContrastFactor,sContrastPoint)
        g_new   = contrast(g,sContrastFactor,sContrastPoint)
        b_new   = contrast(b,sContrastFactor,sContrastPoint)
    elif mode == LutMode.ModePlaneSwap0:
        r_new   = g
        g_new   = b
        b_new   = r
    elif mode == LutMode.ModePlaneSwap1:
        r_new   = b
        g_new   = r
        b_new   = g
    elif mode == LutMode.ModePlaneSwap2:
        r_new   = r
        g_new   = b
        b_new   = g
    elif mode == LutMode.ModeBW:
        v = (r+g+b)/3
        r_new   = v
        g_new   = v
        b_new   = v
    elif mode == LutMode.ModeRandom:
        r_new   = random.random()
        g_new   = random.random()
        b_new   = random.random()
    else: # no change
        r_new   = r
        g_new   = g
        b_new   = b
    
    return r_new,g_new,b_new

if __name__ == "__main__":
    print("TITLE ",sTitle)
    print("LUT_3D_SIZE ",sLut_3d_size)
    print("")

    r = 0.0
    g = 0.0
    b = 0.0
    
    for b_index in range(sLut_3d_size):
        g = 0.0
        for g_index in range(sLut_3d_size):
            r = 0.0
            for r_index in range(sLut_3d_size):
                r_new,g_new,b_new = calc(r,g,b,r_index,g_index,b_index,sMode)
                print("{: .6f} {: .6f} {: .6f}".format(r_new,g_new,b_new))
                r += sDiv
            g += sDiv
        b += sDiv


 

sModeの値を変えると様々な条件のLUT CUBEが生成されるので、それをPhotoShopのLUTテーブルに適用してみる。

元の画像。


Gamma補正。



コントラスト補正。

 



モノクロ。



RGBプレーン入れ替え。 



テーブルの値については0~1の間に収まっていればとりわけ制限はないみたい。
試しに0~1ランダムで生成したLUTでもエラーは出なかった。
こんなサイケな意味の無い絵になるが。



ここで取り上げたのはごく単純なサンプルにすぎず、もっと細部にこだわった調整をすればXXXの映画のYYYな質感のような高度なマニピュレーションも可能になってくる(はず)。

LUT、面白そうなので時間を見つけてもう少し掘り下げてみたいところです。