画像処理の例

Wolfram言語は,Wolfram LibraryLink を使って既存の画像処理ライブラリを効率的に連結することを可能にする.ライブラリへ連結することにより,Wolfram言語内部から既存のコードが利用できる.このチュートリアルでは,LibraryLink と画像処理を使ったいくつかの例を見ていく.

LibraryLink はWolfram言語とCあるいはC++コードを連結する方法を提供する.このインターフェースは低レベルであるが効率的であり,既存のネイティブコードを使いたいと思っているユーザや,画像処理アプリケーションで計算コストの高い部分のCルーチンを開発しようとしているユーザを対象としている.開発の流れで考えられるのは,Wolfram言語を使ってアプリケーションを開発し,ボトルネックを見付け,そのCのルーチンを実装するというものである.このチュートリアルでは,まずWolfram言語の実装を紹介し,ルーチンをCで実装し,両者を比較するという手順を踏む.

このチュートリアルで示す例のソースはドキュメントパクレットにある.

demo_image.cxxスタンドアロンの画像処理の例のソースコード
image_external.c外部ライブラリに接続している画像処理の例のソースコード
image_video.cxx外部ビデオライブラリに接続している画像処理の例のソースコード

ソースファイルは以下のディレクトリにある.

スタンドアロンの例

このコードではOpenCVとLibRawという2つの外部ライブラリが使われている.これらのライブラリはWolfram言語に同梱されていないので,上記のコードをコンパイルし実行するためにはマシンにそれらをインストールする必要がある.

テンプレートを使って型をパラメータ化する

Wolfram言語の画像はさまざまな型("Bit""Byte""Bit16""Real32""Real")をサポートするので, コードの簡素化のためにもコードをもっと管理しやすくするためにもテンプレートを使用することができる.color_negateでは,以下のように型をパラメータ化し,同じ関数を何度も再実装しなくていいようにする.

template <typename T> static T maxValue() {
return -1; // ERROR
}

template <> char maxValue<char>() { return 1; }

template <> raw_t_ubit8 maxValue<raw_t_ubit8>() { return 255; }

template <> raw_t_ubit16 maxValue<raw_t_ubit16>() { return 65535; }

template <> raw_t_real32 maxValue<raw_t_real32>() { return 1.0f; }

template <> raw_t_real64 maxValue<raw_t_real64>() { return 1.0; }

template <typename T>
static void icolor_negate(void *out0, const void *in0, mint length) {
mint ii;
T *out = reinterpret_cast<T *>(out0);
const T *in = reinterpret_cast<const T *>(in0);
for (ii = 0; ii < length; ii++) {
out[ii] = maxValue<T>() - in[ii];
}
}

/* Negate image colors */
EXTERN_C DLLEXPORT int color_negate(WolframLibraryData libData, mint Argc,
MArgument *Args, MArgument res) {
mint length;
MImage image_in, image_out = 0;
void *data_in, *data_out;
int err = LIBRARY_FUNCTION_ERROR;
imagedata_t type;
WolframImageLibrary_Functions imgFuns = libData->imageLibraryFunctions;

if (Argc < 1) {
return err;
}

image_in = MArgument_getMImage(Args[0]);

err = imgFuns->MImage_clone(image_in, &image_out);
if (err)
return err;

type = imgFuns->MImage_getDataType(image_in);
length = imgFuns->MImage_getFlattenedLength(image_in);

data_in = imgFuns->MImage_getRawData(image_in);
data_out = imgFuns->MImage_getRawData(image_out);
if (data_in == NULL || data_out == NULL)
goto cleanup;

switch (type) {
case MImage_Type_Bit:
icolor_negate<char>(data_out, data_in, length);
break;
case MImage_Type_Bit8:
icolor_negate<raw_t_ubit8>(data_out, data_in, length);
break;
case MImage_Type_Bit16:
icolor_negate<raw_t_ubit16>(data_out, data_in, length);
break;
case MImage_Type_Real32:
icolor_negate<raw_t_real32>(data_out, data_in, length);
break;
case MImage_Type_Real:
icolor_negate<raw_t_real64>(data_out, data_in, length);
break;
default:
goto cleanup;
}

MArgument_setMImage(res, image_out);
return err;

cleanup:
imgFuns->MImage_free(image_out);
return err;
}

以下のようにコードを使うことができる.

ライブラリからcolor_negate関数をロードする.
次で,異なる型を持った画像の集合を定義する.
画像にcolor_negate関数を適用する.

許可される型の制限

