計算機視覺項目實戰(三)、基於詞袋模型的場景識別 Scene Recognition with Bag of Words

項目要求

  1. 通過HOG特徵提取和K-means實現圖像的詞袋化特徵表示
  2. 訓練SVM分類器和KNN分類器
  3. 實現基於詞袋模型的場景識別
    在這裏插入圖片描述

項目原理

1. 圖像分類算法

圖像分類是機器視覺中一個重要的問題,其基本概念是:通過算法自動把圖像劃分到特定的概念類別中。圖像分類問題可以描述爲:給定若干個學習好的圖像類別,對輸入的新圖像序列進行處理,並對其做出一個決策,判斷一個已知的類別是否出現在數據中。圖像分類算法分爲訓練和測試兩個階段,其基本流程如下圖所示:

在這裏插入圖片描述

2. 基於詞袋模型的圖像分類技術

詞袋模型最初用於文本分類中,然後逐步引入到了圖像分類任務中。在文本分類中,文本被視爲一些不考慮先後順序的單詞集合。而在圖像分類中,圖像被視爲是一些與位置無關的局部區域的集合,因此這些圖像中的局部區域就等同於文本中的單詞了。在不同的圖像中,局部區域的分佈是不同的。因此,可以利用提取的局部區域的分佈對圖像進行識別。圖像分類和文本分類的不同點在於,在文本分類的詞袋模型算法中,字典是已存在的,不需要通過學習獲得;而在圖像分類中,詞袋模型算法需要通過監督或非監督的學習來獲得視覺詞典。

基於詞袋模型的圖像分類算法一般分爲四步:

  1. 對圖像進行局部特徵向量的提取。爲了取得很好的分類效果,提取的特徵向量需要具備不同程度的不變性,如旋轉,縮放,平移等不變性(SIFT或HOG)
  2. 利用上一步得到的特徵向量集,抽取其中有代表性的向量,作爲單詞,形成視覺詞典;
  3. 對圖像進行視覺單詞的統計,一般判斷圖像的局部區域和某一單詞的相似性是否超過某一閾值。這樣即可將圖像表示成單詞的分佈,即完成了圖像的表示。
  4. 設計並訓練分類器,利用圖像中單詞的分佈進行圖像分類。

3. 梯度方向直方圖HOG特徵提取算法

Histogram of Oriented Gridients,縮寫爲HOG,是目前計算機視覺、模式識別領域很常用的一種描述圖像局部紋理的特徵。

  1. 分隔圖像。因爲HOG是一個局部特徵,所有需要把圖像分割成很多塊,然後對每一塊計算HOG特徵,分割方法有重疊切分(overlap)和不重疊切分(non-overlap)兩種,這兩種策略各有各的好處。

    overlap可以防止對一些局部特徵的切割,比如對於一張人臉圖片,如果分割的時候正好把眼睛從中間切割並且分到了兩個patch中,提取完HOG特徵之後,這會影響接下來的分類效果,但是如果兩個patch之間overlap,那麼至少在一個patch會有完整的眼睛。overlap的缺點是計算量大,因爲重疊區域的像素需要重複計算。

    再說non-overlap,缺點就是上面提到的,有時會將一個連續的物體切割開,得到不太好的HOG特徵,優點是計算量小,尤其是與Pyramid(金字塔)結合時,這個優點更爲明顯。

  2. 計算每個區塊的方向梯度直方圖。利用任意一種梯度算子,例如:sobel,laplacian等,對每個patch進行卷積,計算得到每個像素點處的梯度方向和幅值。再將360度(2*PI)根據需要分割成若干個分,例如:分割成12分,每分包含30度,整個直方圖包含12維。然後根據每個像素點的梯度方向,利用雙線性內插法將其幅值累加到直方圖中。
    在這裏插入圖片描述

4. 詞袋構建:K-means聚類算法

K-means算法是很典型的基於距離的聚類算法,採用距離作爲相似性的評價指標,即認爲兩個對象的距離越近,其相似度就越大。該算法認爲簇是由距離靠近的對象組成的,因此把得到緊湊且獨立的簇作爲最終目標。算法接受一個未標記的數據集,是一種無監督學習。
假設要把數據通過k-means算法分成K個類,可以分爲以下4個步驟:

  1. 隨機選取k個點,作爲聚類中心;
  2. 計算每個點分別到k個聚類中心的距離,然後將該點分到最近的聚類中心,這樣就行成了k個簇;
  3. 再重新計算每個簇的質心(均值);
  4. 重複以上2~4步,直到質心的位置不再發生變化或者達到設定的迭代次數。

