16.5. イメージオブジェクト

16.5.1. 検証環境の準備

Important

OpenCLを使用した画像処理を行なうために画像をGithubのリポジトリに用意しました。以下が素材を含むリポジトリです。

https://github.com/MacKomatsu/OpenCLIntro

この章で扱うイメージファイルでソースコードが使うのは以下の2つです。

ソースディレクトリにダウンロードしたPNGファイルを保存してください。

Figure 16.1. SAMPLE.png

width=200

Figure 16.2. SAMPLE3.png

width=200

16.5.2. Image

Imageクラスは1Dイメージ、1Dイメージバッファ、1Dイメージ配列、2Dイメージ、2Dイメージ配列、3Dイメージオブジェクトを生成します。

Note

詳細は「表:clCreateImage」(Table B.37, “表:clCreateImage”)、「表:host_ptrバッファサイズ」(Table B.38, “表:host_ptrバッファサイズ”)、「表:cl_image_format」(Table B.39, “表:cl_image_format”)、「表:cl_image_desc」(Table B.42, “表:cl_image_desc”)を参照ください。

クラスの定義は以下のようになります。

pyopencl.Image(
    context, #(1)
    flags, #(2)
    format, #(3)
    shape=None, #(4)
    pitches=None,
    hostbuf=None, #(5)
    is_array=False,
    buffer=None):

(1)

有効なOpenCLコンテキストを指定。

(2)

生成されるイメージメモリオブジェクトの割当・確保に使うメモリ領域と使用情報を指定するビットフィールド。

(3)

確保する領域内に格納するイメージ形式プロパティを記述する構造体へのポインタを指定。

(4)

確保する領域内に格納するイメージの型と次元数を記述するshape属性を指定

(5)

アプリケーションが確保済みのイメージデータを指定。

コンストラクタの引数の中にはImageFormatというクラスがあります。

ImageFormat
次元数、CL_R、CL_RGB、CL_RGBA等のチャネル順序等を指定します。

このImageFormatは直接指定することもできますが、手間を減らすためにget_supported_image_formats関数を使うことを推奨します。

では例を見てみましょう。

img_format = cl.get_supported_image_formats(
        ctx,
        cl.mem_flags.READ_WRITE,
        cl.mem_object_type.IMAGE2D)[0]
output_mem = cl.Image(
    context=ctx,
    flags=cl.mem_flags.ALLOC_HOST_PTR,
    format=img_format,
    shape=(300, 300))

この例では2次元のイメージを既に読み込んだ事を前提とします。また幅と高さについても既知の情報とします。つまりイメージオブジェクトをImageクラスを用いて生成する際のは、イメージを読み込み後となります。。

16.5.3. 実装例

それでは実装をしてみたいと思います。以下のソースコードでは、SAMPLE3.pngというファイルをPILライブラリのImage.openという関数を使い読みこみます。読み込んだデータをImageクラスのコンストラクタを呼び出してイメージオブジェクトを生成します。

さらにイメージオブジェクトをカーネルで処理後に、enqueue_read_image関数を使って、イメージオブジェクトからホストのメモリ領域にデータを読み込みます。

最後にImageライブラリ(PILの方です)の関数を使って、ホストメモリ領域にあるイメージデータをimage.pngファイルに書き込みます。

from PIL import Image
import pyopencl as cl
import numpy as np

img = Image.open('image/SAMPLE.png', 'r')

width, height = img.size

imgArray = np.asarray(img).astype(np.uint8)
img.close()

ctx = cl.create_some_context()

queue = cl.CommandQueue(ctx)

img_format = cl.ImageFormat(cl.channel_order.RGBA, cl.channel_type.UNORM_INT8)

input_mem = cl.Image(context=ctx, flags=cl.mem_flags.USE_HOST_PTR, format=img_format, hostbuf=imgArray)
output_mem = cl.Image(context=ctx, flags=cl.mem_flags.ALLOC_HOST_PTR, format=img_format, shape=(300, 300))

