OpenCV计算机视觉开发入门与实践<十二>:XML 和 YAML 文件读写

1.数组的操作

1.1 数组(矩阵)操作函数表

  核心模块 Core 专门提供了一些全局函数用于对数组(矩阵)进行操作。常用函数如表所示。

1.2 寻找数组中最小值和最大值的位置(minMaxLoc)

  全局函数 minMaxLoc 用于寻找数组中的最大值和最小值及其位置,该极值检测遍历整个矩阵,当掩码部位空时,遍历指定的特殊区域。此函数不适用于多通道阵列。如果需要在所有通道中查找最小元素或最大元素,首先使用 Mat::reshape 将数组重新解释为单个通道,或者可以使用extractImageCOI、mixChannels 或 split 提取特定通道。函数 minMaxLoc 声明如下:

cpp 复制代码
void cv::minMaxLoc (InputArray src, double * minVal, double * maxVal = 0, Point 
*minLoc = 0, Point * maxLoc = 0, InputArray mask = noArray() );

  其中参数 src 表示输入的单通道数组(矩阵);参数 minVal 指向返回的最小值的指针,如果传 NULL,就表示不要求最小值;maxVal 指向返回的最大值的指针,如果传 NULL,就表示不要求最大值;minLoc 指向返回最小值的位置(2d 情况下),如果传 NULL,就表示不要求;maxLoc指向返回最小值的位置(2d 情况下),如果传 NULL,就表示不要求;mask 用于指定下级矩阵的操作掩码。

cpp 复制代码
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#pragma comment(lib, "opencv_world4140d.lib") //引用引入库
using namespace std;
using namespace cv;

int main()
{
	Mat image, image_3c;
	image.create(Size(256, 256), CV_8UC1);
	image_3c.create(Size(256, 256), CV_8UC3); //3 通道的图像
	image.setTo(0);
	image_3c.setTo(0);
	image.at<uchar>(10, 200) = 255; //第 10 行、第 200 列处赋值 255
	image_3c.at<uchar>(10, 200) = 255;//第 10 行、第 300 列处赋值
	double maxVal = 0; //最大值一定要赋初值,否则运行时会报错
	Point maxLoc;
	minMaxLoc(image, NULL, &maxVal, NULL, &maxLoc);
	cout << "单通道图像最大值: " << maxVal << endl;
	double min_3c, max_3c;
	minMaxLoc(image_3c, &min_3c, &max_3c, NULL, NULL);
	cout << "3 通道图像最大值: " << max_3c << endl;
	imshow("image", image);
	imshow("image_3c", image_3c);
	waitKey(0);
	return 0;
}

  在代码中,我们利用全局函数 minMaxLoc 查找了单通道和多通道矩阵中的像素最大值。注意,多通道在使用 minMaxLoc 函数时不能给出其最大值和最小值坐标,因为每个像素点其实有多个坐标,所以无法给出。

cpp 复制代码
单通道图像最大值: 255
3 通道图像最大值: 255

2.XML 和 YAML 文件读写

2.1 YAML 文件简介

  编程免不了要写配置文件,写配置也是一门学问。YAML 是专门用来写配置文件的语言,非常简洁和强大,远比 JSON 格式方便。YAML 语言的设计目标是方便人类读写。它实质上是一种通用的数据串行化格式。YAML 的基本语法规则如下:

(1)大小写敏感。

(2)使用缩进表示层级关系。

(3)缩进时不允许使用 Tab 键,只允许使用空格。

(4)缩进的空格数目不重要,只要相同层级的元素左侧对齐即可。

 YAML 支持的数据结构有三种:

(1)对象:键值对的集合,又称为映射(Mapping)、哈希(Hash)、字典(Dictionary)。

(2)数组:一组按次序排列的值,又称为序列(Sequence)、列表(List)。

(3)纯量(Scalar):单个的、不可再分的值。

  由于实现简单,解析成本很低,YAML 特别适合在脚本语言中使用,比如 Ruby、Java、Perl、Python、PHP、JavaScript、Go。除了 Java 和 Go 外,其他都是脚本语言。写 YAML 要比写 XML快得多(无须关注标签或引号),并且比 INI 文档功能更强。比如以下就是一个 YAML 文件:

c 复制代码
%YAML:1.0
---
frameCount: 5
calibrationDate: "Wed Aug 1 11:13:44 2018\n"
cameraMatrix: !!opencv-matrix
 rows: 3
 cols: 3
 dt: d
 data: [ 1000., 0., 320., 0., 1000., 240., 0., 0., 1. ]