4.1. 大數據的聚類算法 Mini Batch K-Means

在項目中,因爲訓練樣本大,特徵數目較多,採用傳統的K-means進行聚類會導致計算時間過長,所以考慮使用Mini Batch KMeans。Mini Batch KMeans是一種能儘量保持聚類準確性下但能大幅度降低計算時間的聚類模型。

與K均值算法相比,數據的更新是在每一個小的樣本集上。對於每一個小批量,通過計算平均值得到更新質心,並把小批量裏的數據分配給該質心,隨着迭代次數的增加,這些質心的變化是逐漸減小的,直到質心穩定或者達到指定的迭代次數,停止計算。由於計算樣本量少,所以會相應的減少運行時間,而且準確度下降的並不明顯,所以在處理大樣本時常常採用該方法進行聚類。

5. 分類器的構建:KNN分類算法

鄰近算法,或者說K最近鄰(kNN,k-NearestNeighbor)分類算法是數據挖掘分類技術中最簡單的方法之一。所謂K最近鄰,就是k個最近的鄰居的意思,說的是每個樣本都可以用它最接近的k個鄰居來代表。KNN屬於懶惰學習,沒有顯式的學習過程,也就是說沒有訓練階段,數據集事先已有了分類和特徵值,待收到新樣本後直接進行處理。
 
 思路是:如果一個樣本在特徵空間中的k個最鄰近的樣本中的大多數屬於某一個類別,則該樣本也劃分爲這個類別。KNN算法中,所選擇的鄰居都是已經正確分類的對象。該方法在定類決策上只依據最鄰近的一個或者幾個樣本的類別來決定待分樣本所屬的類別。
在這裏插入圖片描述
KNN分類算法一般具有以下4個步驟:

  1. 計算測試數據與各個訓練數據之間的距離;
  2. 按照距離的遞增關係進行排序;
  3. 選取距離最小的K個點;
  4. 確定前K個點所在類別的出現頻率;
  5. 返回前K個點中出現頻率最高的類別作爲測試數據的預測分類.

6. 分類器的構建:線性SVM多分類算法

支持向量機分類器(Support Vector Classifier)最開始用於解決二分類問題,基本思想是是根據訓練樣本的分佈,將樣本一分爲二,即它只回答屬於正類還是負類的問題。決定分類邊界位置的樣本並不是所有訓練數據,是其中的兩個類別空間的間隔最小的兩個不同類別的數據點,即“支持向量”。

在面對多分類問題,我們設想的是用多個超平面把空間劃分爲多個區域,每個區域對應一個類別,這一看測試樣本落在哪個區域就知道他的分類。但是這種一次性求解的方法計算量太大,無法實踐,於是便退而求其次,想到採用“一類對其餘”的方法,這樣每次仍然解決一個二分類問題,一個類別訓練一個分類器。這種方法好處是每個優化問題規模較小,分類速度快,但是缺點是容易出現分類重疊和不可分類現象:多個分類器都說屬於自己的類別或每個分類器都說不屬於自己的類別。

於是將負類變成一次只選一個類,這樣便成了最原始的一對一分類問題,但這樣分類器的數目會平方級增多,當有k個類別時,分類器應該有k*(k-1)/2個。而且這種方法仍然存在分類重疊現象。

於是想方法改變分類策略,我們採用DAG SVM來組織分類器。具體策略如下圖所示。
在這裏插入圖片描述
先問分類器"1還是5?",如果是5,再問分類器“2還是5?”這種方法的優點是分類速度快,沒有分類重疊和不可分類現象。

主要內容

0. 具體操作流程->主函數

