當前位置:成語大全網 - 書法字典 - 如何設計單詞袋模型的課型

如何設計單詞袋模型的課型

如何設計單詞袋模型的課型

回顧我過去寫過的壹些詞袋模型,如弓圖像檢索Python實戰、CBIR三劍客BoF、VLAD、FV和詞袋cpp,我所寫的只是對我理解詞袋模型理論有幫助,或者只是壹些面向實驗的驗證,或者更直接的說,只是壹些小玩具。

在我2016的計劃清單裏,有壹個從2015拖過來的目標,就是寫壹個可以面向業務層面的字袋模型。這個計劃隨著VLfeat部分C接口的成功開放而成為可能,這半年來我壹直在關註詳細寫的時候選擇哪些庫的問題。巧的是,這段時間輪子又造出來了,而且已經初見成效,我就在這裏整理總結壹下。在說如何設計壹個課型的書包模型之前,先說壹下庫的選擇。

選擇適當的庫

寫壹個面向應用的詞袋模型,大概會經歷幾個步驟:SIFT特征提取、特征采樣、聚類、構建KD樹、統計詞頻、計算詞頻權重、計算詞頻直方圖、保存數據。當這八個步驟實現後,他們會設計壹些選庫問題,下面會詳細討論。

1)SIFT特征提取選哪個庫?

提取SIFT的庫有很多,下面主要是人用的:

Lowe的SIFT,效果只提供SIFT的二進制可執行文件,丟棄;

Robwhess的OpenSIFT是開源的,效果還不錯。它需要壹些其他的依賴庫,所以不會被更新或丟棄。

OpenCV的SIFT當然是最方便用的,文檔完整,不依賴其他庫,但是SIFT的實現效果不是很好,所以棄用了;

VLfeat中的SIFT和SIFT效果不錯,缺點是C接口文檔不全,網上提供的資料較少,但多讀它的C源代碼就可以了,不依賴其他庫,選擇這個庫提取SIFT就不錯了。在實際提取中,我選擇covdet函數來提取SIFT,這是壹個提取共變的更強大的特征。

去年基本搞清楚了VLfeat中壹些函數的C接口調用方法。covdet把寫給matlab的接口源代碼轉換成C,把matlab提取的結果和我自己轉換成C後提取的結果對比,完全壹致。

2)矩陣運算庫的選擇

雖然不壹定要用矩陣運算,但是除了OpenCV矩陣之外,還可以引入其他矩陣運算庫,給後期實現帶來很大的便利,比如聚類、KD樹構建、詞頻統計等。作為基本庫的操作,以下主要用於選擇矩陣庫:

Eigen,只需要在項目中包含頭文件,提供多平臺版本,比如可以在Android上運行,矩陣操作相對方便,更新快。但是,在PC平臺上開發時,我更喜歡使用下面的Armadillo。

犰狳,這個庫是我最喜歡的矩陣運算庫。在使用語法上,MATLABjie借鑒了Matlab的語法使用習慣,所以熟悉Matlab的開發人員在使用這個庫的時候會覺得很舒服,著名的MLPack就是基於它。另外,它的矩陣運算效率也很高。就像Eigen壹樣,使用時只需要包含頭文件目錄,最新版本中加入了KMeans集群。因此,基於這些優點,在實現字袋模型時,矩陣運算庫的選擇無疑是最佳選擇。

雖然選擇矩陣庫可以大大方便我們的編程,但是會涉及到數據類型的轉換。比如存儲在STL的vector中的數據,會轉換成Armadillo的矩陣進行運算。如果數據轉換頻繁,必然會降低程序的運行效率。所以在編程中,要盡量避免不必要的轉換。

3)多線程並行處理

為了支持SIFT特征提取、KMeans聚類和詞頻統計過程中的並行處理,在選擇並行計算庫時有兩種選擇,壹是使用OpenMP,二是選擇MPI。OpenMP采用* * *內存共享的模式,只需對原程序稍加調整即可實現並行處理,語法易於讀寫;MPI需要對原程序進行很大的重構,寫出來的代碼不是很好讀。所以多線程並行計算庫中選這壹塊,選OpenMP比較好。

單詞袋模型的課型設計

終於可以說核心了。這壹部分講講我自己寫程序時對書包模型的課型設計的體會。首先介紹了自己寫的詞袋模型的類類型,設計了兩個類,壹個是SIFT特征提取的類類型,壹個是詞袋模型的類類型。先說SIFT特征提取的類類型:

