机器学习之用Hog+Svm人脸检测、交通标志和字符识别等(初学者)

首先声明,这里主要用svm进行一个简单的二分类,最后得到结果,我们把正样本设为1,负样本设为0。
这里只是一个简单的介绍,后面会有相关详细介绍的链接,个人认为比较好的,对我们比较有帮助的链接,有兴趣的可以去看看。当然,本文对初学者有点帮助,也特别欢迎大神来拍!
训练样本时候问题:
我在训练的时候,用了各种样本,总结了一点自己的经验。如果要是用Haar特征训练时,正负样本大概比至少为1:10时候才比较好。而svm训练,更讲究正负样本的选择。其实,机器学习也好,深入学习也好,样本比算法重要的多!(当然,你要是想仔细研究算法内部并且自己写的除外)。我们这里先只是用opencv里的自带的svm和hog库。
先说正负样本的选择。
对于正负样本来说,尽可能的接近比随便挑的效果要好太多。比如说,我们负样本的选择是从几张图片随机抠出几万张负样本出来(过程很简单,用opencv十几二十几行代码就可以,需要的可以评论,我发给你)。然后正样本是我们需要的图片。这时候,我们进行训练完毕后,在做检测,会发现结果相当不好,可以这么说,除非你的检测图片正好是你的正样本训练图片。否则基本检测不出来!!
后来改变了选择,正样本不要只要需求的那一部分,比如,我们检测交通标志时,把交通标志的旁边一圈也划进来,当然不要太多。然后,我们把负样本和正样本的选择尽可能相近,比如,我们训练交通标志时,选择限速50为正样本,那么,我们就把限速40作为负样本,这样,最后得到的效果就会非常好。
对于样本大小的选择上,根据不同的样本取不同大小吧,很多人在这点都没有提到,其实,对于初学者来说,往往不会选择样本大小,特别是还没怎么看svm和hog的,对于样本大小往往是朝网上copy。我们选择样本大小时,一般,人脸,交通检测这些,设置为64*64的就可以。而行人的,一般选择长方形,即64*128这种的。正负样本尽可能保持大小相同。
至于如何取得文件夹下所有的图片目录,和如何每隔一行加一个标志符号。这里,可以自己写一个代码,当然,也可以直接利用.bat直接写,具体方法是:
首先,拿正样本为例。
在你放置正样本的文件夹下,即与图片的是同一级的目录,新建一个.txt,名字可以自己取,例如为test.txt。然后,修改后缀名,为.bat。右键编辑(对于没有编辑的win10来说…可以下载一个文本编辑器,我用的是EditPlus)打开,然后,在里面输入
@echo off & setlocal EnableDelayedExpansion
for /f “delims=” %%i in (‘“dir /a/s/b/on .“’) do (
set file=%%~fi
set file=!file:/=/!
echo !file! >> 路径.txt
)
点击保存,然后,直接双击这个文件,就会得到一个名为路径.txt,这里面存放的就是文件夹下所有的路径。
PS:至于有的人说的用cmd写一个,dir /b/s/p/w *.jpg>train_list.txt,应该也可以,不过这个有个问题是如果路径太多,不会全部写进去,所以,还是用上一种方法比较好。
然后,打开路径,Ctrl+A,全部复制,然后放到word里,在word里,Ctrl+h,把jpg换为jpg^p1,其中^p是回车符号,1表示正样本。
同理对负样本这样处理,然后,把所有目录统一拷贝到一个.txt里,放到需要的目录下。
然后,我用的opencv是2.4.9版本。
说明:这是在别人的代码上(后面有链接),感觉不错,改了一下,自己拿来用下。

#include "stdafx.h"

#include "cv.h"    
#include "highgui.h"    
#include "stdafx.h"    
#include     
#include     
#include     
#include     
#include     
using namespace cv;    
using namespace std;

