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

序列化——XML和YAML文件处理

  • 序列化和反序列化
  • 代码实现
    • XML/YAML文件的打开和关闭
    • 写入或读取文本和数字
    • 写入或读取OpenCV数据
    • 写入或读取数组以及map
    • 读取和写入自定义数据类型
  • 输出结果

序列化和反序列化

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

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

本文将介绍在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;elsex.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;elsex.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序列化文件

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/3266784.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

3DGS如何重塑点云配准?港中大开源首例3DGS配准工作!

论文标题&#xff1a; GaussReg: Fast 3D Registration with Gaussian Splatting 论文作者&#xff1a; Jiahao Chang, Yinglin Xu, Yihao Li, Yuantao Chen, and Xiaoguang Han 开源地址&#xff1a;https://jiahao620.github.io/gaussreg 导读&#xff1a; 点云配准是实现…

JavaScript(15)——操作表单元素属性和自定义属性

操作表单元素属性 表单很多情况&#xff0c;也需要修改属性&#xff0c;比如点击眼睛可以看到密码&#xff0c;本质是把表单类型转换为文本框正常的有属性有取值的&#xff0c;跟其他的标签属性没有任何区别 获取&#xff1a;DOM对象.属性名 设置&#xff1a;DOM对象.属性名…

国产超低功耗、±0.5℃精度的数字温度传感芯片 - M601B

温度传感芯片感温原理基于CMOS半导体PN节温度与带隙电压的特性关系&#xff0c;经过小信号放大、模数转换、数字校准补偿后&#xff0c;数字总线输出&#xff0c;具有精度高、一致性好、测温快、功耗低、可编程配置灵活、寿命长等优点。 数字温度传感芯片 - M601B&#xff0c;该…

如何解决 Nginx 与自动驾驶系统的集成问题?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01; 文章目录 如何解决 Nginx 与自动驾驶系统的集成问题&#xff1f; 如何解决 Nginx 与自动驾驶系统的集成问题&#xff1f; 在当今科技飞速发展的时代&#xff0c;自动驾驶…

【基础算法总结】队列 + 宽搜(BFS)

队列 宽搜BFS 1.N 叉树的层序遍历2.二叉树的锯齿形层序遍历3.二叉树最大宽度4.在每个树行中找最大值 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496; 你的支持是对我最大的鼓励&#xff0c;我们一起努力吧!&#x1f603;&#…

配置web服务器练习

4练习要求&#xff1a; 练习一&#xff1a;配置web服务器&#xff0c;当访问网站 www.haha.com 时显示&#xff1a;haha 练习二&#xff1a;配置web服务器&#xff0c;当访问网站 www.xixi.com/secret/ 时显示&#xff1a;this is secret 具体步骤&#xff1a; 1、配置yum…

go程序在windows服务中优雅开启和关闭

本篇主要是讲述一个go程序&#xff0c;如何在windows服务中优雅开启和关闭&#xff0c;废话不多说&#xff0c;开搞&#xff01;&#xff01;&#xff01;   使用方式&#xff1a;go程序 net服务启动 Ⅰ 开篇不利 Windows go进程编译后&#xff0c;为一个.exe文件,直接执行即…

docker挂载部署reids6.2.1

1.拉取镜像 docker pull redis:6.2.12.创建挂在目录&#xff08;根据自己要求修改具体目录&#xff09; mkdir -p /home/admin/redis/{data,conf}3.在/home/admin/redis/conf目录下创建redis.conf文件 cd /home/admin/redis/conf touch redis.conf4.复制下面文本到redis.conf…

实时同步:使用 Canal 和 Kafka 解决 MySQL 与缓存的数据一致性问题

目录 1. 准备工作 2. 将需要缓存的数据存储 Redis 3. 监听 canal 存储在 Kafka Topic 中数据 1. 准备工作 1. 开启并配置MySQL的 BinLog&#xff08;MySQL 8.0 默认开启&#xff09; 修改配置&#xff1a;C:\ProgramData\MySQL\MySQL Server 8.0\my.ini log-bin"HELO…

数据库练习——编写触发器及存储过程

