toruのブログ

月1くらいで記事書きたい。

BT.2020 色域確認用の Hue-Chroma テストパターンの改善

0. おことわり

この記事は筆者の備忘録として書いており、一般の方が読むことを想定していません。
未来の自分が読み返して理解できるように、できるだけ分かりやすく書くつもりですが、前提となる説明が省略されている箇所が多数あります。ご了承下さい。

1. 背景

  • 昨年に BT.2407 実装の効果確認のために 図1に示す Hue-Chromaテストパターンを作成した
  • 最近、とある作業で当該パターンを使ったところ Yellow で想定外の色ズレを発見した
  • 個人的に許せない事だったので修正することにした

2. 目的

  • 昨年に作成した Hue-Chromaテストパターンの色ズレの原因を特定する
  • 色ズレを修正したテストパターンを作成する

3. 結論

  • 色ズレ の原因は CIELAB 空間での Yellow の BT.709 Cups と BT.2020 Cups の位置関係の考察不足
    • 設定した Focal Point に問題があり BT.709 の Gamut内を想定したパッチが BT.709 の Gamut外の値になっていた
    • 詳細は 4.3. を参照
  • 修正前の色ズレのあるテストパターンを図1に、修正後のテストパターンを図2に示す。
    • テストパターンの各ブロックの左上に "■" or "□" マークのある箇所は BT.709 の色域外を意味している
    • 修正前のパターンでは Yellow において想定外の箇所に "■" マークが出てしまっている
f:id:takuver4:20210528142116p:plain f:id:takuver4:20210525194845p:plain
図1. 修正前(色ズレあり) 図2. 修正後(色ズレなし)

4. 詳細

4.1. テストパターンの特性

まずは作成したテストパターンの想定する特性について説明する。 下の図3 に書いた通り、このテストパターンは CIELAB色空間で 水平方向で Hue を、垂直方向で Chroma を変化させたものである。 加えて、各パッチが BT.709 の色域内か判断できるように BT.709 の Gamut Boundary を超えた場合はパッチの左上に小さな ”■” マークを付与してある。

f:id:takuver4:20210528142401p:plain
図3. テストパターンの簡単な説明

4.2. テストパターンの作成方法

続いてテストパターンの具体的な作成方法について説明する。

まず概要から説明する。このパターンは以下の2つを満たすように作成した。

  • ① 垂直方向でなるべく BT.709 の領域が広くなる
  • ② 垂直方向の再下段は各Hue で最も Chroma が大きな値(BT.2020 Cups)となる

Chroma-Lightness 平面での例を以下の図4 に示す。条件①と②を満たすように 各Hue のパッチは BT.2020 Cups と BT.709 Cups を結んだ直線上の値を取るようにした。 図4 は Hue=40° の例であるが、これを各Hue において実施することで最終的なパターンを作成した。

f:id:takuver4:20210528153342p:plain
図4. Chroma-Lightness 平面を使用したパッチの説明

続いて、BT.709 Cups、BT.2020 Cups の求め方について説明する。まず初めに BT.709 と BT.2020 の Gamut Boundary を求めた。 今回は以下の手順で求めた。

  1. 任意の Hue の Chroma-Lightness 平面を Chroma 方向に N_c個、Lightness方向に N_l個に分割する(N_c=20, N_l=10 の例を図5に示す)
  2. 各ブロックの LCH値を RGB値に変換し、0 ≦ R ≦ 1 かつ 0 ≦ G ≦ 1 かつ 0 ≦ B ≦ 1 を満たすブロックを有効ブロックとする(有効ブロックを黄色にした例を図6に示す)
  3. 各 Lightness値に対して Chromaが大きくなる方向にブロックを走査し、最初に無効ブロックとなる直前のブロックを、その Lightness値の Gamut Boundary とする(Gamut Boundary を黒色にした例を図7に示す)
  4. これを Hue を少しずつ変更しながら 0~360°の範囲で行う
f:id:takuver4:20210528172010p:plain f:id:takuver4:20210528172019p:plain f:id:takuver4:20210528172028p:plain
図5. Chroma-Lightness 平面をブロックに分割した例 図6. 有効ブロック (Out-of-Gamut でない領域)を黄色で塗った様子 図7. Gamut Boundary を黒色で塗った様子

もう少しブロック分割を細かくして、N_c=240, N_l=135 とした例を 図8, 図9 に示す。

f:id:takuver4:20210528172515p:plain f:id:takuver4:20210528172525p:plain
図8. N_c=240, N_l=135, Hue=40° の例 図9. N_c=240, N_l=135, Hue=99° の例

更にブロック分割を細かくして、N_c=1024, N_l=32768 とした例を 図10, 図11 に示す。

f:id:takuver4:20210528172644p:plain f:id:takuver4:20210528172657p:plain
図10. N_c=1024, N_l=32768, Hue=40° の例 図11. N_c=1024, N_l=32768, Hue=99° の例

なお、上記で計算した Gamut Boundary は LUT化して気楽に使えるようにした。自分が使い方を思い出せるようにサンプルコードを以下に示す( なお、本コードは筆者環境以外では動かないことが予想される)。

# LUT の作成
from create_gamut_booundary_lut import calc_chroma_boundary_lut
import color_space as cs
import numpy as np

