Opencv學習之路—Opencv下基於HOG特征的KNN算法分類訓練


在計算機視覺研究當中,HOG算法和LBP算法算是基礎算法,但是卻十分重要。后期很多圖像特征提取的算法都是基於HOG和LBP,所以了解和掌握HOG,是學習計算機視覺的前提和基礎。

HOG算法的原理很多資料都可以查到,簡單來說,就是將圖像分成一個cell,通過對每個cell的像素進行梯度處理,進而根據梯度方向和梯度幅度來得到cell的圖像特征。隨后,將每個cell的圖像特征連接起來,得到一個BLock的特征,進而得到一張圖片的特征。Opencv當中自帶HOG算法,可以直接調用,進行圖像的特征提取。但是作為一個初學者,自然應該自己手寫一下HOG算法,這樣能夠更加透徹地去理解。

下面是我自己寫的HOG,代碼比較粗糙,為了適應下面的KNN分類器,HOG算法的接口設計為輸入一張圖片,返回一個vector向量。

class HOG{
private:
    Mat img;
public:
    vector<float>bins;                                //返回一個圖片的HOG特征;
    void GetImage(Mat src);
    void Cut_to_Block();                               //將圖片分割成一個個Block;
    void Cut_to_Cell(int pixel_x, int pixel_y);        //將圖片分割成一個個Cell;
    void Cell_to_bin(int x, int y);                    //對每個Cell進行處理,得到每個Cell的bins;
};

void HOG::GetImage(Mat src){
    bins.clear();
    cvtColor(src, img, COLOR_RGB2GRAY);
    Cut_to_Block();
}

void HOG::Cut_to_Block(){
    for (int i = 1; i <= img.rows - 17; i = i + 8){
        for (int j = 1; j <= img.cols - 17; j = j + 8){
            Cut_to_Cell(i, j);
        }
    }
}

void HOG::Cut_to_Cell(int pixel_x, int pixel_y){
    for (int i = pixel_x, m = 0; m < 2; i = i + 8, m++){
        for (int j = pixel_y, n = 0; n < 2; j = j + 8, n++){
            Cell_to_bin(i, j);
        }
    }
}

void HOG::Cell_to_bin(int x, int y){
    int pixel_x;                  //cell的像素的起始位置行坐標;
    int pixel_y;                  //cell的像素的起始位置縱坐標;
    float pixel[10][10];         //我們一般默認cell為8*8的像素大小,但是為了儲存周邊店的像素,需要多加兩個像素儲存點的位置;
    float gradient_M[9][9];            //保存梯度的幅值;
    float gradient_Angle[9][9];        //保存像素梯度的方向;
    float gradient_h[9][9];
    float gradient_v[9][9];
    float bin[9];                    //存放一個Cell當中的bins值;

    pixel_x = x;
    pixel_y = y;

    //為了計算方便,我們將每個Cell的像素先提取出來,存放在pixel[][]當中;
    for (int i = pixel_x - 1, m = 0; i < pixel_x + 9; i++, m++){
        uchar *data = img.ptr<uchar>(i);
        for (int j = pixel_y - 1, n = 0; j < pixel_y + 9; j++, n++){
            pixel[m][n] = data[j];
        }
    }

    //計算每個像素的梯度幅值和梯度角度;
    for (int i = 1; i<9; i++){
        for (int j = 1; j<9; j++){
            gradient_h[i][j] = pixel[i + 1][j] - pixel[i - 1][j];
            gradient_v[i][j] = pixel[i][j + 1] - pixel[i][j - 1];
            gradient_M[i][j] = sqrt(gradient_h[i][j] * gradient_h[i][j] + gradient_v[i][j] * gradient_v[i][j]);
            gradient_Angle[i][j] = atan2(gradient_h[i][j], gradient_v[i][j]) * 180;
        }
    }

    //根據每個像素的幅值進行維度的區分分類;
    for (int i = 0; i<9; i++){
        bin[i] = 0;
    }

    for (int i = 1; i<9; i++){
        for (int j = 1; j<9; j++){
            if ((gradient_Angle[i][j] >= 0 && gradient_Angle[i][j]<20) || (gradient_Angle[i][j] >= 180 && gradient_Angle[i][j]<200)){
                bin[0] = bin[0] + gradient_M[i][j];
            }
            if ((gradient_Angle[i][j] >= 20 && gradient_Angle[i][j]<40) || (gradient_Angle[i][j] >= 200 && gradient_Angle[i][j]<220)){
                bin[1] = bin[1] + gradient_M[i][j];
            }
            if ((gradient_Angle[i][j] >= 40 && gradient_Angle[i][j]<60) || (gradient_Angle[i][j] >= 220 && gradient_Angle[i][j]<240)){
                bin[2] = bin[2] + gradient_M[i][j];
            }
            if ((gradient_Angle[i][j] >= 60 && gradient_Angle[i][j]<80) || (gradient_Angle[i][j] >= 240 && gradient_Angle[i][j]<260)){
                bin[3] = bin[3] + gradient_M[i][j];
            }
            if ((gradient_Angle[i][j] >= 80 && gradient_Angle[i][j]<100) || (gradient_Angle[i][j] >= 260 && gradient_Angle[i][j]<280)){
                bin[4] = bin[4] + gradient_M[i][j];
            }
            if ((gradient_Angle[i][j] >= 100 && gradient_Angle[i][j]<120) || (gradient_Angle[i][j] >= 280 && gradient_Angle[i][j]<300)){
                bin[5] = bin[5] + gradient_M[i][j];
            }
            if ((gradient_Angle[i][j] >= 120 && gradient_Angle[i][j]<140) || (gradient_Angle[i][j] >= 300 && gradient_Angle[i][j]<320)){
                bin[6] = bin[6] + gradient_M[i][j];
            }
            if ((gradient_Angle[i][j] >= 140 && gradient_Angle[i][j]<160) || (gradient_Angle[i][j] >= 320 && gradient_Angle[i][j]<340)){
                bin[7] = bin[7] + gradient_M[i][j];
            }
            if ((gradient_Angle[i][j] >= 160 && gradient_Angle[i][j] <= 180) || (gradient_Angle[i][j] >= 340 && gradient_Angle[i][j] <= 360)){
                bin[8] = bin[8] + gradient_M[i][j];
            }
        }
    }

    //歸一化;
    float sum_bin = 0;
    for (int i = 0; i<9; i++){
        sum_bin = sum_bin + bin[i];
    }
    for (int i = 0; i<9; i++){
        bin[i] = bin[i] / sum_bin;
        if (bin[i]>0.2){
            bin[i] = 0.2;
        }
    }
    sum_bin = 0;
    for (int i = 0; i<9; i++){
        sum_bin = sum_bin + bin[i];
    }
    for (int i = 0; i<9; i++){
        bin[i] = bin[i] / sum_bin;
    }

    //返回bin[]的值到bins向量當中;
    for (int i = 0; i < 9; i++){
        bins.push_back(bin[i]);
    }
}

