【OpenCV C++20 学习笔记】序列化——XML和YAML文件处理

序列化和反序列化

如果希望永久保存某些对象,而不是每次运行程序的时候重新创建,就需要将对象以字节序列的形式保存起来,这就是序列化。反之,通过字节序列得到储存在其中的原对象就是反序列化。序列化和反序列常用在以下场景中:

  • 将对象保存到一个文件或数据库中
  • 在网络上传送对象

本文将介绍在OpenCV中如何将对象序列化,并保存到XML或YAML文件,然后从这些文件中读取它。

代码实现

XML/YAML文件的打开和关闭

OpenCV中对应XML/YAML数据结构的类是cv::FileStorage。可以用open()函数来打开文件,也可以在构造函数中创建对象:

FileStorage fs(filename, FileStorage::WRITE);	//构造函数
//或者
//FileStorage fs;
//fs.open(filename, FileStorage::WRITE);

不管哪种方法,第2个参数都得要指定打开的方式:FileStorage::WRITE(写)、FileStorage::READ(读)或FileStorage::APPEND(追加)。文件名中的后缀名决定了写入或读取的格式。甚至可以通过指定后缀名为".xml.gz"来压缩文件。
cv::FileStorage对象离开作用域的时候文件会自动关闭,也可以显式关闭文件:

fs.release();

写入或读取文本和数字

在C++中,使用运算符<<来向cv::FileStorage对象中写入内容。写入任何类型的数据都先要指定变量名称

fs << "iterationNr" << 100;

cv::FileStorage对象中读取基本类型的变量,需要在[]操作符中指定变量名。然后,可以用>>运算符或者类型转换来将数据读取到变量中:

int itNr;
//fs["iterationNr"] >> itNr;	//>>运算符
itNr = static_cast<int>(fs["iterationNr"]);

写入或读取OpenCV数据

Mat对象的写入和读取与文本或数字变量相同,也需要先指定变量名:

Mat R{ mat_<uchar>::eye(3,3) },
	T{ Mat_<double>::zeros(3,1) };
//写入
fs << "R" << R;
fs << "T" << T;
//读取
fs["R"] >> R;
fs["T"] >> T;

写入或读取数组以及map

map和数组的区别是,在map数据中每个元素都有一个特定的名称用来获取这个元素,而在数组中则需要按顺序来获取元素。
写入数组时,需要在开头和结尾分别写入"[“和”]"

fs << "strings" << "[";	//字符串数组
fs << "image1.jpg" << "Awesomeness" << "../data/babboon.jpg";
fs << "]";	//关闭数组

写入map数据时,需要在开头和结尾分别写入"{“和”}";先输入元素名称,再输入元素数据:

fs << "Mapping";
fs << "{" << "One" << 1;
fs << 		"Two" << 2 << "}"';

读取map和数组数据的时候需要用到cv::FileNodecv::FileNodeIterator。使用[]操作符后,cv::FileStorage对象返回一个cv::FileNode类型的节点数据。如果这个节点是个数组,则可以使用cv::FileNodeIterator迭代器来依次读取其中的元素。

FileNode n{ fs["string"] };
if (n.type() != FileNode::SEQ) {
	cerr << "字符串不是一个序列!读取失败" << endl;
	return 1;
}

FileNodeIterator it{ n.begin() }, it_end{ n.end() };	//遍历节点
for (; it != it_end; ++it)
	cout << static_cast<string>(*it) << endl;

如果节点是map数据,则使用<<运算符读取即可:

n = fs["Mapping"];
cout << "Two " << static_cast<int>(n["Two"]) << "; ";
cout << "One " << static_cast<int>(n["One"]) << endl << 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 = static_cast<int>(node["A"]);
		X = static_cast<double>(node["X"]);
		id = static_cast<string>(node["id"]);
	}

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

这两个静态函数是对OpenCV库中persistence.hpp定义的同名静态函数的重载,以实现对自定义的数据类型的读写。
注意:在重载的read函数中,我们还实现了对空对象的读取,即读取为默认的MyData对象
重载了这两个函数之后就可以直接使用>>运算符来写入自定义类的对象,因为在persistence.hpp中已经定义了该运算符的重载。
但是<<运算符还必须自定义重载,以读取自定义类的对象:

static ostream& operator<<(ostream& out, const MyData& m) {
	out << "{ id = " << m.id << ", ";
	out << "X = " << m.X << ", ";
	out << "A = " << m.A << "}";
	return out;
}

接下来,就可以像读写其他基本数据类型的变量一样,读写自定义类的对象了:

MyData m{ 1 };
fs << "MyData" << m;	//写入自定义MyData对象

fs["MyData"] >> m;	//读取自定义MyData对象
cout << "MyData = " << endl << m << endl << endl;

输出结果

完整代码如下:

#include <opencv2/core.hpp>
//#include <opencv2/imgcodecs.hpp>
//#include <opencv2/highgui.hpp>

import <iostream>;
import <string>;

using namespace cv;
using namespace std;

static void help(char** av) {
	cout << endl
		<< av[0] << "展示了OpenCV中序列化函数的用法。"						<< endl
		<< "使用说明:"													<< endl
		<< "输出文件要么是XML(xml)或YAML(yml/yaml)类型的。"				<< endl
		<< "还可以通过指定文件后缀名,比如xml.gz或yaml.gz等来压缩输出文件。" << endl
		<< "在FileStorage模块中可以使用<<和>>运算符来序列化对象。"			<< endl
		<< "例如:- 创建一个类并将其序列化;"								<< endl
		<< "     - 读取或写入矩阵。"										<< 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 = static_cast<int>(node["A"]);
		X = static_cast<double>(node["X"]);
		id = static_cast<string>(node["id"]);
	}

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

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];
	{//写
		Mat R{ Mat_<uchar>::eye(3, 3) },
			T{ Mat_<double>::zeros(3, 1) };
		MyData m{ 1 };

		FileStorage fs(filename, FileStorage::WRITE);
		//也可以:
		//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;
		fs << "T" << T;

		fs << "MyData" << m;

		fs.release();
		cout << "完成写入。" << endl;
	}

	{
		cout << endl << "读取:" << endl;
		FileStorage fs;
		fs.open(filename, FileStorage::READ);

		int itNr;
		//fs["iterationNr"] >> itNr;
		itNr = static_cast<int>(fs["iterationNr"]);
		cout << itNr << endl;
		if (!fs.isOpened()) {
			cerr << filename << "打开失败" << endl;
			help(av);
			return 1;
		}

		FileNode n{ fs["strings"] };
		if (n.type() != FileNode::SEQ) {
			cerr << "字符串不是一个序列!读取失败" << endl;
			cout << n.type() << endl;
			return 1;
		}

		FileNodeIterator it{ n.begin() }, it_end{ n.end() };
		for (; it != it_end; ++it)
			cout << static_cast<string>(*it) << endl;

		n = fs["Mapping"];
		cout << "Two " << static_cast<int>(n["Two"]) << "; ";
		cout << "One " << static_cast<int>(n["One"]) << endl << endl;

		MyData m;
		Mat R, T;

		fs["R"] >> R;
		fs["T"] >> T;
		fs["MyData"] >> m;

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

		cout << "试图读取 NonExisting (会用默认值对数据结构进行初始化)。";
		fs["NoExisting"] >> m;
		cout << endl << "NonExisting = " << endl << m << endl;
	}

	cout << endl
		<< "提示:在文本编辑器中打开" << filename << "以查看序列化数据。" << endl;

	return 0;
}

输出结果如下:
序列化输出结果
我们也可以打开XML文件,查看序列化后的储存形式:
XML序列化文件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值