1. 触发器 建立两个表:goods(商品表)、orders(订单表) 在商品表中导入商品记录 mysql> create database mydb16_trigger; Query OK, 1 row affected (0.00 sec)mysql> use mydb16_trigger; Database changed mysql> create table goods(-> gid char(8) primary …

系统架构师(每日一练7)

每日一练 1.关于网络延迟正确的是()。答案与解析 A.在对等网络中&#xff0c;网络的延迟大小与网络中的终端数量无关 B.使用路由器进行数据转发所带来的延迟小于交换机, C.使用internet服务器可最大程度地减小网络延迟 D.服务器延迟的主要影响因素是队列延迟和磁盘10延迟 2.以…

idea中项目目录,文件显示不全问题

问题&#xff1a;idea中项目目录显示不全问题 解决办法1&#xff1a; 删除目录中的.idea文件 用idea重新打开文件就行了 办法2&#xff1a;手动导入为maven项目 1. 2. 3. 4.选择要导入的项目&#xff0c;导入为maven

【网络流】——初识(最大流)

网络流-最大流 基础信息引入一些概念基本性质 最大流定义 Ford–Fulkerson 增广Edmons−Karp算法Dinic 算法参考文献 基础信息 引入 假定现在有一个无限放水的自来水厂和一个无限收水的小区&#xff0c;他们之间有多条水管和一些节点构成。 每一条水管有三个属性&#xff1a…

重拾CSS,前端样式精读-函数(颜色,计算,图像和图形)

前言 本文收录于CSS系列文章中&#xff0c;欢迎阅读指正 在计算机编程中&#xff0c;函数有着重要的作用和意义&#xff0c;它可以实现封装&#xff0c;复用&#xff0c;模块化&#xff0c;参数等功能效果&#xff0c;在如何在CSS中写变量&#xff1f;一文带你了解前端样式利…

AI学习记录 - 图像识别的基础入门

代码实现&#xff0c;图像识别入门其实非常简单&#xff0c;这里使用的是js&#xff0c;其实就是把二维数组进行公式化处理&#xff0c;处理方式如上图&#xff0c;不同的公式代表的不同的意义&#xff0c;这些意义网上其实非常多&#xff0c;这里就不细讲了。 const getSpecif…

【YOLOv8系列】图像分类篇----通过YOLOv8实现图像分类功能

最近需要使用YOLOv8对自己的数据集进行训练,从而实现图像分类的功能,因此记录一下整个过程。 YOLOv8的github地址:https://github.com/ultralytics/ultralytics 参考链接:超详细YOLOv8图像分类全程概述:环境、训练、验证与预测详解 文章目录 一、YOLOv8环境搭建二、准备…

电脑QQ录屏功能怎么用?图文教程,轻松掌握电脑录屏

“想问一下大家知道电脑QQ录屏功能怎么打开吗&#xff1f;一直以来我使用电脑QQ截图非常方便&#xff0c;但不知道原来QQ还有录屏功能。希望知道QQ录屏功能使用方法的朋友教一下我好吗&#xff1f;” 今天&#xff0c;就让我带大家一起探索电脑QQ录屏功能怎么用&#xff1f;看…

怎么注册自己的电子邮件地址

无论是在职场上的工作沟通、日常的在线购物、或是订阅各类新闻资讯&#xff0c;电子邮件都是您不可或缺的数字化工具。本文将手把手引导您完成注册过程&#xff0c;从选择服务商到完成所有必要步骤&#xff0c;帮助您轻松拥有自己的电子邮件账户。 一、选择电子邮件服务商 市…

友盟U-APM——优秀的前端性能监控工具

在数字化转型浪潮的推动下,移动应用已成为企业连接用户、驱动业务增长的核心载体。然而,随着应用复杂度的日益提升,用户对于应用性能稳定性的期待也水涨船高。面对应用崩溃、卡顿、加载缓慢等频发问题,如何确保应用的流畅运行,成为产研团队亟待解决的关键挑战。在此背景下,友盟…

常见的CSS属性(一)——字体、文本、边框、内边距、外边距、背景、行高、圆角、透明度、颜色值

一、字体 二、文本 三、边框 四、外边距 五、内边距 六、背景 七、行高 八、圆角 九、透明度 九、颜色值 元素的继承性是指给父元素设置了某些属性&#xff0c;子元素或后代元素也会有作用。 一、字体 “font-*”是字体相关的属性&#xff0c;具有继承性。代码如下&a…