上一个教程 离散傅立叶变换
下一个教程 如何使用 OpenCV parallel_for_ 实现代码并行化
原作者 | Bernát Gábor |
---|---|
兼容性 | OpenCV >= 3.0 |
目标
您将找到以下问题的答案:
- 如何使用 YAML 或 XML 文件向文件和 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
<< "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: // 数据成员
int A;
double X;
string id;
};
//必须定义这些写入和读取函数,FileStorage 中的序列化才能正常工作
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);
}
// 该函数将把我们的自定义类打印到控制台
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" << "["; // 文本 - 字符串序列
fs << "image1.jpg" << "Awesomeness" << "../data/baboon.jpg";
fs << "]"; // 关闭序列
fs << "Mapping"; // 文本 - 映射
fs << "{" << "One" << 1;
fs << "Two" << 2 << "}";
fs << "R" << R; // cv::Mat
fs << "T" << T;
fs << "MyData" << m; // 你自己的数据结构
fs.release(); // 显式关闭
cout << "Write Done"。<< endl;
}
{// 读取
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"]; // 读取字符串序列 - 获取节点
if (n.type() != FileNode::SEQ)
{
cerr << "strings 不是序列!FAIL" << endl;
return 1;
}
FileNodeIterator it = n.begin(), it_end = n.end(); // 遍历节点
for (; it != it_end; ++it)
cout << (string)*it << endl;
n = fs["Mapping"]; // 从序列中读取映射
cout << "Two " << (int)(n["Two"]) << "; ";
cout << "One " << (int)(n["One"]) << endl << endl;
MyData m;
Mat R, T;
fs["R"] >> R; // 读取 cv::Mat
fs["T"] >> T;
fs["MyData"] >> m; // 读取你自己的 structure_
cout << endl
<< "R = " << R << endl;
cout << "T = " << T << endl << endl;
cout << "MyData = " << endl << m << endl << endl;
//显示非现有节点的默认行为
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} 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)
说明
这里我们只讨论 XML 和 YAML 文件输入。您的输出(及其各自的输入)文件可能只有其中一种扩展名和由此产生的结构。您可以序列化两种数据结构:映射(如 STL 映射和 Python 字典)和元素序列(如 STL 向量)。它们之间的区别在于,在映射中,每个元素都有一个唯一的名称,您可以通过这个名称访问它。而对于序列,则需要通过它们来查询特定项目。
- XML/YAML 文件的打开和关闭。在向此类文件写入任何内容之前,您需要打开文件,并在结束时关闭文件。OpenCV 中的 XML/YAML 数据结构是 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(); // 显式关闭
- Python
s.release()
- 文本和数字的输入和输出。在 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
int itNr;
//fs["iterationNr"] >> itNr;
itNr = (int) fs["iterationNr"];
- 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()
- 向量(数组)和关联映射的输入/输出。正如我之前提到的,我们也可以输出映射和序列(数组、向量)。同样,我们首先要打印变量名,然后指定输出是序列还是映射。
对于序列,在第一个元素之前打印"[“字符,在最后一个元素之后打印”]"字符。使用 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()
对于映射,操作方法相同,但现在我们使用"{“和”}"分隔符:
- C++
s.startWriteStruct('Mapping', cv.FileNode_MAP)
s.write('One', 1)
s.write('Two', 2)
s.endWriteStruct()
- 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())
对于映射,您可以再次使用 [] 操作符(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')
- 读写自己的数据结构 假设你有一个数据结构,例如
- 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 数据结构一样),方法是在类的内部和外部添加一个读取和写入函数。在 Python 中,您可以通过在类内部实现一个读写函数来接近这一点。对于类内部的部分
- 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++ 中,您需要在类外添加以下函数定义:
- 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);
}
这里你可以看到,在读取部分,我们定义了如果用户试图读取一个不存在的节点会发生什么。在这种情况下,我们只需返回默认的初始化值,但一个更简洁的解决方案是返回对象 ID 的减一值。
添加这四个函数后,使用 >> 操作符写入,使用 << 操作符读取(或使用 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 上看到运行时的实例。