opencv自带例子学习-文件输入与输出

IO操作

输入输出(input out),也称io操作。在c++编程里有学过io的知识点,主要是两种,一个是控制台io,即std::cout与std::cin等标准输入输出流的操作。这一种io的操作作用是实现数据在内存与控制台显示的传输。另一个是文件io,即iftream与ofstream对象文件输入输出流的操作,这一种io操作的作用是实现数据在内存与硬盘文件的传输。

那么在opencv里面也是一样的,一样拥有这控制台与文件的两种io操作方式。只不过有所不同的是,opencv里面的数据结构有所不同,io操作的输入输出流对象、文件流对象有所不一样,即被定制化了而已。原理与使用方式在本质上是一只的。

源代码程序

下面先附上代码解释

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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
//包含头文件
#include <opencv2/core.hpp>
#include <iostream>
#include <string>

//使用命名空间,不建议这么做,建议显示使用,如cv::imshow
using namespace cv;
using namespace std;

//显示帮助信息
static void help(char** av)
{
cout << endl
<< av[0] << " shows the usage of the OpenCV serialization functionality." << endl
<< "usage: " << endl
<< av[0] << " outputfile.yml.gz" << endl
<< "The output file may be either XML (xml) or YAML (yml/yaml). You can even compress it by "
<< "specifying this in its extension like xml.gz yaml.gz etc... " << endl
<< "With FileStorage you can serialize objects in OpenCV by using the << and >> operators" << endl
<< "For example: - create a class and have it serialized" << endl
<< " - use it to read and write matrices." << endl;
}


//数据类
class MyData
{
public:
MyData() : A(0), X(0), id()//构造函数
{}
explicit MyData(int) : A(97), X(CV_PI), id("mydata1234") // 显示构造函数,防止隐式调用
{}
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"];
}
public: // Data Members
//数据成员
int A;
double X;
string id;
};

//写数据函数,为静态函数,函数内调用类内成员函数
static void write(FileStorage& fs, const std::string&, const MyData& x)
{
x.write(fs);
}
//读取数据函数,为静态函数,函数内调用类内成员函数
static 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
//重载<<运算符
static 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];//文件路径名
//把MyData类对象的数据写到文件里面
{
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" << "../data/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
//打开文件读取数据到MyData类对象
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;
//读取MyData类数据
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;
}

程序详解

下面来一步一步地学习解读程序

自定义数据类

  1. 首先,程序声明里一个数据类来作为后面的io操作提供数据封装的实体。类的声明如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    //数据类
    class MyData
    {
    public:
    MyData() : A(0), X(0), id()//构造函数
    {}
    explicit MyData(int) : A(97), X(CV_PI), id("mydata1234") // 显示构造函数,防止隐式调用
    {}
    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"];
    }
    public: // Data Members
    //数据成员
    int A;
    double X;
    string id;
    };

    类包含了三个不同类型的数据成员,默认构造函数与显示构造函数,以及write和read两个成员函数,分别承担写入与读取功能。需要明白的是,write写入函数是将MyData的数据成员写入到FileStorage对象fs中,read读取函数是从FileNode对象node读取数据存到MyData之中。Filestorage与FileNode即为opencv中所“定制化的io操作类”。附上解释

FileStorage&FileNode

1
2
3
4
5
6
7
8
//FileStorage
XML/YAML/JSON file storage class that encapsulates all the information necessary for writing or reading data to/from a file.
//FileNode
File Storage Node class.

The node is used to store each and every element of the file storage opened for reading. When XML/YAML file is read, it is first parsed and stored in the memory as a hierarchical collection of nodes. Each node can be a "leaf" that is contain a single number or a string, or be a collection of other nodes. There can be named collections (mappings) where each element has a name and it is accessed by a name, and ordered collections (sequences) where elements do not have names but rather accessed by index. Type of the file node can be determined using FileNode::type method.

Note that file nodes are only used for navigating file storages opened for reading. When a file storage is opened for writing, no data is stored in memory after it is written.

而且,还在类外对读写两个函数再次进行了封装

1
2
3
4
5
6
7
8
9
10
11
12
//写数据函数,为静态函数,函数内调用类内成员函数
static void write(FileStorage& fs, const std::string&, const MyData& x)
{
x.write(fs);
}
//读取数据函数,为静态函数,函数内调用类内成员函数
static void read(const FileNode& node, MyData& x, const MyData& default_value = MyData()){
if(node.empty())
x = default_value;
else
x.read(node);
}

  1. 然后来看看重载了运算符<<

    先看看程序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // This function will print our custom class to the console
    //重载<<运算符
    static ostream& operator<<(ostream& out, const MyData& m)
    {
    out << "{ id = " << m.id << ", ";
    out << "X = " << m.X << ", ";
    out << "A = " << m.A << "}";
    return out;
    }

