オンメモリのFileStorage
この記事はOpenCV Advent Calendar 2015の11日目の記事です。
目次
だいぶ前回の記事で消耗しているため、今回はあっさりと。
筆者の環境
- Windows 10
- Visual Studio 2013
- OpenCV 3.0 (x86)
以下に示すサンプルコードは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)");
ちなみにこのような文字列リテラルについては以下を参照ください。
オンメモリの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();
yaml
にYAML形式の文字列として、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