OpenCV2.0のC++インターフェースにおいて、クラスや構造体の永続化の関数(XML/YAML Persistence)が、1.1のそれと色々変わったのでメモ。2.0のファイル保存機能に関してはまだまだドキュメントも書籍も乏しい状態なので、ソースを眺めてわかったやり方などもひっくるめて書いておく。
まずは、以下に1.1の場合と2.0の場合のファイル保存のサンプルを書く。
ここでは、int型、float型、CvMat型の配列をそれぞれ保存する方法についての例。
1.1の場合
char filename[] = "save_cv.xml"; // file name ////// creating data for save ////////////// int a = 10; float b = 0.1; CvMat** mat = (CvMat**)malloc(3*sizeof(CvMat*)); for(int i=0;i<3;i++){ mat[i] = cvCreateMat(3,3,CV_32FC1); cvSet(mat[i], cvScalar(i)); } //////////////////////////////////////////// // Open File Storage CvFileStorage *cvfs = cvOpenFileStorage(filename,NULL,CV_STORAGE_WRITE); cvWriteInt(cvfs,"a",a); cvWriteReal(cvfs,"b",b); cvStartWriteStruct(cvfs, "mat_array", CV_NODE_SEQ); // create node for(int i=0; i<3; i++){ cvWrite(cvfs,NULL,mat[i]); } cvEndWriteStruct(cvfs); // Close File Storage cvReleaseFileStorage(&cvfs); // release mat for(int i=0; i<3; i++){ cvReleaseMat(mat+i); } free(mat);
2.0の場合
char filename[] = "save_cv2.0.xml"; // file name ////// creating data for save ////////////// int a = 10; float b = 0.1; cv::Mat mat[3]; for(int i=0; i<3; i++){ mat[i].create(3,3,CV_32FC1); mat[i].setTo(cv::Scalar(i)); } //////////////////////////////////////////// // Open File Storage cv::FileStorage cvfs(filename,CV_STORAGE_WRITE); cv::write(cvfs,"a",a); cv::write(cvfs,"b",b); cv::WriteStructContext ws(cvfs, "mat_array", CV_NODE_SEQ); // create node for(int i=0; i<3; i++){ cv::write(cvfs,"",mat[i]); }
保存されるファイルは、1.1、2.0とも同じで、こんな感じになります。
save_cv.xml
<opencv_storage> <a>10</a> <b>0.1000000014901161</b> <mat_array> <_ type_id="opencv-matrix"> <rows>3</rows> <cols>3</cols> <dt>f</dt> <data>0. 0. 0. 0. 0. 0. 0. 0. 0.</data> </_> <_ type_id="opencv-matrix"> <rows>3</rows> <cols>3</cols> <dt>f</dt> <data>1. 1. 1. 1. 1. 1. 1. 1. 1.</data> </_> <_ type_id="opencv-matrix"> <rows>3</rows> <cols>3</cols> <dt>f</dt> <data>2. 2. 2. 2. 2. 2. 2. 2. 2.</data> </_> </mat_array> </opencv_storage>
以下、この例での変更点を列挙する。
- 1.1ではCvFileStorageという構造体を用いて、データの保存などを行っていたのが、2.0からcv::FileStorageクラスに変更になった。ただし旧来のCvFileStorage構造体のポインタを引数として渡すことでFileStorageクラスを作成することも可能。2.0から新たに導入された各種クラスや構造体(例えばKeyPoint構造体)などは、やはり2.0用の永続化関数を使用しないと保存できない。
- 基本的なファイル構造の考え方は、1.1以前のそれと一緒で、ファイルの中の論理構造は木構造になっている。1.1ではCvFileNodeという構造体が各木のノードを表していたのが、2.0からFileNodeというクラスに変更になった。(後述)
- 書き込み時にノードを新たに作成する場合、1.1では"cvStartWriteStruct()"、"cvEndWriteStruct()"という関数を使って書き込みノードの間を囲ってやる必要があったが、2.0からはWriteStructContextクラスを生成することでノードを作成して開き、このクラスを開放(デストラクタ呼び出し)を行うことで、ノードを閉じる。(尚、ノードに配列などの連続値を保存したいときは引数に"CV_NODE_SEQ"を指定し、構造体を保存したい場合は"CV_NODE_MAP"を使用するという点はどちらのバージョンでも同じ)
- 1.1ではデータの型ごとに"cvWriteInt()"や"cvWriteReal()"という形で使い分けていたが、2.0からは全て"cv::write()"関数に統合された。
- 2.0から、ファイル保存時に拡張子".gz"で保存すると、圧縮して保存してくれるようになった。
では次に、読み込みの例
1.1の場合
////// creating data for save ////////////// int a; float b; CvMat** mat = (CvMat**)malloc(3*sizeof(CvMat*)); //////////////////////////////////////////// // Open File Storage CvFileStorage *cvfs = cvOpenFileStorage(filename,NULL,CV_STORAGE_READ); CvFileNode* node = cvGetFileNodeByName(cvfs,NULL,""); // Get Top Node a = cvReadIntByName(cvfs, node, "a"); b = cvReadRealByName(cvfs, node, "b"); CvFileNode* fn = cvGetFileNodeByName(cvfs,node,"mat_array"); CvSeq *s = fn->data.seq; int total = s->total; for(int i=0;i
2.0の場合
////// data for load ////////////// int a; float b; cv::Mat mat[3]; //////////////////////////////////////////// // Open File Storage cv::FileStorage cvfs(filename,CV_STORAGE_READ); cv::FileNode node(cvfs.fs, NULL); // Get Top Node a = node["a"]; b = node["b"]; cv::FileNode fn = node["mat_array"]; for(int i=0;i
違いは、
- 1.1では読み込む型によって例えば"cvReadInt"や"cvReadReal"という形で関数が用意されていたが、2.0ではFileNodeクラスに[]でノードの名前を添字してやれば値が取得できる。ただし、OpenCV定義のクラスや構造体を読み込むときは、"cv::read()"を利用する。
- 1.1では連続したデータに対して、CvSeq構造体に一度ノードの情報を格納してアクセスする必要があったが、2.0ではやはりFileNodeクラスに番号を添字してやれば良い。
以上。