時として,アルゴリズムが特定のデータ型にしか使えなかったり,C内部から異なるデータ型に変換するには冗長過ぎたりする場合がある.そのような場合には,画像の型をCコードでサポートされるものに変換する必要がある.これはLibraryFunctionの呼出しによって自動的に実行できる.以下で定義されたspeia関数を考えてみる.

static void isepia(raw_t_real32 *out, raw_t_real32 *in, mint width, mint height,
mint channels) {
for (mint ii = 0; ii < height; ii++) {
for (mint jj = 0; jj < width; jj++) {
for (mint kk = 0; kk < channels; kk++) {
mint index = channels * (ii * width + jj);
raw_t_real32 r = in[index + 0];
raw_t_real32 g = in[index + 1];
raw_t_real32 b = in[index + 2];

out[index + 0] = r * static_cast<raw_t_real32>(0.393) +
g * static_cast<raw_t_real32>(0.769) +
b * static_cast<raw_t_real32>(0.189);
out[index + 1] = r * static_cast<raw_t_real32>(0.349) +
g * static_cast<raw_t_real32>(0.686) +
b * static_cast<raw_t_real32>(0.168);
out[index + 2] = r * static_cast<raw_t_real32>(0.272) +
g * static_cast<raw_t_real32>(0.534) +
b * static_cast<raw_t_real32>(0.131);

for (int ii = 3; ii < channels; ii++) {
out[index + ii] = in[index + ii];
}
}
}
}
return;
}


EXTERN_C DLLEXPORT int speia(WolframLibraryData libData, mint Argc,
    MArgument *Args, MArgument res) {
    mbool alphaQ;
    int err = 0;
    imagedata_t type;
    colorspace_t cs;
    MImage image_in, image_out;
    mint height, width, channels;
    raw_t_real32 *data_in, *data_out;
    WolframImageLibrary_Functions imgFuns = libData->imageLibraryFunctions;

    if (Argc != 1) {
        return LIBRARY_FUNCTION_ERROR;
    }

    image_in = MArgument_getMImage(Args[0]);
    if (imgFuns->MImage_getColorSpace(image_in) != MImage_CS_RGB)
        return LIBRARY_FUNCTION_ERROR;

    if (imgFuns->MImage_getRank(image_in) == 3)
        return LIBRARY_RANK_ERROR;

    type = imgFuns->MImage_getDataType(image_in);
    height = imgFuns->MImage_getRowCount(image_in);
    width = imgFuns->MImage_getColumnCount(image_in);
    channels = imgFuns->MImage_getChannels(image_in);
    cs = imgFuns->MImage_getColorSpace(image_in);
    alphaQ = imgFuns->MImage_alphaChannelQ(image_in);

    if (type != MImage_Type_Real32)
        return LIBRARY_TYPE_ERROR;

    err = imgFuns->MImage_new2D(width, height, channels, type, cs, True,
        &image_out);
    if (err)
        return LIBRARY_FUNCTION_ERROR;

    data_in = imgFuns->MImage_getReal32Data(image_in);
    data_out = imgFuns->MImage_getReal32Data(image_out);
    if (data_in == NULL || data_out == NULL)
        return LIBRARY_FUNCTION_ERROR;

    ispeia(data_out, data_in, width, height, channels);

    MArgument_setMImage(res, image_out);
    return LIBRARY_NO_ERROR;
}
"Real32"型に自動変換するライブラリからspeia関数をロードする.
前に定義した画像にこのspeia関数を適用する.

画像の共有

Wolfram言語のカーネルとCコードの間で画像の参照を共有すると,メモリ使用量が減少するので,それは重要な最適化となる.一方,Cコードがもっと複雑になったり,メモリリークの危険性にさらされたりする.

外部ライブラリの例

このコードではOpenCVとLibRawという2つの外部ライブラリが使われている.これらのライブラリはWolfram言語に同梱されていないので,上記のコードをコンパイルし実行するためにはマシンにそれらをインストールする必要がある.