disCoeffs: !!opencv-matrix
 rows: 5
 cols: 1
 dt: d
 data: [ 1.0000000000000001e-01, 1.0000000000000000e-02,
 -1.0000000000000000e-03, 0., 0. ]
features:
 - { x:41, y:227, lbp:[ 0, 1, 1, 1, 1, 1, 0, 1 ] }
 - { x:260, y:449, lbp:[ 0, 0, 1, 1, 0, 1, 1, 0 ] }
 - { x:598, y:78, lbp:[ 0, 1, 0, 0, 1, 0, 1, 0 ] }
2.2 写入和读取 YAML\XML 文件的基本步骤

(1)创建 cv::FileStorage 对象,并打开文件。

(2)使用<<写入数据,或者使用>>读取数据。

(3)使用 cv::FileStorage::release()关闭文件。

2.3 XML、YAML 文件的打开

  有两种方法来实例化 FileStorage 对象。

方法 1:

cpp 复制代码
FileStorage fs(fileName,FileStorage::WRITE); //实例化对象 fs
//fs 设定为写入操作
//读取操作时,实例化对象方式写为 FileStorage::READ

方法 2:

cpp 复制代码
FileStorage fs; //实例化对象 fs
fs.open(fileName,FileStorage::WRITE);
2.4 文本和数字的输入和输出

  写入文件使用<<运算符,例如:

cpp 复制代码
fs << "iterationNr" << 100;

  读取文件使用>>运算符,例如:

cpp 复制代码
int itNr;
fs["iterationNr"] >> itNr;
itNr = (int) fs["iterationNr"];
2.5 OpenCV 数据结构的输入和输出

  和基本的 C++形式相同,例如:

cpp 复制代码
Mat R = Mat_<uchar >::eye (3, 3),
T = Mat_<double>::zeros(3, 1);
fs << "R" << R; // Write cv::Mat
fs << "T" << T;
fs["R"] >> R; // Read cv::Mat
fs["T"] >> T;
2.6 vector(arrays)和 maps 的输入和输出

  vector 要注意在第一个元素前加上"",在最后一个元素前加上"",例如:

cpp 复制代码
fs << "strings" << "["; // text - string sequence
fs << "image1.jpg" << "Awesomeness" << "11.jpg";
fs << "]"; // close sequence

对于 map 结构的操作使用的符号是"{"和"}",例如:

cpp 复制代码
fs << "Mapping"; // text - mapping
fs << "{" << "One" << 1;
fs << "Two" << 2 << "}";

  读取这些结构的时候,会用到 FileNode 和 FileNodeIterator 数据结构。FileStorage 类的\[\]操作符会返回 FileNode 数据类型,对于一连串的 node,可以使用 FileNodeIterator 结构,例如:

cpp 复制代码
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;
2.7 文件关闭

  文件关闭操作会在 FileStorage 结构销毁时自动进行,也可调用 fs.release()函数实现。

【例 】生成 YAML 文件并读取

cpp 复制代码
#include <iostream>
#include <ctime>
#include <iomanip>
#include <sstream>
#include <opencv2/opencv.hpp>
#include <time.h>
using namespace std;
using namespace cv;
#pragma comment(lib, "opencv_world4140d.lib") //引用引入库

int writeYaml()
{
	//初始化
	FileStorage fs("test.yaml", FileStorage::WRITE);
	//开始文件写入
	fs << "frameCount" << 5;
	time_t rawtime;

	//time(&rawtime);
	//fs << "calibrationDate" << asctime(localtime(&rawtime));

	struct tm timeinfo;
	char buffer[32];
	time(&rawtime);
	localtime_s(&timeinfo, &rawtime);  // 安全版本
	asctime_s(buffer, sizeof(buffer), &timeinfo);  // 安全版本

	// 移除 asctime 返回的换行符
	std::string timeStr(buffer);
	if (!timeStr.empty() && timeStr.back() == '\n') 
	{
		timeStr.pop_back();
	}
	fs << "calibrationDate" << timeStr;

	//Mat cameraMatrix = (Mat_<double>(3, 3) << 1000, 0, 320, 0, 1000, 240, 0,0, 1);
	int a[3][3] = { 1000, 0, 320, 0, 1000, 240, 0, 0, 1 };
	Mat cameraMatrix(3, 3, CV_32S, a);
	
	//Mat_<double> disCoeffs = (Mat_<double>(5, 1) << 0.1, 0.01, -0.001, 0, 0);
	double b[5][1] = { 0.1, 0.01, -0.001, 0.0, 0.0 };
	Mat disCoeffs(5, 1, CV_64F, b);
	
	fs << "cameraMatrix" << cameraMatrix << "disCoeffs" << disCoeffs;
	fs << "features" << "[";
	for (int i = 0; i < 3; i++)
	{
		int x = rand() % 640;
		int y = rand() % 480;
		uchar lbp = rand() % 256;
		fs << "{:" << "x" << x << "y" << y << "lbp" << "[:";
		for (int j = 0; j < 8; j++)
		{
			fs << ((lbp >> j) & 1);
		}
		fs << "]" << "}";
	}
	fs << "]";
	fs.release();
	printf("文件读写完毕,请在工程目录下查看生成的文件。\n");
	return 0;
}

