本文對前面的幾篇文章進行個總結,實現一個小型的圖像檢索應用。html
一個小型的圖像檢索應用能夠分爲兩部分:數據庫
構建圖像數據庫的過程以下:多線程
獲得圖像集的查詢數據後,對任一圖像查找其在數據庫中的最類似圖像的流程以下:ide
構建圖像集的特徵數據庫的流程一般是offline的,查詢的過程則須要是實時的,基本流程參見下圖:
測試
由兩部分構成:offline的訓練過程以及online的檢索查找ui
下面就使用VLAD表示圖像,實現一個小型的圖像數據庫的檢索程序。下面實現須要的功能模塊編碼
無論是BoW仍是VLAD,都是基於圖像的局部特徵的,本文選擇的局部特徵是SIFT,使用其擴展RootSift。提取到穩定的特徵點尤其的重要,本文使用OpenCV體哦那個的SiftDetecotr
,實例化以下:線程
auto fdetector = xfeatures2d::SIFT::create(0,3,0.2,10);
create
的聲明以下:rest
static Ptr<SIFT> cv::xfeatures2d::SIFT::create ( int nfeatures = 0, int nOctaveLayers = 3, double contrastThreshold = 0.04, double edgeThreshold = 10, double sigma = 1.6 )
我的的一些看法。
設置參數時,主要是設置contrastThreshold
和edgeThreshold
。contrastThreshold
是過濾掉平滑區域的一些不穩定的特徵點,edgeThreshold
是過慮相似邊緣的不穩定關鍵點。設置參數時,應儘可能保證提取的特徵點個數適中,不易過多,也不要過少。另外,contrastThreshold
和edgeThreshold
的平衡,應根據要提取的目標是比較平滑的區域仍是紋理較多的區域,來平衡這兩個參數的設置。code
對於有些圖像,可能設置的提取特徵點的參數叫嚴格,提取特徵點的個數過少,這時候可改變寬鬆一些的參數。
auto fdetector = xfeatures2d::SIFT::create(0,3,0.2,10); fdetector->detectAndCompute(img,noArray(),kpts,feature); if(kpts.size() < 10){ fdetector = xfeatures2d::SIFT::create(); fdetector->detectAndCompute(img,noArray(),kpts,feature); }
閾值10,可根據具體的狀況進行調節。
更多關於sift的內容能夠參看文章:
關於RootSift和VLAD能夠參考前面的文章圖像檢索(4):IF-IDF,RootSift,VLAD。
Vocabulary的構建過程,實際就是對提取到的圖像特徵點的聚類。首先提取圖像庫圖像sift特徵,並將其擴展爲RootSift,而後對提取到的RootSift進行聚類獲得Vocabulary。
這裏建立class Vocabulary
,主要如下方法:
create
從提取到的特徵點構建聚類獲得視覺詞彙表Vocabulary
void Vocabulary::create(const std::vector<cv::Mat> &features,int k) { Mat f; vconcat(features,f); vector<int> labels; kmeans(f,k,labels,TermCriteria(TermCriteria::COUNT + TermCriteria::EPS,100,0.01),3,cv::KMEANS_PP_CENTERS,m_voc); m_k = k; }
load
和save
,爲了使用方便,須要可以將生成的視覺詞彙表Vocabulary
保存問文件(.yml)tranform_vlad
,將輸入的圖像進行轉換爲vlad表示void Vocabulary::transform_vlad(const cv::Mat &f,cv::Mat &vlad) { // Find the nearest center Ptr<FlannBasedMatcher> matcher = FlannBasedMatcher::create(); vector<DMatch> matches; matcher->match(f,m_voc,matches); // Compute vlad Mat responseHist(m_voc.rows,f.cols,CV_32FC1,Scalar::all(0)); for( size_t i = 0; i < matches.size(); i++ ){ auto queryIdx = matches[i].queryIdx; int trainIdx = matches[i].trainIdx; // cluster index Mat residual; subtract(f.row(queryIdx),m_voc.row(trainIdx),residual,noArray()); add(responseHist.row(trainIdx),residual,responseHist.row(trainIdx),noArray(),responseHist.type()); } // l2-norm auto l2 = norm(responseHist,NORM_L2); responseHist /= l2; //normalize(responseHist,responseHist,1,0,NORM_L2); //Mat vec(1,m_voc.rows * f.cols,CV_32FC1,Scalar::all(0)); vlad = responseHist.reshape(0,1); // Reshape the matrix to 1 x (k*d) vector }
class Vocabulary
有如下方法:
Vocabulary
Vocabulary
保存到本地,並提供了load
方法圖像數據庫也就是將圖像VLAD表示的集合,在該數據庫檢索時,返回與query圖像類似的VLAD所對應的圖像。
本文使用OpenCV提供的Mat
構建一個簡單的數據庫,Mat
保存全部圖像的vlad向量組成的矩陣,在檢索時,實際就是對該Mat
的檢索。
聲明類class Database
,其具備如下功能:
add
添加圖像到數據庫save
和load
將數據庫保存爲文件(.yml)retrieval
檢索,對保存的vald向量的Mat
建立索引,返回最類似的結果。在上面實現了特徵點的提取,構建視覺詞彙表,構建圖像表示爲VLAD的數據庫,這裏將其組合到一塊兒,建立Trainer
類,方便訓練使用。
class Trainer{ public: Trainer(); ~Trainer(); Trainer(int k,int pcaDim,const std::string &imageFolder, const std::string &path,const std::string &identifiery,std::shared_ptr<RootSiftDetector> detector); void createVocabulary(); void createDb(); void save(); private: int m_k; // The size of vocabulary int m_pcaDimension; // The retrain dimensions after pca Vocabulary* m_voc; Database* m_db; private: /* Image folder */ std::string m_imageFolder; /* training result identifier,the name suffix of vocabulary and database voc-identifier.yml,db-identifier.yml */ std::string m_identifier; /* The location of training result */ std::string m_resultPath; };
使用Trainer
須要配置
yml
形式,命名規則是voc-m_identifier.yml
和db-m_identifier.yml
。 爲了方便測試不一樣參數的數據,這裏設置一個後綴參數m_identifier
,來區分不一樣的參數的訓練數據。其使用代碼以下:
int main(int argc, char *argv[]) { const string image_200 = "/home/test/images-1"; const string image_6k = "/home/test/images/sync_down_1"; auto detector = make_shared<RootSiftDetector>(5,5,10); Trainer trainer(64,0,image_200,"/home/test/projects/imageRetrievalService/build","test-200-vl-64",detector); trainer.createVocabulary(); trainer.createDb(); trainer.save(); return 0; }
偷懶,沒有配置爲參數,使用時須要設置好圖像的路徑,以及訓練後數據的保存數據。
在Database
中,已經實現了retrieval
的方法。 這裏之因此再封裝一層,是爲了更好的契合業務上的一些需求。好比,圖像的一些預處理,分塊,多線程處理,查詢結果的過濾等等。關於Searcher
和具體的應用耦合比較深,這裏只是簡單的實現了個retrieval
方法和查詢參數的配置。
class Searcher{ public: Searcher(); ~Searcher(); void init(int keyPointThreshold); void setDatabase(std::shared_ptr<Database> db); void retrieval(cv::Mat &query,const std::string &group,std::string &md5,double &score); void retrieval(std::vector<char> bins,const std::string &group,std::string &md5,double &score); private: int m_keyPointThreshold; std::shared_ptr<Database> m_db; };
使用也很簡單了,從文件中加載Vaocabulary
和Database
,設置Searcher
的參數。
Vocabulary voc; stringstream ss; ss << path << "/voc-" << identifier << ".yml"; cout << "Load vocabulary from " << ss.str() << endl; voc.load(ss.str()); cout << "Load vocabulary successful." << endl; auto detector = make_shared<RootSiftDetector>(5,0.2,10); auto db = make_shared<Database>(detector); cout << "Load database from " << path << "/db-" << identifier << ".yml" << endl; db->load1(path,identifier); db->setVocabulary(voc); cout << "Load database successful." << endl; Searcher s; s.init(10); s.setDatabase(db);
上圖來總結下整個流程
Vocabulary
Database