基于OpenCV實(shí)現(xiàn)小型的圖像數(shù)據(jù)庫(kù)檢索功能
本文對(duì)前面的幾篇文章進(jìn)行個(gè)總結(jié),實(shí)現(xiàn)一個(gè)小型的圖像檢索應(yīng)用。
一個(gè)小型的圖像檢索應(yīng)用可以分為兩部分:
- train,構(gòu)建圖像集的特征數(shù)據(jù)庫(kù)。
- retrieval,檢索,給定圖像,從圖像庫(kù)中返回最類似的圖像
構(gòu)建圖像數(shù)據(jù)庫(kù)的過(guò)程如下:
- 生成圖像集的視覺(jué)詞匯表(Vocabulary)
提取圖像集所有圖像的sift特征
對(duì)得到的sifte特征集合進(jìn)行聚類,聚類中心就是Vocabulary
- 對(duì)圖像集中的圖像重新編碼表示,可使用BoW或者VLAD,這里選擇VLAD.
- 將圖像集中所有圖像的VLAD表示組合到一起得到一個(gè)VLAD表,這就是查詢圖像的數(shù)據(jù)庫(kù)。
得到圖像集的查詢數(shù)據(jù)后,對(duì)任一圖像查找其在數(shù)據(jù)庫(kù)中的最相似圖像的流程如下:
- 提取圖像的sift特征
- 加載Vocabulary,使用VLAD表示圖像
- 在圖像數(shù)據(jù)庫(kù)中查找與該VLAD最相似的向量
構(gòu)建圖像集的特征數(shù)據(jù)庫(kù)的流程通常是offline的,查詢的過(guò)程則需要是實(shí)時(shí)的,基本流程參見(jiàn)下圖:
由兩部分構(gòu)成:offline的訓(xùn)練過(guò)程以及online的檢索查找
各個(gè)功能模塊的實(shí)現(xiàn)
下面就使用VLAD表示圖像,實(shí)現(xiàn)一個(gè)小型的圖像數(shù)據(jù)庫(kù)的檢索程序。下面實(shí)現(xiàn)需要的功能模塊
- 特征點(diǎn)提取
- 構(gòu)建Vocabulary
- 構(gòu)建數(shù)據(jù)庫(kù)
第一步,特征點(diǎn)的提取
不管是BoW還是VLAD,都是基于圖像的局部特征的,本文選擇的局部特征是SIFT,使用其擴(kuò)展RootSift。提取到穩(wěn)定的特征點(diǎn)尤為的重要,本文使用OpenCV體哦那個(gè)的SiftDetecotr
,實(shí)例化如下:
auto fdetector = xfeatures2d::SIFT::create(0,3,0.2,10);
create
的聲明如下:
static Ptr<SIFT> cv::xfeatures2d::SIFT::create ( int nfeatures = 0, int nOctaveLayers = 3, double contrastThreshold = 0.04, double edgeThreshold = 10, double sigma = 1.6 )
- nfeatures 設(shè)置提取到的特征點(diǎn)的個(gè)數(shù),每個(gè)sift的特征點(diǎn)都根據(jù)其對(duì)比度(local contrast)計(jì)算出來(lái)一個(gè)分?jǐn)?shù)。設(shè)置了該值后,會(huì)根據(jù)分?jǐn)?shù)排序,只保留前nfeatures個(gè)返回
- nOctaveLayers 每個(gè)octave中的層數(shù),該值可以根據(jù)圖像的分辨率大小計(jì)算出來(lái)。D.Lowe論文中該值為3
- contrastThreshold 過(guò)濾掉低對(duì)比度的不穩(wěn)定特征點(diǎn),該值越大,提取到的特征點(diǎn)越少
- edgeThreshold 過(guò)濾邊緣處的特征點(diǎn),該值越大,提取到的特征點(diǎn)就越多
- sigma 高斯濾波器的參數(shù),該濾波器應(yīng)用于第0個(gè)Octave
個(gè)人的一些見(jiàn)解。
設(shè)置參數(shù)時(shí),主要是設(shè)置contrastThreshold
和edgeThreshold
。contrastThreshold
是過(guò)濾掉平滑區(qū)域的一些不穩(wěn)定的特征點(diǎn),edgeThreshold
是過(guò)慮類似邊緣的不穩(wěn)定關(guān)鍵點(diǎn)。設(shè)置參數(shù)時(shí),應(yīng)盡量保證提取的特征點(diǎn)個(gè)數(shù)適中,不易過(guò)多,也不要過(guò)少。另外,contrastThreshold
和edgeThreshold
的平衡,應(yīng)根據(jù)要提取的目標(biāo)是比較平滑的區(qū)域還是紋理較多的區(qū)域,來(lái)平衡這兩個(gè)參數(shù)的設(shè)置。
對(duì)于有些圖像,可能設(shè)置的提取特征點(diǎn)的參數(shù)叫嚴(yán)格,提取特征點(diǎn)的個(gè)數(shù)過(guò)少,這時(shí)候可改變寬松一些的參數(shù)。
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,可根據(jù)具體的情況進(jìn)行調(diào)節(jié)。
更多關(guān)于sift的內(nèi)容可以參看文章:
- 圖像檢索(1): 再論SIFT-基于vlfeat實(shí)現(xiàn) 使用輕量級(jí)的視覺(jué)庫(kù)vlfeat提取sift特征,其提取的特征覺(jué)得更穩(wěn)定一些,但是使用上就不如OpenCV方便了。
- chabaoo.cn/article/181945.htm
關(guān)于RootSift和VLAD可以參考前面的文章chabaoo.cn/article/231900.htm
第二步,構(gòu)建Vocabulary
Vocabulary的構(gòu)建過(guò)程,實(shí)際就是對(duì)提取到的圖像特征點(diǎn)的聚類。首先提取圖像庫(kù)圖像sift特征,并將其擴(kuò)展為RootSift,然后對(duì)提取到的RootSift進(jìn)行聚類得到Vocabulary。
這里創(chuàng)建class Vocabulary
,主要以下方法:
create
從提取到的特征點(diǎn)構(gòu)建聚類得到視覺(jué)詞匯表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
,為了使用方便,需要能夠?qū)⑸傻囊曈X(jué)詞匯表Vocabulary
保存問(wèn)文件(.yml)tranform_vlad
,將輸入的圖像進(jìn)行轉(zhuǎn)換為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
有以下方法:
- 從圖像列表中構(gòu)建視覺(jué)詞匯表
Vocabulary
- 將生成的
Vocabulary
保存到本地,并提供了load
方法 - 將圖像表示為VLAD
第三步,創(chuàng)建圖像數(shù)據(jù)庫(kù)
圖像數(shù)據(jù)庫(kù)也就是將圖像VLAD表示的集合,在該數(shù)據(jù)庫(kù)檢索時(shí),返回與query圖像相似的VLAD所對(duì)應(yīng)的圖像。
本文使用OpenCV提供的Mat
構(gòu)建一個(gè)簡(jiǎn)單的數(shù)據(jù)庫(kù),Mat
保存所有圖像的vlad向量組成的矩陣,在檢索時(shí),實(shí)際就是對(duì)該Mat
的檢索。
聲明類class Database
,其具有以下功能:
add
添加圖像到數(shù)據(jù)庫(kù)save
和load
將數(shù)據(jù)庫(kù)保存為文件(.yml)retrieval
檢索,對(duì)保存的vald向量的Mat
創(chuàng)建索引,返回最相似的結(jié)果。
第四步,Trainer
在上面實(shí)現(xiàn)了特征點(diǎn)的提取,構(gòu)建視覺(jué)詞匯表,構(gòu)建圖像表示為VLAD的數(shù)據(jù)庫(kù),這里將其組合到一起,創(chuàng)建Trainer
類,方便訓(xùn)練使用。
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
需要配置
- 圖像集所在的目錄視覺(jué)
- 詞匯表的大?。ň垲愔行牡膫€(gè)數(shù))
- PCA后VLAD保留的維度,可先不管設(shè)置為0,不進(jìn)行PCA訓(xùn)練后數(shù)據(jù)的保存路徑。
- 訓(xùn)練后的數(shù)據(jù)保存為
yml
形式,命名規(guī)則是voc-m_identifier.yml
和db-m_identifier.yml
。 為了方便測(cè)試不同參數(shù)的數(shù)據(jù),這里設(shè)置一個(gè)后綴參數(shù)m_identifier
,來(lái)區(qū)分不同的參數(shù)的訓(xùn)練數(shù)據(jù)。
其使用代碼如下:
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; }
偷懶,沒(méi)有配置為參數(shù),使用時(shí)需要設(shè)置好圖像的路徑,以及訓(xùn)練后數(shù)據(jù)的保存數(shù)據(jù)。
第五步,Searcher
在Database
中,已經(jīng)實(shí)現(xiàn)了retrieval
的方法。 這里之所以再封裝一層,是為了更好的契合業(yè)務(wù)上的一些需求。比如,圖像的一些預(yù)處理,分塊,多線程處理,查詢結(jié)果的過(guò)濾等等。關(guān)于Searcher
和具體的應(yīng)用耦合比較深,這里只是簡(jiǎn)單的實(shí)現(xiàn)了個(gè)retrieval
方法和查詢參數(shù)的配置。
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; };
使用也很簡(jiǎn)單了,從文件中加載Vaocabulary
和Database
,設(shè)置Searcher
的參數(shù)。
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);
Summary
上圖來(lái)總結(jié)下整個(gè)流程
- 創(chuàng)建
Vocabulary
- 創(chuàng)建
Database
- Search Similary list
到此這篇關(guān)于基于OpenCV實(shí)現(xiàn)小型的圖像數(shù)據(jù)庫(kù)檢索的文章就介紹到這了,更多相關(guān)OpenCV圖像數(shù)據(jù)庫(kù)檢索內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
pandas創(chuàng)建DataFrame的方式小結(jié)
今天給大家整理了pandas創(chuàng)建DataFrame的方式小結(jié),現(xiàn)在我們就來(lái)看看這三種生成Dataframe的方式,每種方式通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧2021-09-09Face++ API實(shí)現(xiàn)手勢(shì)識(shí)別系統(tǒng)設(shè)計(jì)
這篇文章主要為大家詳細(xì)介紹了Face++ API實(shí)現(xiàn)手勢(shì)識(shí)別系統(tǒng)設(shè)計(jì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-11-11只用50行Python代碼爬取網(wǎng)絡(luò)美女高清圖片
第一次寫文章,技術(shù)不成熟之處望各位大神輕噴,今天教大家只用50行Python代碼爬取網(wǎng)絡(luò)美女圖片是怎么操作的,文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)python的小伙伴們很有幫助哦,需要的朋友可以參考下2021-06-06Python實(shí)現(xiàn)企業(yè)微信通知機(jī)器人的方法詳解
這篇文章主要為大家詳細(xì)介紹了如何使用Python實(shí)現(xiàn)對(duì)企業(yè)微信進(jìn)行群通知的功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2023-02-02python的time模塊和datetime模塊實(shí)例解析
這篇文章主要介紹了python的time模塊和datetime模塊實(shí)例解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11你們要的Python繪畫3D太陽(yáng)系詳細(xì)代碼
這篇文章主要給大家介紹了關(guān)于如何利用Python 繪畫3D太陽(yáng)系,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-10-10