if __name__ == '__main__':
    hue_sample = 1024
    chroma_sample = 32768
    ll_num = 1024
    cs_name = cs.BT2020
    lut = calc_chroma_boundary_lut(
        lightness_sample=ll_num, chroma_sample=chroma_sample,
        hue_sample=hue_sample, cs_name=cs_name)
    np.save(
        f"./lut/lut_sample_{ll_num}_{hue_sample}_{chroma_sample}_{cs_name}.npy",
        lut)
# 任意の Hue, Lightness の Gamut Boundary の LCH値を取得
from create_gamut_booundary_lut import calc_chroma_boundary_lut,\
    get_gamut_boundary_lch_from_lut
import color_space as cs
import numpy as np
from colour.utilities import tstack

if __name__ == '__main__':
    lut = np.load("./lut/lut_sample_1024_1024_32768_ITU-R BT.2020.npy")
    hue = 40
    lightness_num = 11
    lightness_array = np.linspace(0, 100, lightness_num)
    hue_array = np.ones_like(lightness_array) * hue
    lh_array = tstack([lightness_array, hue_array])
    gamut_boundary_lch = get_gamut_boundary_lch_from_lut(
        lut=lut, lh_array=lh_array)
    print(gamut_boundary_lch)

    """
    [[   0.            0.           40.        ]
     [  10.           26.41562112   40.        ]
     [  20.           53.00088577   40.        ]
     [  30.           79.69424616   40.        ]
     [  40.          106.60514221   40.        ]
     [  50.          133.87300364   40.        ]
     [  60.          145.00828857   40.        ]
     [  70.00000153   99.57528305   40.        ]
     [  80.00000153   60.49513295   40.        ]
     [  90.00000076   27.6507864    40.        ]
     [ 100.            0.           40.        ]]
    """

github.com

BT.709/BT.2020 の Gamut Boundary が求まったら、各Hue に対して Chroma が最大となる点を探し、そこを BT.709 Cups/BT.2020 Cups とした。

さて、図9、図11 から分かるとおり、今回の Gamut Boundary の計算方法には Yellow で理論値とズレる欠点がある。 これについて改善方法を色々と検討したのだが、対応するには費用対効果が悪いと判断して Yellow のズレは修正を諦めた(残念無念)。

ちなみに、Chroma-Lightness 平面ではなく a*-b* 平面で観測するとズレは 図12のようになる。

f:id:takuver4:20210528221255p:plain:w540
図12. ab 平面での様子

長々と書いてきたが上記の通りに Gamut Boundary、Cups を求めることでテストパターンを作成した。 以後で、なぜ色ズレが生じてしまったのかを解説していく。

4.3. テストパターンの色ズレの原因と対策

それでは問題のあった Yellow について Chroma-Lightness 平面の様子を確認してみる。 以下の図13は全体の様子を、図14 は一部を拡大して表示したものである。 図14 を見ると分かるように、BT.709 Cups と BT.2020 Cups を結んだ直線が BT.709 の Gamut をはみ出している。 これにより意図せぬ色ズレが発生してしまっていた。

f:id:takuver4:20210528225409p:plain f:id:takuver4:20210528231706p:plain
図13. Hue=104° の全体 図14. 左図の Lightness 90~100 の範囲を拡大して表示

今回はこれを防ぐために、図13 の Focal Point の値を 50~90 に制限することにした。制限した結果を 図15、図16 に示す。

f:id:takuver4:20210528233049p:plain f:id:takuver4:20210528233058p:plain
図15. 修正後の Hue=104° の全体 図16. 左図の Lightness 90~100 の範囲を拡大して表示

Focal Point の上限を 90 にした理由は Yellow で BT.709 の外側に出てしまうのを防ぐためである。 下限を 50 にした理由は、本記事では詳細を省略するが DCI-P3 Cups と BT.2020 Cups を結んだ直線を考慮すると下限値も設ける必要があったからである。

各Hue に対して Focal Point の制限前後をプロットした結果を動画1 に示す。

動画1. Focal Point を制限した場合の様子 (BT.709 - BT.2020)

また、DCI-P3 と BT.2020 のペアに対して同様のことを行った結果を 動画2 に示す。

動画2. Focal Point を制限した場合の様子 (DCI-P3 - BT.2020)

4.4 対策後のテストパターン

このようにして対策を施したテストパターンを図17、図18 に示す。パッチの左上に付いている "□" は DCI-P3 領域であることを、"■" は BT.2020 領域であることを意味している。

f:id:takuver4:20210529093019p:plain f:id:takuver4:20210529093031p:plain
図17 修正後のテストパターン(水平ブロック数32) 図17 修正後のテストパターン(水平ブロック数128)

ブロック数を変化させた版や4K版をまとめた zip は以下で公開している。(誰も欲しがらないと思うが)再配布さえしなければ自由に使ってもらって構わない。 なお、今回は ICC Profile(Gamma2.4, BT.2020, D65) をテストパターンに埋め込んである。注意して頂きたい。

drive.google.com

5. 感想

今回の記事、死ぬほど書きづらかった。自分に説明するだけで精一杯。第三者に説明するとか無理ゲーやで…。