这个重载的作用英文注释已经解释了,作用即将MyData类对象m里面的数据打印显示到控制台屏幕上面,从函数的原型也可以看得出,参数包含ostream对象与MyData对象,从而把MyData对象数据传到输出流ostream对象上面,结果自然是与std::cout一样内容显示到控制台屏幕上面。

文件读写

  1. 最后我们来看看本次主题,opencv怎么进行文件的读写操作。

    程序中读文件和写文件所用到的类是FileStorage

    1
    2
    //FileStorage
    XML/YAML/JSON file storage class that encapsulates all the information necessary for writing or reading data to/from a file.

    这里可以看到,FileStorage读写文件的格式是XML/YAML/JSON类型的数据格式,这些数据格式有着比较明显的特点

    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
    //XML
    <!-- Copyright w3school.com.cn -->
    <note>
    <to>George</to>
    <from>John</from>
    <heading>Reminder</heading>
    <body>Don't forget the meeting!</body>
    </note>

    //YAML
    ---
    # 一位职工记录
    name: Example Developer
    job: Developer
    skill: Elite
    employed: True
    foods:
    - Apple
    - Orange
    - Strawberry
    - Mango
    languages:
    ruby: Elite
    python: Elite
    dotnet: Lame
    //JSON
    "firstName" : "John"

    { "firstName":"John" , "lastName":"Doe" }

    {
    "employees": [
    { "firstName":"John" , "lastName":"Doe" },
    { "firstName":"Anna" , "lastName":"Smith" },
    { "firstName":"Peter" , "lastName":"Jones" }
    ]
    }

    程序中读写操作的是json格式的文件。来学习一下具体的文件读写操作

    首先是文件的打开方式

    1
    2
    3
    FileStorage fs(filename, FileStorage::WRITE);//以写的方式打开文件
    FileStorage fs;
    fs.open(filename, FileStorage::READ);//以读的方式打开文件

    接着是读写json格式文件

    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
    //一对一键值对读写
    fs << "iterationNr" << 100;//写到文件
    itNr = (int) fs["iterationNr"];//从文件读取出来
    cout << itNr;
    //中括号数组形式的读写

    //写到文件
    fs << "strings" << "["; // text - string sequence
    fs << "image1.jpg" << "Awesomeness" << "../data/baboon.jpg";
    fs << "]"; // close sequence

    //从文件读取出来
    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;

    //花括号json对象的读写
    //写文件
    fs << "Mapping"; // text - mapping
    fs << "{" << "One" << 1;
    fs << "Two" << 2 << "}";
    //读文件
    n = fs["Mapping"]; // Read mappings from a sequence
    cout << "Two " << (int)(n["Two"]) << "; ";
    cout << "One " << (int)(n["One"]) << endl << endl;

    //矩阵以及MyData类数据的读写
    fs << "R" << R; // cv::Mat
    fs << "T" << T;
    fs << "MyData" << m;

    fs["R"] >> R; // Read cv::Mat
    fs["T"] >> T;
    //读取MyData类数据
    fs["MyData"] >> m;

程序运行的结果如下

控制台会显示如下内容

控制台显示

打开写入的文件,内容如下

文件内容

重载运算符

  1. 最后,来学习一下如何给自定义类去重载<<、>>运算符

    重载>>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class Date  
    {
    int mo, da, yr;
    public:
    Date(int m, int d, int y)
    {
    mo = m; da = d; yr = y;
    }
    friend ostream& operator<<(ostream& os, const Date& dt);
    };

    istream& operator>> ( istream& is, Date& dt )
    {
    is >> dt.mo >> dt.da >> dt.yr;
    return is;
    }

    重载<<

    1
    2
    3
    4
    5
    ostream& operator<<(ostream& os, const Date& dt)  
    {
    os << dt.mo << '/' << dt.da << '/' << dt.yr;
    return os;
    }

总结回顾

io操作一般有内存变量与控制台和内存变量与硬盘文件两类,opencv中实现操作的类为FileStorage和FileNode等。FileStorage支持读写的文件格式为XML/YAML/JSON,json数据格式有单一键值对、中括号数组格式和花括号json对象格式。

-------------本文结束感谢您的阅读-------------