输入输出XML和YAML文件

目的

你将得到以下几个问题的答案:

  • 如何将文本写入YAML或XML文件,及如何从从OpenCV中读取YAML或XML文件中的文本
  • 如何利用YAML或XML文件存取OpenCV数据结构
  • 如何利用YAML或XML文件存取自定义数据结构?
  • OpenCV中相关数据结构的使用方法,如 :xmlymlpers:FileStorage <filestorage>, FileNodeFileNodeIterator.

代码

你可以 点击此处下载 或直接从OpenCV代码库中找到源文件。 samples/cpp/tutorial_code/core/file_input_output/file_input_output.cpp

以下用简单的示例代码演示如何逐一实现所有目的.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#include <opencv2/core/core.hpp>
#include <iostream>
#include <string>

using namespace cv;
using namespace std;

class MyData
{
public:
    MyData() : A(0), X(0), id()
    {}
    explicit MyData(int) : A(97), X(CV_PI), id("mydata1234") // explicit to avoid implicit conversion
    {}
    void write(FileStorage& fs) const                        //Write serialization for this class
    {
        fs << "{" << "A" << A << "X" << X << "id" << id << "}";
    }
    void read(const FileNode& node)                          //Read serialization for this class
    {
        A = (int)node["A"];
        X = (double)node["X"];
        id = (string)node["id"];
    }
public:   // Data Members
    int A;
    double X;
    string id;
};

//These write and read functions must be defined for the serialization in FileStorage to work
void write(FileStorage& fs, const std::string&, const MyData& x)
{
    x.write(fs);
}
void read(const FileNode& node, MyData& x, const MyData& default_value = MyData()){
    if(node.empty())
        x = default_value;
    else
        x.read(node);
}

// This function will print our custom class to the console
ostream& operator<<(ostream& out, const MyData& m) 
{ 
    out << "{ id = " << m.id << ", ";
    out << "X = " << m.X << ", ";
    out << "A = " << m.A << "}";
    return out;
}

int main(int ac, char** av)
{
    if (ac != 2)
    {
        help(av);
        return 1;
    }

    string filename = av[1];
    { //write
        Mat R = Mat_<uchar>::eye(3, 3),
            T = Mat_<double>::zeros(3, 1);
        MyData m(1);

        FileStorage fs(filename, FileStorage::WRITE);

        fs << "iterationNr" << 100;
        fs << "strings" << "[";                              // text - string sequence
        fs << "image1.jpg" << "Awesomeness" << "baboon.jpg";
        fs << "]";                                           // close sequence
        
        fs << "Mapping";                              // text - mapping
        fs << "{" << "One" << 1;
        fs <<        "Two" << 2 << "}";               

        fs << "R" << R;                                      // cv::Mat
        fs << "T" << T;

        fs << "MyData" << m;                                // your own data structures

        fs.release();                                       // explicit close
        cout << "Write Done." << endl;
    }

    {//read
        cout << endl << "Reading: " << endl;
        FileStorage fs; 
        fs.open(filename, FileStorage::READ);

        int itNr; 
        //fs["iterationNr"] >> itNr;
        itNr = (int) fs["iterationNr"];
        cout << itNr;
        if (!fs.isOpened())
        {
            cerr << "Failed to open " << filename << endl;
            help(av);
            return 1;
        }

        FileNode n = fs["strings"];                         // Read string sequence - Get node
        if (n.type() != FileNode::SEQ)
        {
            cerr << "strings is not a sequence! FAIL" << endl;
            return 1;
        }

        FileNodeIterator it = n.begin(), it_end = n.end(); // Go through the node
        for (; it != it_end; ++it)
            cout << (string)*it << endl;
        
        
        n = fs["Mapping"];                                // Read mappings from a sequence
        cout << "Two  " << (int)(n["Two"]) << "; "; 
        cout << "One  " << (int)(n["One"]) << endl << endl; 
        

        MyData m;
        Mat R, T;

        fs["R"] >> R;                                      // Read cv::Mat
        fs["T"] >> T;
        fs["MyData"] >> m;                                 // Read your own structure_

        cout << endl 
            << "R = " << R << endl;
        cout << "T = " << T << endl << endl;
        cout << "MyData = " << endl << m << endl << endl;

        //Show default behavior for non existing nodes
        cout << "Attempt to read NonExisting (should initialize the data structure with its default).";  
        fs["NonExisting"] >> m;
        cout << endl << "NonExisting = " << endl << m << endl;
    }

    cout << endl 
        << "Tip: Open up " << filename << " with a text editor to see the serialized data." << endl;

    return 0;
}

