OpenCVのファイルストレージでマップのキーを取得する

マップはキーとバリューで構成されるけども、どんなキーがあるのかを知る術が無い(OpenCV1.X系の場合)。
多分内部的にはキー値のデータはどこかに持っているはずなんで、色々調べた。


リファレンスによるとCvFileNodeの各データ型へのポインタが共用体で宣言されている。


http://opencv.jp/opencv-1.0.0/document/opencvref_cxcore_file.html

/* ファイルストレージの基本要素 - スカラー,またはコレクション */
typedef struct CvFileNode
{
    int tag;
    struct CvTypeInfo* info; /* 型情報(ユーザ定義オブジェクト用,そうでない場合は0) */
    union
    {
        double f; /* 浮動小数点のスカラー値 */
        int i;    /* 整数のスカラー値 */
        CvString str; /* 文字列 */
        CvSeq* seq; /* シーケンス(整列されたファイルノードのコレクション) */
        struct CvMap* map; /* マップ(名前付けされたファイルノードのコレクション) */
    } data;
}
CvFileNode;


struct CvMap が怪しいが、これに騙されてはいけない。ソースをちゃんと参照すると違う型になっている。


opencv/inclue/opencv/extypes.h (Line:1745)

typedef struct CvGenericHash CvFileNodeHash;

/* Basic element of the file storage - scalar or collection: */
typedef struct CvFileNode
{
    int tag;
    struct CvTypeInfo* info; /* type information
            (only for user-defined object, for others it is 0) */
    union
    {
        double f; /* scalar floating-point number */
        int i;    /* scalar integer number */
        CvString str; /* text string */
        CvSeq* seq; /* sequence (ordered collection of file nodes) */
        CvFileNodeHash* map; /* map (collection of named file nodes) */
    } data;
}
CvFileNode;


本当に怪しいのは struct CvMap ではなくて CvFileNodeHash
typedef元である struct CvGenericHash は、ファイルストレージ関連の実装ファイルにあった。


cxcore/cxpersistence.cpp (Line:144)

typedef struct CvGenericHash
{
    CV_SET_FIELDS()
    int tab_size;
    void** table;
}
CvGenericHash;


セットで宣言されていてデータは void** table にある。
データの取り方はセットの上位データのシーケンスでいけるはず。どのデータ型で取り出すかが問題だけど、cxpersistence.cpp を色々見てみると CvFileMapNode* として取り出すみたい。


cxcore/cxpersistence.cpp (Line:154)

typedef struct CvFileMapNode
{
    CvFileNode value;
    const CvStringHashNode* key;
    struct CvFileMapNode* next;
}
CvFileMapNode;

キーとバリューそのままの名前のフィールドがある。


opencv/cxtypes.h (Line:1735)

/* All the keys (names) of elements in the readed file storage
   are stored in the hash to speed up the lookup operations: */
typedef struct CvStringHashNode
{
    unsigned hashval;
    CvString str;
    struct CvStringHashNode* next;
}
CvStringHashNode;


出た。(const CvStringHashNode*)->str がキーっぽい。


まとめると、CvFileNode がマップタイプの場合、(CvGenericHash *CvFileNode.data.map)->table にデータがある。
それを CvFileMapNode* として取り出して key->str を参照すればよさそう。
ただし、CvGenericHash と CvFileMapNode は隠された構造体なので、自前で明示的に宣言してあげる必要がある。


それらをふまえて マップのキーと値を表示するプログラム

#include <iostream>
#include <string>
#include <vector>

#include <opencv/cv.h>
#include <opencv/highgui.h>

using namespace std;


typedef struct CvGenericHash
{
    CV_SET_FIELDS()
    int tab_size;
    void** table;
} CvGenericHash;

typedef struct CvFileMapNode
{
    CvFileNode value;
    const CvStringHashNode* key;
    struct CvFileMapNode* next;
} CvFileMapNode;


vector<string> getKeys(const CvFileNode *node)
{
    vector<string> result;
    
    if (CV_NODE_IS_MAP(node->tag)) {
        CvGenericHash *map = (CvGenericHash*)node->data.map;
        int n = map->active_count;
        result.resize(n);
        for (int i = 0; i < n; i++) {
            CvFileMapNode *p = CV_GET_SEQ_ELEM(CvFileMapNode, map, i);
            result.at(i) = string(p->key->str.ptr);
        }
    }
    
    return result;
}


int main(int argc, const char *argv[])
{
    const char *filename = "sample.xml";
        
    CvFileStorage *fs = cvOpenFileStorage(filename, NULL, CV_STORAGE_READ);
    CvFileNode *node  = cvGetRootFileNode(fs);
    
    vector<string> keys = getKeys(node);
    
    typedef vector<string>::iterator I;
    for (I i = keys.begin(); i != keys.end(); i++) {
        const char *key   = i->c_str();
        const char *value = cvReadStringByName(fs, node, key);
        cout << key << " = " << value << endl;
    }
    
    cvReleaseFileStorage(&fs);
    
    return 0;
}


OpenCV2.X系では既存のメソッドだけでもっと簡単に取得できます。
じゃあ、そっち使えよという...

#include <iostream>
#include <string>
#include <vector>

#include <opencv/cv.h>
#include <opencv/highgui.h>

using namespace std;


vector<string> getKeys(const cv::FileNode *node)
{
    vector<string> result;
    
    if (node->isMap()) {
        result.reserve(node->size());
        cv::FileNodeIterator iter = node->begin();
        while (iter != node->end()) {
            result.push_back((*iter).name());
            iter++;
        }
    }
    
    return result;
}


int main(int argc, const char *argv[])
{
    const char *filename = "sample.xml";
    
    cv::FileStorage fs(filename, cv::FileStorage::READ);
    cv::FileNode node = fs.root();
    
    vector<string> keys = getKeys(&node);
    
    typedef vector<string>::iterator I;
    for (I i = keys.begin(); i != keys.end(); i++) {
        const char *key   = i->c_str();
        const char *value = string(node[key]).c_str();
        cout << key << " = " << value << endl;
    }
    
    return 0;
}
<?xml version="1.0"?>  
<opencv_storage>  
  <C>".c"</C>
  <Perl>".pl"</Perl>
  <Ruby>".rb"</Ruby>
  <Python>".py"</Python>
  <Objective-C>".m"</Objective-C>
</opencv_storage>