program = cl.Program(ctx, """
    __constant sampler_t sampler = CLK_NORMALIZED_COORDS_FALSE |
             CLK_ADDRESS_CLAMP | CLK_FILTER_NEAREST;

    __kernel void pass_through(
        image2d_t src_image,
        write_only image2d_t dst_image) {
        int x = get_global_id(0);
            int y = get_global_id(1);
            int2 coord = (int2)(x, y);

            float4 pixel = read_imagef(src_image, sampler, coord);

            write_imagef(dst_image, coord, pixel);
    }
    """).build()

kernel = cl.Kernel(program, name="pass_through")

kernel.set_arg(0, input_mem)
kernel.set_arg(1, output_mem)

cl.enqueue_nd_range_kernel(queue, kernel, global_work_size=(2,), local_work_size=(1,))

outArray = np.ndarray((300, 300, 4)).astype(np.uint8)

cl.enqueue_read_image(queue=queue, mem=output_mem, hostbuf=outArray, origin=(0, 0, 0), region=(300, 300, 1))

print(outArray[0][0:5])

new_img = Image.fromarray(outArray, mode="RGBA")

new_img.save("image/new_image.png", "PNG")

カーネル関数による画像データ処理

Note

詳しくは「OpenCL-Cイメージ処理関数」(Section 17.10, “OpenCL-Cイメージ処理関数”)を参照ください。

以下が前の項目で使ったプログラムオブジェクトを構成するカーネル関数です。

__constant sampler_t sampler = CLK_NORMALIZED_COORDS_FALSE |
             CLK_ADDRESS_CLAMP | CLK_FILTER_NEAREST; //(1)

__kernel void helloworld(read_only image2d_t src_image, //(2)
                        write_only image2d_t dst_image) { //(3)

        int2 coord = (int2)(get_global_id(0), get_global_id(1)); //(4)
        float4 pixel = read_imagef( //(5)
                                        src_image, //(6)
                                        sampler, //(7)
                                        (int2)(coord.x,coord.y)); //(8)

        write_imagef(dst_image, coord, pixel); //(9)
}

(1)

サンプラーを指定。(データ型により使えるパラメーターが異なります。例えば、`CL_UNORM_INT8`の場合は`CLK_NORMALIZED_COORDS_FALSE`と`CLK_FILTER_NEAREST`は必須の設定です)

(2)

イメージオブジェクトはimage2d_t型として宣言します。読み込みにしか使いませんので、`read_only`修飾子を付加します。

(3)

イメージオブジェクトはimage2d_t型として宣言します。書き込みにしか使いませんので、`write_only`修飾子を付加します。

(4)

x,y座標を取得します。この場合の座標とはインデックス空間内で定義されます。

(5)