代码分析

这里我们仅讨论XML和YAML文件输入。你的输出(和相应的输入)文件可能仅具有其中一个扩展名以及对应的文件结构。XML和YAML的串行化分别采用两种不同的数据结构: mappings (就像STL map) 和 element sequence (比如 STL vector>。二者之间的区别在map中每个元素都有一个唯一的标识名供用户访问;而在sequences中你必须遍历所有的元素才能找到指定元素。

  1. XML\YAML 文件的打开和关闭。 在你写入内容到此类文件中前,你必须先打开它,并在结束时关闭它。在OpenCV中标识XML和YAML的数据结构是 FileStorage 。要将此结构和硬盘上的文件绑定时,可使用其构造函数或者 open() 函数:

    string filename = "I.xml";
    FileStorage fs(filename, FileStorage::WRITE);
    \\...
    fs.open(filename, FileStorage::READ);

    无论以哪种方式绑定,函数中的第二个参数都以常量形式指定你要对文件进行操作的类型,包括:WRITE, READ 或 APPEND。文件扩展名决定了你将采用的输出格式。如果你指定扩展名如 .xml.gz ,输出甚至可以是压缩文件。

    FileStorage 对象被销毁时,文件将自动关闭。当然你也可以显示调用 release 函数:

    fs.release();                                       // 显示关闭
    
  2. 输入\输出文本和数字。 数据结构使用与STL相同的 << 输出操作符。输出任何类型的数据结构时,首先都必须指定其标识符,这通过简单级联输出标识符即可实现。基本类型数据输出必须遵循此规则:

    fs << "iterationNr" << 100;
    

    读入则通过简单的寻址(通过 [] 操作符)操作和强制转换或 >> 操作符实现:

    int itNr;
    fs["iterationNr"] >> itNr;
    itNr = (int) fs["iterationNr"];
    
  3. 输入\输出OpenCV数据结构。 其实和对基本类型的操作方法是相同的:

    Mat R = Mat_<uchar >::eye  (3, 3),
        T = Mat_<double>::zeros(3, 1);
    
    fs << "R" << R;                                      // 写 cv::Mat
    fs << "T" << T;
    
    fs["R"] >> R;                                      // 读 cv::Mat
    fs["T"] >> T;
    
  4. 输入\输出 vectors(数组)和相应的maps. 之前提到我们也可以输出maps和序列(数组, vector)。同样,首先输出变量的标识符,接下来必须指定输出的是序列还是map。

    对于序列,在第一个元素前输出”[“字符,并在最后一个元素后输出”]“字符:

    fs << "strings" << "[";                              // 文本 - 字符串序列
    fs << "image1.jpg" << "Awesomeness" << "baboon.jpg";
    fs << "]";                                           // 序列结束
    

    对于maps使用相同的方法,但采用”{“和”}“作为分隔符。

    fs << "Mapping";                              // 文本 - mapping
    fs << "{" << "One" << 1;
    fs <<        "Two" << 2 << "}";
    

    对于数据读取,可使用 FileNodeFileNodeIterator 数据结构。 FileStorage 的[] 操作符将返回一个 FileNode 数据类型。如果这个节点是序列化的,我们可以使用 FileNodeIterator 来迭代遍历所有元素。

    FileNode n = fs["strings"];                         // 读取字符串序列 - 获取节点
    if (n.type() != FileNode::SEQ)
    {
        cerr << "strings is not a sequence! FAIL" << endl;
        return 1;
    }
    
    FileNodeIterator it = n.begin(), it_end = n.end(); // 遍历节点
    for (; it != it_end; ++it)
        cout << (string)*it << endl;
    

    对于maps类型,可以用 [] 操作符访问指定的元素(或者 >> 操作符):

    n = fs["Mapping"];                                // 从序列中读取map
    cout << "Two  " << (int)(n["Two"]) << "; ";
    cout << "One  " << (int)(n["One"]) << endl << endl;
    
  5. 读写自定义数据类型。 假设你定义了如下数据类型:

    class MyData
    {
    public:
          MyData() : A(0), X(0), id() {}
    public:   // 数据成员
       int A;
       double X;
       string id;
    };
    

    添加内部和外部的读写函数,就可以使用OpenCV I/O XML/YAML接口对其进行序列化(就像对OpenCV数据结构进行序列化一样)。内部函数定义如下:

    void write(FileStorage& fs) const                        //对自定义类进行写序列化
    {
      fs << "{" << "A" << A << "X" << X << "id" << id << "}";
    }
    
    void read(const FileNode& node)                          //从序列读取自定义类
    {
      A = (int)node["A"];
      X = (double)node["X"];
      id = (string)node["id"];
    }
    

    接下来在类的外部定义以下函数:

    void write(FileStorage& fs, const std::string&, const MyData& x)
    {
    x.write(fs);
    }
    
    void read(const FileNode& node, MyData& x, const MyData& default_value = MyData())
    {
    if(node.empty())
        x = default_value;
    else
        x.read(node);
    }
    

    这儿可以看到,如果读取的节点不存在,我们返回默认值。更复杂一些的解决方案是返回一个对象ID为负值的实例。

    一旦添加了这四个函数,就可以用 >> 操作符和 << 操作符分别进行读,写操作:

    MyData m(1);
    fs << "MyData" << m;                               // 写自定义数据结构
    fs["MyData"] >> m;                                 // 读自定义数据结构
    

    或试着读取不存在的值:

    fs["NonExisting"] >> m;   // 请注意不是 fs << "NonExisting" << m
    cout << endl << "NonExisting = " << endl << m << endl;
    

结果

好的,大多情况下我们只输出定义过的成员。在控制台程序的屏幕上,你将看到:

Write Done.

Reading:
100image1.jpg
Awesomeness
baboon.jpg
Two  2; One  1


R = [1, 0, 0;
  0, 1, 0;
  0, 0, 1]
T = [0; 0; 0]

MyData =
{ id = mydata1234, X = 3.14159, A = 97}

Attempt to read NonExisting (should initialize the data structure with its default).
NonExisting =
{ id = , X = 0, A = 0}

Tip: Open up output.xml with a text editor to see the serialized data.

然而, 在输出的xml文件中看到的结果将更加有趣:

<?xml version="1.0"?>
<opencv_storage>
<iterationNr>100</iterationNr>
<strings>
  image1.jpg Awesomeness baboon.jpg</strings>
<Mapping>
  <One>1</One>
  <Two>2</Two></Mapping>
<R type_id="opencv-matrix">
  <rows>3</rows>
  <cols>3</cols>
  <dt>u</dt>
  <data>
    1 0 0 0 1 0 0 0 1</data></R>
<T type_id="opencv-matrix">
  <rows>3</rows>
  <cols>1</cols>
  <dt>d</dt>
  <data>
    0. 0. 0.</data></T>
<MyData>
  <A>97</A>
  <X>3.1415926535897931e+000</X>
  <id>mydata1234</id></MyData>
</opencv_storage>

或YAML文件:

%YAML:1.0
iterationNr: 100
strings:
   - "image1.jpg"
   - Awesomeness
   - "baboon.jpg"
Mapping:
   One: 1
   Two: 2
R: !!opencv-matrix
   rows: 3
   cols: 3
   dt: u
   data: [ 1, 0, 0, 0, 1, 0, 0, 0, 1 ]
T: !!opencv-matrix
   rows: 3
   cols: 1
   dt: d
   data: [ 0., 0., 0. ]
MyData:
   A: 97
   X: 3.1415926535897931e+000
   id: mydata1234

你也可以看到动态实例: YouTube here .

翻译者

刘瑞华 <lurvhua [at] 163.com>

Table Of Contents

Previous topic

离散傅立叶变换

Next topic

与 OpenCV 1 同时使用

This Page