Skip to content

使用 XML / YAML / JSON 文件进行文件输入输出

你将找到以下问题的答案:

  • 如何使用 YAML、XML 或 JSON 文件在 OpenCV 中向文件打印和读取文本条目?
  • 如何对 OpenCV 数据结构执行相同的操作?
  • 如何对自定义数据结构执行此操作?
  • 如何使用 OpenCV 数据结构,例如 cv::FileStorage、cv::FileNode 或 cv::FileNodeIterator。

C++

你可以从此处下载,也可以在 OpenCV 源码库的 samples/cpp/tutorial_code/core/file_input_output/file_input_output.cpp 中找到它。

下面是一份实现目标清单中所有内容的示例代码:

#include <opencv2/core.hpp>
#include <iostream>
#include <string>
using namespace cv;
using namespace std;
static void help(char** av)
{
cout << endl
<< av[0] << " shows the usage of the OpenCV serialization functionality." << endl << endl
<< "usage: " << endl
<< av[0] << " [output file name] (default outputfile.yml.gz)" << endl << endl
<< "The output file may be XML (xml), YAML (yml/yaml), or JSON (json)." << endl
<< "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 << endl;
}
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
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)
{
string filename;
if (ac != 2)
{
help(av);
filename = "outputfile.yml.gz";
}
else
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);
// or:
// FileStorage fs;
// fs.open(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 operation to file:" << filename << " completed successfully." << 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;
}

Python

你可以从此处下载,也可以在 OpenCV 源码库的 samples/python/tutorial_code/core/file_input_output/file_input_output.py 中找到它。

from __future__ import print_function
import numpy as np
import cv2 as cv
import sys
def help(filename):
print (
'''
{0} shows the usage of the OpenCV serialization functionality. \n\n
usage:\n
python3 {0} [output file name] (default outputfile.yml.gz)\n\n
The output file may be XML (xml), YAML (yml/yaml), or JSON (json).\n
You can even compress it by specifying this in its extension like xml.gz yaml.gz etc...\n
With FileStorage you can serialize objects in OpenCV.\n\n
For example: - create a class and have it serialized\n
- use it to read and write matrices.\n
'''.format(filename)
)
class MyData:
A = 97
X = np.pi
name = 'mydata1234'
def __repr__(self):
s = '{ name = ' + self.name + ', X = ' + str(self.X)
s = s + ', A = ' + str(self.A) + '}'
return s
def write(self, fs, name):
fs.startWriteStruct(name, cv.FileNode_MAP|cv.FileNode_FLOW)
fs.write('A', self.A)
fs.write('X', self.X)
fs.write('name', self.name)
fs.endWriteStruct()
def read(self, node):
if (not node.empty()):
self.A = int(node.getNode('A').real())
self.X = node.getNode('X').real()
self.name = node.getNode('name').string()
else:
self.A = self.X = 0
self.name = ''
def main(argv):
if len(argv) != 2:
help(argv[0])
filename = 'outputfile.yml.gz'
else :
filename = argv[1]
# write
R = np.eye(3,3)
T = np.zeros((3,1))
m = MyData()
s = cv.FileStorage(filename, cv.FileStorage_WRITE)
# or:
# s = cv.FileStorage()
# s.open(filename, cv.FileStorage_WRITE)
s.write('iterationNr', 100)
s.startWriteStruct('strings', cv.FileNode_SEQ)
for elem in ['image1.jpg', 'Awesomeness', '../data/baboon.jpg']:
s.write('', elem)
s.endWriteStruct()
s.startWriteStruct('Mapping', cv.FileNode_MAP)
s.write('One', 1)
s.write('Two', 2)
s.endWriteStruct()
s.write('R_MAT', R)
s.write('T_MAT', T)
m.write(s, 'MyData')
s.release()
print ('Write operation to file:', filename, 'completed successfully.')
# read
print ('\nReading: ')
s = cv.FileStorage()
s.open(filename, cv.FileStorage_READ)
n = s.getNode('iterationNr')
itNr = int(n.real())
print (itNr)
if (not s.isOpened()):
print ('Failed to open ', filename, file=sys.stderr)
help(argv[0])
exit(1)
n = s.getNode('strings')
if (not n.isSeq()):
print ('strings is not a sequence! FAIL', file=sys.stderr)
exit(1)
for i in range(n.size()):
print (n.at(i).string())
n = s.getNode('Mapping')
print ('Two',int(n.getNode('Two').real()),'; ')
print ('One',int(n.getNode('One').real()),'\n')
R = s.getNode('R_MAT').mat()
T = s.getNode('T_MAT').mat()
m.read(s.getNode('MyData'))
print ('\nR =',R)
print ('T =',T,'\n')
print ('MyData =','\n',m,'\n')
print ('Attempt to read NonExisting (should initialize the data structure',
'with its default).')
m.read(s.getNode('NonExisting'))
print ('\nNonExisting =','\n',m)
print ('\nTip: Open up',filename,'with a text editor to see the serialized data.')
if __name__ == '__main__':
main(sys.argv)

