1. 程式人生 > >HOG+SVM行人檢測的兩種方法

HOG+SVM行人檢測的兩種方法

關於HOG+SVM,CSDN上有一些非常好的文章,這裡給出我覺得寫的比較好的幾篇,僅供大家參考

以上就是個人覺得寫的比較好的部落格,基本上將上面的部落格看懂了,HOG也比較理解了,如果還想輸入瞭解HOG,建議直接看OpenCV HOG的原始碼

下面,就說說使用OpenCV 中的HOG+SVM實現行人檢測的兩種方式

說明:程式執行環境為VS2013+OpenCV3.0

第一種

先說第一種方式,直接上程式碼:

///////////////////////////////////HOG+SVM識別方式2///////////////////////////////////////////////////  
void Train()
{
    ////////////////////////////////讀入訓練樣本圖片路徑和類別///////////////////////////////////////////////////
    //影象路徑和類別
    vector<string> imagePath;
    vector<int
> imageClass; int numberOfLine = 0; string buffer; ifstream trainingData(string(FILEPATH)+"TrainData.txt"); unsigned long n; while (!trainingData.eof()) { getline(trainingData, buffer); if (!buffer.empty()) { ++numberOfLine; if
(numberOfLine % 2 == 0) { //讀取樣本類別 imageClass.push_back(atoi(buffer.c_str())); } else { //讀取影象路徑 imagePath.push_back(buffer); } } } //關閉檔案 trainingData.close(); ///
/////////////////////////////獲取樣本的HOG特徵///////////////////////////////////////////////////
//樣本特徵向量矩陣 int numberOfSample = numberOfLine / 2; Mat featureVectorOfSample(numberOfSample, 3780, CV_32FC1);//矩陣中每行為一個樣本 //樣本的類別 Mat classOfSample(numberOfSample, 1, CV_32SC1); Mat convertedImage; Mat trainImage; // 計算HOG特徵 for (vector<string>::size_type i = 0; i <= imagePath.size() - 1; ++i) { //讀入圖片 Mat src = imread(imagePath[i], -1); if (src.empty()) { cout << "can not load the image:" << imagePath[i] << endl; continue; } //cout << "processing:" << imagePath[i] << endl; // 歸一化 resize(src, trainImage, Size(64, 128)); // 提取HOG特徵 HOGDescriptor hog(cvSize(64, 128), cvSize(16, 16), cvSize(8, 8), cvSize(8, 8), 9); vector<float> descriptors; double time1 = getTickCount(); hog.compute(trainImage, descriptors);//這裡可以設定檢測視窗步長,如果圖片大小超過64×128,可以設定winStride double time2 = getTickCount(); double elapse_ms = (time2 - time1) * 1000 / getTickFrequency(); //cout << "HOG dimensions:" << descriptors.size() << endl; //cout << "Compute time:" << elapse_ms << endl; //儲存到特徵向量矩陣中 for (vector<float>::size_type j = 0; j <= descriptors.size() - 1; ++j) { featureVectorOfSample.at<float>(i, j) = descriptors[j]; } //儲存類別到類別矩陣 //!!注意類別型別一定要是int 型別的 classOfSample.at<int>(i, 0) = imageClass[i]; } ///////////////////////////////////使用SVM分類器訓練/////////////////////////////////////////////////// //設定引數,注意Ptr的使用 Ptr<SVM> svm = SVM::create(); svm->setType(SVM::C_SVC); svm->setKernel(SVM::LINEAR);//注意必須使用線性SVM進行訓練,因為HogDescriptor檢測函式只支援線性檢測!!! svm->setTermCriteria(TermCriteria(CV_TERMCRIT_ITER, 1000, FLT_EPSILON)); //使用SVM學習 svm->train(featureVectorOfSample, ROW_SAMPLE, classOfSample); //儲存分類器(裡面包括了SVM的引數,支援向量,α和rho) svm->save(string(FILEPATH) + "Classifier.xml"); /* SVM訓練完成後得到的XML檔案裡面,有一個數組,叫做support vector,還有一個數組,叫做alpha,有一個浮點數,叫做rho; 將alpha矩陣同support vector相乘,注意,alpha*supportVector,將得到一個行向量,將該向量前面乘以-1。之後,再該行向量的最後新增一個元素rho。 如此,變得到了一個分類器,利用該分類器,直接替換opencv中行人檢測預設的那個分類器(cv::HOGDescriptor::setSVMDetector()), */ //獲取支援向量機:矩陣預設是CV_32F Mat supportVector = svm->getSupportVectors();// //獲取alpha和rho Mat alpha;//每個支援向量對應的引數α(拉格朗日乘子),預設alpha是float64的 Mat svIndex;//支援向量所在的索引 float rho = svm->getDecisionFunction(0, alpha, svIndex); //轉換型別:這裡一定要注意,需要轉換為32的 Mat alpha2; alpha.convertTo(alpha2, CV_32FC1); //結果矩陣,兩個矩陣相乘 Mat result(1, 3780, CV_32FC1); result = alpha2*supportVector; //乘以-1,這裡為什麼會乘以-1? //注意因為svm.predict使用的是alpha*sv*another-rho,如果為負的話則認為是正樣本,在HOG的檢測函式中,使用rho+alpha*sv*another(another為-1) for (int i = 0; i < 3780; ++i) result.at<float>(0, i) *= -1; //將分類器儲存到檔案,便於HOG識別 //這個才是真正的判別函式的引數(ω),HOG可以直接使用該引數進行識別 FILE *fp = fopen((string(FILEPATH) + "HOG_SVM.txt").c_str(), "wb"); for (int i = 0; i<3780; i++) { fprintf(fp, "%f \n", result.at<float>(0,i)); } fprintf(fp, "%f", rho); fclose(fp); } // 使用訓練好的分類器識別 void Detect() { Mat img; FILE* f = 0; char _filename[1024]; // 獲取測試圖片檔案路徑 f = fopen((string(FILEPATH) + "TestData.txt").c_str(), "rt"); if (!f) { fprintf(stderr, "ERROR: the specified file could not be loaded\n"); return; } //載入訓練好的判別函式的引數(注意,與svm->save儲存的分類器不同) vector<float> detector; ifstream fileIn(string(FILEPATH) + "HOG_SVM.txt", ios::in); float val = 0.0f; while (!fileIn.eof()) { fileIn >> val; detector.push_back(val); } fileIn.close(); //設定HOG HOGDescriptor hog; hog.setSVMDetector(detector);// 使用自己訓練的分類器 //hog.setSVMDetector(HOGDescriptor::getDefaultPeopleDetector());//可以直接使用05 CVPR已訓練好的分類器,這樣就不用Train()這個步驟了 namedWindow("people detector", 1); // 檢測圖片 for (;;) { // 讀取檔名 char* filename = _filename; if (f) { if (!fgets(filename, (int)sizeof(_filename)-2, f)) break; //while(*filename && isspace(*filename)) // ++filename; if (filename[0] == '#') continue; //去掉空格 int l = (int)strlen(filename); while (l > 0 && isspace(filename[l - 1])) --l; filename[l] = '\0'; img = imread(filename); } printf("%s:\n", filename); if (!img.data) continue; fflush(stdout); vector<Rect> found, found_filtered; double t = (double)getTickCount(); // run the detector with default parameters. to get a higher hit-rate // (and more false alarms, respectively), decrease the hitThreshold and // groupThreshold (set groupThreshold to 0 to turn off the grouping completely). //多尺度檢測 hog.detectMultiScale(img, found, 0, Size(8, 8), Size(32, 32), 1.05, 2); t = (double)getTickCount() - t; printf("detection time = %gms\n", t*1000. / cv::getTickFrequency()); size_t i, j; //去掉空間中具有內外包含關係的區域,保留大的 for (i = 0; i < found.size(); i++) { Rect r = found[i]; for (j = 0; j < found.size(); j++) if (j != i && (r & found[j]) == r) break; if (j == found.size()) found_filtered.push_back(r); } // 適當縮小矩形 for (i = 0; i < found_filtered.size(); i++) { Rect r = found_filtered[i]; // the HOG detector returns slightly larger rectangles than the real objects. // so we slightly shrink the rectangles to get a nicer output. r.x += cvRound(r.width*0.1); r.width = cvRound(r.width*0.8); r.y += cvRound(r.height*0.07); r.height = cvRound(r.height*0.8); rectangle(img, r.tl(), r.br(), cv::Scalar(0, 255, 0), 3); } imshow("people detector", img); int c = waitKey(0) & 255; if (c == 'q' || c == 'Q' || !f) break; } if (f) fclose(f); return; } void HOG_SVM2() { //如果使用05 CVPR提供的預設分類器,則不需要Train(),直接使用Detect檢測圖片 Train(); Detect(); } int main() { //HOG+SVM識別方式1:直接輸出類別 //HOG_SVM1(); //HOG+SVM識別方式2:輸出圖片中的存在目標的矩形 HOG_SVM2(); }

這裡我想說明一下TrainData.txt,這個檔案放置了所有樣本的路徑和類別,如下:
這裡寫圖片描述

於如何讀取正負樣本的路徑到txt檔案,可以使用批處理檔案,批處理檔案我上傳到了CSDN,大家可以去下載
點選下載

正負樣本至少保證有1000,不能太少,否則效果就不好了,其中HOG_SVM.txt裡面包含了判別函式的引數,這個引數可以直接給HOG用
下面就是我的測試效果:
這裡寫圖片描述

這裡寫圖片描述

檢測效果還可以.
測試圖片我也上傳到網上了
點選下載

當然你也可以不用自己訓練分類器,直接使用OpenCV自帶的分類器,OpenCV自帶的分類器使用的是05年CVPR那篇文章中作者訓練好的分類器,下面我們就來看看效果:
這裡寫圖片描述

這裡寫圖片描述

圖中可以看出,OpenCV自帶的分類器效果要比自己訓練的好,主要原因大概有以下幾點
1.訓練樣本不足,我的正負樣本才900多
2.正樣本圖片不夠清晰,導致特徵提取有比較大的誤差

最近有人在執行部落格中程式的時候出現了問題,讓我看看程式,我也不太清楚什麼問題,所以我將整個程式和程式測試資料打包了一下,上傳到了CSDN上。
點選下載
解壓後,將”Pedestrians64x128”資料夾放置在D盤根目錄,然後使用HOG.cpp新建一個工程,直接可以執行。

注:環境是VS2013+OpenCV3.0.0,Release版本

第二種

下面說說第二種方式,第二種方式就是傳統的方式了,就是對於測試樣本,提取特徵,然後使用訓練好的分類器進行識別,程式碼

///////////////////////////////////HOG+SVM識別方式1///////////////////////////////////////////////////
void HOG_SVM1()
{
    ////////////////////////////////讀入訓練樣本圖片路徑和類別///////////////////////////////////////////////////
    //影象路徑和類別
    vector<string> imagePath;
    vector<int> imageClass;
    int numberOfLine = 0;
    string buffer;
    ifstream trainingData(string(FILEPATH) + "TrainData.txt", ios::in);
    unsigned long n;

    while (!trainingData.eof())
    {
        getline(trainingData, buffer);
        if (!buffer.empty())
        {
            ++numberOfLine;
            if (numberOfLine % 2 == 0)
            {
                //讀取樣本類別
                imageClass.push_back(atoi(buffer.c_str()));
            }
            else
            {
                //讀取影象路徑
                imagePath.push_back(buffer);
            }
        }
    }
    trainingData.close();


    ////////////////////////////////獲取樣本的HOG特徵///////////////////////////////////////////////////
    //樣本特徵向量矩陣
    int numberOfSample = numberOfLine / 2;
    Mat featureVectorOfSample(numberOfSample, 3780, CV_32FC1);//矩陣中每行為一個樣本

    //樣本的類別
    Mat classOfSample(numberOfSample, 1, CV_32SC1);

    //開始計算訓練樣本的HOG特徵
    for (vector<string>::size_type i = 0; i <= imagePath.size() - 1; ++i)
    {
        //讀入圖片
        Mat src = imread(imagePath[i], -1);
        if (src.empty())
        {
            cout << "can not load the image:" << imagePath[i] << endl;
            continue;
        }
        cout << "processing" << imagePath[i] << endl;

        //縮放
        Mat trainImage;
        resize(src, trainImage, Size(64, 128));

        //提取HOG特徵
        HOGDescriptor hog(Size(64, 128), Size(16, 16), Size(8, 8), Size(8, 8), 9);
        vector<float> descriptors;
        hog.compute(trainImage, descriptors);//這裡可以設定檢測視窗步長,如果圖片大小超過64×128,可以設定winStride
        cout << "HOG dimensions:" << descriptors.size() << endl;

        //儲存特徵向量矩陣中
        for (vector<float>::size_type j = 0; j <= descriptors.size() - 1; ++j)
        {
            featureVectorOfSample.at<float>(i, j) = descriptors[j];
        }

        //儲存類別到類別矩陣
        //!!注意類別型別一定要是int 型別的
        classOfSample.at<int>(i, 0) = imageClass[i];
    }


    ///////////////////////////////////使用SVM分類器訓練///////////////////////////////////////////////////    
    //設定引數
    //參考3.0的Demo
    Ptr<SVM> svm = SVM::create();
    svm->setKernel(SVM::RBF);
    svm->setType(SVM::C_SVC);
    svm->setC(10);
    svm->setCoef0(1.0);
    svm->setP(1.0);
    svm->setNu(0.5);
    svm->setTermCriteria(TermCriteria(CV_TERMCRIT_EPS, 1000, FLT_EPSILON));

    //使用SVM學習         
    svm->train(featureVectorOfSample, ROW_SAMPLE, classOfSample);

    //儲存分類器
    svm->save("Classifier.xml");


    ///////////////////////////////////使用訓練好的分類器進行識別///////////////////////////////////////////////////
    vector<string> testImagePath;
    ifstream testData(string(FILEPATH) + "TestData.txt", ios::out);
    while (!testData.eof())
    {
        getline(testData, buffer);
        //讀取
        if (!buffer.empty())
            testImagePath.push_back(buffer);

    }
    testData.close();

    ofstream fileOfPredictResult(string(FILEPATH) + "PredictResult.txt"); //最後識別的結果
    for (vector<string>::size_type i = 0; i <= testImagePath.size() - 1; ++i)
    {
        //讀取測試圖片
        Mat src = imread(testImagePath[i], -1);
        if (src.empty())
        {
            cout << "Can not load the image:" << testImagePath[i] << endl;
            continue;
        }

        //縮放
        Mat testImage;
        resize(src, testImage, Size(64, 64));

        //測試圖片提取HOG特徵
        HOGDescriptor hog(cvSize(64, 64), cvSize(16, 16), cvSize(8, 8), cvSize(8, 8), 9);
        vector<float> descriptors;
        hog.compute(testImage, descriptors);
        cout << "HOG dimensions:" << descriptors.size() << endl;

        Mat featureVectorOfTestImage(1, descriptors.size(), CV_32FC1);
        for (int j = 0; j <= descriptors.size() - 1; ++j)
        {
            featureVectorOfTestImage.at<float>(0, j) = descriptors[j];
        }

        //對測試圖片進行分類並寫入檔案
        int predictResult = svm->predict(featureVectorOfTestImage);
        char line[512];
        //printf("%s %d\r\n", testImagePath[i].c_str(), predictResult);
        std::sprintf(line, "%s %d\n", testImagePath[i].c_str(), predictResult);
        fileOfPredictResult << line;

    }
    fileOfPredictResult.close();
}

int main()
{
    //HOG+SVM識別方式1:直接輸出類別
    HOG_SVM1();

    //HOG+SVM識別方式2:輸出圖片中的存在目標的矩形
    //HOG_SVM2();

}

大家可以分別使用自己的資料集測試一下上面的兩種方式,如果有上面疑問,歡迎留言討論

非常感謝您的閱讀,如果您覺得這篇文章對您有幫助,歡迎掃碼進行讚賞。
這裡寫圖片描述