HOG+SVM,4個級別(圖、window、block、cell),push_back深拷貝淺拷貝,求余的妙用(OpenCV案例源碼train_HOG.cpp解讀)


有所更改,參數不求完備,但求實用。源碼參考D:\source\opencv-3.4.9\samples\cpp\train_HOG.cpp,OpenCV3.4.9版本,內容與低版本略有不同。

【功能】HOG特征適合外形相似的目標識別。如圖片中識別行人。

 

【知識點1】

方向梯度直方圖(Histogram of Oriented Gradient, HOG)是一種在計算機視覺和圖像處理中用來進行物體檢測的特征描述子,HOG特征通過計算和統計圖像局部區域的梯度方向直方圖來構成特征。

HOG特征就是直方圖,以梯度幅值作為權重,梯度主要存在於邊緣,所以適合外形相似的目標識別。對噪聲非常敏感,可以先濾波。

【知識點2】1——圖,2——檢測窗window,3——block,4——cell。參考https://blog.csdn.net/Pierce_KK/article/details/89501308

 注意:檢測窗2是不動的,HOG特征檢測的是window,而不是圖1。也就是說圖1中2以外的區域永遠不會被檢測。block在window中移動,cell也是不動的。

window2可以大些保證目標包含在里面,window是block的整數倍,block是cell的整數倍,(window尺寸-block尺寸)是block移動步長的整數倍。cell默認8*8

【知識點3】push_back深拷貝淺拷貝,深拷貝不同內存,處理后者,前者不受影響。淺拷貝指向同一內存,其一變則都變。

【知識點4】求余,a%b的結果會鎖定在0~(b-1)范圍。

【知識點拓展】詳解請參考https://www.zhihu.com/question/45833619

【樣本】

1、正樣本尺寸統一
2、負樣本尺寸≥正樣本≥檢測窗,且負樣本不允許出現檢測的目標

【解讀參考】https://blog.csdn.net/xiao__run/article/details/82902267

【HOG原理】https://livezingy.com/hogdescriptor-in-opencv3-1/

https://cloud.tencent.com/developer/article/1434949

https://blog.csdn.net/zhanghenan123/article/details/80853523

【疑問討論】

computeHOGs()中對正、負樣本原圖進行了摳取,sample_neg()對負樣本原圖進行了摳取,也就是說負樣本原圖被摳取了兩次,感覺沒必要。最終是以computeHOGs()中window大小來檢測正負樣本的HOG特征。

【版本說明】3.4.9版本imread讀取行人樣本素材png會失敗,因為這些素材是老解碼標准,新版本opencv已經更新了libjpg和libpng,無法讀取舊版本。

測試結果圖上,矩形框越綠,說明置信度越高,越准確

#include<opencv2\opencv.hpp>
#include <iostream>

using namespace cv;
using namespace cv::ml;
using namespace std;

//自定義函數
vector< float > get_svm_detector(const Ptr< SVM >& svm);
void convert_to_ml(const std::vector< Mat > & train_samples, Mat& trainData);
void load_images(const String & dirname, vector< Mat > & img_lst, bool showImages);
void sample_neg(const vector< Mat > & full_neg_lst, vector< Mat > & neg_lst, const Size & size);
void computeHOGs(const Size wsize, const vector< Mat > & img_lst, vector< Mat > & gradient_lst, bool use_flip);
void test_trained_detector(String obj_det_filename, String test_dir, String videofilename);

