天天看點

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

(3)FileNodeIterator

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(),X(),id(){}
    explicit MyData(int):A(),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 != )
    {
        //help(av);
        return ;
    }

    string filename = av[];
    {
        //write
        Mat R = Mat_<uchar>::eye(, ), T = Mat_<double>::zeros(,);
        MyData m();

        FileStorage fs(filename, FileStorage::WRITE);

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

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

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

        FileNode n = fs["strings"];
        if(n.type() != FileNode::SEQ)
        {
            cerr << "string is not a sequence! FAIL" << endl;
            return ;
        }

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

程式分析:

1.XML/YAML檔案打開和關閉

前面已經講過,可以使用類FileStorage實作對檔案的打開操作。對于檔案的關閉而言,當FileStorage對象銷毀時檔案會自動關閉,也可以顯式調用FileStorage::release()函數進行。如fs.release()

2.輸入輸出文本和數字

在對XML和YAML檔案進行輸入輸出操作時,和C++中STL用相同的操作符“<<”輸出(寫入檔案)和“>>”輸入(讀取檔案),對于基本的資料類型可以這樣列印數值.

輸出任意一種資料類型之前首先需要指出變量的名稱,可以通過列印變量的名稱來達到這個目的,如下:

fs << "iterationNr" << ;
           

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

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

3.opencv資料結構的輸入輸出

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

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

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

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

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

可以看出在其根檔案夾下生成了mydata1234.xml檔案

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

Windows(win 7)運作如下:

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

在其Debug檔案夾下生成mydata檔案

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

繼續閱讀