第十九節、基於傳統影象處理的目標檢測與識別(詞袋模型BOW+SVM附程式碼)
在上一節、我們已經介紹了使用HOG和SVM實現目標檢測和識別,這一節我們將介紹使用詞袋模型BOW和SVM實現目標檢測和識別。
一 詞袋介紹
詞袋模型(Bag-Of-Word)的概念最初不是針對計算機視覺的,但計算機視覺會使用該概念的升級。詞袋最早出現在神經語言程式學(NLP)和資訊檢索(IR)領域,該模型忽略掉文字的語法和語序,用一組無序的單詞來表達一段文字或者一個文件。
我們使用BOW在一系列文件中構建一個字典,然後使用字典中每個單詞次數構成向量來表示每一個文件。比如:
- 文件1:I like OpenCV and I like Python;
- 文件2:I like C++ and Python;
- 文件3:I don't like artichokes;
對於這三個文件,我們建立如下的字典:
dic = {1:'I', 2:'like', 3:'OpenCV', 4:'and', 5:'Python', 6:'C++', 7:'don\'t', 8:'artichokes'}
該字典一共有8項。使用這8項構成的向量來表示每個文件,每個向量包含字典中的所有單詞,向量的每個元素表示文件中每個單詞出現的次數。則上面三個文件可以使用如下向量來表示:
[2,2,1,1,1,0,0,0]
[ 1,1,0,1,1,1,0,0]
[1,1,0,0,0,0,1,1]
每一個向量都可以看做是一個文件的直方圖表示或被當做特徵,這些特徵可以用來訓練分類器。在實際中,也有許多有效地應用,比如垃圾郵箱過濾。
二 計算機視覺中的BOW
與應用到文字的BOW模型類比,我們可以把BOW模型應用到計算機視覺,我們把影象的特徵當做單詞,把影象“文字化”之後,有助於大規模的影象檢索。
1、BOW基本步驟
- 特徵提取:提取資料集中每幅影象的特徵點,然後提取特徵描述符,形成特徵資料(如:SIFT或者SURF方法);
- 學習詞袋:把處理好的特徵資料全部合併,利用聚類把特徵詞分為若干類,此若干類的數目由自己設定,每一類相當於一個視覺詞彙;
- 利用視覺詞袋量化影象特徵:每一張影象由很多視覺詞彙組成,我們利用統計的詞頻直方圖,可以表示影象屬於哪一類;
這個過程需要獲取視覺詞彙(visual word)字典,從一定程度上來說,詞彙越多越好,因此我們需要的資料集也相應的越大越好;
2、BOW視覺化
下面我們來對BOW過程進行視覺化,
- 假設我們有三個目標類,分別是人臉、自行車和吉他。首先從影象中提取出相互獨立的視覺詞彙(假設使用SIFT方法):
通過觀察會發現,同一類目標的不同例項之間雖然存在差異,但我們仍然可以找到它們之間的一些共同的地方,比如說人臉,雖然說不同人的臉差別比較大,但眼睛,嘴,鼻子等一些比較細小的部位,卻觀察不到太大差別,我們可以把這些不同例項之間共同的部位提取出來,作為識別這一類目標的視覺詞彙。
- 將所有的視覺詞彙集合在一起:
- 利用K-means演算法構造詞彙字典。K-means演算法是一種基於樣本間相似性度量的間接聚類方法,此演算法以$k$為引數,把$n$個物件分為$k$個簇,以使簇內具有較高的相似度,而簇間相似度較低。SIFT演算法提取的視覺詞彙向量之間根據距離的遠近,可以利用K-Means演算法將詞義相近的詞彙合併,作為詞彙字典中的基礎詞彙,假定我們將$k$設為4,那麼詞彙字典的構建過程如下:
- 利用詞彙字典的中詞彙表示影象。利用SIFT演算法,可以從每幅影象中提取很多個特徵點,這些特徵點都可以用詞彙字典中的詞彙近似代替,通過統計詞彙字典中每個詞彙在影象中出現的次數,可以將影象表示成為一個$k=4$維數值向量:
上圖中,我們從人臉、自行車和吉他三個目標類影象中提取出的不同視覺詞彙,而構造的詞彙字典中,會把詞義相近的視覺詞彙合併為同一類,經過合併,詞彙表中只包含了四個視覺詞彙,分別按索引值標記為1,2,3,4。通過觀察可以看到,它們分別屬於自行車、人臉、吉他、人臉類。統計這些詞彙在不同目標類中出現的次數可以得到每幅影象的直方圖表示(我們假定存在誤差,實際情況亦不外如此):
人臉: [3,30,3,20] 自行車:[20,3,3,2] 吉他: [8,12,32,7]
其實這個過程非常簡單,就是針對人臉、自行車和吉他這三個文件,抽取出相似的部分(或者詞義相近的視覺詞彙合併為同一類),構造一個字典,字典中包含4個視覺單詞,即:
dic = {1:'自行車', 2:'人臉', 3:'吉他', 4:'人臉類'}
最終人臉、自行車和吉他這三個文件皆可以用一個4維向量表示,最後根據三個文件相應部分出現的次數繪製對應的直方圖。
需要說明的是,以上過程只是針對三個目標類非常簡單的一個示例,實際應用中,為了達到較好的效果,單詞表中的詞彙數量$k$往往非常龐大,並且目標類數目越多,對應的$k$值也越大,一般情況下,$k$的取值在幾百到上千,在這裡取$k=4$僅僅是為了方便說明。
三 目標識別
對於影象和視訊檢測中的目標型別沒有具體限制,但是為了使結果的準確度在可以接收的範圍內,需要一個足夠大的資料集,包括訓練影象的大小也需要一樣。
如果自己構建資料集將會花費較長的時間,因此,在這裡我們利用現成的資料集,在網上可以下載這樣的資料集,這裡我們使用貓和狗的資料集:
https://www.kaggle.com/c/dogs-vs-cats-redux-kernels-edition/data
訓練集一共包含25000張照片,其中一半是狗(正樣本),一半是貓(負樣本),在這裡我們就使用其中的部分資料集,訓練一個二分類器;
- 首先我們選取一定數量的正負樣本(這裡選擇的為10,沒有選擇全部樣本是因為資料量大,計算速度就會很慢,而且該數值小一些有時候效果會更好),然後使用SIFT演算法提取特徵資料,並使用聚類分類(k=40),形成詞彙字典;
- 選取更多正負樣本資料集(這裡選擇的是400),利用視覺詞袋(即詞彙字典)量化每一個樣本特徵,並使用SVM進行訓練;
- 對100個樣本進行測試;
程式碼如下:
# -*- coding: utf-8 -*- """ Created on Wed Oct 17 09:38:26 2018 @author: zy """ ''' 詞袋模型BOW+SVM 目標識別 以狗和貓資料集二分類為例 如果是狗 返回True 如果是貓 返回False ''' import numpy as np import cv2 class BOW(object): def __init__(self,): #建立一個SIFT物件 用於關鍵點提取 self.feature_detector = cv2.xfeatures2d.SIFT_create() #建立一個SIFT物件 用於關鍵點描述符提取 self.descriptor_extractor = cv2.xfeatures2d.SIFT_create() def path(self,cls,i): ''' 用於獲取圖片的全路徑 ''' return '%s/%s/%s.%d.jpg'%(self.train_path,cls,cls,i+1) def fit(self,train_path,k): ''' 開始訓練 args: train_path:訓練集圖片路徑 我們使用的資料格式為 train_path/dog/dog.i.jpg train_path/cat/cat.i.jpg k:k-means引數k ''' self.train_path = train_path #FLANN匹配 引數algorithm用來指定匹配所使用的演算法,可以選擇的有LinearIndex、KTreeIndex、KMeansIndex、CompositeIndex和AutotuneIndex,這裡選擇的是KTreeIndex(使用kd樹實現最近鄰搜尋) flann_params = dict(algorithm=1,tree=5) flann = cv2.FlannBasedMatcher(flann_params,{}) #建立BOW訓練器,指定k-means引數k 把處理好的特徵資料全部合併,利用聚類把特徵詞分為若干類,此若干類的數目由自己設定,每一類相當於一個視覺詞彙 bow_kmeans_trainer = cv2.BOWKMeansTrainer(k) pos = 'dog' neg = 'cat' #指定用於提取詞彙字典的樣本數 length = 10 #合併特徵資料 每個類從資料集中讀取length張圖片(length個狗,length個貓),通過聚類建立視覺詞彙 for i in range(length): bow_kmeans_trainer.add(self.sift_descriptor_extractor(self.path(pos,i))) bow_kmeans_trainer.add(self.sift_descriptor_extractor(self.path(neg,i))) #進行k-means聚類,返回詞彙字典 也就是聚類中心 voc = bow_kmeans_trainer.cluster() #輸出詞彙字典 <class 'numpy.ndarray'> (40, 128) print(type(voc),voc.shape) #初始化bow提取器(設定詞彙字典),用於提取每一張影象的BOW特徵描述 self.bow_img_descriptor_extractor = cv2.BOWImgDescriptorExtractor(self.descriptor_extractor,flann) self.bow_img_descriptor_extractor.setVocabulary(voc) #建立兩個陣列,分別對應訓練資料和標籤,並用BOWImgDescriptorExtractor產生的描述符填充 #按照下面的方法生成相應的正負樣本圖片的標籤 1:正匹配 -1:負匹配 traindata,trainlabels = [],[] for i in range(400): #這裡取200張影象做訓練 traindata.extend(self.bow_descriptor_extractor(self.path(pos,i))) trainlabels.append(1) traindata.extend(self.bow_descriptor_extractor(self.path(neg,i))) trainlabels.append(-1) #建立一個SVM物件 self.svm = cv2.ml.SVM_create() #使用訓練資料和標籤進行訓練 self.svm.train(np.array(traindata),cv2.ml.ROW_SAMPLE,np.array(trainlabels)) def predict(self,img_path): ''' 進行預測樣本 ''' #提取圖片的BOW特徵描述 data = self.bow_descriptor_extractor(img_path) res = self.svm.predict(data) print(img_path,'\t',res[1][0][0]) #如果是狗 返回True if res[1][0][0] == 1.0: return True #如果是貓,返回False else: return False def sift_descriptor_extractor(self,img_path): ''' 特徵提取:提取資料集中每幅影象的特徵點,然後提取特徵描述符,形成特徵資料(如:SIFT或者SURF方法); ''' im = cv2.imread(img_path,0) return self.descriptor_extractor.compute(im,self.feature_detector.detect(im))[1] def bow_descriptor_extractor(self,img_path): ''' 提取影象的BOW特徵描述(即利用視覺詞袋量化影象特徵) ''' im = cv2.imread(img_path,0) return self.bow_img_descriptor_extractor.compute(im,self.feature_detector.detect(im)) if __name__ == '__main__': #測試樣本數量,測試結果 test_samples = 100 test_results = np.zeros(test_samples,dtype=np.bool) #訓練集圖片路徑 狗和貓兩類 進行訓練 train_path = './data/cat_and_dog/data/train' bow = BOW() bow.fit(train_path,40) #指定測試影象路徑 for index in range(test_samples): dog = './data/cat_and_dog/data/train/dog/dog.{0}.jpg'.format(index) dog_img = cv2.imread(dog) #預測 dog_predict = bow.predict(dog) test_results[index] = dog_predict #計算準確率 accuracy = np.mean(test_results.astype(dtype=np.float32)) print('測試準確率為:',accuracy) #視覺化最後一個 font = cv2.FONT_HERSHEY_SIMPLEX if test_results[0]: cv2.putText(dog_img,'Dog Detected',(10,30),font,1,(0,255,0),2,cv2.LINE_AA) cv2.imshow('dog_img',dog_img) cv2.waitKey(0) cv2.destroyAllWindows()
執行結果如下:
參考文章:
[1]OpenCV 3計算機視覺
[2]BOW 原理及程式碼解析