使用cv::FileStorage读写YAML文件

数据存储

OpenCV提供了一种序列化和反序列化的机制,用于将不同数据类型的数据以YAML或XML格式写入磁盘或者从磁盘读取。这种方法可以用来加载或者保存任何OpenCV的数值变量(包括基本数据变量,像int和float等)到一个文件中。

上述文件读写的基本机制是建立在cv::FileStorage对象基础上的,cv::FileStorage实际代表磁盘中的一个文件,但与普通方式不同的是,它让人们以一种简单而自然的方式访问文件中的数据。

cv::FileStorage的写入

cv::FileStorage对象代表一个YAML或XML格式的文件。你可以给定文件名参数创建一个cv::FileStorage对象或者使用默认构造函数创建一个未打开的cv::FileStorage对象,稍后再使用cv::FileStorage::open()函数打开。flag参数可以为cv::FileStorage::WRITE或cv::FileStorage::APPEND。

FileStorage::open(string fileName, int flag);

一旦成功打开了你想要写入的文件,便可以像对标准输出流输出数据一样使用操作符cv::FileStorage::operator<<()进行写入操作。你可以以这种简单的方式写入,是因为函数内部为你完成了许多复杂的工作。

cv::FileStorage内部数据的存储主要有两种方式,“mapping”(键/值对)和“sequence”(一系列未命名的条目)。在最顶层,你写入的数据都在一个mapping中,你可以放置其他的mapping或sequence,甚至在mapping中继续放入mapping等,只要你愿意。

myFileStorage << "someInteger" << 27;                               //保存一个整数
myFileStorage << "anArray" << cv::Mat::eye(3,3,CV_32F);             //保存一个数组

如果要创建一个序列条目,首先你得为它提供一个string类型的名字,接下来才是序列数据。条目内容可以是数字(整型或浮点型等),一个字符串或别的OpenCV数据类型。

如果你要创建一个新的mapping或sequence,可以使用特殊符号{(用于mapping)或[(用于sequence)。一旦开始创建,就可以为其添加元素,最终以}或]分别结束一个mapping或sequence。

myFileStorage << "theCat" << "{";
myFileStorage << "fur" << "gray" << "eyes" << "green" << "weightLbs" << 16;
myFileStorage << "}";

一旦完成创建一个mapping,需要按顺序输入条目名以及对应的值。如果创建的是sequence,只需要一个接一个地输入元素即可,知道sequence结束。

