OpenCVでアニメ制作ソフトOpenToonzのプラグインを書くよ

やりたいこと

先日公開されたばかりのオープンソースアニメ制作ソフトOpenToonzを利用して、以下のように映像にエフェクトをかけるプラグインを作ります。OpenToonzのプラグインOpenCVを利用することがサポートされているので、基本的に画像処理の知識だけで作れます。

完成品1: Kmeansによる減色プラグイン

どうせなら機械学習みが少しでもあるプラグインを作ろう、ということで、入力画像の全ピクセルをクラスタリングして、クラスタ中心の色に量子化することで減色して、実写背景をアニメになじませるものをプロトタイプ作ってみました。

減色(k=10) 入力画像
f:id:Hi_king:20160401225718p:plain f:id:Hi_king:20160401225739j:plain

github.com

完成品2: ネガポジ反転

こちらはプラグインの作り方の練習も兼ねてのネガポジ反転の簡単な実装です。映像でネガポジ反転ガチャガチャやったらちょっと目に悪い映像になりそうですねw

反転 入力画像
https://raw.githubusercontent.com/Hi-king/opentoonz_not/master/sample/hokusai_not.0001.png https://raw.githubusercontent.com/Hi-king/opentoonz_not/master/sample/hokusai.jpg

github.com

作り方

OpenToonzのプラグインの作り方の基本は、ネガポジ反転を例に以下の記事に書きました。

qiita.com

また、公式の開発ドキュメントも以下にあります。 opentoonz_plugin_utility/opentoonz_plugin_utility_ja.md at master · opentoonz/opentoonz_plugin_utility · GitHub

詳細はQiitaとgithubを観ていただくとして、ここではOpenCVによる処理の部分のみの話をします。 OpenToonzのプラグインは、opentoonz_plugin_utilityに定義されている、tnzu::Fx クラスを継承して作るのですが、パラメタの設定や入力画像数の設定などを飛ばすと、基本的には Fx::compute メソッドをオーバーライドし、そこに処理を書くことでエフェクトがつけられます

ネガポジ反転

CV::Matは ~ 演算子で反転が行えるので、以下の数行のコードだけで実現できます。

  int compute(Config const& config, Params const& params, Args const& args,
              cv::Mat& retimg) override try{
    DEBUG_PRINT(__FUNCTION__);

    // 画像の読み込み
    if (args.invalid(PORT_INPUT)) {
      return 0;
    }
    cv::Mat input_img;
    input_img = args.get(PORT_INPUT);

    // RGBAのうちRGBを反転
    std::vector<cv::Mat> planes;
    cv::split(input_img, planes);
    planes[0] = ~planes[0];
    planes[1] = ~planes[1];
    planes[2] = ~planes[2];

    // 反転したチャンネルをマージして結果画像に
    cv::merge(planes, retimg);
    return 0;
  } catch (cv::Exception const& e) {
    DEBUG_PRINT(e.what());
  }

k-meansでの減色

やりたかった、ピクセルごとのk-meansについて、そのものずばりのサンプルがあったので、大いに参考にさせていただきました k-meansクラスタリングによる画像分割,減色 | OpenCV.jp

  int compute(Config const& config, Params const& params, Args const& args,
              cv::Mat& retimg) override try {
    DEBUG_PRINT(__FUNCTION__);

    // 画像・パラメタの読み込み
    if (args.invalid(PORT_INPUT)) {
      return 0;
    }
    int const k = params.get<int>(PARAM_K);
    cv::Mat input_img, img_reshaped, img_reshaped_converted, labels, centroids;
    input_img = args.get(PORT_INPUT);
    cv::TermCriteria criteria(cv::TermCriteria::MAX_ITER+cv::TermCriteria::EPS, 50, FLT_EPSILON);

    // (width, height)の画像データを、(1, width x height)のベクトル系列に直す
    img_reshaped = input_img.reshape(0, input_img.rows * input_img.cols);
    img_reshaped.convertTo(img_reshaped_converted, CV_32FC4);

    // kmeans
    cv::kmeans(img_reshaped_converted, k, labels, criteria, 1, cv::KMEANS_PP_CENTERS, centroids);

    // 各ピクセルをクラスタ中心で置き換える
    for (int y = 0; y < img_reshaped.cols; ++y) {
        for (int x = 0; x < img_reshaped.rows; ++x) {
            uint assignment = labels.at<uint>(y, x);
            int channels = img_reshaped.channels();
            for (int c = 0; c < channels; ++c) {
                img_reshaped_converted.at<cv::Vec4f>(y, x)[c] = centroids.at<float>(assignment, c);
            }
        }
    }
    img_reshaped_converted.convertTo(img_reshaped, img_reshaped.type());

    // (width, height)の形に直して出力画像に
    retimg = img_reshaped.reshape(0, input_img.rows);

    return 0;
  } catch (cv::Exception const& e) {
    DEBUG_PRINT(e.what());
  }

ちなみに、速度に関しては特に気をつけてないので、kを大きくすると結構時間がかかってしまいます。また、減色アルゴリズムとして考えると、RGB色空間でやってるとか、空間的距離を考慮してないとかの問題もあります。まぁでも、結果画像はそこそこ面白いと思うので、軽く使う分には全然ありかな、と思います。

まとめ

OpenToonzのプラグイン作ってみた記事を書きました。OpenToonzのドメインの問題をあまり考えず、結構OpenCVドメインに集中して実装できるので、普段画像処理をやっている方なら手軽に実装できるかと思います。OpenToonzは先日公開されたばかりで、まだまだ色々整備されておらず、いいプラグインを作れば、皆が使うようなデファクトスタンダードとなることもありうると思うので、楽しいんじゃないかな、と思います。