int main(int argc, char** argv)
{
    String pos_dir = "./Data/Ng";//正樣本路徑
    String neg_dir = "./Data/Ok";//負樣本路徑
    String test_dir = "./Data/test";//測試樣本路徑
    String obj_det_filename = "HOGpedestrian64x128.xml";//訓練后生成的模型
    String videofilename = ""; //視頻文件,無則處理圖像,有則只處理視頻
    int detector_width = 64;//檢測窗大小,能夠框住所有正樣本里的目標,可以大點。必須是滑動塊block的倍數
    int detector_height = 128;
    bool test_detector = "HOGpedestrian64x128.xml";//已有模型
    bool visualization = false;//可視化訓練步驟
    bool flip_samples = false; //樣本是否旋轉180°

    //if (test_detector)//如果已有模型,則直接用於測試
    //{
    //    test_trained_detector(obj_det_filename, test_dir, videofilename);
    //    exit(0);
    //}

    vector< Mat > pos_lst, full_neg_lst, neg_lst, gradient_lst;
    vector< int > labels;
    load_images(pos_dir, pos_lst, visualization);//讀取正樣本
    load_images(neg_dir, full_neg_lst, false);//讀取負樣本
    //摳取制作負樣本
    sample_neg(full_neg_lst, neg_lst, Size(detector_width, detector_height));//制作負樣本,原圖中摳取檢測窗大小的圖
    //提取正樣本HOG特征
    computeHOGs(Size(detector_width, detector_height), pos_lst, gradient_lst, flip_samples);
    size_t positive_count = gradient_lst.size();
    labels.assign(positive_count, +1);//labels中前positive_count個對象賦值為+1,對應gradient_lst中正樣本
    clog << "正樣本個數:" << positive_count << endl;

    //提取負樣本HOG特征
    computeHOGs(Size(detector_width, detector_height), neg_lst, gradient_lst, flip_samples);//此時gradient_lst包含了正、負樣本集的HOG特征
    size_t negative_count = gradient_lst.size() - positive_count;
    labels.insert(labels.end(), negative_count, -1);//labels中后negative_count個對象賦值為-1,對應gradient_lst中負樣本
    CV_Assert(positive_count < labels.size());
    clog << "負樣本個數:" << negative_count << endl;

    //格式化特征樣本集,使之行為樣本,列為HOG特征
    Mat train_data;
    convert_to_ml(gradient_lst, train_data);//正、負樣本集轉換為用於訓練的數據

    //設定SVM參數
    Ptr< SVM > svm = SVM::create();
    svm->setCoef0(0.0);//都是默認值
    svm->setDegree(3);
    svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 1000, 1e-3));
    svm->setGamma(0);
    svm->setKernel(SVM::LINEAR);//LINEAR
    svm->setNu(0.5);
    svm->setP(0.1); // for EPS_SVR
    svm->setC(0.01); 
    svm->setType(SVM::EPS_SVR); // EPS_SVR回歸問題;C_SVC分類問題; 
    //訓練
    svm->train(train_data, ROW_SAMPLE, labels);

    //生成模型,檢測窗64*128,滑動塊16*16,滑塊步長8*8,cell 8*8,cell中梯度方向統計到9個范圍(維度)中
    //參考 https://blog.csdn.net/NSSC_K/article/details/88547916
    HOGDescriptor hog;//默認參數winSize(64,128), blockSize(16,16), blockStride(8,8),    cellSize(8, 8), nbins(9)
    hog.winSize = Size(detector_width, detector_height);
    hog.setSVMDetector(get_svm_detector(svm));
    hog.save(obj_det_filename);//保存模型
    //測試
    test_trained_detector(obj_det_filename, test_dir, videofilename);

    return 0;
}
vector< float > get_svm_detector(const Ptr< SVM >& svm)
{
    // get the support vectors
    Mat sv = svm->getSupportVectors();
    const int sv_total = sv.rows;
    // get the decision function
    Mat alpha, svidx;
    double rho = svm->getDecisionFunction(0, alpha, svidx);
    //*括號中的條件不滿足時,返回錯誤
    CV_Assert(alpha.total() == 1 && svidx.total() == 1 && sv_total == 1);
    CV_Assert((alpha.type() == CV_64F && alpha.at<double>(0) == 1.) ||
        (alpha.type() == CV_32F && alpha.at<float>(0) == 1.f));
    CV_Assert(sv.type() == CV_32F);

    vector< float > hog_detector(sv.cols + 1);
    memcpy(&hog_detector[0], sv.ptr(), sv.cols*sizeof(hog_detector[0]));//內存拷貝函數,從源src所指的內存地址的起始位置開始拷貝n個字節到目標dst所指的內存地址的起始位置中。
    hog_detector[sv.cols] = (float)-rho;
    return hog_detector;
}

