OpenCV進階---介紹SVM

red_ear發表於2019-04-02

1. 學習目標:

目標OpenCV函式
訓練cv::ml::SVM::train
測試cv::ml::SVM::test

2. OpenCV理論

       支援向量機(SVM)是由超平面定義的判別分類器。 換句話說,給定標記的訓練資料(監督學習),演算法輸出最佳超平面,用來對新示例進行分類。

對於屬於兩個類別之一的線性可分的2D點集合,找到分離的直線。:

                                                                                          

note

        在這個例子中,我們處理笛卡爾平面中的線和點,而不是高維空間中的超平面和向量。重要的是要理解這只是因為我們的直覺更好地建立在容易想象的例子上。但是,相同的概念適用於要分類的示例位於維度大於2的空間中的任務。
       在上圖中,您可以看到存在多行提供問題的解決方案。他們中的任何一個比其他人更好嗎?我們可以直觀地定義一個標準來估計線條的價值:如果線條過於接近點,則線條很差,因為它會對噪聲敏感,泛化能力弱。因此,我們的目標應該是儘可能從所有點找到通過的線。

然後,SVM演算法的操作基於找到給出訓練樣本的最大距離的超平面。這個距離在SVM理論中稱為“margin”。因此,最佳超平面為訓練的輸出結果。

  •                                                                                  

如何計算最佳超平面 

                                                           f(x)= \beta ^{_{0}}+\beta ^{T}x

β為權重(斜率),β0為偏置(截距)。

通過縮放β和β0,可以以無數種不同的方式表示最佳超平面(平行線)。 在超平面的所有可能表示中,一般所選擇的是

                                                        

其中x表示最接近超平面的訓練樣本。 通常,最接近超平面的訓練樣本稱為支援向量。 這種表示稱為規範超平面

下面計算點x與超平面(β,β0)的距離distance:

                                                         

特別地,對於規範超平面,分子等於1並且到支援向量的距離是

                                               

上文中引入的邊距margin(此處表示為M)是距離支援向量距離的兩倍: 

                                                             

最後,使M最大化的問題等同於在受到某些約束的情況下最小化函式L(β)的問題。 約束模擬超平面的要求,以正確地分類所有訓練樣本xi。從形式上看, 

                                        

yi為樣本對應的labels。

這是拉格朗日優化的問題,其可以使用拉格朗日乘數來求解以獲得權重向量β和最優超平面的偏差β0。

詳細推導請看SVM數學理論基礎

 

3. Code

#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/ml.hpp>
using namespace cv;
using namespace cv::ml;
int main(int, char**)
{
    // Set up training data
    int labels[4] = {1, -1, -1, -1};
    float trainingData[4][2] = { {501, 10}, {255, 10}, {501, 255}, {10, 501} };
    Mat trainingDataMat(4, 2, CV_32F, trainingData);
    Mat labelsMat(4, 1, CV_32SC1, labels);
    // Train the SVM
    Ptr<SVM> svm = SVM::create();
    svm->setType(SVM::C_SVC);
    svm->setKernel(SVM::LINEAR);
    svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 100, 1e-6));
    svm->train(trainingDataMat, ROW_SAMPLE, labelsMat);
    // Data for visual representation
    int width = 512, height = 512;
    Mat image = Mat::zeros(height, width, CV_8UC3);
    // Show the decision regions given by the SVM
    Vec3b green(0,255,0), blue(255,0,0);
    for (int i = 0; i < image.rows; i++)
    {
        for (int j = 0; j < image.cols; j++)
        {
            Mat sampleMat = (Mat_<float>(1,2) << j,i);
            float response = svm->predict(sampleMat);
            if (response == 1)
                image.at<Vec3b>(i,j)  = green;
            else if (response == -1)
                image.at<Vec3b>(i,j)  = blue;
        }
    }
    // Show the training data
    int thickness = -1;
    circle( image, Point(501,  10), 5, Scalar(  0,   0,   0), thickness );
    circle( image, Point(255,  10), 5, Scalar(255, 255, 255), thickness );
    circle( image, Point(501, 255), 5, Scalar(255, 255, 255), thickness );
    circle( image, Point( 10, 501), 5, Scalar(255, 255, 255), thickness );
    // Show support vectors
    thickness = 2;
    Mat sv = svm->getUncompressedSupportVectors();
    for (int i = 0; i < sv.rows; i++)
    {
        const float* v = sv.ptr<float>(i);
        circle(image,  Point( (int) v[0], (int) v[1]), 6, Scalar(128, 128, 128), thickness);
    }
    imwrite("result.png", image);        // save the image
    imshow("SVM Simple Example", image); // show it to the user
    waitKey();
    return 0;
}

4. 程式碼詳解

training data

int labels[4] = {1, -1, -1, -1};
float trainingData[4][2] = { {501, 10}, {255, 10}, {501, 255}, {10, 501} };

之後將使用的函式cv :: ml :: SVM :: train需要將訓練資料儲存為浮點數的cv :: Mat物件。 因此,我們從上面定義的陣列建立這些物件:

    Mat trainingDataMat(4, 2, CV_32F, trainingData);
    Mat labelsMat(4, 1, CV_32SC1, labels);

設定SVM引數

    Ptr<SVM> svm = SVM::create();
    svm->setType(SVM::C_SVC);
    svm->setKernel(SVM::LINEAR);
    svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 100, 1e-6));
  • SVM的型別。我們在這裡選擇可用於n級分類的型別C_SVC(n≥2)。這種型別的重要特徵是它處理非線性類效果好(即,當訓練資料是非線性可分離的時)。此功能在這裡並不重要,因為資料是線性可分的,我們選擇此SVM型別只是最常用的。
  • SVM核心的型別。我們還沒有談到核心函式,因為它們對我們正在處理的訓練資料不感興趣。不過,我們現在簡要解釋一下核心函式背後的主要思想。它是對訓練資料的對映,以改善其與線性可分離資料集的相似性。此對映包括增加資料的維度,並使用核心函式高效完成。我們在這裡選擇LINEAR型別,這意味著不需要對映。此引數使用cv :: ml :: SVM :: setKernel定義。
  • 演算法的停止條件。實現SVM訓練過程以迭代方式求解約束二次優化問題。在這裡,我們指定了最大迭代次數和容差誤差,因此我們允許演算法以較少的步數完成,即使尚未計算最佳超平面。此引數在結構cv :: TermCriteria中定義。
  • 訓練SVM我們呼叫方法cv :: ml :: SVM :: train來構建SVM模型。
    svm->train(trainingDataMat, ROW_SAMPLE, labelsMat);

 SVM分類

方法cv :: ml :: SVM :: predict用於使用訓練的SVM對輸入樣本進行分類。 在此示例中,我們使用此方法以根據SVM執行的預測為空間著色。 換句話說,遍歷影像將其畫素解釋為笛卡爾平面的點。 每個點都根據SVM預測的類別著色; 如果它是帶有標籤1的類,則為綠色;如果是帶有標籤-1的類,則為藍色。

Vec3b green(0,255,0), blue(255,0,0);
    for (int i = 0; i < image.rows; i++)
    {
        for (int j = 0; j < image.cols; j++)
        {
            Mat sampleMat = (Mat_<float>(1,2) << j,i);
            float response = svm->predict(sampleMat);
            if (response == 1)
                image.at<Vec3b>(i,j)  = green;
            else if (response == -1)
                image.at<Vec3b>(i,j)  = blue;
        }
    }

 支援向量

       我們在這裡使用幾種方法來獲取有關支援向量的資訊。 方法cv :: ml :: SVM :: getSupportVectors獲取所有支援向量。 我們在這裡使用這些方法來查詢支援向量的訓練樣本並突出顯示它們。

thickness = 2;
    Mat sv = svm->getUncompressedSupportVectors();
    for (int i = 0; i < sv.rows; i++)
    {
        const float* v = sv.ptr<float>(i);
        circle(image,  Point( (int) v[0], (int) v[1]), 6, Scalar(128, 128, 128), thickness);
    }

 

Result

  • 程式碼開啟一個影像並顯示兩個類的訓練示例。 一個類的點用白色圓圈表示,另一個類為黑色。
  • SVM經過訓練並用於對影像的所有畫素進行分類。 這導致影像在藍色區域和綠色區域中分割。 兩個區域之間的邊界是最佳分離超平面。
  • 最後,使用訓練示例周圍的灰色環顯示支援向量。
  •             

 

 

相關文章