1. 程式人生 > >基於支援向量機的影象分類(下篇:MATLAB實現)

基於支援向量機的影象分類(下篇:MATLAB實現)

摘要:本文通過圖文詳細介紹如何利用支援向量機對影象進行分類,經過上篇文章對原理的介紹,這裡介紹利用MATLAB程式設計實現。後續章節將介紹的主要部分有:

  • 圖片資料集整理
  • 特徵提取
  • SVM訓練與測試
  • 分類結果評價
  • 結果顯示

1. 前言

機器學習是人工智慧研究發展到一定階段的必然產物。二十世紀八十年代是機器學習成為一個獨立學科的學科領域、各種機器學習技術百花綻放的時期。支援向量機於1995年正式發表[Cortes and Vapnik,1995],由於在文字分類任務中的卓越效能[Joachims,1998],很快成為機器學習的主流技術,並直接掀起了“統計學習”(statistical learning

)在2000年前後的高潮。——《機器學習》 周志華

2010年前後,隨著計算能力的迅猛提升和大資料的湧現,神經網路研究在“深度學習”的名義下又重新崛起,並迎來又一次發展高潮。近年研究SVM的論文少了很多,SVM的風頭很多時候確實已被強勢崛起的深度學習浪潮所淹沒,95年的SVM比我們年齡還大,有點彷彿英雄遲暮的感覺。不過在我看來,實現簡單而且非常強大的分類演算法SVM仍然有其研究價值,與神經網路相比SVM亦有過人之處,如特徵維數多於樣本數的情況,而小樣本學習至今仍是深度學習的一大難題。

當淺層神經網路效果不佳時,人們將目光轉向支援向量機,而支援向量機亦不負眾望,以不錯的成績讓人們對機器學習重拾信心。感謝支援向量機,感謝在神經網路幾經起落的時候,支援向量機繼往開來、自成一脈,填補了機器學習的一段空窗期,讓這條曲折向上的研究之路綿延至今迎來了現在人工智慧百花齊放的時代!

接下來就通過簡單的圖片分類問題,通過MATLAB程式理解認識一下這一簡單而強大的分類演算法——支援向量機.

2. 圖片資料集整理

首先需要準備好分類的資料集,資料的整理是機器學習中的重要一環。這裡我們自行整理一個用於分類的圖片集,圖片集有四類圖片,分別為車、貓、花、魚。從百度上下載這四種圖片,並分別存放在四個資料夾中,如下圖所示

四類圖片每類分別下載100張左右的圖片,這四百張圖片作為分類的資料集,以7:3的比例將其分為訓練圖片集和測試圖片集,分別放到picturestestPictures兩個資料夾中。這兩個資料夾下同上圖一樣都有car、cat、flw、fsh四個資料夾,值得注意的是測試樣本的圖片應可能不出現在訓練集圖片庫中。做好以上工作,用於分類的圖片集就準備完畢了。

(當然用於分類的圖片集常用的是cafir10圖片集,這個資料集是寫論文或研究時普遍用到的,可能會在後面的文章中介紹其用法,這裡就暫時不使用cafir了。)

為了便於後面的特徵提取等對每張圖片進行的操作,這裡在程式中有必要將圖片檔案的儲存位置、數量、類別等資訊整理到一個數據結構中,新建一個m檔案,程式程式碼如下

dir=('D:\pictures');
testdir=('D:\testPictures\test');
trainingSet = imageSet(dir,'recursive');
testSet = imageSet(testdir,'recursive');

以上程式碼中用到*imageSet( )函式是一個圖片集整理的函式(MATLAB R2016b及以上版本支援),返回的是dir檔案路徑下資料夾內的檔案資訊。例如得到的trainingSet為一個14的imageSet變數,每個imageSet變數由Description、ImageLocation、Count三個屬性組成,分別代表對子檔案的描述、檔案儲存位置和圖片數量。如下圖所示是testSet(1)內部情況

3. 主要步驟

和深度學習的演算法相比,傳統的機器學習在進行圖片分類時輸入的不是原始圖片而是先進行一個特徵提取的步驟。在上篇中已經介紹了特徵提取的相關內容,這裡用的是方向梯度直方圖(HOG)以及灰度共生矩陣(GLCM)。

3.1 GLCM提取