def projSceneRecBoW():
'''
    Step 0: 初始化操作(選擇特徵提取器,分類器和數據文件路徑,並設置類別名稱和訓練參數)
'''
    #FEATURE = 'tiny image'
    #FEATURE = 'bag of words'
    FEATURE = 'tiny image'

    #CLASSIFIER = 'nearest neighbor'
    #CLASSIFIER = 'support vector machine'
    CLASSIFIER = 'nearest neighbor'

    data_path = '../data/'
    categories = ['Kitchen', 'Store', 'Bedroom', 'LivingRoom', 'Office',
           'Industrial', 'Suburb', 'InsideCity', 'TallBuilding', 'Street',
           'Highway', 'OpenCountry', 'Coast', 'Mountain', 'Forest']        
    abbr_categories = ['Kit', 'Sto', 'Bed', 'Liv', 'Off', 'Ind', 'Sub',
        'Cty', 'Bld', 'St', 'HW', 'OC', 'Cst', 'Mnt', 'For']
	#每個類別的訓練集和測試集的大小
    num_train_per_cat = 100
	# 得到4個1500個元素的列表,分別存儲了訓練集每張圖片的相對地址,測試集每張圖片的相對地址,訓練集每張圖片的類別標籤,測試集每張圖片的類別標籤
    print('Getting paths and labels for all train and test data.')
    train_image_paths, test_image_paths, train_labels, test_labels = \
        get_image_paths(data_path, categories, num_train_per_cat)
        
    '''
    Step 1: 得到每張圖像的特徵
    '''
    print('Using %s representation for images.' % FEATURE)
    if FEATURE.lower() == 'tiny image':
 		# 若使用微圖像特徵提取方法,每張圖像會返回一個256*1的特徵列表,所以訓練集和測試集各得到一個1500*256的特徵矩陣。
        train_image_feats = get_tiny_images(train_image_paths)
        test_image_feats  = get_tiny_images(test_image_paths)

    elif FEATURE.lower() == 'bag of words':
        if not os.path.isfile('vocab.npy'):
        	#詞袋數據文件,由build_vocabulary()函數生成,需要耗費大量時間,所以以生成則直接調用。
            print('No existing visual word vocabulary found. Computing one from training images.')
            vocab_size = 200 #詞袋大小
			#構建詞袋
            vocab = build_vocabulary(train_image_paths, vocab_size)
            np.save('vocab.npy', vocab)
        #訓練集和測試集圖片的詞袋直方圖表示
        train_image_feats = get_bags_of_words(train_image_paths)
        test_image_feats  = get_bags_of_words(test_image_paths)

    elif FEATURE.lower() == 'placeholder':
        train_image_feats = []
        test_image_feats = []
    else:
        raise ValueError('Unknown feature type!')

    '''
 	Step 2: 通過分類器訓練測試集圖像並進行分類,返回一個N*1的列表,其中N是測試集大小,存儲了與之前定義的categories對應的類別。
    '''
    print('Using %s classifier to predict test set categories.' % CLASSIFIER)

    if CLASSIFIER.lower() == 'nearest neighbor':
        predicted_categories = nearest_neighbor_classify(train_image_feats, train_labels, test_image_feats)

    elif CLASSIFIER.lower() == 'support vector machine':
        predicted_categories = svm_classify(train_image_feats, train_labels, test_image_feats)

    elif CLASSIFIER.lower() == 'placeholder':
		#爲每張圖片隨機分類
        random_permutation = np.random.permutation(len(test_labels))
        predicted_categories = [test_labels[i] for i in random_permutation]
    else:
        raise ValueError('Unknown classifier type')

 	'''
    ## Step 3:結果可視化,該函數項目本身已經提供。
    '''
    create_results_webpage2( train_image_paths, \
                            test_image_paths, \
                            train_labels, \
                            test_labels, \
                            categories, \
                            abbr_categories, \
                            predicted_categories, \
                            FEATURE, \
                            CLASSIFIER)

1. 圖像特徵表示

我們通過兩種相互獨立的手段獲取圖像的特徵,第一種手段是直接獲得圖像的tiny特徵,優點是速度快,缺點是精度差,最後的分類準確率大於在20%左右,用來對比襯托出詞袋模型的高準確率。第二種手段就是通過構建詞袋模型,具體過程在下面會結合相關函數介紹,分類準確率在60%左右,相比於tiny特徵有很大提升,缺點是訓練時間較長。

1.1. 圖像的tiny特徵表示

微型圖像表徵具有理解容易,實現簡單等優點。Torralba等人的微圖像特徵表示技術可以在最小的尺度空間內表示原始圖像的特徵。一個簡單的做法是把原圖像縮小到固定像素(這裏我設置成16*16像素)並進行歸一化操作,最後進行一維輸出。

函數:get_tiny_images()

