1. 程式人生 > >opencv學習(十七)之XML和YAML檔案讀寫操作

opencv學習(十七)之XML和YAML檔案讀寫操作

可能大部分人到現在接觸的XML和YAML檔案很少,等以後訓練人臉模型進行人臉識別的時候用的就多了。現在先了解一下這兩種檔案型別。
XML:Extensible Markup Language,可擴充套件標記語言,標準通用語言的子集,是一種用於標記電子檔案使其具有結構性的標記語言。它可以用來標記資料、定義資料型別,是一種允許使用者對自己的標記語言進行定義的源語言。XML的簡單使其易於在任何應用程式中讀寫資料,這使XML很快成為資料交換的唯一公共語言。可擴充套件標記語言檔案的內容包括幾乎所有的萬國碼Unicode字元
YAML : Yet Another Markup Language,從其字面意識可以知道YAML是另一種標記語言,但是為了強調這種語言以資料作為中心,而不是以置標語言為重點,它是一種直觀的能夠被電腦識別的資料序列化格式,是一個可讀性高並且容易被人類於都,容易和指令碼語言互動,用來表達資料序列的程式語言。它是XML的資料描述語言,語法比XML簡單。

(1)FileStorage
opencv提供了對XML和YAML檔案進行讀寫操作的FileStorage類,其定義有兩種形式,如下:

C++:  FileStorage::FileStorage()
C++:  FileStorage::FileStorage(const string& source, int flags, const string& encoding=string())

根據其兩種不同的構造形式,可以有兩種FileStorage使用方法
. 第一種:其建構函式不帶引數,可以定義一個FileStorage物件,通過FileStorage的成員函式對XML和YAML檔案進行讀寫操作如

FileStorage fs;
fs.open("123.xml",FileStorage::WRITE);

對於open函式其建構函式如下:

C++: bool FileStorage::open(const string& filename, int flags, const string& encoding=string());

其引數含義與第二種建構函式相同,見下面說明。

. 第二種:其建構函式帶引數,對引數進行解釋如下
.const string& source: 讀入檔案的名字或字串,檔案的副檔名.xml或.yml/.yaml決定了檔案使XML型別還是YAML型別。如果檔案是一個壓縮檔案,可以在副檔名字尾條件.gz進行訪問,如:myHugeMatrix.xml.gz。如果FileStorage::WRITE和FileStorage::MEMORY都指定了,sourec僅能輸出指定型別檔案(.xml或.yml等)
. int flags:

操作型別識別符號,FileStorage類的操作識別符號有如下型別:
。FileStorage::READ: 開啟檔案進行讀的操作
。FileStorage::WRITE: 開啟檔案進行寫的操作
。FileStorage::APPEND: 開啟檔案進行補充操作(Open the file for appending)
。FileStorage::MEMORY:從輸入的檔案(source)讀取資料或把資料寫入到內部緩衝器(通過FileStorage::release進行記憶體釋放)
. const string& encoding=string(): 檔案結束符。當前還不支援UTF-16型別的XML,必須用8-bit的結束符來代替。

可以通過FileStorage::isOpened函式來判斷檔案是否正確開啟,如果正確開啟則函式返回true,否則返回false。對檔案進行操作應該養成對檔案是否操作成功進行判斷的習慣。

(2)FileNode
FileNode是檔案儲存節點類,對於進行讀操作的檔案節點用於儲存每個檔案元素。當讀取XML或YMAL檔案,節點是第一個被解析並作為一個節點結合儲存到儲存器中。每個節點都可以作為一個包含一個數字或一個字元或其他節點的“葉子”。每個節點都有一個名字並可以通過節點的名字對節點進行訪問,這些節點就組成了一個集合,就算節點沒有名字也可以通過元素的索引對節點結合進行排序。檔案節點型別可以通過FileNode::type()方法進行指定。
值得注意的是節點只用用來對檔案的讀取提供引導,而檔案進行寫操作後將沒有資料儲存在記憶體中。FileNode有三種構造形式:

C++: FileNode::FileNode();
C++: FileNode::FileNode(const CvFileStorage* fs, const CvFileNode* node);
C++: FileNode::FileNode(const FileNode& node);


FileNodeIterator用於迭代訪問序列(sequences)和對映表(mappings).是一種典型的STL符號,通過node.begin()和node.end()來標識序列的開始和結束位置。其也有三種構造形式,如下:

C++: FileNodeIterator::FileNodeIterator()
C++: FileNodeIterator::FileNodeIterator(const CvFileStorage* fs, const CvFileNode* node, size_t ofs=0)
C++: FileNodeIterator::FileNodeIterator(const FileNodeIterator& it)