`read_imagef`は浮動小数点数で表した画素情報)を所定のチャネル順序(戻り値型がfloat4をしているので、RGBA等の4チャネルが前提で、`src_image`から取り出します。

(6)

引数にあるイメージオブジェクト、`src_image`を指定。

(7)

サンプラーを指定。

(8)

画素の座標を指定。

(9)

イメージオブジェクト`dst_image`に画素情報を書き込み。

16.5.4. イメージデータのバッファ

Note

「表:host_ptrバッファサイズ」(Table B.38, “表:host_ptrバッファサイズ”)を参照ください。

16.5.5. ImageFormat

Note

詳細は「表:cl_image_format」(Table B.39, “表:cl_image_format”)、「表:image_channel_order」(Table B.40, “表:image_channel_order”)と「表:image_channel_data_type」(Table B.41, “表:image_channel_data_type”)を参照ください。

イメージの形式を記述するユーティリティクラスは次の通り定義されています。

class pyopencl.ImageFormat(
    [channel_order, channel_type])

channel_orderは、チャネル数とチャネルの配置、つまりイメージ内に保存されるチャネルのメモリ配置を指定します。

channel_typeは、チャネルのデータ型を指定するクラスです。

class pyopencl.channel_type

サポートされているビットフィールドの値は以下の通りです。

FLOAT
HALF_FLOAT
SIGNED_INT16
SIGNED_INT32
SIGNED_INT8
SNORM_INT16
SNORM_INT8
UNORM_INT16
UNORM_INT8
UNORM_INT_101010
UNORM_SHORT_555
UNORM_SHORT_565
UNSIGNED_INT16
UNSIGNED_INT32
UNSIGNED_INT8

`channel_type`の値では、`UNORM_SHORT_565`、`UNORM_SHORT_555`、`UNORM_INT_101010`は特別なイメージ形式となります。

各チャネル要素は`unsigned short`型または`unsigned int`型に詰められています。データの詰め方としては、最初のチャネルがビットフィールド内の最上位ビットになるように詰められます。次のチャネルは占有するビット領域が連続になるようにすぐ下のビット領域に詰められます。

`UNORM_SHORT_565`の場合、Rが15から11ビット、Gが10から5ビット、Bが4から0ビットに詰められます。

`UNORM_SHORT_555`の場合、15ビット目は未定義となり、Rが14から10ビット、Gが9から5ビット、Bが4から0ビットに詰められます。

`UNORM_INT_101010`の場合、31から30ビットは未定義となり、R が29から20ビット、G が19から10ビット、B が9から0ビットに詰められます。

実装例

2次元イメージオブジェクトで使う一般的な、イメージ記述子とイメージ型式は以下のようになります。

img_format = cl.ImageFormat(cl.channel_order.RGBA, cl.channel_type.UNORM_INT8)

ウェブの世界で使うCSSで使う型式とするにはUNSIGNED_INT8`とを選びます。`UNORM_INT8`は[0.0,…,1.0]`の正規化された値をとります。画像処理をするには、正規化した方が便利ですので覚えておくと良いでしょう。

16.5.6. イメージ記述子(mem_object_type)

Note

NOTE: 詳細は「表:cl_image_desc」(Table B.42, “表:cl_image_desc”)を参照ください。

OpenCLでイメージを処理する場合、イメージ記述子を設定しなくてはいけません。記述子で最低限設定が必要なものは、`cl_mem_object_type`と、高さ、幅、奥行きに関する情報です。

pyopencl.mem_object_typeについては以下のものを設定できます。

  • IMAGE1D
  • IMAGE1D_BUFFER
  • IMAGE1D_ARRAY
  • IMAGE2D
  • IMAGE2D_ARRAY *

幅、高さ、奥行きについてはピクセル数などを指定できます。本書では`300 x 300`画素の画像を使うことが多いです。

16.5.7. get_supported_image_formats

get_supported_image_formats関数はコンテキスト内のデバイスがサポートするイメージ型式の和集合を戻します。イメージメモリオブジェクトに関する以下の情報を指定したときにOpenCL実装がサポートするイメージ形式の一覧取得に使うことができます。

Note

詳しくは「表:clGetSupportedImageFormats」(Table B.43, “表:clGetSupportedImageFormats”)を参照ください。

関数の定義は以下のようになります。

pyopencl.get_supported_image_formats(
    context, #(1)
    flags, #(2)
    image_type) #(3)

(1)

イメージオブジェクトを生成する有効なOpenCLコンテキストを指定。

(2)

イメージオブジェクトの割当に使うメモリ領域や、バッファオブジェクトの使用情報を指定するビットフィールド。

(3)

イメージ型を記述。IMAGE1D、 IMAGE1D_BUFFER、IMAGE2D、 IMAGE3D、IMAGE1D_ARRAYまたはIMAGE2D_ARRAYの指定ができる。

16.5.8. サポートするイメージ型式

Note

詳細は「表:image_formats」(Table B.39, “表:cl_image_format”)を参照ください。

Copyright 2018-2019, by Masaki Komatsu