HalideとOpenCVとの連携


Halideは画像処理記述用のDSLです。
どういったものかは公式ページをご覧いただくか、福嶋先生がある程度の日本語資料を投稿されていますので、そちらを参照ください。

HalideとOpenCVとの連携

Halideには画像ファイルの読み込み/書き込み関数がありませんので、別途用意する必要があります。
ここではOpenCVのimread/imwriteを使ってみましょう。
処理はカラー画像のグレイスケール化にでもしておきましょうか。

gray1.cpp
#include <Halide.h>
#include <opencv2/opencv.hpp>

int main()
{
    cv::Mat cv_src = cv::imread("rgb.png");
    cv::Mat cv_dst(cv_src.size(), CV_8UC1);

    Halide::Buffer<uint8_t> hal_src(cv_src.data, cv_src.cols, cv_src.rows, 3);  
    Halide::Buffer<uint8_t> hal_dst(cv_dst.data, cv_dst.cols, cv_dst.rows);    // 1chなのでx,yだけでOK

    Halide::Func GrayScale;
    Halide::Var x, y;

    Halide::Expr gray =
        0.114f * hal_src(x, y, 0) +     //B
        0.587f * hal_src(x, y, 1) +     //G
        0.299f * hal_src(x, y, 2);      //R

    GrayScale(x, y) = Halide::cast<uint8_t>(Halide::min(gray, 255));

    GrayScale.realize(hal_dst);     // 出力先をhal_dstにして、グレイスケール化の実行

    cv::imwrite("gray1.png", cv_dst);
}

Buffer<T>のコンストラクタ先頭の引数にMatのdataポインタを渡すことで、直接Mat画像をHalideで扱うことができます。
ポインタを共用しているので、Bufferを書き換えればMatの中身も書き換わります。

HalideとOpenCVのメモリ配置の違い

さて、実際に生成された画像はというと…

なんじゃこりゃ。失敗ですね。

なにが原因かというと、Halideのデフォルトのメモリ配置がプレーナ型を想定しているためです。
プレーナ型とは、以下のようなメモリ配置です。
画像が色チャンネル枚分並んでいる、という形でしょうか。

BBBBBBBBGGGGGGGGRRRRRRRR

一方、OpenCVのMatはインターリーブ型になっています。
1pixelごとにB,G,Rと並んでいる、素直な形です。
OpenCVを触っている人ならおなじみですね。

BGRBGRBGRBGRBGRBGRBGRBGR

ということで、上記のプログラムはhal_srcの作成時に齟齬が生じてしまっています。
直すのは簡単で、HalideのBufferにインターリーブ型であると教えてやれば大丈夫です。

gray2.cpp
#include <Halide.h>
#include <opencv2/opencv.hpp>

int main()
{
    cv::Mat cv_src = cv::imread("rgb.png");
    cv::Mat cv_dst(cv_src.size(), CV_8UC1);

    Halide::Buffer<uint8_t> hal_src = 
        Halide::Buffer<uint8_t>::make_interleaved(cv_src.data, cv_src.cols, cv_src.rows, 3);   // ここでインターリーブ型だと教えてやる    
    Halide::Buffer<uint8_t> hal_dst(cv_dst.data, cv_dst.cols, cv_dst.rows);

    Halide::Func GrayScale;
    Halide::Var x, y;

    Halide::Expr gray =
        0.114f * hal_src(x, y, 0) +     //B
        0.587f * hal_src(x, y, 1) +     //G
        0.299f * hal_src(x, y, 2);      //R

    GrayScale(x, y) = Halide::cast<uint8_t>(Halide::min(gray, 255));

    GrayScale.realize(hal_dst);     // 出力先をhal_dstにして、グレイスケール化の実行

    cv::imwrite("gray2.png", cv_dst);
}

こうすると、うまくできます。