OpenCV学习笔记之“文件I/O,XML/YAML”


基础篇

OpenCV提供了将数据保存成XML和YAML格式的方法,这两种格式相交TXT而言,有更好的可视性,更重要的是方便了OpenCV的读写,既可以保存普通的数据类型(如整型,浮点型,字符串等),也可以保存OpenCV自己的数据结构(如Mat)。

1.基本操作

要实现读写,先创建一个FileStorage对象
FileStorage Fs("FileIOTest.xml", FileStorage::WRITE);
读写完成后关闭
Fs.release();

2.一个例子

接下来通过OpenCV的一个例子来介绍一下读写的相关操作
#include "opencv2/opencv.hpp"
#include <time.h>
using namespace cv;
int main(int, char** argv)
{
FileStorage fs("test.yml", FileStorage::WRITE);
fs << "frameCount" << 5;


time_t rawtime; time(&rawtime);
fs << "calibrationDate" << asctime(localtime(&rawtime));


Mat cameraMatrix = (Mat_<double>(3,3) << 1000, 0, 320, 0, 1000, 240, 0, 0, 1);
Mat distCoeffs = (Mat_<double>(5,1) << 0.1, 0.01, -0.001, 0, 0);
fs << "cameraMatrix" << cameraMatrix << "distCoeffs" << distCoeffs;


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 << "]";


fs.release();
return 0;
}


运行的得到的结果如下:
%YAML:1.0
frameCount: 5
calibrationDate: "Fri Jun 17 14:09:29 2011\n"
cameraMatrix: !!opencv-matrix
rows: 3
cols: 3
dt: d
data: [ 1000., 0., 320., 0., 1000., 240., 0., 0., 1. ]
distCoeffs: !!opencv-matrix
rows: 5
cols: 1
dt: d
data: [ 1.0000000000000001e-01, 1.0000000000000000e-02,
-1.0000000000000000e-03, 0., 0. ]
features:
- { x:167, y:49, lbp:[ 1, 0, 0, 1, 1, 0, 1, 1 ] }
- { x:298, y:130, lbp:[ 0, 0, 0, 1, 0, 0, 1, 1 ] }
- { x:344, y:158, lbp:[ 1, 1, 0, 0, 0, 0, 1, 0 ] }

进阶篇

1. YAML和XML的两种索引方式

C++中所有的变量,都是通过变量名来表示和区分的的,从简单的整型,浮点型,到数组,结构体等。在xml和yaml中也是一样,默认的方式就是用 名字来索引,这种数据结构叫做mapping。例如写入一个整型数,为了和别的数字区分,就给他取个名字:
fs << "frameCount" << 5;
相当于,每次写入都是一个由<<element_name<<element_value组成的一个pair;

但是如果我要写入100个frameCount,自然不会给每一个都取一个名字,C++的做法是使用vector来表示,每一元素用下标来表示
同样的,在xml和yaml中,如果有很多相同类型的元素,我们可以用一个Sequence来表示。

YAML和XML可以包含各种不同的数据结构,那么如何才能区分他们,并且找到我们想要的数据呢?
这里OpenCV提供了两种索引的方法,一种是用名字来索引(mappings),一种用序号来索引(sequences)。

Mappings

这种结构和C++里面的struct很相似,所有的元素都通过名字来唯一索引,因此 名字也必须是唯一的,可以类比C++的结构体,结构体里面的变量名自然是唯一的(如果重复了,在写文件的时候不会报错,但是读取的时候会报错)。

如果要写一个mapping,需要写入一个"{",然后依次写入每一个元素的名字和以及它的数值,最后以一个“}”结束,例如
</pre>这里并没有使用{},原因是OpenCV中默认XML和YAML文件的最顶层使用的是mapping,因此相当于在文件的的一头一尾都加上了{};</div><div></div><div>在读取mapping结构的数据时,使用fs["element_name"]来读取对应的数据。</div><div><pre name="code" class="cpp">int frameCount;
fs["frameCount"]>>frameCount;

Sequences

序列结构和C++里面的vector比较相似,元素没有名字,都通过序号来索引,上例中的feature就是一个序列。注意,是序列中的 元素没有名字,而并非序列本身。序列作为一个整体是用名字索引的。一般使用迭代器来访问序列中的元素。

在上面贴出的例子中,在括号后面加上冒号{:},意思是在保存的文件中,数据按一行来显示,数据间用逗号隔开。如果不加冒号,则每个元素一行,用换行符隔开。

事实上一个文件就是Mapping和Sequence的各种嵌套组合成的,其实跟一个cpp文件很相似,一般都用变量名区分(简单的数据类型和结构体),数组的元素用索引。

2.自定义结构体的读写

假设我们希望将1000张图像的特征写入FeaturesLib.yml,每个特征包含一个string类型的图像路径,和一个Mat类型的特征向量。
Fs<<"feature"<<"[";

while( std::getline( imagePathFile, imagePath) )
{

	Fs<<"{"<<"imagePath"<<imagePath;
	feature = GetFeature(imagePath.c_str());
	Fs<<"data"<<feature<<"}";
			
	std::cout<<"已生成第"<<num<<"张图片特征"<<std::endl;

}

Fs<<"]";
Fs.release();


刚开始的时候,用元素是否有名字来区分mapping还是sequence,比较容易混淆。因为Sequence的元素没有名字,打算Sequence这个整体是有名字的。说的都是这个结构其中元素的索引方法,而非这个结构本身。比如,我要找一个sequence中的第5个元素,那么我首先要找到这个sequence,而这个sequence本身是由mapping来索引的。建议大家不要想着有没有名字,而是根据数据结构是否是同一类型来区分数组和结构体。存储数组用sequence,存储结构体用mapping。

你可能感兴趣的:(OpenCV学习笔记之“文件I/O,XML/YAML”)