zoukankan      html  css  js  c++  java
  • HOG+SVM,4个级别(图、window、block、cell),push_back深拷贝浅拷贝,求余的妙用(OpenCV案例源码train_HOG.cpp解读)

    有所更改,参数不求完备,但求实用。源码参考D:sourceopencv-3.4.9samplescpp rain_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<opencv2opencv.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;
            }
        }
    }
  • 相关阅读:
    pymysql
    flask WTForms
    线程安全问题
    flask学习2
    @functools.wraps(func)
    Solidity开发神器Remix
    Web3j实现智能合约
    基于Ubuntu Docker环境下进行以太坊实践
    以太坊RLP机制分析
    以太坊网络服务分析
  • 原文地址:https://www.cnblogs.com/xixixing/p/12411292.html
Copyright © 2011-2022 走看看