//把訓練樣本集train_samples轉換為可用於機器學習的數據trainData(其實就是保證:每行為1個樣本,列是HOG特征)
void convert_to_ml(const vector< Mat > & train_samples, Mat& trainData)
{
    const int rows = (int)train_samples.size();//行數等於訓練樣本個數
    const int cols = (int)std::max(train_samples[0].cols, train_samples[0].rows);//列數取樣本圖片中寬度與高度中較大的那一個
    Mat tmp(1, cols, CV_32FC1);
    trainData = Mat(rows, cols, CV_32FC1);

    for (size_t i = 0; i < train_samples.size(); ++i)
    {
        CV_Assert(train_samples[i].cols == 1 || train_samples[i].rows == 1);

        if (train_samples[i].cols == 1)//如果是列向量(HOG特征組成的),則轉置為行,保證1行1個樣本
        {
            transpose(train_samples[i], tmp);//轉置
            tmp.copyTo(trainData.row((int)i));
        }
        else if (train_samples[i].rows == 1)//如果是行向量,則保持不變
        {
            train_samples[i].copyTo(trainData.row((int)i));
        }
    }
}
//讀取圖像,dirname路徑下的圖像保存到img_lst容器中,showImages是否顯示每一張圖
void load_images(const String & dirname, vector< Mat > & img_lst, bool showImages = false)
{
    vector< String > files;
    glob(dirname, files);//獲取路徑中的所有圖像

    for (size_t i = 0; i < files.size(); ++i)
    {
        Mat img = imread(files[i]);
        if (img.empty())
        {
            cout << files[i] << " is invalid!" << endl;
            continue;
        }

        if (showImages)
        {
            imshow("image", img);
            waitKey(1);
        }
        img_lst.push_back(img);//push_back函數是在結尾插入一個新元素,不會覆蓋原來vector中的對象        
    }
}
//負樣本制作,從full_neg_lst負樣本容器中負樣本圖的隨機部位裁剪出size大小的圖,保存到neg_lst容器中
void sample_neg(const vector< Mat > & full_neg_lst, vector< Mat > & neg_lst, const Size & size)
{
    Rect box;
    box.width = size.width;
    box.height = size.height;

    const int size_x = box.width;
    const int size_y = box.height;

    for (size_t i = 0; i < full_neg_lst.size(); i++)
    if (full_neg_lst[i].cols > box.width && full_neg_lst[i].rows > box.height)//圖片大於矩形
    {
        box.x = rand() % (full_neg_lst[i].cols - size_x);//a%b得到的結果范圍是0~b-1
        box.y = rand() % (full_neg_lst[i].rows - size_y);
        Mat roi = full_neg_lst[i](box);
        neg_lst.push_back(roi.clone());//深拷貝(不同內存),如果淺拷貝(同一內存)那么在操作roi時,原圖的roi區域也改變。
    }
}
//計算HOG特征,wsize尺寸的移動窗提取img_lst容器中圖像的HOG特征,保存到gradient_lst容器中,use_flip是否對img_lst翻轉180°
void computeHOGs(const Size wsize, const vector< Mat > & img_lst, vector< Mat > & gradient_lst, bool use_flip)
{
    HOGDescriptor hog;
    hog.winSize = wsize;
    Mat gray;
    vector< float > descriptors;

    for (size_t i = 0; i < img_lst.size(); i++)
    {
        if (img_lst[i].cols >= wsize.width && img_lst[i].rows >= wsize.height)
        {
            Rect r = Rect((img_lst[i].cols - wsize.width) / 2, //摳取原圖,sample_neg()已經對負樣本進行了摳取,所以負樣本共被摳取兩次
                (img_lst[i].rows - wsize.height) / 2,
                wsize.width,
                wsize.height);
            cvtColor(img_lst[i](r), gray, COLOR_BGR2GRAY);
            hog.compute(gray, descriptors, Size(8, 8));//Size(8,8)為滑動塊block移動步長,計算HOG特征,保存到descriptors
            gradient_lst.push_back(Mat(descriptors).clone());//防止處理gradient_lst中對象時,干擾到源數據,所以深拷貝。
            if (use_flip)
            {
                flip(gray, gray, 1);//旋轉180度
                hog.compute(gray, descriptors, Size(8, 8));
                gradient_lst.push_back(Mat(descriptors).clone());
            }
        }
    }
}
//測試模型,用訓練好的模型obj_det_filename,測試test_dir目錄中的圖片或者測試videofilename視頻幀(兩者都有,則測試后者)
void test_trained_detector(String obj_det_filename, String test_dir, String videofilename)
{
    HOGDescriptor hog;
    hog.load(obj_det_filename);

    vector< String > files;
    glob(test_dir, files);

    int delay = 0;
    VideoCapture cap;

    if (videofilename != "")
    {
        if (videofilename.size() == 1 && isdigit(videofilename[0]))
            cap.open(videofilename[0] - '0');
        else
            cap.open(videofilename);
    }

    obj_det_filename = "testing " + obj_det_filename;
    namedWindow(obj_det_filename, WINDOW_NORMAL);

    for (size_t i = 0;; i++)
    {
        Mat img;

        if (cap.isOpened())
        {
            cap >> img;
            delay = 1;
        }
        else if (i < files.size())
        {
            img = imread(files[i]);
        }

        if (img.empty())
        {
            return;
        }

        vector< Rect > detections;
        vector< double > foundWeights;
        //檢測多目標
        hog.detectMultiScale(img, detections, foundWeights);//detections存儲矩形框信息,foundWeights是每個目標的置信度(0~1,越接近1越准確)
        for (size_t j = 0; j < detections.size(); j++)
        {
            Scalar color = Scalar(0, foundWeights[j] * foundWeights[j] * 255, 0);//框越綠越准確
            rectangle(img, detections[j], color, img.cols / 400 + 1);//畫矩形框
        }

        imshow(obj_det_filename, img);

        if (waitKey(delay) == 27)
        {
            return;
        }
    }
}

 


免責聲明!

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



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