本文對前面的幾篇文章進行個總結,實現一個小型的圖像檢索應用。
一個小型的圖像檢索應用可以分為兩部分:
- train,構建圖像集的特征數據庫。
- retrieval,檢索,給定圖像,從圖像庫中返回最類似的圖像
構建圖像數據庫的過程如下:
- 生成圖像集的視覺詞匯表(Vocabulary)
- 提取圖像集所有圖像的sift特征
- 對得到的sifte特征集合進行聚類,聚類中心就是Vocabulary
- 對圖像集中的圖像重新編碼表示,可使用BoW或者VLAD,這里選擇VLAD.
- 將圖像集中所有圖像的VLAD表示組合到一起得到一個VLAD表,這就是查詢圖像的數據庫。
得到圖像集的查詢數據后,對任一圖像查找其在數據庫中的最相似圖像的流程如下:
- 提取圖像的sift特征
- 加載Vocabulary,使用VLAD表示圖像
- 在圖像數據庫中查找與該VLAD最相似的向量
構建圖像集的特征數據庫的流程通常是offline的,查詢的過程則需要是實時的,基本流程參見下圖:
由兩部分構成:offline的訓練過程以及online的檢索查找
各個功能模塊的實現
下面就使用VLAD表示圖像,實現一個小型的圖像數據庫的檢索程序。下面實現需要的功能模塊
- 特征點提取
- 構建Vocabulary
- 構建數據庫
第一步,特征點的提取
不管是BoW還是VLAD,都是基於圖像的局部特征的,本文選擇的局部特征是SIFT,使用其擴展RootSift。提取到穩定的特征點尤為的重要,本文使用OpenCV體哦那個的SiftDetecotr
,實例化如下:
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 設置提取到的特征點的個數,每個sift的特征點都根據其對比度(local contrast)計算出來一個分數。設置了該值后,會根據分數排序,只保留前nfeatures個返回
- nOctaveLayers 每個octave中的層數,該值可以根據圖像的分辨率大小計算出來。D.Lowe論文中該值為3
- contrastThreshold 過濾掉低對比度的不穩定特征點,該值越大,提取到的特征點越少
- edgeThreshold 過濾邊緣處的特征點,該值越大,提取到的特征點就越多
- sigma 高斯濾波器的參數,該濾波器應用於第0個Octave
個人的一些見解。
設置參數時,主要是設置contrastThreshold
和edgeThreshold
。contrastThreshold
是過濾掉平滑區域的一些不穩定的特征點,edgeThreshold
是過慮類似邊緣的不穩定關鍵點。設置參數時,應盡量保證提取的特征點個數適中,不易過多,也不要過少。另外,contrastThreshold
和edgeThreshold
的平衡,應根據要提取的目標是比較平滑的區域還是紋理較多的區域,來平衡這兩個參數的設置。
對於有些圖像,可能設置的提取特征點的參數叫嚴格,提取特征點的個數過少,這時候可改變寬松一些的參數。
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的內容可以參看文章:
- 圖像檢索(1): 再論SIFT-基於vlfeat實現 使用輕量級的視覺庫vlfeat提取sift特征,其提取的特征覺得更穩定一些,但是使用上就不如OpenCV方便了。
- SIFT特征詳解
關於RootSift和VLAD可以參考前面的文章圖像檢索(4):IF-IDF,RootSift,VLAD。
第二步,構建Vocabulary
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
第三步,創建圖像數據庫
圖像數據庫也就是將圖像VLAD表示的集合,在該數據庫檢索時,返回與query圖像相似的VLAD所對應的圖像。
本文使用OpenCV提供的Mat
構建一個簡單的數據庫,Mat
保存所有圖像的vlad向量組成的矩陣,在檢索時,實際就是對該Mat
的檢索。
聲明類class Database
,其具有以下功能:
add
添加圖像到數據庫save
和load
將數據庫保存為文件(.yml)retrieval
檢索,對保存的vald向量的Mat
創建索引,返回最相似的結果。
第四步,Trainer
在上面實現了特征點的提取,構建視覺詞匯表,構建圖像表示為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
需要配置
- 圖像集所在的目錄
- 視覺詞匯表的大小(聚類中心的個數)
- PCA后VLAD保留的維度,可先不管設置為0,不進行PCA
- 訓練后數據的保存路徑。 訓練后的數據保存為
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;
}
偷懶,沒有配置為參數,使用時需要設置好圖像的路徑,以及訓練后數據的保存數據。
第五步,Searcher
在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);
Summary
上圖來總結下整個流程
-
創建
Vocabulary
-
創建
Database
-
Search Similary list