天天看點

ORBSLAM2學習(五):DBoW2源碼分析(OrbDatabase部分)0 前言1 工程運作結果2 代碼分析3. 小結

0 前言

       接着上一篇部落格連結繼續做學習記錄,這一次分析demo中OrbDatabase的應用和原理。

1 工程運作結果

       demo中main函數内容如下所示。

int main()
{
  vector<vector<cv::Mat > > features;
  loadFeatures(features);// 提取特征
  testVocCreation(features);// 建立OrbVocabulary對象
  wait();
  testDatabase(features);// 利用前面建立好的OrbVocabulary對象建立OrbDatabase對象并用于圖像的比對
  return 0;
} 
           

       重點是testDatabase()部分,先看一下程式的運作結果(隻截圖了testDatabase()運作效果部分)

ORBSLAM2學習(五):DBoW2源碼分析(OrbDatabase部分)0 前言1 工程運作結果2 代碼分析3. 小結

       結果表明程式建立了一個OrbDatabase對象,然後利用它對輸入圖像在database中檢索最為相似的圖像。demo表明了OrbDatabase的用途,這個用途在VSLAM中回環檢測的作用就是在已經儲存了關鍵幀的database中尋找與輸入圖像相似的圖像作為備選圖像。

2 代碼分析

2.1 準備知識

       先介紹DBoW2中的兩個概念:正向索引(Direct index)和逆向索引(Inverse index)

       正向索引:相對于圖像而言,儲存一幅圖像中每個特征的索引和它在字典中所屬的單詞索引。意義在于當對兩幅圖像做涉及到特征點的比對計算時,可以利用“屬于同一單詞索引的特征更有可能比對“的設定規則,來加速比對。

       逆向索引:相對于字典中的單詞而言,儲存該單詞出現過的圖像的圖像索引,和該單詞在圖像中的權重。意義在于加速在OrbDatabase中根據相似度尋找相似圖像的計算。

       然後介紹這兩種概念在DBoW2中的資料結構構造。對于正向索引,每幅圖像對應一個FeatureVector對象,看一下FeatureVector的定義是class FeatureVector: public std::map<NodeId, std::vector<unsigned int> >,可以知道它就是一個NodeId和特征索引序列一一對應的std::map類。代碼中定義了addFeature()操作用于更新這個map,且通過insert()操作保證了map中的内容是按照關鍵字(NodeId)升序排列的。

       OrbDatabase中會加入多個圖像記錄,是以定義了  typedef std::vector<FeatureVector> DirectFile表示多條記錄的正向索引。

       對于逆向索引,每一個索引對應一個IFRow對象,檢視IFRow的定義為  typedef std::list<IFPair> IFRow,那麼再檢視IFPair的定義如下。

struct IFPair
  {
    EntryId entry_id;// database中的記錄id
    WordValue word_weight;// 該IFPair代表的單詞在記錄id代表圖像中的權重
    IFPair(){}
    IFPair(EntryId eid, WordValue wv): entry_id(eid), word_weight(wv) {}
    inline bool operator==(EntryId eid) const { return entry_id == eid; }
  }; 
           

       它其實就是一個<圖像id,單詞權值>的記錄,由于OrbDatabase中有多條記錄,是以一個單詞對應着多條記錄,使用std::list<IFPair> IFRow表示。同時已經建立了的OrbVocabulary對象中包含多個單詞,是以定義typedef std::vector<IFRow> InvertedFile代表逆向索引,且InvertedFile的元素數量等于OrbVocabulary中單詞的數量。

2.2 代碼分析

       testDatabase中的函數定義如下。

void testDatabase(const vector<vector<cv::Mat > > &features)
{
  cout << "Creating a small database..." << endl;
  // 加載之前建立并儲存的OrbVocabulary檔案
  OrbVocabulary voc("small_voc.yml.gz");
  OrbDatabase db(voc, false, 0); //将OrbVocabulary檔案作為形參建立OrbDatabase對象

  // 向OrbDatabase中增加記錄(代表圖像的特征描述子)
  for(int i = 0; i < NIMAGES; i++)
  {
    db.add(features[i]);
  }
  cout << "... done!" << endl;
  cout << "Database information: " << endl << db << endl;

  // 利用OrbDatabase按照相似度高低查詢圖像
  cout << "Querying the database: " << endl;
  QueryResults ret;
  for(int i = 0; i < NIMAGES; i++)
  {
    db.query(features[i], ret, 4);
    cout << "Searching for Image " << i << ". " << ret << endl;
  }
  cout << endl;

  // 儲存
  cout << "Saving database..." << endl;
  db.save("small_db.yml.gz");
  cout << "... done!" << endl;
  
  // 加載  
  cout << "Retrieving database once again..." << endl;
  OrbDatabase db2("small_db.yml.gz");
  cout << "... done! This is: " << endl << db2 << endl;
}
           

1)建立OrbDatabase對象

template<class TDescriptor, class F>
template<class T>
TemplatedDatabase<TDescriptor, F>::TemplatedDatabase
  (const T &voc, bool use_di, int di_levels)
  : m_voc(NULL), m_use_di(use_di), m_dilevels(di_levels)
{
  setVocabulary(voc);
  clear();
}
           

       檢視代碼發現構造函數中主要做了:将傳入的voc指派給OrbDatabase中的字典成員變量m_voc,然後清空正向索引

m_dfile,為逆向索引m_ifile根據m_voc中單詞的數量配置設定空間。

2)向OrbDatabase對象中增加圖像記錄

