読者です 読者をやめる 読者になる 読者になる

オンメモリのFileStorage

この記事はOpenCV Advent Calendar 2015の11日目の記事です。

qiita.com

目次

だいぶ前回の記事で消耗しているため、今回はあっさりと。

筆者の環境

以下に示すサンプルコードは3.0限定ですが、基本的にはOpenCV2.x系でも変わりはないと思います。

やりたいこと

cv::Algorithmメモリへ保存/メモリから読み込みしたい

オンメモリの意義

大抵はファイルからの読み込み・ファイルへの書き出しで充分ですし、むしろその方が使いやすいです。

しかし実用を踏まえたアプリケーションで使う場合は、オンメモリの意義が出てきます。 固定のファイルを配置するのが面倒・困難な環境はありますし、その言語環境が提供するリソースファイルのようなところからデータストリームを取得する方が取り回しが良いケースも多々あります。また、ファイルI/Oが気になるレベルの大量のリクエストを捌くようなシステムでも、オンメモリの利点が生きます。

リファレンス

ファイルの場合

オンメモリの実装の紹介の前に、普通のファイルの場合の使われ方を見ておきます。 以降は、cv::Algorithmを継承しているcv::ml::SVMを例としています。(継承するようになったのはOpenCV3.0以降)

ファイルからcv::Algorithmを読み込み

cv::Ptr<cv::ml::SVM> svm = cv::Algorithm::load<cv::ml::SVM>(R"(C:\hoge\svm.txt)");

ファイルにcv::Algorithmを保存

svm->save(R"(C:\fuga\svm.txt)");

ちなみにこのような文字列リテラルについては以下を参照ください。

https://ja.wikipedia.org/wiki/C%2B%2B11#.E6.96.B0.E3.81.9F.E3.81.AA.E6.96.87.E5.AD.97.E5.88.97.E3.83.AA.E3.83.86.E3.83.A9.E3.83.AB

オンメモリのFileStorage

メモリだけどFileとはこれいかに。

メモリ領域にcv::Algorithmを書き込む

// テスト用SVMオブジェクト
auto svm = cv::Algorithm::load<cv::ml::SVM>(R"(C:\hoge\svm.txt)");

// メモリに保存
cv::FileStorage fs(".yml", cv::FileStorage::WRITE | cv::FileStorage::MEMORY);
svm->write(fs);

// 文字列で取得
cv::String yaml = fs.releaseAndGetString();

yamlYAML形式の文字列として、SVMオブジェクトの内部データが書かれました。

メモリ領域からcv::Algorithmを構築

上記コードで作ったyaml文字列から、SVMを復元しましょう。

auto svm2 = cv::Algorithm::loadFromString<cv::ml::SVM>(yaml);

あれ、落ちる。

OpenCV Error: Unspecified error (The node is neither a map nor an empty collection) 
in cvGetFileNodeByName, file C:\builds\master_PackSlave-win32-vc12-share\opencv\modules\core\src\persistence.cpp, line 739

cv::Algorithm::saveの秘密

cv::Algorithm::saveはうまくいったのに、何が違うのでしょう。そこで、このsave関数を見てみます。

void Algorithm::save(const String& filename) const
{
    FileStorage fs(filename, FileStorage::WRITE);
    fs << getDefaultName() << "{";
    fs << "format" << (int)3;
    write(fs);
    fs << "}";
}

なんかやってる・・・

saveとwriteの差

出力のdiffを取りました。最初8行のみ示します。

write

%YAML:1.0
svmType: C_SVC
kernel:
   type: RBF
   gamma: 100.
C: 1.
term_criteria: { epsilon:9.9999999999999995e-007, iterations:1000 }
var_count: 1

save

%YAML:1.0
opencv_ml_svm:
   format: 3
   svmType: C_SVC
   kernel:
      type: RBF
      gamma: 100.
   C: 1.

違いますね。saveはまずインデントが違います。トップレベルにopencv_ml_svmというのがあります。

パクってリベンジ メモリ領域からcv::Algorithmを構築

cv::Algorithm::saveをパクり、ファイル保存ではなくメモリ領域(文字列)を返す関数を作ります。

cv::String getAlgorithmString(const cv::Algorithm &algorithm)
{
    cv::FileStorage fs(".yml", cv::FileStorage::WRITE | cv::FileStorage::MEMORY);
    fs << algorithm.getDefaultName() << "{";
    fs << "format" << (int)3;
    algorithm.write(fs);
    fs << "}";

    return fs.releaseAndGetString();
}

今度は成功です。

auto svm = cv::Algorithm::load<cv::ml::SVM>(R"(C:\hoge\svm.txt)");

auto yaml = getAlgorithmString(*svm);

auto svm2 = cv::Algorithm::loadFromString<cv::ml::SVM>(yaml);

もうちょっと良い方法

write -> loadFromStringの流れは明らかに狙ったスムーズな設計だと思ったのですが、なぜこんなことに。

これは何か使い方を間違えている気がします。試行錯誤の結果、こうすれば特殊なwriteが不要になりました。

loadFromStringの最後の引数objnameが大事でした

// テスト用SVMオブジェクト
auto svm = cv::Algorithm::load<cv::ml::SVM>(R"(C:\hoge\svm.txt)");

// メモリに保存
cv::FileStorage fs(".yml", cv::FileStorage::WRITE | cv::FileStorage::MEMORY);
svm->write(fs);
auto yaml = fs.releaseAndGetString();

// 文字列から復元
auto svm2 = cv::Algorithm::loadFromString<cv::ml::SVM>(yaml, "opencv_ml_svm"); // ここ!!

objnameなんて知らないよ

どうやって調べたらいいのか。わざわざ1回YAMLに書いて見てみるなんて面倒。ですのでちょっと改良版です。

// テスト用SVMオブジェクト
auto svm = cv::Algorithm::load<cv::ml::SVM>(R"(C:\hoge\svm.txt)");

// メモリに保存
cv::FileStorage fs(".yml", cv::FileStorage::WRITE | cv::FileStorage::MEMORY);
svm->write(fs);
auto yaml = fs.releaseAndGetString();

// 文字列から復元
auto objname = cv::ml::SVM::create()->getDefaultName(); // !!
auto svm2 = cv::Algorithm::loadFromString<cv::ml::SVM>(yaml, objname); 

遠回りをしましたが、おかげでgetDefaultName()が肝心であることに気づけました。

まとめ

  • cv::FileStorageはファイルに出さなくても使える
  • cv::Algorithmはメモリ領域にあるデータから構築でき、また保存もできる
  • cv::FileStorageは普段全然使わなかったので四苦八苦
  • わかってしまえば、よくできていますね

自作クラスのFileStorageへの対応

cv::Algorithmを継承するクラスはcv::FileStorageへの対応ができており、上記のような簡単な読み書き操作が可能です。自作クラスをこれに対応させる場合は、以下を参考にしましょう。

File Input and Output using XML and YAML files — OpenCV 2.4.12.0 documentation