示例程式碼:

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

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
    {

        fs << "{ " << "A" << A << "X" << X << "id" << id << "}";
    }
    void read(const FileNode& node)
    {
        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 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);

        fs << "iterationNr"<<100;
        fs << "strings" << "[";
        fs << "image1.jpg" << "Awesomeness" << "baboon.jpg";
        fs << "]";

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

        fs << "R" << R;
        fs << "T" << T;

        fs << "MyData" << m;

        fs.release();
        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;
            return 1;
        }

        FileNode n = fs["strings"];
        if(n.type() != FileNode::SEQ)
        {
            cerr << "string 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;
        }
        n = fs["Mapping"];
        cout << "Two " << (int)(n["Two"]) << "; ";
        cout << "One " << (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;

        //Show default behavior for non existing nodes
        cout << "Attempt to read NonExisting(should initialize the data structure with its defaule).";
        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;
}

程式分析:
1.XML/YAML檔案開啟和關閉
前面已經講過,可以使用類FileStorage實現對檔案的開啟操作。對於檔案的關閉而言,當FileStorage物件銷燬時檔案會自動關閉,也可以顯式呼叫FileStorage::release()函式進行。如fs.release()

2.輸入輸出文字和數字
在對XML和YAML檔案進行輸入輸出操作時,和C++中STL用相同的操作符“<<”輸出(寫入檔案)和“>>”輸入(讀取檔案),對於基本的資料型別可以這樣列印數值.
輸出任意一種資料型別之前首先需要指出變數的名稱,可以通過列印變數的名稱來達到這個目的,如下:

fs << "iterationNr" << 100;

讀取檔案使用基本操作符”>>”如:

int itNr;
fs["iterationNr"] >> itNr;
itNr = (int)fs["iterationNr"];

3.opencv資料結構的輸入輸出
opencv資料結構的輸入輸出和基本的C++輸入輸出形式相同

Mat R = Mat_<uchar>::eye(3, 3), T = Mat_<double>::zeros(3, 1);

fs << "R" << R;     //R資料寫入檔案
fs << "T" << T;     //T資料寫入檔案

fs["R"] >> R;           //從檔案中讀取資料R
fs["T"] >> T;           //從檔案中讀取資料T

4.vector(arrays)和maps輸入輸出
就向前面程式中寫的,我們同樣可以對array,vector和maps進行輸出操作,首先打印出變數的名字,之後支出要輸入變數的型別是序列(vector, array)還是maps.

如果資料型別是vector和array類,要在元素前面加上”[“在元素後面加上”]”符號,如下:

fs << "string" << "[";
fs << "image1.jpg" << "Awesomeness" << "baboon.jpg";
fs << "]";

對於資料結構是maps型別,需要在元素前面和最後加上”{“和”}”符號,如下:

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

從檔案中讀取這些資料的時候我們需要用到FileNode和FileNodeIterator資料結構。FileStorage類操作符”[]”返回的是一個FileNode資料型別,對於連續的節點,可以通過FileNodeIterator進行迭代遍歷,其函式功能和C++中iterator一樣。

FileNode n = fs["string"];
if(n.type() != FileNode::SEQ)
{
    cerr << "string 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型別的資料結構可以再次使用”[]”操作符來訪問給定的變數物件如下:

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

5.讀取和吸入自己定義的資料結構
假定已經定義瞭如下資料結構:

class MyData
{
public:
    MyData():A(0),X(0),id(){}
public:
int A;
double X;
string id;
};

可以通過opencv提供的對XML和YAML檔案的介面,在類內或類外各新增新增一個讀取函式和一個寫入函式實現對檔案的連續操作如下:

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"];
}

然後需要在類外新增如下函式定義

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

從這裡就可以看出,如果我們定義了一個讀取部分試圖讀取一個不存在的節點,這種情況下就會返回一個預設初始化的值,更詳細的解決方案是返回-1作為專案程式碼。
一旦添加了這四個函式可以使用”>>”和”<<”進行寫入和讀取的操作。

MyData m(1);
fs << "MyData" <<m;     //自定義的資料結構
fs["MyData"] >> m;      //讀取自己的資料結構

或嘗試讀取一個不存在檔案

fs["NonExisting"] >> m;
cout << endl << "NonExisting = " << endl << m << endl;

程式執行結果:

Linux環境執行方式如下,首先要先建立一個CMakeLists.txt檔案,其語法格式請參考《opencv之在Linux下編譯opencv程式的兩種方式g++、cmake》對於程式中出現的argc及argv[]執行方式,請參考《程式命令列argc\argv》
下面分別給出在Linux和Windows下命令執行的截圖以供參考
Linux環境(Ubuntu)
這裡寫圖片描述
可以看出在其根資料夾下生成了mydata1234.xml檔案
這裡寫圖片描述

Windows(win 7)執行如下:
這裡寫圖片描述
在其Debug資料夾下生成mydata檔案
這裡寫圖片描述