Input and output of XML and YAML files

Posted by hkucsis on Tue, 07 Dec 2021 05:54:48 +0100

1. Objectives

You will find answers to the following questions:

  • How to print and read text entries into a file and OpenCV using YAML or XML files?
  • How to do the same OpenCV data structure?
  • How do you do this for your data structure?
  • Use OpenCV data structures, such as cv::FileStorage, cv::FileNode, or cv::FileNodeIterator.

2. Source code

The following is sample code on how to implement all the contents listed in the target list.

2.1 C + + code

#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") // 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)
{
    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" << "[";                              // 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;
        if (!fs.isOpened())
        {
            cerr << "Failed to open " << filename << endl;
            help(av);
            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;
    return 0;
}

2.2 Python code

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)

3. Result display

3.1 console output results:

3.2 generated yml file

%YAML:1.0
---
iterationNr: 100
strings:
   - "image1.jpg"
   - Awesomeness
   - "../data/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+00
   id: mydata1234

3.3 generated XML file

<?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>

4. Code interpretation

Here we only discuss XML and YAML file input. Your output (and its corresponding input) file may have only one of these extensions and structures. They are two data structures that can be serialized: mapping (like STL map and Python Dictionary) and element sequence (like STL vector). The difference between them is that in a map, each element has a unique name that you can access through. For sequences, you need to traverse them to query for specific items.

  • XML/YAML files open and close. Before writing anything to such a file, you need to open it and close it at the end. The XML/YAML data structure in OpenCV is cv::FileStorage. To specify which file this structure is bound to on your hard disk, you can use its constructor or open() function:
s = cv.FileStorage(filename, cv.FileStorage_WRITE)
# or:
# s = cv.FileStorage()
# s.open(filename, cv.FileStorage_WRITE)

The second parameter is a constant that specifies the type of operation that can be performed on them: WRITE, READ, or APPEND. The extension specified in the file name also determines the output format that will be used. If you specify the extension *. xml.gz *, the output may even be compressed.

When the cv::FileStorage object is destroyed, the file is automatically closed. However, you can call it explicitly by using the release function: s.release()

  • Input and output of text and numbers (int, string, etc.). In c + +, the data structure uses the < < output operator in the STL library. In Python, use cv::FileStorage::write() instead. To output any type of data structure, we first need to specify its name. We just need to push the name into the stream in c + +. In Python, the first argument to the write function is the name. For basic types, you can print the value: s.write('iterationNr', 100).
    Read in is a simple addressing (through [] operator) and cast operation or read through > > operator. In Python, we use getNode() and real() to address:
int itNr;
//fs["iterationNr"] >> itNr;
itNr = (int) fs["iterationNr"];
  • Input / output of OpenCV data structure (Mat, etc.). These types behave exactly like the basic types of c + + and Python:
# write
R = np.eye(3,3)
T = np.zeros((3,1))
s.write('R_MAT', R)
s.write('T_MAT', T)
# read
R = s.getNode('R_MAT').mat()
T = s.getNode('T_MAT').mat()
  • Vector (array) and input / output (vector array map) of association mapping. As I mentioned earlier, we can also output maps and sequences (arrays, vectors). Similarly, we first print the name of the variable, and then we must specify whether the output is a sequence or a map.

For sequences, the "[" character is printed before the first element and the "]" character is printed after the last element (in C + +). In Python, call FileStorage.startWriteStruct(structure_name, struct_type), where struct_ Type cv2.FileNode_MAP or cv2.FileNode_SEQ to start writing structure. Call FileStorage.endWriteStruct() to complete the structure:

s.startWriteStruct('strings', cv.FileNode_SEQ)
for elem in ['image1.jpg', 'Awesomeness', '../data/baboon.jpg']:
    s.write('', elem)
s.endWriteStruct()

For mapping, the operation is the same, but now we use the "{" and "}" separators (in C + +)

s.startWriteStruct('Mapping', cv.FileNode_MAP)
s.write('One', 1)
s.write('Two', 2)
s.endWriteStruct()

To read data from these files, we use the cv::FileNode and cv::FileNodeIterator data structures. The [] operator of the cv::FileStorage class (or getNode() function in Python) returns the cv::FileNode data type. If the nodes are contiguous, you can use cv::FileNodeIterator to traverse all items. In Python, the at() function can be used to address elements in a sequence, and the size() function returns the length of the sequence:

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())

For mapping, you can use the [] operator (at() function in Python) again to access the given item (or also use the > > operator):

n = s.getNode('Mapping')
print ('Two',int(n.getNode('Two').real()),'; ')
print ('One',int(n.getNode('One').real()),'\n')
  • Read and write your own data structures. Suppose you have such a data structure:
class MyData:
    def __init__(self):
        self.A = self.X = 0
        self.name = ''

In c + +, it can be serialized by adding read and write functions inside and outside the class through the OpenCV I/O XML/YAML interface (as in the case of OpenCV data structure). In Python, you can do this by implementing a read-write function in a class. Internal part:

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 = ''

As you can see here, in the read section, we define what happens if the user tries to read a node that does not exist. In this case, we only return the default initialization value, but the more detailed solution is to return an object ID with a - 1 value for the instance.

After adding these functions, use the > > operator for write operations and the < < operator for read operations (or Python defined input / output functions):

m = MyData()
m.write(s, 'MyData')
m.read(s.getNode('MyData'))

Or try to read a non-existent read:

print ('Attempt to read NonExisting (should initialize the data structure',
            'with its default).')
m.read(s.getNode('NonExisting'))
print ('\nNonExisting =','\n',m)

Topics: OpenCV