def get_tiny_images(image_paths):
    N = len(image_paths)
    size = 16
    tiny_images = []
    for each in image_paths:
      image = Image.open(each)
      image = image.resize((size, size))
      image = (image - np.mean(image))/np.std(image)
      image = image.flatten()
      tiny_images.append(image)
    tiny_images = np.asarray(tiny_images)
    return tiny_images

效果

原始圖像,微小化圖像,歸一化圖像依次如下圖所示。
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

1.2 圖像的詞袋模型構建

具體操作過程中主要有以下幾步:

  1. 利用SIFT或者HOG算法,從訓練集的每張圖像中提取局部特徵(一個圖像大約有幾十個),每一類圖片、每一張圖片所有的局部特徵都聚集在一起。
  2. 利用K-means對訓練集的局部特徵進行聚類,聚成k(項目中爲200)類,合併相近的特徵,構成一個視覺詞彙(就是每一類的中心),至此,我們就得到了一個包含K個詞彙的單詞表。
  3. 利用單詞表中的詞彙表示圖像,具體過程見1.2.2

1.2.1 圖像特徵提取,以及詞袋構建

函數:build_vocabulary(image_paths, vocab_size):

功能:訓練集圖片的HOG特徵提取,使用K-means對提取特徵進行聚類後返回特徵中心。

def build_vocabulary(image_paths, vocab_size):
    image_list = [imread(file) for file in image_paths]
    cells_per_block = (2, 2)
    z = cells_per_block[0]
    pixels_per_cell = (4, 4)
    feature_vectors_images = []
    for image in image_list:
        feature_vectors = hog(image, feature_vector=True, pixels_per_cell=pixels_per_cell,cells_per_block=cells_per_block, visualize=False)
        feature_vectors = feature_vectors.reshape(-1, z*z*9)
        feature_vectors_images.append(feature_vectors)
    all_feature_vectors = np.vstack(feature_vectors_images)
    kmeans = MiniBatchKMeans(n_clusters=vocab_size, max_iter=500).fit(all_feature_vectors) # change max_iter for lower compute time
    vocabulary = np.vstack(kmeans.cluster_centers_)
    return vocabulary

具體步驟:

  1. 基於scikit-image的HOG特徵提取
    相關參數:
    -> image: input image, 輸入圖像
    -> feature_vector: 將輸出轉換爲一維向量.
    -> pixels_per_cell : 每個cell的像素數, 是一個tuple類型數據,本項目中爲(4,4)
    -> cell_per_block : 每個BLOCK內有多少個cell, tuple類型, 本項目中爲(2,2), 意思是將block均勻劃分爲2x2的塊
    -> visualise: 是否輸出梯度圖

    輸入圖像和梯度效果圖如下所示:
    輸入圖像
    HOG梯度圖

  2. 基於scikit-image的mini batch K-means特徵聚類
    相關參數:
    -> n_clusters:整形,缺省值=8 ,生成的聚類數,即產生的質心(centroids)數。
    -> max_iter:整形,缺省值=300 ,執行一次k-means算法所進行的最大迭代數。
    方法:
    -> fit(X[,y]): 計算k-means聚類。

1.2.2 統計詞頻,構建圖像直方圖

在上一步構建完詞典後,下面通過該特徵詞典,分別統計訓練集和測試集中每幅圖像的詞頻。
具體做法: 我們把之前每一個圖像分割成很多的patch,現在統計每一個patch和前面聚類得到的每一箇中心的距離。離哪個質心近,直方圖中相對應的bin就加1,計算完這幅圖像所有的patch後對直方圖進行歸一化(/L2),之後圖像就可以用這個直方圖進行表示了(images_histograms[i] = histogram)
在所有圖像計算完成之後,就可以進行分類了。

函數get_bags_of_words(image_paths)
def get_bags_of_words(image_paths):
    vocab = np.load('vocab.npy')
    print('Loaded vocab from file.')
    vocab_length = vocab.shape[0]
    image_list = [imread(file) for file in image_paths]
    # Instantiate empty array
    images_histograms = np.zeros((len(image_list), vocab_length))
    cells_per_block = (2, 2) # Change for lower compute time
    z = cells_per_block[0]
    pixels_per_cell = (4, 4) # Change for lower compute time
    feature_vectors_images = []
    for i, image in enumerate(image_list):
        feature_vectors = hog(image, feature_vector=True, pixels_per_cell=pixels_per_cell,cells_per_block=cells_per_block, visualize=False)
        feature_vectors = feature_vectors.reshape(-1, z*z*9)
        histogram = np.zeros(vocab_length)
        distances = cdist(feature_vectors, vocab)  
        closest_vocab = np.argsort(distances, axis=1)[:,0]
        indices, counts = np.unique(closest_vocab, return_counts=True)
        histogram[indices] += counts
        histogram = histogram / norm(histogram)
        images_histograms[i] = histogram
    return images_histograms

