toruのブログ

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

Colour Science for Python を使った画像データの読み書き

背景

筆者はこれまで画像データの読み書きは OpenCV または OpenImageIO の Python用Bindings を叩く自作のラッパーを作って行っていた。が、Color Science for Python に良い感じの関数があったので、これを使ってみることにした。

colour.readthedocs.io

おことわり

本記事は colour-science 0.3.14 および OpenImageIO 2.2.0 の環境で確認しています。特に OpenImageIO が無い環境では殆ど何も動かないと思いますのでご注意ください。

目的

  • Color Science for Pythonwrite_image, read_image の動作確認を行う
  • 具体的には筆者が利用する表1.のファイル形式について 1bitの情報も失うことなく Write/Read できることを確認する
  • 合わせて Davinci Resolve や NUKE などのツール類で正しく読み込めることも確認する
表1. 調査するファイル形式
ファイル形式 bit深度 備考
TIFF 8bit, 16bit 整数型
PNG 8bit, 16bit 整数型
DPX 10bit, 12bit 整数型
OpenEXR 16bit, 32bit 浮動小数点型

結論

表1の全てのファイル形式について write_image, read_image を使った読み書きを行い、bit欠損が生じないことを確認した。確認コードは以下。

github.com

また、Davinci Resolve と NUKE にて各種形式のファイルが読み込めることも確認した。例として Davinci Resolve で 10bit DPX ファイルを開いた際のスクリーンショットを図1 に示す。

f:id:takuver4:20191124124706p:plain
図1. Davinci Resolve で DPX ファイルを確認する様子

サンプルコード

例として、8bit PNG と 10bit DPX ファイルの Write/Read のコードを示す。

8bit PNG

import numpy as np
from colour import write_image, read_image

if __name__ == '__main__':
    os.chdir(os.path.dirname(os.path.abspath(__file__)))
    # main_func()

    # 256x256 の画像データを作成。この時点では全画素の値は1。
    img = np.ones((256, 256, 3), dtype=np.uint8)

    # 上記の箱を上書きするグラデーションデータを 1Line分作成
    gradation_achromatic = np.uint8(np.arange(256))

    # カラーデータ化。今回は黄色にした。
    gradation_yellow = np.dstack(
        (gradation_achromatic, gradation_achromatic,
         np.zeros_like(gradation_achromatic)))

    # 最初に作った img を上書き。numpy のブロードキャストを利用。
    # (256, 256, 3) のデータに (1, 256, 3) を乗算すると
    # numpy が空気を読んで (1, 256, 3) --> (256, 256, 3) に拡張する。
    img = img * gradation_yellow

    # write_image を使ってファイルに吐き出す。
    # なお、write_image の第1引数は float 型にする必要がある。
    # 16bit PNG にする場合は bit_depth='uint16' にするだけで良い
    write_image(img/255, "8bit.png", bit_depth='uint8')

    # read_image を使ってファイル読み込み
    after_img = read_image("8bit.png", bit_depth='uint8')

    print(np.all(img == after_img))  # データが同一なら True が返る

上記コードで生成される画像は以下。

f:id:takuver4:20191124131352p:plain
図2 サンプルコードで生成した画像

10bit DPX

DPX ファイルの bit深度は ImageAttribute_Specification を利用して指定する必要があることに注意すること。ImageAttribute_Specification で指定できるオプションについては OpenImageIO のドキュメントを参照すること。

import numpy as np
from colour import write_image, read_image
from colour.io.image import ImageAttribute_Specification

if __name__ == '__main__':
    img = np.ones((768, 1024, 3), dtype=np.uint16)
    gradation_achromatic = np.uint16(np.arange(1024))
    gradation_cyan = np.dstack(
        (np.zeros_like(gradation_achromatic),
         gradation_achromatic, gradation_achromatic))
    img = img * gradation_cyan

    # write_image を使ってファイルに吐き出す。
    # 10bit DPX の場合 bit_depth は 'uint16' を指定する。
    # DPX の bit深度は別途 attributes オプションで指定する。
    bit_option = ImageAttribute_Specification("oiio:BitsPerSample", 10)
    write_image(
        img/1023, "10bit.dpx", bit_depth='uint16', attributes=[bit_option])

    # read_image を使ってファイル読み込み
    # 10bit での read はできないので、float型で読んでから uint32 に変換する
    after_img = read_image("10bit.dpx", bit_depth='float32')
    after_img = np.uint32(np.round(after_img * 1023))

    print(np.all(img == after_img))  # データが同一なら True が返る

感想

write_image, read_image がちゃんと動いているのを確認できたので、これからメインで使っていこうと思う。

参考資料