myFileStorage << “theTeam" << "[";
myFileStorage << "eddie" << "tom" << "scott";
myFileStorage << "]";

一旦完成写工作,便可以使用成员函数cv::FileStorage::release()关闭文件。

示例:使用cv::FileStorage创建一个test.yaml文件

#include <opencv2/opencv.hpp>
#include <time.h>

using namespace cv;

int main() {

    FileStorage fs("test.yaml", FileStorage::WRITE);

    fs << "frameCount" << 5;

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

    Mat cameraMatrix = (
            Mat_<double>(3,3)
                    << 1000, 0, 320, 0, 1000, 240, 0, 0, 1
            );

    Mat distCoeffs = (
            Mat_<double>(5,1)
                    << 0.1, 0.01, -0.001, 0, 0
            );

    fs << "cameraMatrix" << cameraMatrix << "distCoffes" << distCoeffs;

    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();
    return 0;
}

CmakeLists.txt:

cmake_minimum_required(VERSION 3.14)
project(write_yaml_using_FileStorage)

set(CMAKE_CXX_STANDARD 14)

set(OpenCV_DIR "/opt/ros/kinetic/share/OpenCV-3.3.1-dev")
message(${OpenCV_DIR})
find_package( OpenCV 3 REQUIRED )

add_executable(write_yaml_using_FileStorage main.cpp)

target_link_libraries( write_yaml_using_FileStorage ${OpenCV_LIBS} )

程序运行结果如下:

%YAML:1.0
---
frameCount: 5
calibrationDate: "Tue Jul 30 20:48:34 2019\n"
cameraMatrix: !!opencv-matrix
   rows: 3
   cols: 3
   dt: d
   data: [ 1000., 0., 320., 0., 1000., 240., 0., 0., 1. ]
distCoffes: !!opencv-matrix
   rows: 5
   cols: 1
   dt: d
   data: [ 1.0000000000000001e-01, 1.0000000000000000e-02,
       -1.0000000000000000e-03, 0., 0. ]
features:
   - { x:103, y:166, lbp:[ 1, 0, 0, 1, 0, 1, 1, 0 ] }
   - { x:115, y:113, lbp:[ 1, 1, 1, 1, 1, 1, 1, 1 ] }
   - { x:586, y:12, lbp:[ 1, 0, 0, 1, 0, 1, 0, 0 ] }

在这个示例代码中,你会注意到有时候mapping或sequence的所有数据在一行,有时候每个元素一行。这并不是自动格式化造成的,而是由于mapping的起始字符"{:“和”}",sequence的起始字符"[:“和”]"的变化造成的。这个特点只对YAML格式的输出有意义,如果输出文件是XML格式,那么这些细微的差别将会被忽略,mapping与sequence的存储将不会有区别。

使用cv::FileStorage读取文件

cv::FileStorage对象在打开之后,既可以用来写入,也可以用来读取。唯一的区别是flag的值为cv::FileStorage::READ。跟写入时一样,也可以用默认构造函数创建一个未打开的FileStorage对象,之后再通过调用cv::FileStorage::open()函数打开。

FileStorage::open(string fileName, int flag);

一旦文件被打开,接下来便可对其中的数据进行读取,首先需要确定你要访问的数据名
,即FileStorage最顶层的mapping中的关键字,可以通过重载操作符cv::FileStorage::operator获取自己需要的数据。然而,该操作符的返回值并不是你想要的数据,而是一个FileNode对象。

接下里,如果该值是基本数据类型,则可以通过强制类型转换直接读取,如果该数据是一个mapping对象,可以通过重载操作符[ ],并给定所需关键字访问,如果该数据为sequence对象,可以通过重载的[ ]和一个整型参数访问其中的数据,也可以通过cv::FileNodeIterator迭代器进行访问。在完成读取之后,记得调用成员函数cv::FileStorage::release()关闭文件。

cv::FileNode

当成功构建一个cv::FileNode对象后,便可以用它来完成许多工作。如果它直接表示一个实际的对象(或者一个数字或字符串),你就可以直接使用重载操作符cv::FileNode::operator>>(),将它的值加载到对应类型的变量中。

cv::Mat anArray;
myFileStorage["calibrationMatrix"] >> anArray;

cv::FileNode对象同样支持直接赋值给一些基本数据类型。

int aNumber;
myFileStorage["someInteger"] >> aNumber;

与下面这种方式等价:

int aNumber;
aNumber = (int)myFileStorage["someInteger"];

使用迭代器可以完成对FileNode的遍历。给定一个cv::FileNode对象,成员函数cv::FileNode::begin()和cv::FileNode::end()可以分别返回该mapping或sequence的首元素的迭代器和末尾元素的迭代器。该迭代对象可以通过cv::FileNodeIterator::operator*()操作符来进行解引用操作并返回迭代器对应的位置的FileNode对象。这些迭代器支持常见的自增以及自减操作符。

示例:使用cv::FileNode读取test.yaml文件

#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main(){
    cv::FileStorage fs2("test.yaml", cv::FileStorage::READ);

    int frameCount = (int)fs2["frameCount"];

    std::string date;
    fs2["calibrationDate"] >> date;

    cv::Mat cameraMatrix, distCoeffs2;
    fs2["cameraMatrix"] >> cameraMatrix;
    fs2["distCoffes"] >> distCoeffs2;


    cout << "frameCount: "         << frameCount     << endl
          << "calibration date: "   << date          << endl
          << "camera matrix: "      << cameraMatrix  << endl
          << "distoration coeffs: " << distCoeffs2   << endl;

    cv::FileNode features = fs2["features"];
    cv::FileNodeIterator it = features.begin(), it_end = features.end();
    int idx = 0;
    std::vector<uchar> lbpval;
    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();
}

程序运行结果如下:
在这里插入图片描述

参考

《学习OpenCV 3》(中文版)

  • 8
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值