/**************************************************************
说明:把所有的训练样本(包括正样本和负样本)信息,储存到
F:\\MLHogSvm\\TrainData.txt中,格式为:
上面是:图片路径,其下面为标志,1为正样本,0为负样本
每隔一行做一次,最后放到上面的txt中
最后的.xml放到F:\\MLHogSvm目录下
**************************************************************/
int _tmain(int argc, _TCHAR* argv[])
{
    int ImgWidht = 64;  
    int ImgHeight = 64;//训练图像大小,可以自己在这里调节,不过最后要看看

    vector<string> img_path;//输入文件名变量   
    vector<int> img_catg;    
    int nLine = 0;    
    string buf;    
    ifstream svm_data( "F:\\MLHogSvm\\TrainData.txt" );//首先,这里搞一个文件列表,把训练样本图片的路径都写在这个txt文件中,使用bat批处理文件可以得到这个txt文件     
    unsigned long n;    

    while( svm_data )//将训练样本文件依次读取进来    
    {    
        if( getline( svm_data, buf ) )    
        {    
            nLine ++;    
            if( nLine % 2 == 0 )//这里的分类比较有意思,看得出来上面的SVM_DATA.txt文本中应该是一行是文件路径,接着下一行就是该图片的类别,可以设置为0或者1,当然多个也无所谓   
            {    
                 img_catg.push_back( atoi( buf.c_str() ) );//atoi将字符串转换成整型,标志(0,1),注意这里至少要有两个类别,否则会出错    
            }    
            else    
            {    
                img_path.push_back( buf );//图像路径    
            }    
        }    
    }    
    svm_data.close();//关闭文件    

    CvMat *data_mat, *res_mat;    
    int nImgNum = nLine / 2; //读入样本数量 ,因为是每隔一行才是图片路径,所以要除以2   
    ////样本矩阵,nImgNum:横坐标是样本数量, WIDTH * HEIGHT:样本特征向量,即图像大小    
    data_mat = cvCreateMat( nImgNum, 3780, CV_32FC1 );  //这里第二个参数,即矩阵的列是由下面的descriptors的大小决定的,可以由descriptors.size()得到,且对于不同大小的输入训练图片,这个值是不同的  
    cvSetZero( data_mat );    
    //类型矩阵,存储每个样本的类型标志    
    res_mat = cvCreateMat( nImgNum, 1, CV_32FC1 );    
    cvSetZero( res_mat );    

    IplImage* src;    
    IplImage* trainImg=cvCreateImage(cvSize(ImgWidht,ImgHeight),8,3);//需要分析的图片,这里默认设定图片是64*64大小,所以上面定义了1764,如果要更改图片大小,可以先用debug查看一下descriptors是多少,然后设定好再运行    

    //开始搞HOG特征  
    for( string::size_type i = 0; i != img_path.size(); i++ )    
    {    
            src=cvLoadImage(img_path[i].c_str(),1);    
            if( src == NULL )    
            {    
                cout<<" can not load the image: "<continue;    
            }    

            cout<<" processing "<//读取图片       
            HOGDescriptor *hog=new HOGDescriptor(cvSize(ImgWidht,ImgHeight),cvSize(16,16),cvSize(8,8),cvSize(8,8),9);  //具体意思见参考文章1,2       
            vector<float>descriptors;//结果数组       
            hog->compute(trainImg, descriptors,Size(1,1), Size(0,0)); //调用计算函数开始计算       
            cout<<"HOG dims: "<//CvMat* SVMtrainMat=cvCreateMat(descriptors.size(),1,CV_32FC1);    
            n=0;    
            for(vector<float>::iterator iter=descriptors.begin();iter!=descriptors.end();iter++)    
            {    
                cvmSet(data_mat,i,n,*iter);//把HOG存储下来    
                n++;    
            }    
                //cout<rows<
            cvmSet( res_mat, i, 0, img_catg[i] );    
            cout<<" end processing "<" "<//新建一个SVM 
    CvSVMParams param;//这里是参数  
    CvTermCriteria criteria;      
    criteria = cvTermCriteria( CV_TERMCRIT_EPS, 1000, FLT_EPSILON );      
    param = CvSVMParams( CvSVM::C_SVC, CvSVM::RBF, 10.0, 0.09, 1.0, 10.0, 0.5, 1.0, NULL, criteria );      

/*  SVM种类:CvSVM::C_SVC     
    Kernel的种类:CvSVM::RBF     
    degree:10.0(此次不使用)     
    gamma:8.0     
    coef0:1.0(此次不使用)     
    C:10.0     
    nu:0.5(此次不使用)     
    p:0.1(此次不使用)     
    然后对训练数据正规化处理,并放在CvMat型的数组里。     
                                                        */         
    //☆☆☆☆☆☆☆☆☆(5)SVM学习☆☆☆☆☆☆☆☆☆☆☆☆           
    svm.train( data_mat, res_mat, NULL, NULL, param );//训练啦      
    //☆☆利用训练数据和确定的学习参数,进行SVM学习☆☆☆☆       
    svm.save( "F:\\MLHogSvm\\SVM_DATA.xml" );  

    cvReleaseMat( &data_mat );    
    cvReleaseMat( &res_mat );
    return 0;
}

然后是进行检测的代码,我们平时写的时候,也建议把这两个分开来写,可以写成两个工程,或者用MFC写成两个不同的模块都可以的。

#include "stdafx.h"

#include "cv.h"    
#include "highgui.h"    
#include "stdafx.h"    
#include     
#include     
#include     
#include     
#include     
using namespace cv;    
using namespace std;

/*********************************************************************
说明:
读取svm.load("F:\\MLHogSvm\\SVM_DATA.xml"),读入原来写入的.xml
先F:\\MLHogSvm\\TrainTest.txt中,存放的是要进行测试的图片的信息。
预测结果(1为正样本,0为负样本)放入"F:\\MLHogSvm\\SVM_PREDICT.txt" 中
**********************************************************************/

int _tmain(int argc, _TCHAR* argv[])
{
    int ImgWidht = 64;  
    int ImgHeight = 64;//训练图像大小,可以自己在这里调节,不过最后要看看
    IplImage *test;    
    vector<string> img_tst_path;    
    ifstream img_tst( "F:\\MLHogSvm\\TrainTest.txt" );//输入测试图片路径信息  
    string buf;
    while( img_tst )    
    {    
        if( getline( img_tst, buf ) )    
        {    
            img_tst_path.push_back( buf );    
        }    
    }    
    img_tst.close();
    CvMat *test_hog = cvCreateMat( 1, 3780, CV_32FC1 );//注意这里的1764,同上面一样    
    char line[512];    
    ofstream predict_txt( "F:\\MLHogSvm\\SVM_PREDICT.txt" );//把预测结果存储在这个文本中    
    for( string::size_type j = 0; j != img_tst_path.size(); j++ )//依次遍历所有的待检测图片    
    {    
        test = cvLoadImage( img_tst_path[j].c_str(), 1);    
        if( test == NULL )    
        {    
             cout<<" can not load the image: "<continue;    
         }    

        IplImage* trainImg=cvCreateImage(cvSize(ImgWidht,ImgHeight),8,3);   
        cvZero(trainImg);    
        cvResize(test,trainImg);   //读取图片       
        HOGDescriptor *hog=new HOGDescriptor(cvSize(ImgWidht,ImgHeight),cvSize(16,16),cvSize(8,8),cvSize(8,8),9);       
        vector<float>descriptors;//结果数组       
        hog->compute(trainImg, descriptors,Size(1,1), Size(0,0)); //调用计算函数开始计算       
        cout<<"HOG dims: "<1,descriptors.size(),CV_32FC1);    
        unsigned long n=0;    
        for(vector<float>::iterator iter=descriptors.begin();iter!=descriptors.end();iter++)    
            {    
                cvmSet(SVMtrainMat,0,n,*iter);    
                n++;    
            }    

        CvSVM svm;//新建一个SVM
        svm.load("F:\\MLHogSvm\\SVM_DATA.xml");
        int ret = svm.predict(SVMtrainMat);//获取最终检测结果,这个predict的用法见 OpenCV的文档   
        std::sprintf( line, "%s %d\r\n", img_tst_path[j].c_str(), ret );    
        predict_txt<return 0;
}

说明一下,这会在”F:\MLHogSvm\SVM_PREDICT.txt” 里进行说明,如果检测出来的测试图片为需要的,就是标记为1,如果是无关的图片,就会被标记为0(这里随意取,也可以自己设置为其他数,不过要记住的,在后面的检测会用到)。

其实,我们从这段代码也可以看出,用hog和svm检测,最后得出的,只是一个分类结果,就是,可以把一张图片,分成是否是正样本还是负样本的类型。如果是需要的,就分到正样本里(当然,这是完全理想状态下)。

然后,进行检测时,还是需要再次处理,比如,一幅图片,如何才能得到我们想要的大体正样本的框的地方。
参考了N篇论文之后,之后得到一个结果…硕士论文不靠谱。。不是,是大体都一样,简单来讲,如果你真的需要一个实时在线检测的,即使你用了多线程GPU加速之类的,还是达不到要求。说明算法真的是有问题。
最后总结来说,如果是离线检测,那么,RGB转HSI,再进行边缘检测,颜色填充,再边缘检测,再进行模式匹配的效果比较好。
如果你要求速率很高,推荐一种颜色空间,叫做SVF向量颜色空间,最后得到的效果不错,当然,也可以用基于RGB的,不过效果一般。其实,最后要是想要快速结果,GPU加速应该是必不可少的,否则,用个HOG检测就用了8、9秒,还玩个锤子!在线检测,一般用SVF或者基于HSI的吧,就正负样本多采集一点才好。

好了,最后总结说,正负样本多多益善,尽可能相近。对于机器学习来讲,采集样本往往重要的多,如果你按照代码测试过后,还是发现得不到想要的结果。那基本可以断定,你所取得样本是有问题的。所以,弄好样本!才是机器学习的重中之重!!!

链接:http://blog.csdn.net/qianqing13579/article/details/46509037
http://blog.csdn.net/v_july_v/article/details/7624837

你可能感兴趣的:(机器学习,计算机视觉)