MATLAB中灰度共生矩陣的提取可以呼叫*graycomatrix( )*函式,不過這裡為了取不同方向(0、45、90、135度)的灰度共生矩陣,通過迴圈計算各個方向的灰度共生矩陣並進行歸一化處理(計算對比度、逆差距、熵、自相關),然後取平均值和方差作為最終提取的特徵。

新建一個m檔案並命名為getGLCMFeatures,輸入以下程式碼

function [features] = getGLCMFeatures(image)
features_all  = [];
for i = 1:10
    glcm = graycomatrix(image, 'Offset', [0,i]);
    stats = graycoprops(glcm);
    
    glcm45 = graycomatrix(image, 'Offset', [-i,i]);
    stats45 = graycoprops(glcm45);
    
    glcm90 = graycomatrix(image, 'Offset', [-i,0]);
    stats90 = graycoprops(glcm90);
    
    glcm135 = graycomatrix(image, 'Offset', [-i,-i]);
    stats135 = graycoprops(glcm135);
    
    stats7x4 = [stats.Contrast stats.Correlation stats.Energy stats.Homogeneity;
        stats45.Contrast stats45.Correlation stats45.Energy stats45.Homogeneity;
        stats90.Contrast stats90.Correlation stats90.Energy stats90.Homogeneity;
        stats135.Contrast stats135.Correlation stats135.Energy stats135.Homogeneity];
    features_all = [features_all mean(stats7x4,1) std(stats7x4,0,1)];
end
features = features_all;

新建的getGLCMFeatures函式輸入為彩色影象轉換後的灰度影象矩陣,輸出為提取後的灰度共生矩陣特徵。

3.2 合併特徵

自己編寫一個提取特徵的函式命名為extractFeature,這個函式輸入為整理過的訓練集和測試集,輸出為訓練集的特徵、標籤和測試集的特徵、標籤。這個函式的功能是將HOG特徵和前面提取的GLCM特徵合併。

程式碼第2到13行是為了確定每張圖片提取特徵後得到的矩陣大小,以方便後面的操作同時也是為了預分配空間以提高程式碼效率。首先取第一張圖片進行灰度化以及閾值分割,將圖片大小調整在256*256的範圍(統一大小)分別進行HOG和GLCM的特徵提取,分別得到兩種特徵向量,取兩個向量的長度之和就是一張圖片特徵提取後的總長度了。

function [trainingFeatures,trainingLabels,testFeatures,testLabels]=extractFeature(trainingSet,testSet)
%% 確定特徵向量尺寸
img = read(trainingSet(1), 1);
%轉化為灰度影象
img=rgb2gray(img);
%轉化為2值影象
lvl = graythresh(img);
img = im2bw(img, lvl);
img=imresize(img,[256 256]);
cellSize = [4 4];
[hog_feature, vis_hog] = extractHOGFeatures(img,'CellSize',cellSize);
glcm_feature = getGLCMFeatures(img);
SizeOfFeature = length(hog_feature)+ length(glcm_feature);

%% 構建訓練樣本特徵向量和訓練樣本標籤
trainingFeatures = [];
trainingLabels   = [];
for digit = 1:numel(trainingSet)       
    numImages = trainingSet(digit).Count;
    features  = zeros(numImages, SizeOfFeature, 'single');%初始化特徵向量
    % 遍歷每張圖片
    for i = 1:numImages
        img = read(trainingSet(digit), i);% 取出第i張圖片
        
        img=rgb2gray(img);                % 轉化為灰度影象
        glcm_feature = getGLCMFeatures(img);  % 提取GLCM特徵
       
        lvl = graythresh(img);            % 閾值化
        img = im2bw(img, lvl);            % 轉化為2值影象
        img=imresize(img,[256 256]);
        % 提取HOG特徵
        [hog_feature, vis_hog] = extractHOGFeatures(img,'CellSize',cellSize);
        % 合併兩個特徵
        features(i, :) = [hog_feature glcm_feature];
    end
    % 使用影象描述作為訓練標籤
    labels = repmat(trainingSet(digit).Description, numImages, 1);  
    % 逐個新增每張訓練圖片的特徵和標籤
    trainingFeatures = [trainingFeatures; features];
    trainingLabels   = [trainingLabels; labels];       
end