分類篩選描述符{

公共:

sift descriptor(){ };

STD::string imageName;

STD::vector & lt;STD::vector & lt;float & gt& gt框架;

STD::vector & lt;STD::vector & lt;float & gt& gt描述者;

void cov det _ key points _ and _ descriptor(cv::Mat & amp;img,STD::vector & lt;STD::vector & lt;float & gt& gt& amp框架,STD::vector & lt;STD::vector & lt;float & gt& gt& ampdesctor,bool rooSIFT,bool verbose);

STD::vector & lt;float & gtroot sift(STD::vector & lt;float & gt& ampdst);

void序列化(STD::of stream & amp;outfile) const {

STD::string tmpImageName = imageName;

int strSize =(int)imagename . size();

outfile . write((char *)& amp;strSize,sizeof(int));

outfile . write((char *)& amp;tmpImageName[0],sizeof(char)* strSize);//寫入文件名

int descSize =(int)desctor . size();

outfile . write((char *)& amp;descSize,sizeof(int));

//寫入sift特征

for(int I = 0;我& ltdescSizei++ ){

outfile . write((char *)& amp;(desctor[i][0]),sizeof(float)* 128);

outfile . write((char *)& amp;(frame[i][0]),sizeof(float)* 6);

}

}

靜態siftDesctor反序列化(STD::if stream & amp;ifs) {

sift descriptor sift desc;

int strSize = 0;

ifs . read((char *)& amp;strSize,sizeof(int));//寫入文件名

siftDesc.imageName =

sift desc . imagename . resize(strSize);

ifs . read((char *)& amp;(siftDesc.imageName[0]),sizeof(char)* strSize);//讀入文件名

int descSize = 0;

ifs . read((char *)& amp;descSize,sizeof(int));

//讀入sift特征和幀。

for(int I = 0;我& ltdescSizei++ ){

STD::vector & lt;float & gttmpDesc(128);

ifs . read((char *)& amp;(tmpDesc[0]),sizeof(float)* 128);

sift desc . desctor . push _ back(tmpDesc);

STD::vector & lt;float & gttmp frame(6);

ifs . read((char *)& amp;(tmpFrame[0]),sizeof(float)* 6);

sift desc . frame . push _ back(tmp frame);

}

返回siftDesc

}

};

在設計SIFT特征提取的類類型時,對於每張圖像,SIFT特征提取後,需要保存imageName、128維SIFT特征和6維frame,因為imageName、desctor和frame三個成員是必須的。先說這個成員,ImageName。在最後保存文件名時,比較合理的做法是不要帶入文件所在的目錄路徑,因為最後寫入的數據可能會轉移到其他電腦上,帶入路徑不方便用戶欣賞後對數據的處理。剛開始設計的時候沒考慮這個地方。另外,最初設計cov det _ key points _ and _ descriptors()方法時,在方法中讀取圖像,然後提取特征。當時的想法是讓這樣的特征提取器使用起來更方便(只能處理要走線的圖像的文件名),但後來發現沒必要設計這種方法。這種蹩腳的方式使得您在稍後顯示結果時需要再次讀取圖像,從而降低了成本。

除了三個重要的成員變量之外,序列化和反序列化的兩種方法也非常重要。序列化的目的是保存三個重要的成員變量,可以避免我們再想聚類時提取特征的尷尬。反序列化允許我們讀取保存的數據,所以需要添加三個成員變量和兩個方法。

先說壹下單詞袋模型的類類型,先看類定義:

bowModel類{

公共:

bow model(){ };

bowModel(int _numWords,STD::vector & lt;sift descriptor & gt;_imgFeatures,STD::vector & lt;STD::vector & lt;int & gt& gt_words):numWords(_numWords)、imgFeatures(_imgFeatures)、words(_ words){ };

int numNeighbors = 1;

int numWords

STD::vector & lt;sift descriptor & gt;imgFeatures

STD::vector & lt;STD::vector & lt;int & gt& gt詞;

cv::Mat centroids _ opencvMat;

cv::flann::Index opencv _ buildKDTree(cv::Mat & amp;centroids _ opencvMat);

void序列化(STD::of stream & amp;outfile) const {

int imgFeatsSize =(int)img features . size();

outfile . write((char *)& amp;imgFeatsSize,sizeof(int));

//編寫imgFeatures和單詞

for(int I = 0;我& ltimgFeatsSizei++ ){

imgFeatures[i]。序列化(outfile);

outfile . write((char *)& amp;(words[i][0]),sizeof(int)* img features[I]. desctor . size());

}

}

靜態bowModel反序列化(STD::if stream & amp;ifs) {

bowModel弓;

int imgFeatsSize

ifs . read((char *)& amp;imgFeatsSize,sizeof(int));

bow . words . resize(imgFeatsSize);

for(int I = 0;我& ltimgFeatsSizei++) {

//讀入imgFeatures

auto sift desc = sift descriptor::Deserialize(ifs);

bow . img features . push _ back(sift desc);

//讀入單詞

BoW.words[i]。resize(sift desc . desctor . size());

ifs . read((char *)& amp;(BoW.words[i][0]),sizeof(int)* sift desc . desctor . size());

}

回力弓;

}

};

上面最重要的有三個,壹個是成員STD::vector < sift descriptor & gt;ImgFeatures以及序列化和反序列化方法。對於從圖像中提取的每個特征,通過實例化SIFTDESTOR來保存imageName、desctor和frame。這樣,我們用STL的vector保存了所有圖像的SIFTDESTOR實例。序列化時,我們通過調用從SIFT特征中提取的類類型中定義的序列化方法來保存每個實例。在讀取數據時,進程基本上是原始的準進程。通過這樣設計SIFT特征提取的類類型和bag模型的類類型,在讀寫數據時,我們可以通過內循環和外循環讀寫數據的壹個實例,從而優雅地完成圖片的特征提取、數據保存和讀寫。

對於數據讀寫,我們做了壹些研究,壹個是通過HDF5,壹個是通過BOOST庫。HDF5非常適合保存大量數據,讀寫效率高。但是在C++中,不如在Python中使用HDF5方便,而且BOOST也查閱了相應的資料,所以寫起來也比較復雜,所以最終只選擇了fstream進行數據讀寫。經過測試,數據讀寫還是比較高效的,所以暫時采用這個方案。