2. 分類器的構建

2.1. 使用KNN分類器進行特徵分類

我們通過查找訓練集圖像的相應最似特徵來預測測試圖像的類別。對於任意給定的KNN參數K給出相對於測試集圖像最近的K個鄰居,並通過投票法返回最臨近鄰居的分類。
輸入參數:

  1. 訓練圖像特徵:一個n*d的numpy數組,n是訓練集大小,d是特徵向量維度。
  2. 訓練圖像標籤:一個n*1的列表,記錄了訓練數據的真實分類標籤。
  3. 測試圖像特徵:一個m*d的numpy數組,m是測試集大小,d是特徵向量維度。

返回:
一個m*1的numpy數組,表示測試集中每個圖片的預測類別。
函數內容:

  1. 獲取每個測試圖像特徵與每個訓練圖像特徵之間的距離,這裏使用了歐式距離,輸入兩個d* n的矩陣(d是每個圖片特徵數,n是圖片數)返回n* n的矩陣,表示測試集每個圖片和訓練集每個圖片之間的距離。
  2. 找出與每個測試圖像距離最短的k個訓練集圖片。
  3. 確定這k個訓練集圖片的類別標籤。
  4. 用投票法選出最常見的標籤,作爲該測試圖片的預測類別。

函數:nearest_neighbor_classify()

def nearest_neighbor_classify(train_image_feats, train_labels, test_image_feats):
    k = 5;
    distances = cdist(test_image_feats, train_image_feats, 'euclidean')
    sorted_indices = np.argsort(distances, axis=1)
    knns = sorted_indices[:,0:k]
    labels = np.zeros_like(knns)
    get_labels = lambda t: train_labels[t]
    vlabels = np.vectorize(get_labels)
    labels = vlabels(knns) 
    labels = mode(labels,axis=1)[0]
    return labels

2.2. 使用SVM分類器進行特徵分類

函數:svm_classify(train_image_feats, train_labels, test_image_feats)

輸入參數:

  1. 訓練圖像特徵:一個n*d的numpy數組,n是訓練集大小,d是特徵向量維度。
  2. 訓練圖像標籤:一個n*1的列表,記錄了訓練數據的真實分類標籤。
  3. 測試圖像特徵:一個m*d的numpy數組,m是測試集大小,d是特徵向量維度。

返回:
一個m*1的numpy數組,表示測試集中每個圖片的預測類別。

sklearn svm.LinearSVC參數說明:
-> random_state : 在隨機數據混洗時使用的僞隨機數生成器的種子。 如果是int,則random_state是隨機數生成器使用的種子。
->tol : float, 公差停止標準。

def svm_classify(train_image_feats, train_labels, test_image_feats):
    clf = LinearSVC(random_state=0, tol=1e-5)
    clf.fit(train_image_feats, train_labels)
    test_predictions = clf.predict(test_image_feats)
    return test_predictions

結果展示

特徵提取器:NULL
分類器:NULL
準確率:6.9%(純隨機)
在這裏插入圖片描述
特徵提取器:Tiny
分類器:KNN
準確率:18.9%
在這裏插入圖片描述
特徵提取器:Tiny
分類器:SVM
準確率:22.3%
在這裏插入圖片描述

特徵提取器:BOW(HOG+K-means)
分類器:KNN
準確率:58.1%
在這裏插入圖片描述
特徵提取器:BOW(HOG+K-means)
分類器:SVM
準確率:68.9%
在這裏插入圖片描述

參考資料

Introduction to Computer Vision
Scene Recognition with Bag of Words
基於詞袋模型的圖像分類算法
Beyond Bags of Features: Spatial Pyramid Matching for Recognizing Natural Scene Categories
Histograms of oriented gradients for human detection
Learn OpenCV-Histogram of Oriented Gradients
SVM入門(十)將SVM用於多類分類

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章