OpenCLを使用した画像処理を行なうために画像をGithubのリポジトリに用意しました。以下が素材を含むリポジトリです。
https://github.com/MacKomatsu/OpenCLIntro
この章で扱うイメージファイルでソースコードが使うのは以下の2つです。
ソースディレクトリにダウンロードしたPNGファイルを保存してください。
Imageクラスは1Dイメージ、1Dイメージバッファ、1Dイメージ配列、2Dイメージ、2Dイメージ配列、3Dイメージオブジェクトを生成します。
詳細は「表: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):
有効なOpenCLコンテキストを指定。 | |
生成されるイメージメモリオブジェクトの割当・確保に使うメモリ領域と使用情報を指定するビットフィールド。 | |
確保する領域内に格納するイメージ形式プロパティを記述する構造体へのポインタを指定。 | |
確保する領域内に格納するイメージの型と次元数を記述するshape属性を指定 | |
アプリケーションが確保済みのイメージデータを指定。 |
コンストラクタの引数の中にはImageFormatというクラスがあります。
この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クラスを用いて生成する際のは、イメージを読み込み後となります。。
それでは実装をしてみたいと思います。以下のソースコードでは、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")
詳しくは「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) }
サンプラーを指定。(データ型により使えるパラメーターが異なります。例えば、`CL_UNORM_INT8`の場合は`CLK_NORMALIZED_COORDS_FALSE`と`CLK_FILTER_NEAREST`は必須の設定です) | |
イメージオブジェクトはimage2d_t型として宣言します。読み込みにしか使いませんので、`read_only`修飾子を付加します。 | |
イメージオブジェクトはimage2d_t型として宣言します。書き込みにしか使いませんので、`write_only`修飾子を付加します。 | |
x,y座標を取得します。この場合の座標とはインデックス空間内で定義されます。 | |
`read_imagef`は浮動小数点数で表した画素情報)を所定のチャネル順序(戻り値型がfloat4をしているので、RGBA等の4チャネルが前提で、`src_image`から取り出します。 | |
引数にあるイメージオブジェクト、`src_image`を指定。 | |
サンプラーを指定。 | |
画素の座標を指定。 | |
イメージオブジェクト`dst_image`に画素情報を書き込み。 |
「表:host_ptrバッファサイズ」(Table B.38, “表:host_ptrバッファサイズ”)を参照ください。
詳細は「表: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ビットに詰められます。
NOTE: 詳細は「表:cl_image_desc」(Table B.42, “表:cl_image_desc”)を参照ください。
OpenCLでイメージを処理する場合、イメージ記述子を設定しなくてはいけません。記述子で最低限設定が必要なものは、`cl_mem_object_type`と、高さ、幅、奥行きに関する情報です。
pyopencl.mem_object_typeについては以下のものを設定できます。
幅、高さ、奥行きについてはピクセル数などを指定できます。本書では`300 x 300`画素の画像を使うことが多いです。
get_supported_image_formats関数はコンテキスト内のデバイスがサポートするイメージ型式の和集合を戻します。イメージメモリオブジェクトに関する以下の情報を指定したときにOpenCL実装がサポートするイメージ形式の一覧取得に使うことができます。
詳しくは「表:clGetSupportedImageFormats」(Table B.43, “表:clGetSupportedImageFormats”)を参照ください。
関数の定義は以下のようになります。
pyopencl.get_supported_image_formats( context, #(1) flags, #(2) image_type) #(3)
詳細は「表:image_formats」(Table B.39, “表:cl_image_format”)を参照ください。
Copyright 2018-2019, by Masaki Komatsu