int readYaml()
{
	// 改变 console 字体颜色
	system("color 6F");
	//初始化
	FileStorage fs2("test.yaml", FileStorage::READ);
	// 第一种方法:对 FileNote 操作
	int frameCount = (int)fs2["frameCount"];
	std::string date;
	//第二种方法:使用 FileNote 运算符
	fs2["calibrationDate"] >> date;
	Mat cameraMatrix2, disCoeffs2;
	fs2["cameraMatrix"] >> cameraMatrix2;
	fs2["disCoeffs"] >> disCoeffs2;
	cout << "framCount: " << frameCount << endl
		<< "calibration date: " << date << endl
		<< "camera matrix: " << cameraMatrix2 << endl
		<< "distortion coeffs: " << disCoeffs2 << endl;
	FileNode features = fs2["features"];
	FileNodeIterator it = features.begin(), it_end = features.end();
	int idx = 0;
	std::vector<uchar> lbpval;
	//使用 FileNoteIterator 遍历序列
	for (; it != it_end; ++it, ++idx)
	{
		cout << "feature #" << idx << ": ";
		cout << "x=" << (int)(*it)["x"] << ", y =" << (int)(*it)["y"] << ", lbp: (";
		(*it)["lbp"] >> lbpval;
		for (int i = 0; i < (int)lbpval.size(); i++)
		{
			cout << " " << (int)lbpval[i];
		}
		cout << ")" << endl;
	}
	fs2.release();
	printf("\n 文件读取完毕,请输入任意键结束程序~");
	getchar();
	return 0;
}

int main()
{
	writeYaml();
	puts("YAML 文件读取内容如下:");
	readYaml();
}

运行如下:

  在代码中,我们新建了一个 YAML 文件并写入数据,然后读取该文件的内容,并在终端显示出来。

【例 】同时支持 XML 和 YAML 的读写

cpp 复制代码
#include <iostream>
#include <opencv2/core.hpp>
#include <iostream>
#include <string>
using namespace cv;
using namespace std;
#pragma comment(lib, "opencv_world4140d.lib") //引用引入库

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 = "test.xml";
	{ 
		//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 << endl;
		if (!fs.isOpened())
		{
			cerr << "Failed to open " << filename << endl;
			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;
}

运行如下:

  代码根据 filename 的文件名来创建不同类型的文件,如果是 YAML,比如 test.yaml,就会新建并产生 XML 数据到文件中。文件写入完毕后,开始读并在终端显示。

相关推荐
ryanuo71 小时前
Shadcn/ui × Qt 6/QML:一次从 Web UI 到桌面 UI 的组件化实践
c++·qt·ui·shadcn
余额瞒着我当琳2 小时前
C++--vector第二讲:手写 C++ STL:vector 源码剖析与迭代器失效分析
android·java·c++
程序员-珍2 小时前
安卓开发:同名 styles.xml 在不同分支/文件夹的合并规则
android·xml·前端
郝学胜-神的一滴2 小时前
[简化版 GAMES 104] 现代游戏引擎 06:从Tick时序到邮局模型,拆解确定性世界的底层密码
开发语言·c++·游戏引擎·图形渲染·软件开发·opengl
不会代码的小猴2 小时前
6. Qt网络编程
开发语言·c++·笔记·qt·算法
沐风老师2 小时前
从零开始学3dMax插件开发!
c++·3dmax插件·3dmax·maxscript
TAN-90°-2 小时前
Deep Learning for Computer Vision——Training CNNs and CNN Architectures
人工智能·深度学习·神经网络·算法·机器学习·计算机视觉·cnn
小玮看世界11 小时前
从“画图”到“Mermaid”:AI语义理解与架构绘制的演进之路
人工智能·深度学习·计算机视觉
wuyk55513 小时前
98.C语言易混难点:字符数组与字符串指针的底层差异
c语言·开发语言·c++·stm32·嵌入式硬件·算法