1.目标
您将找到以下问题的答案:
- 如何打印和读取文本条目到一个文件和OpenCV使用YAML或XML文件?
- 如何做相同的OpenCV数据结构?
- 如何为自己的数据结构做到这一点?
- 使用OpenCV的数据结构,如
cv::FileStorage
,cv::FileNode
或cv::FileNodeIterator
。
2.源代码
下面是如何实现目标列表中列举的所有内容的示例代码。
2.1 C++代码
#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
<< "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") // 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)
{
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);
// 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 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;
}
2.2Python代码
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} outputfile.yml.gz\n\n
The output file may be either in XML, YAML or JSON. You can even compress it\n
by specifying this in its extension like xml.gz yaml.gz etc... With\n
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])
exit(1)
# write
R = np.eye(3,3)
T = np.zeros((3,1))
m = MyData()
filename = argv[1]
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 Done.')
# 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)
3.结果展示
3.1 控制台输出结果:
3.2生成的yml文件
%YAML:1.0
---
iterationNr: 100
strings:
- "image1.jpg"
- Awesomeness
- "../data/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+00
id: mydata1234
3.3生成的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>
4.代码解释
这里我们只讨论XML和YAML文件输入。您的输出(及其相应的输入)文件可能只有其中一个扩展名和结构。它们是两种可以序列化的数据结构:mapping(像STL map和Python字典)和元素序列(像STL vector)。它们之间的区别是,在一个映射中,每个元素都有一个惟一的名称,通过您可以访问它。对于序列,您需要遍历它们来查询特定的项。
XML/YAML
文件打开和关闭。在向这样的文件写入任何内容之前,您需要打开它,并在结束时关闭它。OpenCV中的XML/YAML
数据结构为cv::FileStorage
。要指定这个结构在你的硬盘上绑定到哪个文件,你可以使用它的构造函数或open()
函数:
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函数来显式地调用它:s.release()
- 文本和数字的输入和输出(int, string等)。在c++中,数据结构使用STL库中的
<<
输出操作符。在Python中,使用cv::FileStorage::write()
代替。要输出任何类型的数据结构,我们首先需要指定它的名称。我们只需要在c++中将名称推入流就可以做到这一点。在Python中,write
函数的第一个参数是名称。对于基本类型,你可以在后面打印值:s.write('iterationNr', 100)
。
读入是一个简单的寻址(通过[]操作符
)和强制类型转换操作或通过>>
操作符进行读。在Python中,我们使用getNode()
和real()
来寻址:
int itNr;
//fs["iterationNr"] >> itNr;
itNr = (int) fs["iterationNr"];
- OpenCV数据结构的输入/输出(Mat等)。这些类型的行为与c++和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()
- 向量(数组)和关联映射的输入/输出(vector array map)。正如我之前提到的,我们也可以输出映射和序列(数组、向量)。同样,我们首先打印变量的名称,然后必须指定输出是序列还是映射。
对于序列,在第一个元素之前打印"["
字符,在最后一个元素之后打印"]"
字符(在C++中
)。在Python中,调用FileStorage.startWriteStruct(structure_name, struct_type)
,其中struct_type
为cv2.FileNode_MAP
或cv2.FileNode_SEQ
来开始写结构。调用FileStorage.endWriteStruct()
来完成结构:
s.startWriteStruct('strings', cv.FileNode_SEQ)
for elem in ['image1.jpg', 'Awesomeness', '../data/baboon.jpg']:
s.write('', elem)
s.endWriteStruct()
对于映射,操作是相同的,但现在我们使用“{”
和“}”
分隔符(在C++中
)
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()
函数返回序列的长度:
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())
对于映射,你可以再次使用[]
操作符(Python中的at()
函数)来访问给定项(或者也使用>>
操作符):
n = s.getNode('Mapping')
print ('Two',int(n.getNode('Two').real()),'; ')
print ('One',int(n.getNode('One').real()),'\n')
- 读写您自己的数据结构。假设你有这样一个数据结构:
class MyData:
def __init__(self):
self.A = self.X = 0
self.name = ''
在c++中,可以通过OpenCV I/O XML/YAML接口(就像OpenCV数据结构的情况一样)通过在类内外添加读和写函数来序列化它。在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 = ''
这里可以看到,在read部分中,我们定义了如果用户试图读取一个不存在的节点会发生什么。在这种情况下,我们只返回默认的初始化值,但是更详细的解决方案是为实例返回一个- 1值的对象ID。
添加这几个函数后,使用>>
操作符进行写操作,使用<<
操作符进行读操作(或Python定义的输入/输出函数):
m = MyData()
m.write(s, 'MyData')
m.read(s.getNode('MyData'))
或者尝试读取一个不存在的read:
print ('Attempt to read NonExisting (should initialize the data structure',
'with its default).')
m.read(s.getNode('NonExisting'))
print ('\nNonExisting =','\n',m)