for(int i = 0; i < NIMAGES; i++)
  {
    db.add(features[i]);
  }
           

       實際上傳入的是從圖像中提取得到的特征描述子,檢視OrbDatabase的add函數,這裡根據形參,會 先把傳入的特征利用m_voc轉換為BowVector對象,然後調用template<class TDescriptor, class F>

EntryId TemplatedDatabase<TDescriptor, F>::add(const BowVector &v,

  const FeatureVector &fv)函數,該函數的具體内容如下:

template<class TDescriptor, class F>
EntryId TemplatedDatabase<TDescriptor, F>::add(const BowVector &v,
  const FeatureVector &fv)
{
  EntryId entry_id = m_nentries++;// 儲存database中記錄數量
  BowVector::const_iterator vit;
  std::vector<unsigned int>::const_iterator iit;

  if(m_use_di)// demo中這裡不執行
  {
    // update direct file
    if(entry_id == m_dfile.size())
    {
      m_dfile.push_back(fv);
    }
    else
    {
      m_dfile[entry_id] = fv;
    }
  }
  // 實際上就是在構造逆向索引的内容
  for(vit = v.begin(); vit != v.end(); ++vit)
  {
    const WordId& word_id = vit->first;
    const WordValue& word_weight = vit->second;
    
    IFRow& ifrow = m_ifile[word_id];
    ifrow.push_back(IFPair(entry_id, word_weight));
  }  
  return entry_id;
}
           

       發現其實函數中做的就是 利用m_voc中已經生成的單詞和輸入圖像轉換得到的BowVector構造逆向索引。

3)利用OrbDatabase檢索圖像

       之後程式中利用構造好的OrbDatabase檢索database中與輸入圖像相似度高的圖像。

QueryResults ret;
  for(int i = 0; i < NIMAGES; i++)
  {
    db.query(features[i], ret, 4);
    cout << "Searching for Image " << i << ". " << ret << endl;
  }
           

       QueryResults用于儲存檢索結果,具體定義檢視代碼即可。調用OrbDatabase的query()函數進行檢索,檢視代碼發現函數中首先把傳入的特征利用m_voc轉換為BowVector,然後根據m_voc的評分規則調用不同的query分支(後面發現其實query中就是利用m_voc中計算圖像間相似度的規則來做檢索),demo中調用的是queryL1()分支,内容如下。

template<class TDescriptor, class F>
void TemplatedDatabase<TDescriptor, F>::queryL1(const BowVector &vec, 
  QueryResults &ret, int max_results, int max_id) const
{
  BowVector::const_iterator vit;
  typename IFRow::const_iterator rit;
  std::map<EntryId, double> pairs;// 儲存輸入圖像與EntryId對應圖像之間的相似度
  std::map<EntryId, double>::iterator pit;
  for(vit = vec.begin(); vit != vec.end(); ++vit)
  {
    const WordId word_id = vit->first;
    const WordValue& qvalue = vit->second;        
    const IFRow& row = m_ifile[word_id];// 利用之前記錄的單詞word_id在database的各個圖像的權值
    for(rit = row.begin(); rit != row.end(); ++rit)// 計算輸入圖像與database中各個圖像的相似度
    {
      const EntryId entry_id = rit->entry_id;
      const WordValue& dvalue = rit->word_weight;
      if((int)entry_id < max_id || max_id == -1)
      {
        double value = fabs(qvalue - dvalue) - fabs(qvalue) - fabs(dvalue);// 計算規則與OrbVocabulary中相同
        pit = pairs.lower_bound(entry_id);
        if(pit != pairs.end() && !(pairs.key_comp()(entry_id, pit->first)))
        {
          pit->second += value;
        }
        else
        {
          pairs.insert(pit, 
            std::map<EntryId, double>::value_type(entry_id, value));
        }
      }
      
    } // for each inverted row
  } // for each query word
	
  ret.reserve(pairs.size());
  for(pit = pairs.begin(); pit != pairs.end(); ++pit)
  {
    ret.push_back(Result(pit->first, pit->second));
  }
	
  std::sort(ret.begin(), ret.end());// 升序排列,由于前面計算的score帶有負号,是以score值越小相似度越高

  if(max_results > 0 && (int)ret.size() > max_results)
    ret.resize(max_results);
  QueryResults::iterator qit;
  for(qit = ret.begin(); qit != ret.end(); qit++) 
    qit->Score = -qit->Score/2.0;// 真正的score,介于[0,1]之間,值越大代表相似度越高
}
           

4)儲存與加載

       主要利用cv::FileStorage進行讀寫,不再叙述。

3. 小結

       總結一下DBoW2中OrbDatabase和OrbVocabulary各自的用途。

       OrbVocabulary需要一個圖像集合輸入,它會提取圖像特征然後做聚類操作,形成一個很多表征一類特征的“單詞”組成的詞典。有了這個詞典,我們可以用它将兩幅輸入圖像做“圖像-特征-BowVector對象”的轉換,之後計算相似度。

     OrbDatabase構造時需要一個OrbVocabulary對象作為輸入,目的是得到已經聚類生成的“單詞”。之後通過向OrbDatabase中加入圖像記錄的方式,來構造兩個映射關系:正向索引和逆向索引,設計它們的目的都是為了加速比對計算。逆向索引可加速在database中多幅圖像中尋找與輸入圖像最為相似結果的運算過程。正向索引主要用在計算輸入圖像與備選圖像間特征比對關系時加快比對速度。

繼續閱讀