寫完了HOG算法,下面就開始寫KNN 分類器了。KNN算法很容易理解,就是在一個元素周圍選取最鄰近的K個元素,然后分析這k個元素當中,哪一類占的比例最大,那么這個元素就屬於該類。

同樣Opencv當中也有KNN算法,為類CvKNearest(),直接調用便可以進行訓練,具體地可以查閱相關文檔。

class KNN{
private:
    vector < vector < float >> datatrain;
    vector<int> dataclass;
    CvKNearest *knn;
public:
    KNN();
    //對從HOG算法傳遞出來的數據進行整合處理,src表示一張圖的HOG特征數組,classfile表示這張圖所代表的分類;
    void Data_integration(vector<float> src, int classfile);
    void KNN_Train();                      //將HOG得到的數據進行相關處理,然后進行KNN訓練;
    int KNN_Test(vector<float> src);      //將KNN訓練好之后,傳入一個HOG特征值,返回一個分類;
};

KNN::KNN(){
    knn=new CvKNearest();
}

void KNN::Data_integration(vector<float> src, int classfile){
    datatrain.push_back(src);
    dataclass.push_back(classfile);
}

void KNN::KNN_Train(){
    CvMat *DataTrain=cvCreateMat(390,900,CV_32FC1);
    CvMat *DataClass=cvCreateMat(390,1,CV_32FC1);
    for(int i=0;i<390;i++){
        cvmSet(DataClass,i,0,dataclass[i]);
        for(int j=0;j<900;j++){
            cvmSet(DataTrain,i,j,datatrain[i][j]);
        }
    }
    knn->train(DataTrain,DataClass,0,false,30,false);
}

int KNN::KNN_Test(vector<float> src){
    CvMat *DataSample = cvCreateMat(1, 900, CV_32FC1);
    for (int i = 0; i < 900; i++){
        cvmSet(DataSample,0,i,src[i]);
    }
    int k;
    k = (int)knn->find_nearest(DataSample, 30);
    return k;
}

有個HOG 和 KNN,那現在就可以進行訓練了。我有了13類車牌圖片進行訓練,每類30張。

在這里,有一個東西要注意一下,那就是批量讀取圖片。我采用了一個很笨的方法,那就是把每張圖片的地址存在一個txt文檔當中,然后先讀取地址,然后在讀取圖片。這樣的方法,在圖片數量較少的情況下可以使用的,但是圖片數量成千上百張,就很麻煩了。

int main(){
    HOG Hog;
    KNN Knn;
    string Imageadress[390];
    ifstream fin("train.txt");           //圖片地址事先保存在train.txt文件當中;
    for (int i = 0; i<390; i++){
        getline(fin, Imageadress[i]);    //從文件當中一行一行讀出地址,保存到Imgaeadree當中;
    }

    Mat Image[390];
    for (int i = 0; i < 390; i++){
        Image[i] = imread(Imageadress[i], 1);   //讀入圖片文件;
    }

    for (int i = 0; i < 390; i++){
        int k = 0;
        k = i / 30;             //通過整除30,來獲得該圖片屬於哪個分類當中的;
        Hog.GetImage(Image[i]);
        Knn.Data_integration(Hog.bins, k);
    }
    Knn.KNN_Train();

    //進行檢測;
    ifstream testin("test4.txt");
    string testImageadress[70];
    Mat testimg[70];
    for(int i=0;i<70;i++)
    {
        getline(testin,testImageadress[i]);
    }

    for(int i=0;i<70;i++){
        testimg[i]=imread(testImageadress[i],1);
    }

    int count=0;
    for(int i=0;i<70;i++){
        int k;
        Hog.GetImage(testimg[i]);
        k=Knn.KNN_Test(Hog.bins);
        cout<<k<<endl;
        if(k!=3){
            count++;
        }
    }
    cout<<"錯誤的數量:"<<count<<endl;
}

訓練完畢之后,我又使用13類圖片,每類70張,進行檢測分類。

很不幸,識別結果不是很理想,奔馳等簡單的車牌識別率很高,可以達到百分之百,但是復雜的車牌識別率就瞬間下來了,當中的原因,是因為HOG算法寫得有問題啊,不夠好,需要改進。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM