TF-IDF是一種用於信息檢索的經常使用加權技術,在文本檢索中,用以評估詞語對於一個文件數據庫中的其中一份文件的重要程度。詞語的重要性隨着它在文件中出現的頻率成正比增長,但同時會隨着它在文件數據庫中出現的頻率成反比降低。像‘的’,‘咱們’,‘地’等這些經常使用詞在全部文章中出現的頻率會很高,並不能很好的表徵一個文檔的內容。html
一樣的在圖像檢索中也引入了IF-IDF權重,python
詞頻(Term Frequency,TF) 一個visual word
在一個圖像中出現的頻率的很高,則說明該visual word 可以很好的表明圖像的內容。 \[TF = \frac{圖像中某個word出現的次數}{圖像word的總的個數}\]數據庫
逆文檔詞頻(Inverse Document Frequency,IDF) 一些常見的word,會在每一圖像出現的頻率都很高,可是這些word並不能很好的表示圖像的內容,因此要給這一部分word低一些的權重。IDF,描述一個word的廣泛重要性,如古歐word在不少的圖像中出現的頻率都很高,則給予其較低的權重。\[IDF = \log(\frac{圖像的總個數}{含有該word的圖像數 + 1})\]
將分母+1是爲了防止除數爲0的狀況出現。從上式中能夠看出,包含當前word的圖像個數越多,IDF的值越小,說明該詞越不重要。反之,該詞越重要。app
計算獲得了TF和IDF,則有
\[TF-IDF = TF * IDF\]性能
從TF和IDF的計算公式能夠看出,IDF是針對整個圖像數據庫而言的,能夠在訓練完成後計算一次獲得。而TF則是針對具體的某張圖像來講的,須要屢次計算。編碼
將TF-IDF權值賦給BoW向量,再進行\(l_2\)的歸一化,便可獲得一個可用於圖像檢索的向量。spa
void compute_idf(const vector<<vector<int>> &bow,vector<float> &idf){ int img_count = bow.size(); int clu_count = bow[0].size(); idf = vector<float>(clu_count,1.0); for(int i = 0; i < img_count; i ++){ for(int j = 0; j < clu_count; j ++){ if(bow[i][j] != 0) idf[j] ++; } } for(int i = 0; i < idf.size(); i ++){ idf[i] = log(img_count / idf[i]); } }
上面代碼計算的是圖像庫的IDF(IDF是針對整個圖像庫而言的)。
針對某一張圖片,都須要計算一次TF。TF的計算公式:\(TF = \frac{圖像中某個word出現的次數}{圖像word的總的個數}\),能夠看着是對圖像的BoW向量進行\(l_1\)的歸一化。rest
void compute_tf(const vector<int> &bow,vector<float> &tf){ tf = vector<float>(bow.size(),0); int sum = 0; // All words in the image for(int i = 0; i < bow.size(); i ++){ sum += bow[i]; } for(int i = 0; i < bow.size(); i ++){ tf[i] = (float)bow[i] / sum; } }
在Arandjelovic和Zisserman 2012的論文[ Three things everyone should know to improve object retrieval](https://www.robots.ox.ac.uk/~vgg/publications/2012/Arandjelovic12/arandjelovic12.pdf " Three things everyone should know to improve object retrieval") 提出了RootSift。code
當比較直方圖時,使用歐氏距離一般比卡方距離或Hellinger核時的性能差,可是在使用sift特徵點爲何一直都使用歐氏距離呢?
不管是對sift特徵點進行匹配,仍是對sift特徵集合進行聚類獲得視覺詞彙表,又或者對圖像進行BoW編碼,都使用的是歐氏距離。可是sift特徵描述子本質上也是一種直方圖,爲何對sift特徵描述子進行比較的時候要使用歐氏距離呢,有沒有一種更精確的比較方法呢?orm
sift描述子統計的是關鍵點鄰域的梯度直方圖,更詳細的介紹能夠參考圖像檢索(1): 再論SIFT-基於vlfeat實現
Zisserman 認爲只因此一直使用歐氏距離來測量sift特徵的類似度,是因爲在sift提出的時候,使用的是歐氏距離的度量,能夠找出一種比較歐氏距離更爲精確的度量方法。Arandjelovic和Zisserman提出了RootSift對sift特徵進行擴展。
在提取獲得sift的描述向量\(x\)後,進行如下處理,便可獲得RootSift
1.對特徵向量\(x\)進行\(l_1\)的歸一化(\(l_1-normalize\))獲得\(x'\)
2.對\(x'\)的每個元素求平方根
3.進行\(l_2-normalize\),可選
最後一步,是否進行\(l_2\)的歸一化,有些不一致。 在paper 並無指出須要進行\(l_2\)的歸一化,可是在presentation, 卻有\(l_2\)歸一化這一步。也有認爲,顯式地執行L2規範化是不須要的。經過採用L1規範,而後是平方根,已經有L2標準化的特徵向量,不須要進一步的標準化。
# import the necessary packages import numpy as np import cv2 class RootSIFT: def __init__(self): # initialize the SIFT feature extractor self.extractor = cv2.DescriptorExtractor_create("SIFT") def compute(self, image, kps, eps=1e-7): # compute SIFT descriptors (kps, descs) = self.extractor.compute(image, kps) # if there are no keypoints or descriptors, return an empty tuple if len(kps) == 0: return ([], None) # apply the Hellinger kernel by first L1-normalizing and taking the # square-root descs /= (descs.sum(axis=1, keepdims=True) + eps) descs = np.sqrt(descs) #descs /= (np.linalg.norm(descs, axis=1, ord=2) + eps) # return a tuple of the keypoints and descriptors return (kps, descs)
來自 https://www.pyimagesearch.com/2015/04/13/implementing-rootsift-in-python-and-opencv/
for(int i = 0; i < siftFeature.rows; i ++){ // Conver to float type Mat f; siftFeature.row(i).convertTo(f,CV_32FC1); normalize(f,f,1,0,NORM_L1); // l1 normalize sqrt(f,f); // sqrt-root root-sift rootSiftFeature.push_back(f); }
局部聚合向量(Vector of Locally Aggregated Descriptors,VLAD)
前面介紹的BoW方法,在圖像的檢索和檢索中有這普遍的應用。BoW經過聚類,對圖像的局部特徵進行從新編碼,有很強的表示能力,而且使用SVM這樣基於樣本間隔的分類器,也能取得了很好的分類效果。可是在圖像規模比較大的狀況下,因爲視覺詞彙表Vocabulary
大小的限制,BoW對圖像的表示會愈來愈粗糙,編碼後損失的圖像信息較多,檢索精度也隨之而下降。
2010年,論文Aggregating local descriptors into a compact image representation中提出了一對新的圖像表示方法,VLAD。從三個方面進行改進:
BoW的表示方法中,是統計每一個特徵詞彙在圖像中出現的頻率。VLAD則是求落在同一個聚類中心的特徵和該聚類中心殘差的累加和。公式表示以下:
\[ v_{i,j} = \sum_{x\ such\ that\ NN(x)=c_i}x_j - c_{i,j} \]
\(x_j\)是圖像的第\(j\)個特徵點,\(c_i\)則是和該特徵點最近的聚類中心,\(x_j - c_{i,j}\)表示特徵點和其最近聚類中心的差。假如使用的是sift特徵,視覺詞彙表Vocabulary的大小爲\(k\),則能夠獲得\(k\)個128維的向量\(v_{i,j}\)。
而後將這\(k*d\)個向量(\(d\)爲圖像特徵的長度,例如sift爲128維)\(v_{i,j}\)拉伸爲一個\(k*d\)長度的一維向量,再對拉伸後的向量作一個\(l_2\)的歸一化就獲得圖像的VLAD表示。
因爲,VLAD是特徵和其最鄰近的聚類中心的殘差和,該向量的不少份量不少份量都爲0,也就是說該向量是稀疏的(sparse,不多的份量佔有大部分的能量),因此能夠對VLAD進行降維處理(例如,PCA)能進一步縮小向量的大小。
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 }
藉助於OpenCV實現仍是比較簡單的。這裏使用FlannBasedMatcher
的match
方法來查找特徵最鄰近的聚類中心(視覺詞彙)。能夠分爲如下三步:
subtract
計算特徵和其最近鄰的聚類中心的差值,add
將同一個聚類中心的差值累加起來responseHist
進行\(l_2\)的歸一化處理reshape
方法,將矩陣拉伸爲一維\(k*d(128)\)的一維向量VLAD。Bow一般要搭配TF-IDF進行使用,可是因爲Vocabulary的大小的限制,VLAD是一個不錯的替代選擇。RootSift是對原生sift的擴展。