%% 提取測試圖片集的特徵向量
testFeatures = [];
testLabels   = [];
for digit = 1:numel(testSet)
           
    numImages = testSet(digit).Count;
    %初始化特徵向量
    features  = zeros(numImages, SizeOfFeature, 'single');
    
    for i = 1:numImages
        
        img = read(testSet(digit), i);
        %轉化為灰度影象
        img=rgb2gray(img);
        glcm_feature = getGLCMFeatures(img);
        %轉化為2值影象
        lvl = graythresh(img);
        img = im2bw(img, lvl);
        img=imresize(img,[256 256]);
        [hog_4x4, vis4x4] = extractHOGFeatures(img,'CellSize',cellSize);
        features(i, :) = [hog_4x4 glcm_feature];
    end
    
    % 使用影象描述作為訓練標籤
    labels = repmat(testSet(digit).Description, numImages, 1);
        
    testFeatures = [testFeatures; features];
    testLabels=[testLabels; labels];
        
end
end

程式碼18-41行是構建訓練樣本特徵向量和訓練樣本標籤,與前面步驟相似,只不過現在是遍歷訓練集每一張圖片,對其進行灰度化、閾值化、調整大小,然後進行特徵提取,將HOG特徵和GLCM特徵合併成一個向量作為特徵矩陣的一行即一張圖片的特徵向量。樣本的標籤構建則將每張圖片所處的資料夾的名字作為該圖片的標籤,並與特徵向量順序相對應。

第47-73行是構建測試樣本特徵向量和訓練樣本標籤,這裡將圖片集換成了測試集,而步驟與訓練集是一致的。

3.3 SVM訓練與測試

呼叫前面的特徵提取函式得到訓練和測試用的特徵向量與對應的標籤,便可以進行SVM的訓練和測試。MATLAB自帶的訓練svm函式可以用fitcecoc函式,測試可以用predict函式預測結果,訓練和測試的程式碼如下

% 訓練一個svm分類器
% fitcecoc 使用11的方案
classifier = fitcecoc(trainingFeatures, trainingLabels);
save classifier.mat classifier;

% 使用測試影象的特徵向量預測樣本標籤
predictedLabels = predict(classifier, testFeatures);

程式碼中classifier為訓練得到的SVM分類器,利用該分類器以及測試集特徵向量預測測試集的標籤predictLabels。後面可以將predictLabels與實際的測試標籤進行對比即可評估分類好壞。

3.4 分類結果評價

在上一篇文章中提到過了,為了評價分類的好壞可以通過混淆矩陣,通過計算混淆矩陣對角線上的值佔每行總數的比值得出分類正確率,其實現程式碼如下

%% 評估分類器
% 使用沒有標籤的影象資料進行測試,生成一個混淆矩陣表明分類效果
confMat=confusionmat(testLabels, predictedLabels)
accuracy=(confMat(1,1)/sum(confMat(1,:))+confMat(2,2)/sum(confMat(2,:))+...
    confMat(3,3)/sum(confMat(3,:))+confMat(4,4)/sum(confMat(4,:)))/4

其結果如下圖所示

3.5 結果顯示

儘管以上程式碼能得到分類正確率,但我們希望更直觀的看到輸入一張圖片後SVM分類器的分類結果,這裡編寫一個函式通過圖形視窗顯示預測結果。新建一個m檔案命名為Predict,輸入如下程式碼

function [] = Predict(imageurl)
load classifier.mat;
figure;
img = imread(imageurl);
imshow(img);

%提取影象的特徵向量
%轉化為灰度影象
img=rgb2gray(img);
glcm_feature = getGLCMFeatures(img);
%轉化為2值影象
lvl = graythresh(img);
img = im2bw(img, lvl);

% imshow(img);
% figure
img=imresize(img,[256 256]);
[hog_4x4, ~] = extractHOGFeatures(img,'CellSize',[4 4]);
testFeature = [hog_4x4 glcm_feature];


% 使用測試影象的特徵向量預測樣本標籤
predictedLabel = predict(classifier, testFeature);

str = ['分類結果:' predictedLabel];
dim = [0.25 0.0004 0.2 0.2];
annotation('textbox', dim, 'string', str, 'fontsize', 20, 'color', 'g','edgecolor', 'none');

函式輸入為圖片的儲存路徑,呼叫函式則會通過圖形視窗顯示圖片及分類結果,如在命令視窗輸入如下程式碼

Predict('D:\testPictures\test\car\car9.jpg');

