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,320,1000,240,1); Mat distCoeffs = (Mat_<double>(5,1) << 0.1,0.01,-0.001,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.,1000.,240.,1. ] distCoeffs: !!opencv-matrix rows: 5 cols: 1 dt: d data: [ 1.0000000000000001e-01,1.0000000000000000e-02,-1.0000000000000000e-03,0. ] features: - { x:167,y:49,lbp:[ 1,1,1 ] } - { x:298,y:130,lbp:[ 0,1 ] } - { x:344,y:158,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就是一个序列。注意,是序列中的
元素没有名字,而并非序列本身。序列作为一个整体是用名字索引的。一般使用迭代器来访问序列中的元素。
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。