这里我们只讨论 XML、YAML 和 JSON 文件的输入。你的输出文件(及其对应的输入文件)只能是这些扩展名之一,并具有相应的结构。你可以序列化两种数据结构:映射(mapping,如 STL map 和 Python 字典)和元素序列(element sequence,如 STL vector)。两者的区别在于:在 map 中每个元素都有一个唯一的名称,可以通过它进行访问;而对于序列,你需要遍历它们才能查询某个特定项。

  1. XML/YAML/JSON 文件的打开与关闭。 在向这类文件写入任何内容之前,你需要先打开它,并在结束时关闭它。OpenCV 中的 XML/YAML/JSON 数据结构是 cv::FileStorage。要指定该结构绑定到硬盘上的哪个文件,你可以使用它的构造函数或它的 open() 函数:

    C++

    FileStorage fs(filename, FileStorage::WRITE);
    // or:
    // FileStorage fs;
    // fs.open(filename, FileStorage::WRITE);

    Python

    s = cv.FileStorage(filename, cv.FileStorage_WRITE)
    # or:
    # s = cv.FileStorage()
    # s.open(filename, cv.FileStorage_WRITE)

    无论使用哪种方式,第二个参数都是一个常量,用于指定你能对其执行的操作类型:WRITE、READ 或 APPEND。文件名中指定的扩展名也决定了将使用的输出格式。如果你指定类似 .xml.gz 的扩展名,输出甚至可以被压缩。

    当 cv::FileStorage 对象被销毁时,文件会自动关闭。不过,你也可以使用 release 函数显式地关闭它:

    C++

    fs.release(); // explicit close

    Python

    s.release()
  2. 文本和数字的输入与输出。 在 C++ 中,该数据结构使用 STL 库中的 << 输出运算符;在 Python 中,则使用 cv::FileStorage.write()。要输出任何类型的数据结构,我们首先需要指定它的名称。在 C++ 中,只需把名称推入流中即可;在 Python 中,write 函数的第一个参数就是名称。对于基本类型,你可以紧接着打印其值:

    C++

    fs << "iterationNr" << 100;

    Python

    s.write('iterationNr', 100)

    读入则是简单的寻址(通过 [] 运算符)加类型转换操作,或通过 >> 运算符读取。在 Python 中,我们用 getNode() 寻址并使用 real():

    C++

    int itNr;
    //fs["iterationNr"] >> itNr;
    itNr = (int) fs["iterationNr"];

    Python

    n = s.getNode('iterationNr')
    itNr = int(n.real())
  3. OpenCV 数据结构的输入/输出。 它们的行为与基本的 C++ 和 Python 类型完全相同:

    C++

    Mat R = Mat_<uchar>::eye(3, 3),
    T = Mat_<double>::zeros(3, 1);
    fs << "R" << R; // cv::Mat
    fs << "T" << T;
    fs["R"] >> R; // Read cv::Mat
    fs["T"] >> T;

    Python

    R = np.eye(3,3)
    T = np.zeros((3,1))
    s.write('R_MAT', R)
    s.write('T_MAT', T)
    R = s.getNode('R_MAT').mat()
    T = s.getNode('T_MAT').mat()
  4. 向量(数组)和关联 map 的输入/输出。 如前所述,我们也可以输出 map 和序列(数组、vector)。同样,我们先打印变量名,然后必须指定输出是序列还是 map。

    对于序列,在第一个元素之前打印 [ 字符,在最后一个元素之后打印 ] 字符。在 Python 中,调用 FileStorage.startWriteStruct(structure_name, struct_type) 开始写入结构,其中 struct_type 为 cv2.FileNode_MAP 或 cv2.FileNode_SEQ;调用 FileStorage.endWriteStruct() 结束结构:

    C++

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

    Python

    s.startWriteStruct('strings', cv.FileNode_SEQ)
    for elem in ['image1.jpg', 'Awesomeness', '../data/baboon.jpg']:
    s.write('', elem)
    s.endWriteStruct()

    对于 map,流程相同,只是改用 { 和 } 作为分隔字符:

    C++

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

    Python

    s.startWriteStruct('Mapping', cv.FileNode_MAP)
    s.write('One', 1)
    s.write('Two', 2)
    s.endWriteStruct()

    要读取这些内容,我们使用 cv::FileNode 和 cv::FileNodeIterator 数据结构。cv::FileStorage 类的 [] 运算符(或 Python 中的 getNode() 函数)返回一个 cv::FileNode 数据类型。如果该节点是序列型的,我们可以使用 cv::FileNodeIterator 遍历其中的项。在 Python 中,可以使用 at() 函数寻址序列中的元素,size() 函数返回序列的长度:

    C++

    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;

    Python

    n = s.getNode('strings')
    if (not n.isSeq()):
    print ('strings is not a sequence! FAIL', file=sys.stderr)
    exit(1)
    for i in range(n.size()):
    print (n.at(i).string())

    对于 map,你可以再次使用 [] 运算符(Python 中的 at() 函数)访问给定项(也可以使用 >> 运算符):

    C++

    n = fs["Mapping"]; // Read mappings from a sequence
    cout << "Two " << (int)(n["Two"]) << "; ";
    cout << "One " << (int)(n["One"]) << endl << endl;

    Python

    n = s.getNode('Mapping')
    print ('Two',int(n.getNode('Two').real()),'; ')
    print ('One',int(n.getNode('One').real()),'\n')
  5. 读写自定义数据结构。 假设你有如下数据结构:

    C++

    class MyData
    {
    public:
    MyData() : A(0), X(0), id() {}
    public: // Data Members
    int A;
    double X;
    string id;
    };

    Python

    class MyData:
    def __init__(self):
    self.A = self.X = 0
    self.name = ''

    在 C++ 中,可以通过 OpenCV 的 I/O XML/YAML 接口序列化它(就像 OpenCV 自带的数据结构一样),方法是在类的内部和外部各添加一对 read 和 write 函数。在 Python 中,你可以通过在类内部实现 read 和 write 函数来达到类似效果。类的内部部分:

    C++

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

    Python

    def write(self, fs, name):
    fs.startWriteStruct(name, cv.FileNode_MAP|cv.FileNode_FLOW)
    fs.write('A', self.A)
    fs.write('X', self.X)
    fs.write('name', self.name)
    fs.endWriteStruct()
    def read(self, node):
    if (not node.empty()):
    self.A = int(node.getNode('A').real())
    self.X = node.getNode('X').real()
    self.name = node.getNode('name').string()
    else:
    self.A = self.X = 0
    self.name = ''

    在 C++ 中,你还需要在类外部添加以下函数定义:

    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);
    }

    可以看到,在 read 部分我们定义了当用户尝试读取不存在的节点时的行为。这里我们只是返回默认初始化值,但更详细的方案可以是例如对对象 ID 返回 -1 这样的值。

    添加好这四个函数之后,就可以使用 >> 运算符写入、使用 << 运算符读取(或 Python 中定义的输入/输出函数):

    C++

    MyData m(1);
    fs << "MyData" << m; // your own data structures
    fs["MyData"] >> m; // Read your own structure_

    Python

    m = MyData()
    m.write(s, 'MyData')
    m.read(s.getNode('MyData'))

    或者尝试读取一个不存在的项:

    C++

    cout << "Attempt to read NonExisting (should initialize the data structure with its default).";
    fs["NonExisting"] >> m;
    cout << endl << "NonExisting = " << endl << m << endl;

    Python

    print ('Attempt to read NonExisting (should initialize the data structure',
    'with its default).')
    m.read(s.getNode('NonExisting'))
    print ('\nNonExisting =','\n',m)

程序基本上只是把定义的数字打印出来。在你的控制台屏幕上可以看到:

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 视频中观看它的运行实例。