輸出結果如下圖

4. 完整程式碼

為了方便使用這裡貼出完整程式碼

主函式:

clear;
dir=('D:\pictures');
testdir=('D:\testPictures\test');
trainingSet = imageSet(dir,'recursive');
testSet = imageSet(testdir,'recursive');

[trainingFeatures,trainingLabels,testFeatures,testLabels]=extractFeature(trainingSet,testSet);
%% 
%訓練一個svm分類器
%fitcecoc 使用11的方案
classifier = fitcecoc(trainingFeatures, trainingLabels);
save classifier.mat classifier;

% 使用測試影象的特徵向量預測樣本標籤
predictedLabels = predict(classifier, testFeatures);

%% 評估分類器
%使用沒有標籤的影象資料進行測試,生成一個混淆矩陣表明分類效果
confMat=confusionmat(testLabels, predictedLabels)
accuracy=(confMat(1,1)/sum(confMat(1,:))+confMat(2,2)/sum(confMat(2,:))+...
    confMat(3,3)/sum(confMat(3,:))+confMat(4,4)/sum(confMat(4,:)))/4

Predict('D:\testPictures\test\car\car9.jpg');

getGLCMFeatures.m:

function [features] = getGLCMFeatures(image)
features_all  = [];
for i = 1:10
    glcm = graycomatrix(image, 'Offset', [0,i]);
    stats = graycoprops(glcm);
    
    glcm45 = graycomatrix(image, 'Offset', [-i,i]);
    stats45 = graycoprops(glcm45);
    
    glcm90 = graycomatrix(image, 'Offset', [-i,0]);
    stats90 = graycoprops(glcm90);
    
    glcm135 = graycomatrix(image, 'Offset', [-i,-i]);
    stats135 = graycoprops(glcm135);
    
    stats7x4 = [stats.Contrast stats.Correlation stats.Energy stats.Homogeneity;
        stats45.Contrast stats45.Correlation stats45.Energy stats45.Homogeneity;
        stats90.Contrast stats90.Correlation stats90.Energy stats90.Homogeneity;
        stats135.Contrast stats135.Correlation stats135.Energy stats135.Homogeneity];
    features_all = [features_all mean(stats7x4,1) std(stats7x4,0,1)];
end
features = features_all;

extractFeature.m:

function [trainingFeatures,trainingLabels,testFeatures,testLabels]=extractFeature(trainingSet,testSet)
%% 確定特徵向量尺寸
img = read(trainingSet(1), 1);
%轉化為灰度影象
img=rgb2gray(img);
%轉化為2值影象
lvl = graythresh(img);
img = im2bw(img, lvl);
img=imresize(img,[256 256]);
cellSize = [4 4];
[hog_feature, vis_hog] = extractHOGFeatures(img,'CellSize',cellSize);
glcm_feature = getGLCMFeatures(img);
SizeOfFeature = length(hog_feature)+ length(glcm_feature);

%% 構建訓練樣本特徵向量和訓練樣本標籤
trainingFeatures = [];
trainingLabels   = [];
for digit = 1:numel(trainingSet)       
    numImages = trainingSet(digit).Count;
    features  = zeros(numImages, SizeOfFeature, 'single');%初始化特徵向量
    % 遍歷每張圖片
    for i = 1:numImages
        img = read(trainingSet(digit), i);% 取出第i張圖片
        
        img=rgb2gray(img);                % 轉化為灰度影象
        glcm_feature = getGLCMFeatures(img);  % 提取GLCM特徵
       
        lvl = graythresh(img);            % 閾值化
        img = im2bw(img, lvl);            % 轉化為2值影象
        img=imresize(img,[256 256]);
        % 提取HOG特徵
        [hog_feature, vis_hog] = extractHOGFeatures(img,'CellSize',cellSize);
        % 合併兩個特徵
        features(i, :) = [hog_feature glcm_feature];
    end
    % 使用影象描述作為訓練標籤
    labels = repmat(trainingSet(digit).Description, numImages, 1);  
    % 逐個新增每張訓練圖片的特徵和標籤
    trainingFeatures = [trainingFeatures; features];
    trainingLabels   = [trainingLabels; labels];       
end


%% 提取測試圖片集的特徵向量
testFeatures = [];
testLabels   = [];
for digit = 1:numel(testSet)
           
    numImages = testSet(digit)