OpenCV3.0のyml/xmlファイルの読み書きについて

# 注意事項

この記事は，OpenCV3.xの書き方について書いています．2.xとは書き方が異なりますのでご注意ください．
OpenCV2.xについては，[こちら](http://opencv.jp/opencv2-x-samples/xml_yaml)に日本語ドキュメントがあります．

# cv::FileStorageについて

OpenCVの中にcv::FileStorageクラスというクラスがあります．
このクラスを使用することにより,cv::Matの中身やキーポイント点などのパラメータを1~3行程度でアウトプットできるようになります．アウトプット形式としては，yml形式かxml形式で保存ができます．

また，このクラスはOpenCV3.xから仕様が変わっています．

このクラスを使用して，ファイルにcv::Matやintなどの変数を書き出す際に，オペレータが使用できるようになりました．そのため，cv::Matの書き込み／読み込みがオペレータにより１行書くだけで，できてしまうのです．

取り出した特徴量・キーポイント点の座標の保存や，カメラキャリブレーションのパラメータ保存などで使えると思います．また，OpenCVを使用して大規模な開発を独りで行っている時の中間ファイルの作成や，卒研などをチームでやる場合にXML/YMLのフォーマットを先に決めて，分担して開発することなどが簡単にできるようになります．

XML形式かYML形式どちらで出力するかは，開くファイルの拡張子で自動的に決まります．

詳しくは，[OpenCVのドキュメント](http://docs.opencv.org/3.0-beta/modules/core/doc/xml_yaml_persistence.html)に書いてあります（英語です）．

# データをxml形式で書き出す

コードの例としては，以下のようになります．

```cpp
#include<iostream>
#include<string>
#include"OpenCV31.h"

using namespace std;

int main(void)
{
	//書き込みでXMLファイルを開く
	cv::FileStorage fs("test.xml", cv::FileStorage::WRITE);
	if (!fs2.isOpened()){
		std::cout << "File can not be opened." << std::endl;
		return -1;
	}

	//int型の格納方法
	int formatCount = 4;
	fs << "formatCount" << formatCount;

	//std::stringの格納方法
	std::string imgName = "nantara.bmp";
	fs << "formatName" << imgName;

	//cv::Matの格納方法(画像行列の代わりに3x3の行列を格納)
	cv::Mat ImgMatrix = (cv::Mat_<double>(3, 3) << 100, 0, 320, 0, 100, 240, 0, 0, 1);
	fs << "ImgMatrix" << ImgMatrix;

	//ノードの作成
	//バイナリパターンの保存
	fs << "features" << "[";
	for (int i = 0; i < 3; i++){
		int x = rand() % 640;
		int y = rand() % 480;
	}
	fs << "]";

	//書き出し
	fs.release();
	return 0;
}
```

上記の処理により出力されるXMLは以下の通りです．

```xml

<?xml version="1.0"?>
<opencv_storage>
<formatCount>4</formatCount>
<formatName>nantara.bmp</formatName>
<ImgMatrix type_id="opencv-matrix">
  <rows>3</rows>
  <cols>3</cols>
  <dt>d</dt>
  <data>
    100. 0. 320. 0. 100. 240. 0. 0. 1.</data></ImgMatrix>
<features>
  <_><x>41</x>
    <y>227</y>
    <lbp>
      0 1 1 1 1 1 0 1</lbp></_>
  <_><x>260</x>
    <y>449</y>
    <lbp>
      0 0 1 1 0 1 1 0</lbp></_>
  <_><x>598</x>
    <y>78</y>
    <lbp>
      0 1 0 0 1 0 1 0</lbp></_></features>
</opencv_storage>
```

## ちょこっと解説

### ファイルオープン

```cpp
//書き込みでXMLファイルを開く
cv::FileStorage fs("test.xml", cv::FileStorage::WRITE);
```

上記の一行でファイルオープン処理を行っています．
変数の後に，開きたい（作成したい）ファイル名と，モードを入力します．
モードは読み込み（cv::FileStorage::READ）と書き込み（cv::FileStorage::WRITE）の2種類があります
（リファレンスを観ると2種類以外にもモードがありますが，読み書きに必要なのはこの2つです）．
なお，yml形式のファイルが作成したい場合は，上記のコードの"test.xml"を"test.yml"に変更するだけで，
ymlの形式で保存されます（超便利）．

### オペレータ

```cpp
//int型の格納方法
int formatCount = 4;
fs << "formatCount" << formatCount;
```

オペレータは，int,float,double,std::string,cv::Matなどに対応しています．オペレータでファイル変数に最初に入れた文字列がxmlのタグになります．注意点としては，タグの情報を最初に入れないとxmlのフォーマットで格納されません．
以下のコードのように連続で数値データを入れた場合はエラーになります．

#### まちがい

```cpp
//int型の格納方法
int formatCount = 4;
int errorCount = 5;
fs << "formatCount" << formatCount << errorCount; //実行エラーになる
```

#### せいかい

```cpp
//int型の格納方法
int formatCount = 4;
int errorCount = 5;
fs << "formatCount" << formatCount << "errorCount" << errorCount; //必ずタグ名を入れる
```

#### ↓せいかいの結果↓

```xml
<?xml version="1.0"?>
<opencv_storage>
<formatCount>4</formatCount>
<errorCount>5</errorCount>
</opencv_storage>
```

せいかいの場合は，ちゃんとタグ付けされて保存されます．

## まとめる

特徴量やキーポイント点など数値をまとめて保存したい場合は，"[","]","{:","}","[:","]"を使用してパラメータを区切ります．

```cpp
//ノードの作成
//バイナリパターンの保存
fs << "features" << "[";  //メインタグとシーケンスの開始
for (int i = 0; i < 3; i++){
	int x = rand() % 640;
	int y = rand() % 480;
	uchar lbp = rand() % 256;

	//各要素
	fs << "{:" << "x" << x << "y" << y << "lbp" << "[:";
	for (int j = 0; j < 8; j++){
		fs << ((lbp >> j) & 1);
	}
	fs << "]" << "}";
}
fs << "]";
```

# 保存したxmlファイルの読み込み

コードの例としては，以下のようになります．

```cpp
int main(void)
{
	cv::FileStorage fs2("test.xml", cv::FileStorage::READ);
	if (!fs2.isOpened()){
		std::cout << "File can not be opened." << std::endl;
		return -1;
	}

	// first method: use (type) operator on FileNode.
	int frameCount = (int)fs2["frameCount"];

	cv::Mat cameraMatrix2, distCoeffs2;
	fs2["cameraMatrix"] >> cameraMatrix2;
	fs2["distCoeffs"] >> distCoeffs2;

	cout << "frameCount: " << frameCount << endl
		<< "camera matrix: " << cameraMatrix2 << endl
		<< "distortion coeffs: " << distCoeffs2 << endl;

	cv::FileNode features = fs2["features"];
	cv::FileNodeIterator it = features.begin();
    cv::FileNodeIterator it_end = features.end();
	int idx = 0;
	std::vector<uchar> lbpval;

	// iterate through a sequence using FileNodeIterator
	for (; it != it_end; ++it, idx++){
		cout << "feature #" << idx << ": ";
		cout << "x=" << (int)(*it)["x"] << ", y=" << (int)(*it)["y"] << ", lbp: (";
		// you can also easily read numerical arrays using FileNode >> std::vector operator.
		(*it)["lbp"] >> lbpval;
		for (int i = 0; i < (int)lbpval.size(); i++)
			cout << " " << (int)lbpval[i];
		cout << ")" << endl;
	}

	fs2.release();
	return 0;
}
```

読み込みの場合は，std::map<>を知っていると似たように使えるので理解しやすいと思います．
タグの情報がリストのインデックスになっているので，[]の中にタグ名を書いてオペレータで変数に値を受け取ります．
変数がリスト化されている場合は,cv::FileNodeでノードを取り出し，イテレータで要素にアクセスします．

# 最後に

自分も最近知って使ってみたばかりなので，もう少しいい書き方があるかもです．