Needs["LibraryLink`"]
Needs["CCompilerDriver`"]
opencvDir = "c:\\opencv";
opencvIncludeDir=FileNameJoin[{opencvDir,"build","include",#}]&/@{"","opencv","opencv2", "opencv2\\imgproc"};
opencvLibDir=FileNameJoin[{opencvDir,"build","x64","vc11",#}]&/@{"bin","lib"};
opencvLibraries={"opencv_core246","opencv_highgui246","opencv_imgproc246"};
librawDir="c:\\libraw";
librawIncludeDir=FileNameJoin[{librawDir,"libraw"}];
librawLibDir=FileNameJoin[{librawDir,"build"}];
librawLibraries={"libraw"};
lib=CreateLibrary[{FileNameJoin[{sourcePath, "image_external.c"}]},"image_external","IncludeDirectories"Append[opencvIncludeDir,librawIncludeDir],"LibraryDirectories"Append[opencvLibDir,librawLibDir],"Libraries"Join[opencvLibraries,librawLibraries],"Defines""WIN32"];
LibraryLoad[FileNameJoin[{opencvDir,"build","x64","vc11","bin",#}]]&/@{"opencv_core246","opencv_highgui246","opencv_imgproc246"};
LibraryLoad[FileNameJoin[{librawLibDir,"libraw"}]];

OpenCVの例

以下はhttp://opencv.orgで取得できるOpenCVライブラリを使って,モルフォロジー演算の膨張を実装する例である. 関数opencv_dilateはグレースケールの2D画像,および平方の形式で構成要素を定義する半径 という2つの引数を取る.OpenCVは64ビットの浮動小数点形式の画像をサポートしていないので,入力画像がMImage_Type_Realという型の場合は,IPL_DEPTH_32Fが使われる.

これでソースコードを表示する.
関数をロードする.
関数がロードされると,通常の関数として使うことができる.
組込みのDilation関数と比較する.

ビデオからフレームをインポートする

FFMPEG (http://www.ffmpeg.org)はさまざまな形式のビデオを読むためのライブラリである.以下ではWolfram言語内から基本的なビデオプレーヤーを実装する方法を示す.

このリンクを使うと,ファイルを開き,それをマネージドライブラリ式として登録し,ビデオ内部で位置を制御して画像フレームを出力することができる.

ソースコードを表示する.
必要な従属ライブラリをロードする.
関数をロードする.
ビデオをロードし,VideoInstanceを取得する.
ファイルを開き,内部データをそのインスタンスにバインドすることができる.
ビデオの次のフレームを取得することができる.

未加工画像のインポート

LibRaw (http://www.libraw.org)は,デジタルカメラから取得された未加工ファイル(CRW/CR2,NEF,RAF,DNG等)を読み込むためのライブラリである.以下でそれを使って,未加工画像をWolfram言語にインポートする方法を示す.

以下で示される関数read_raw_imageはファイルへのパスを取り,画像の式を返す.

EXTERN_C DLLEXPORT int read_raw_image(WolframLibraryData libData, mint Argc, MArgument *Args, MArgument res) {
    int err;
    int check;
    MImage out;
    char * file;
    libraw_data_t *iprc = libraw_init(0);
    libraw_processed_image_t * img;
    WolframImageLibrary_Functions imgFuns = libData->imageLibraryFunctions;

    err = LIBRARY_FUNCTION_ERROR;
    file = MArgument_getUTF8String(Args[0]);

    libraw_open_file(iprc, file);
    libraw_unpack(iprc);

    iprc->params.output_bps = 8;

    check = libraw_dcraw_process(iprc);
    if (check != LIBRAW_SUCCESS) goto cleanup;

    img = libraw_dcraw_make_mem_image(iprc, &check);
    if (img == NULL) goto cleanup;
    if (img->type != LIBRAW_IMAGE_BITMAP || img->colors != 3) goto cleanup;
    
    if (img->bits == 16) {
        raw_t_ubit16 * raw_data = (raw_t_ubit16*)img->data;
        imgFuns->MImage_new2D(
            img->width, img->height, 3,
            MImage_Type_Bit16, MImage_CS_RGB, 1,
            &out);
        memcpy(imgFuns->MImage_getBit16Data(out),
             raw_data,
             img->width * img->height * 3 * sizeof(raw_t_ubit16));
    } else if (img->bits == 8) {
        raw_t_ubit8 * raw_data = (raw_t_ubit8*)img->data;
        imgFuns->MImage_new2D(img->width, img->height, 3,
                             MImage_Type_Bit8, MImage_CS_RGB, 1,
                             &out);
        memcpy(imgFuns->MImage_getByteData(out),
             raw_data,
             img->width * img->height * 3 * sizeof(raw_t_ubit8));
    } else {
        goto cleanup;
    }
    
    MArgument_setMImage(res, out);
    err = LIBRARY_NO_ERROR;

cleanup:
    libData->UTF8String_disown(file);
    libraw_dcraw_clear_mem(img);
    return err;
}
関数をロードする.
未加工ファイルを取得する.
ファイルをインポートする.