.. _fileInputOutputXMLYAML: 输入输出XML和YAML文件 ********************************************** 目的 ==== 你将得到以下几个问题的答案: .. container:: enumeratevisibleitemswithsquare + 如何将文本写入YAML或XML文件,及如何从从OpenCV中读取YAML或XML文件中的文本 + 如何利用YAML或XML文件存取OpenCV数据结构 + 如何利用YAML或XML文件存取自定义数据结构? + OpenCV中相关数据结构的使用方法,如 :xmlymlpers:`FileStorage `, :xmlymlpers:`FileNode ` 或 :xmlymlpers:`FileNodeIterator `. 代码 =========== 你可以 :download:`点击此处下载 <../../../../samples/cpp/tutorial_code/core/file_input_output/file_input_output.cpp>` 或直接从OpenCV代码库中找到源文件。 :file:`samples/cpp/tutorial_code/core/file_input_output/file_input_output.cpp` 。 以下用简单的示例代码演示如何逐一实现所有目的. .. literalinclude:: ../../../../samples/cpp/tutorial_code/core/file_input_output/file_input_output.cpp :language: cpp :linenos: :tab-width: 4 :lines: 1-7, 21-154 代码分析 =========== 这里我们仅讨论XML和YAML文件输入。你的输出(和相应的输入)文件可能仅具有其中一个扩展名以及对应的文件结构。XML和YAML的串行化分别采用两种不同的数据结构: *mappings* (就像STL map) 和 *element sequence* (比如 STL vector>。二者之间的区别在map中每个元素都有一个唯一的标识名供用户访问;而在sequences中你必须遍历所有的元素才能找到指定元素。 1. **XML\\YAML 文件的打开和关闭。** 在你写入内容到此类文件中前,你必须先打开它,并在结束时关闭它。在OpenCV中标识XML和YAML的数据结构是 :xmlymlpers:`FileStorage ` 。要将此结构和硬盘上的文件绑定时,可使用其构造函数或者 *open()* 函数: .. code-block:: cpp string filename = "I.xml"; FileStorage fs(filename, FileStorage::WRITE); \\... fs.open(filename, FileStorage::READ); 无论以哪种方式绑定,函数中的第二个参数都以常量形式指定你要对文件进行操作的类型,包括:WRITE, READ 或 APPEND。文件扩展名决定了你将采用的输出格式。如果你指定扩展名如 *.xml.gz* ,输出甚至可以是压缩文件。 当 :xmlymlpers:`FileStorage ` 对象被销毁时,文件将自动关闭。当然你也可以显示调用 *release* 函数: .. code-block:: cpp fs.release(); // 显示关闭 #. **输入\\输出文本和数字。** 数据结构使用与STL相同的 << 输出操作符。输出任何类型的数据结构时,首先都必须指定其标识符,这通过简单级联输出标识符即可实现。基本类型数据输出必须遵循此规则: .. code-block:: cpp fs << "iterationNr" << 100; 读入则通过简单的寻址(通过 [] 操作符)操作和强制转换或 >> 操作符实现: .. code-block:: cpp int itNr; fs["iterationNr"] >> itNr; itNr = (int) fs["iterationNr"]; #. **输入\\输出OpenCV数据结构。** 其实和对基本类型的操作方法是相同的: .. code-block:: cpp Mat R = Mat_::eye (3, 3), T = Mat_::zeros(3, 1); fs << "R" << R; // 写 cv::Mat fs << "T" << T; fs["R"] >> R; // 读 cv::Mat fs["T"] >> T; #. **输入\\输出 vectors(数组)和相应的maps.** 之前提到我们也可以输出maps和序列(数组, vector)。同样,首先输出变量的标识符,接下来必须指定输出的是序列还是map。 对于序列,在第一个元素前输出”[“字符,并在最后一个元素后输出”]“字符: .. code-block:: cpp fs << "strings" << "["; // 文本 - 字符串序列 fs << "image1.jpg" << "Awesomeness" << "baboon.jpg"; fs << "]"; // 序列结束 对于maps使用相同的方法,但采用”{“和”}“作为分隔符。 .. code-block:: cpp fs << "Mapping"; // 文本 - mapping fs << "{" << "One" << 1; fs << "Two" << 2 << "}"; 对于数据读取,可使用 :xmlymlpers:`FileNode ` 和 :xmlymlpers:`FileNodeIterator ` 数据结构。 :xmlymlpers:`FileStorage ` 的[] 操作符将返回一个 :xmlymlpers:`FileNode ` 数据类型。如果这个节点是序列化的,我们可以使用 :xmlymlpers:`FileNodeIterator ` 来迭代遍历所有元素。 .. code-block:: cpp FileNode n = fs["strings"]; // 读取字符串序列 - 获取节点 if (n.type() != FileNode::SEQ) { cerr << "strings is not a sequence! FAIL" << endl; return 1; } FileNodeIterator it = n.begin(), it_end = n.end(); // 遍历节点 for (; it != it_end; ++it) cout << (string)*it << endl; 对于maps类型,可以用 [] 操作符访问指定的元素(或者 >> 操作符): .. code-block:: cpp n = fs["Mapping"]; // 从序列中读取map cout << "Two " << (int)(n["Two"]) << "; "; cout << "One " << (int)(n["One"]) << endl << endl; #. **读写自定义数据类型。** 假设你定义了如下数据类型: .. code-block:: cpp class MyData { public: MyData() : A(0), X(0), id() {} public: // 数据成员 int A; double X; string id; }; 添加内部和外部的读写函数,就可以使用OpenCV I/O XML/YAML接口对其进行序列化(就像对OpenCV数据结构进行序列化一样)。内部函数定义如下: .. code-block:: cpp 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"]; } 接下来在类的外部定义以下函数: .. code-block:: cpp void write(FileStorage& fs, const std::string&, const MyData& x) { x.write(fs); } void read(const FileNode& node, MyData& x, const MyData& default_value = MyData()) { if(node.empty()) x = default_value; else x.read(node); } 这儿可以看到,如果读取的节点不存在,我们返回默认值。更复杂一些的解决方案是返回一个对象ID为负值的实例。 一旦添加了这四个函数,就可以用 >> 操作符和 << 操作符分别进行读,写操作: .. code-block:: cpp MyData m(1); fs << "MyData" << m; // 写自定义数据结构 fs["MyData"] >> m; // 读自定义数据结构 或试着读取不存在的值: .. code-block:: cpp fs["NonExisting"] >> m; // 请注意不是 fs << "NonExisting" << m cout << endl << "NonExisting = " << endl << m << endl; 结果 ====== 好的,大多情况下我们只输出定义过的成员。在控制台程序的屏幕上,你将看到: .. code-block:: bash 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文件中看到的结果将更加有趣: .. code-block:: xml 100 image1.jpg Awesomeness baboon.jpg 1 2 3 3
u
1 0 0 0 1 0 0 0 1
3 1
d
0. 0. 0.
97 3.1415926535897931e+000 mydata1234
或YAML文件: .. code-block:: 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 here `_ . .. raw:: html
翻译者 =============== 刘瑞华