如何使用sklearn中的SVM

如何使用sklearn中的SVM,第1张

CvSVMParams::CvSVMParams() : svm_type(CvSVM::C_SVC), kernel_type(CvSVM::RBF), degree(0), gamma(1), coef0(0), C(1), nu(0), p(0), class_weights(0) SVM种类:CvSVM::C_SVC C_SVC该类型可以用于n-类分类问题 (n>=2),其重要逗游特征是它可漏旅以处山搜销.

 支持向量机在解决二分类问题方面有着强大的威力(当然也可以解决多分类问题),性别识别是典型的二分类模式识别问题,因此很适合用SVM进行处理,同时OpenCv又对SVM进行了很好的封装,调用非常方便,因此我们在这个性别识别程序中考虑加入SVM方法。

在这里我们采用了HOG+SVM的模式来进行,即先提取图像的HOG特征,然后将这些HOG特征输入SVM中进行训练。

一、SVM概述

SVM的数学原理十分复杂,我们不在这里过多讨论,有关OpenCv中SVM的用法,这里为大家提供两篇博客以供参考:OpenCV的SVM用法以及OpenCV 2.4+ C++ SVM介绍。

二、HOG特征概述

HOG特征是图像的梯度特征,具体参见:目标检测的图像特征提取之(一)HOG特征

三、建立训练集

这里继续沿用上一篇博文中提到的性别识别训练集,400张男性人脸样本400张女塌扮知性人脸样本,下载地址:性别识别数据集。

四、算法的训练与测试

1、建立控制台工程,配置OpenCv环境

这里将工程命名为:GenderSVM。

2、编写批量读取函数read_csv()

只要涉及到训练,都需要批量读取训练样本的 *** 团消作,SVM也不例外,因此需要先编写批量读取函数read_csv()。考虑缺和到之前的批量读取函数必须一次性将所有训练样本读入内存中,内存消耗较大,在这里做一个小小的改进:

void read_csv(String&csvPath,Vector<String>&trainPath,Vector<int>&label,char separator = '')

{

string line,path,classLabel

ifstream file(csvPath.c_str(),ifstream::in)

while (getline(file,line))

{

stringstream lines(line)

getline(lines,path,separator)

getline(lines,classLabel)

if (!path.empty()&&!classLabel.empty())

{

trainPath.push_back(path)

label.push_back(atoi(classLabel.c_str()))

}

}

}

可见这里我们将输入参数由vector<Mat>改为vector<String>,然后返回装有训练样本的所有路径的容器,需要时在根据其中的路径进行读取,降低了内存占用量。

3、读入训练样本路径

string trainCsvPath = "E:\\性别识别数据库—CAS-PEAL\\at.txt"

vector<String>vecTrainPath

vector<int>vecTrainLabel

read_csv(trainCsvPath,vecTrainPath,vecTrainLabel)

顺利批量读入路径:

4、训练初始化

在提取HOG特征之前,需要初始化训练数据矩阵:

/**********初始化训练数据矩阵**********/

int iNumTrain = 800

Mat trainDataHog

Mat trainLabel = Mat::zeros(iNumTrain,1,CV_32FC1)

需要强调的是SVM的训练数据必须都是CV_32FC1格式,因此这里显式的将标签矩阵trainLabel初始化为CV_32FC1格式,trainDataHog稍后进行初始化。

5、提取图像HOG特征

接下来循环读入所有的训练样本,提取HOG特征,放在训练数据矩阵中。考虑嵌套代码的复杂性,这里先给出整体代码,稍后解释:

/**********提取HOG特征,放入训练数据矩阵中**********/

Mat imageSrc

for (int i = 0i <iNumTraini++)

{

imageSrc = imread(vecTrainPath[i].c_str(),1)

resize(imageSrc,imageSrc,Size(64,64))

HOGDescriptor *hog = new HOGDescriptor(cvSize(64,64),cvSize(16,16),

cvSize(8,8),cvSize(8,8),9)

vector<float>descriptor

hog->compute(imageSrc,descriptor,Size(1,1),Size(0,0))

if (i == 0)

{

trainDataHog = Mat::zeros(iNumTrain,descriptor.size(),CV_32FC1)

}

int n = 0

for (vector<float>::iterator iter = descriptor.begin()iter != descriptor.end()iter++)

{

trainDataHog.at<float>(i,n) = *iter

n++

}

trainLabel.at<float>(i,0) = vecTrainLabel[i]

}

接下来我们对这段代码进行详细解释。

(1)循环读入训练样本

从vecTrainPath容器中逐条取出训练样本路径,然后读取:

imageSrc = imread(vecTrainPath[i].c_str(),1)

(2)尺寸归一化

我们这里将图像尺寸归一化为64*64,这是因为当时在写程序时参考了一篇关于HOG特征的博客。这里的尺寸大家可以随意设定,当然也会影响最终的识别效率,64*64可能并不是一个最优的尺寸:

imageSrc = imread(vecTrainPath[i].c_str(),1)

resize(imageSrc,imageSrc,Size(64,64))

(3)计算HOG特征

OpenCv给出的HOG特征计算接口非常简洁,三句话即完成:

HOGDescriptor *hog = new HOGDescriptor(cvSize(64,64),cvSize(16,16),

cvSize(8,8),cvSize(8,8),9)

vector<float>descriptor

hog->compute(imageSrc,descriptor,Size(1,1),Size(0,0))

提取的特征以容器的数据 结构形式给出。至于计算时的参数设定,参见我之前提供的那两篇博客即可。

(4)初始化数据矩阵trainDataHog

前面提到,SVM中用到的训练数据矩阵必须是CV_32FLOAT形式的,因此需要对数据矩阵显示的指定其尺寸和类型。然后由于trainDataHog行数为训练样本个数,而列数为图片HOG特征的维数,因此无法在进行HOG特征提取之前确定其尺寸,因此这里选择在进行完第一张样本的HOG特征、得到对应维数之后,在进行初始化:

if (i == 0)

{

trainDataHog = Mat::zeros(iNumTrain,descriptor.size(),CV_32FC1)

}

(5)将得到的HOG特征存入数据矩阵

得到的HOG特征是浮点数容器的形式,我们需要将其转换成矩阵的形式以便于训练SVM,这就涉及到了vector和Mat两个数据结构的遍历。vector遍历这里推荐使用迭代器的方式,而Mat遍历这里则选择了相对耗时但是最简单的方式——直接使用at函数:

int n = 0

for (vector<float>::iterator iter = descriptor.begin()iter != descriptor.end()iter++)

{

trainDataHog.at<float>(i,n) = *iter

n++

}

trainLabel.at<float>(i,0) = vecTrainLabel[i]

训练得到的HOG特征如图所示:

可见在当前的参数设定下,提取到的HOG特征为1764维,共800张训练样本,每一行代表一个图片的HOG特征向量。通过“ctrl+鼠标滚轮”放大观察特征向量的具体参数:

6、训练SVM分类器

有关OpenCv中SVM分类器的使用可以参见以下博客:OpenCV 2.4+ C++ SVM介绍。

首先,初始化相关参数:

/**********初始化SVM分类器**********/

CvSVM 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**********/

svm.train(trainDataHog,trainLabel,Mat(),Mat(),param)

svm.save("E:\\性别识别数据库—CAS-PEAL\\SVM_SEX_Model.txt")

注意我们这里选择将分类器保存为txt形式:

当然,我们可以打开这个txt文件,查看里面的参数:

7、测试分类效果

测试过程和训练过程基本相同,读取图片、尺寸归一化、提取HOG特征、预测:

/**********测试SVM分类性能**********/

Mat testImage = imread("E:\\性别识别数据库—CAS-PEAL\\测试样本\\女性测试样本\\face_35.bmp")

resize(testImage,testImage,Size(64,64))

HOGDescriptor *hog = new HOGDescriptor(cvSize(64,64),cvSize(16,16),

cvSize(8,8),cvSize(8,8),9)

vector<float>descriptor

hog->compute(testImage,descriptor,Size(1,1),Size(0,0))

Mat testHog = Mat::zeros(1,descriptor.size(),CV_32FC1)

int n = 0

for (vector<float>::iterator iter = descriptor.begin()iter != descriptor.end()iter++)

{

testHog.at<float>(0,n) = *iter

n++

}

int predictResult = svm.predict(testHog)

8、完整代码

 这里给出HOG+SVM进行性别识别的完整代码:

// GenderSVM.cpp : 定义控制台应用程序的入口点。

//

#include "stdafx.h"

#include <opencv2\opencv.hpp>

#include <iostream>

#include <sstream>

#include <fstream>

using namespace std

using namespace cv

void read_csv(String&csvPath,vector<String>&trainPath,vector<int>&label,char separator = '')

{

string line,path,classLabel

ifstream file(csvPath.c_str(),ifstream::in)

while (getline(file,line))

{

stringstream lines(line)

getline(lines,path,separator)

getline(lines,classLabel)

if (!path.empty()&&!classLabel.empty())

{

trainPath.push_back(path)

label.push_back(atoi(classLabel.c_str()))

}

}

}

int _tmain(int argc, _TCHAR* argv[])

{

/**********批量读入训练样本路径**********/

string trainCsvPath = "E:\\性别识别数据库—CAS-PEAL\\at.txt"

vector<String>vecTrainPath

vector<int>vecTrainLabel

read_csv(trainCsvPath,vecTrainPath,vecTrainLabel)

/**********初始化训练数据矩阵**********/

int iNumTrain = 800

Mat trainDataHog

Mat trainLabel = Mat::zeros(iNumTrain,1,CV_32FC1)

/**********提取HOG特征,放入训练数据矩阵中**********/

Mat imageSrc

for (int i = 0i <iNumTraini++)

{

imageSrc = imread(vecTrainPath[i].c_str(),1)

resize(imageSrc,imageSrc,Size(64,64))

HOGDescriptor *hog = new HOGDescriptor(cvSize(64,64),cvSize(16,16),

cvSize(8,8),cvSize(8,8),9)

vector<float>descriptor

hog->compute(imageSrc,descriptor,Size(1,1),Size(0,0))

if (i == 0)

{

trainDataHog = Mat::zeros(iNumTrain,descriptor.size(),CV_32FC1)

}

int n = 0

for (vector<float>::iterator iter = descriptor.begin()iter != descriptor.end()iter++)

{

trainDataHog.at<float>(i,n) = *iter

n++

}

trainLabel.at<float>(i,0) = vecTrainLabel[i]

}

/**********初始化SVM分类器**********/

CvSVM 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**********/

svm.train(trainDataHoghttp://www.yingtaow.com?trainLabel,Mat(),Mat(),param)

svm.save("E:\\性别识别数据库—CAS-PEAL\\SVM_SEX_Model.txt")

/**********测试SVM分类性能**********/

Mat testImage = imread("E:\\性别识别数据库—CAS-PEAL\\测试样本\\女性测试样本\\face_35.bmp")

resize(testImage,testImage,Size(64,64))

HOGDescriptor *hog = new HOGDescriptor(cvSize(64,64),cvSize(16,16),

cvSize(8,8),cvSize(8,8),9)

vector<float>descriptor

hog->compute(testImage,descriptor,Size(1,1),Size(0,0))

Mat testHog = Mat::zeros(1,descriptor.size(),CV_32FC1)

int n = 0

for (vector<float>::iterator iter = descriptor.begin()iter != descriptor.end()iter++)

{

testHog.at<float>(0,n) = *iter

n++

}

int predictResult = svm.predict(testHog)

return 0

}

五、总结

以上就是通过HOG特征+SVM进行性别识别的完整代码,在编写代码的过程中遇到了一些有趣的问题,这里稍作总结。

1、变量命名格式

当代码量很大的时候,变量的命名格式就显得十分重要,相信大家早已不用那种a、b、m、n这种简单的无意义的命名方法了。在C++中推荐大家使用匈牙利命名法,即“类型缩写+变量名缩写”的命名格式。例如vecTrainPath这个变量名,前缀“vec”表明这个变量是一个vector格式的变量,而“TrainPath”则表明这个容器中存放的是训练样本的路径。这种命名方式在大型工程中非常重要,还有一点需要注意的是当变量名中出现多个缩略短语时,推荐第一个短语小写,其他短语的首字母大写。

2、为何选择HOG特征

通过实验发现,直接将图像向量化后输入SVM(不经过特征提取)的方式的正确率将不理想。虽然本质上像素本身最能代表图像的语义信息,但由于SVM并不具备特征提取能力,因此效果不佳。确切的说,特征提取是模式分类的必要过程,即便是深度学习也不例外,因为深度学习(DeepLearning)本质上也是一种特征提取的手段,只不过提取得到的特征更深层,更抽象,表现力更强。为此我之前曾专门写过一篇博客进行阐述:浅谈模式识别中的特征提取

当然这里大家可以尝试提取其他特征之后再进行分类,甚至可以考虑通过提起深度特征来进行分类,这里只是以HOG特征为例而已。

4、有关vector的一些使用(为什么不用int型数组)

在这段代码中我们大量用到了vector结构,这是C++11的新特性。仔细观察,其实vector结构的最明显的一个优势就是能够动态分配大小,实时添加/删除元素,这点是数组所不能实现的。虽然可以通过new *** 作符来实现数组的动态分配,但我们仍推荐大家在需要使用可动态变化的数组的场合,使用vector。

OpenCV开发SVM算法是基于LibSVM软件包开发的,LibSVM是台湾大学林智仁(Lin Chih-Jen)等开发设计的一个简单、易于使用和快速有效举神凳的SVM模式识别与回归的软件包。用OpenCV使用SVM算法的大概流程是1)设置训练样本集需要两组数据,一组是数据的类别,一组是数据的向量信息。2)设置SVM参数利用瞎胡CvSVMParams类实现类内的成员变量svm_type表示SVM类型:CvSVM::C_SVC C-SVCCvSVM:正旅:NU_SVC v-SVCCvSVM::ONE_CLASS 一类SVMCvSVM::EPS_SVR e-SVRCvSVM::NU_SVR v-SVR成员变量kernel_type表示核函数的类型:CvSVM::LINEAR 线性:u‘vCvSVM::POLY 多项式:(r*u'v + coef0)^degreeCvSVM::RBF RBF函数:exp(-r|u-v|^2)CvSVM::SIGMOID sigmoid函数:tanh(r*u'v + coef0)成员变量degree针对多项式核函数degree的设置,gamma针对多项式/rbf/sigmoid核函数的设置,coef0针对多项式/sigmoid核函数的设置,Cvalue为损失函数,在C-SVC、e-SVR、v-SVR中有效,nu设置v-SVC、一类SVM和v-SVR参数,p为设置e-SVR中损失函数的值,class_weightsC_SVC的权重,term_crit为SVM训练过程的终止条件。其中默认值degree = 0,gamma = 1,coef0 = 0,Cvalue = 1,nu = 0,p = 0,class_weights = 03)训练SVM调用CvSVM::train函数建立SVM模型,第一个参数为训练数据,第二个参数为分类结果,最后一个参数即CvSVMParams4)用这个SVM进行分类调用函数CvSVM::predict实现分类5)获得支持向量除了分类,也可以得到SVM的支持向量,调用函数CvSVM::get_support_vector_count获得支持向量的个数,CvSVM::get_support_vector获得对应的索引编号的支持向量。实现代码如下:view plain// step 1: float labels[4] = {1.0, -1.0, -1.0, -1.0} Mat labelsMat(3, 1, CV_32FC1, labels) float trainingData[4][2] = { {501, 10}, {255, 10}, {501, 255}, {10, 501} } Mat trainingDataMat(3, 2, CV_32FC1, trainingData) // step 2: CvSVMParams params params.svm_type = CvSVM::C_SVC params.kernel_type = CvSVM::LINEAR params.term_crit = cvTermCriteria(CV_TERMCRIT_ITER, 100, 1e-6) // step 3: CvSVM SVM SVM.train(trainingDataMat, labelsMat, Mat(), Mat(), params) // step 4: Vec3b green(0, 255, 0), blue(255, 0, 0) for (int i=0iimage.rowsi++) { for (int j=0jimage.colsj++) { Mat sampleMat = (Mat_float(1,2) i,j) float response = SVM.predict(sampleMat) if (fabs(response-1.0) 0.0001) { image.atVec3b(j, i) = green } else if (fabs(response+1.0) 0.001) { image.atVec3b(j, i) = blue } } }// step 5: int c = SVM.get_support_vector_count() for (int i=0i i++) { const float* v = SVM.get_support_vector(i) } OpenCV支持的目标检测的方法是利用样本的Haar特征进行的分类器训练,得到的级联boosted分类器(Cascade Classification)。注意,新版本的C++接口除了Haar特征以外也可以使用LBP特征。先介绍一下相关的结构,级联分类器的计算特征值的基础类FeatureEvaluator,功能包括读 *** 作read、复制clone、获得特征类型getFeatureType,分配图片分配窗口的 *** 作setImage、setWindow,计算有序特征calcOrd,计算绝对特征calcCat,创建分类器特征的结构create函数。级联分类器类CascadeClassifier。目标级联矩形的分组函数groupRectangles。接下来,我尝试使用CascadeClassifier这个级联分类器类检测视频流中的目标(haar支持的目标有人脸、人眼、嘴、鼻、身体。这里尝试比较成熟的人脸和眼镜)。用load函数加载XML分类器文件(目前提供的分类器包括Haar分类器和LBP分类器(LBP分类器数据较少))具体步骤如下:1)加载级联分类器调用CascadeClassifier类成员函数load实现,代码为:view plainCascadeClassifier face_cascade face_cascade.load("haarcascade_frontalface_alt.xml") 2)读取视频流这部分比较基础啦~~从文件中读取图像序列,读取视频文件,读取摄像头视频流看过我之前的文章,这3种方法应该了然于心。3)对每一帧使用该分类器这里先将图像变成灰度图,对它应用直方图均衡化,做一些预处理的工作。接下来检测人脸,调用detectMultiScale函数,该函数在输入图像的不同尺度中检测物体,参数image为输入的灰度图像,objects为得到被检测物体的矩形框向量组,scaleFactor为每一个图像尺度中的尺度参数,默认值为1.1,minNeighbors参数为每一个级联矩形应该保留的邻近个数(没能理解这个参数,-_-|||),默认为3,flags对于新的分类器没有用(但目前的haar分类器都是旧版的,CV_HAAR_DO_CANNY_PRUNING利用Canny边缘检测器来排除一些边缘很少或者很多的图像区域,CV_HAAR_SCALE_IMAGE就是按比例正常检测,CV_HAAR_FIND_BIGGEST_OBJECT只检测最大的物体,CV_HAAR_DO_ROUGH_SEARCH只做初略检测),默认为0.minSize和maxSize用来限制得到的目标区域的范围。这里调用的代码如下:view plainface_cascade.detectMultiScale( frame_gray, faces, 1.1, 2, 0|CV_HAAR_SCALE_IMAGE, Size(30, 30) ) 4)显示目标这个也比较简单,调用ellips函数将刚才得到的faces矩形框都显示出来更进一步,也可以在得到的每一幅人脸中得到人眼的位置,调用的分类器文件为haarcascade_eye_tree_eyeglasses.xml,先将脸部区域选为兴趣区域ROI,重复上诉步骤即可,这里就不详细介绍了。当然,感兴趣的朋友也可以试试其他的xml文件作为分类器玩一下啊,感觉LBP特征虽然xml文件的大小很小,但效果还可以,不过我没有做过多的测试。光说不练假把式,最后贴上效果图和源代码的下载地址代码下载地址:http://download.csdn.net/detail/yang_xian521/3800468OpenCV配套的教程Tutorials对于Video的部分,没有实例进行说明,我只能摸石头过河啦,之前试过一个camShift做目标检测,这次试一试光流法做运动估计。这里使用的光流法是比较常用的 Lucas-Kanade方法。对于光流法的原理,我就不过多介绍了,主要讲使用OpenCV如何实现。首先利用goodFeaturesToTrack函数得到图像中的强边界作为跟踪的特征点,接下来要调用calcOpticalFlowPyrLK函数,输入两幅连续的图像,并在第一幅图像里选择一组特征点,输出为这组点在下一幅图像中的位置。再把得到的跟踪结果过滤一下,去掉不好的特征点。再把特征点的跟踪路径标示出来。说着好简单哦~~程序的效果和代码下载http://download.csdn.net/detail/yang_xian521/3811478视频捕捉的对象中,背景通常保持不变。一般分析中关注移动的前景物体,威力提取出前景物体,需要建立背景的模型,将模型和当前帧进行比对检测前景物体。前景提取应用非常广泛,特别是在智能监控领域中。如果有不含前景物体的背景图片,提取前景的工作相对容易,只需要比对当前帧和背景图片的不同,调用函数absdiff实现。但是大多数情况,获得背景图片是不可能的,比如在复杂的场景下,或者有光线条件的变化。因此,就需要动态的变换背景。一种简单的办法是对所观察到的图片取平均,但这样做也有很多弊端,首先,这种办法在计算背景图片的前需要输入大量的图片,其次我们进行取平均的过程中不能有前景物体进入。所以一种相对好的办法是动态建立背景图片并实时更新。具体的实现过程主要分为两部分:一部分是调用absdiff函数找出当前图片和背景图片的区别,这之中使用了threshold函数去除为前景,当前图片像素与背景图片像素变化超过一定阈值的时候才认定其为前景;另一个工作是更新背景图片,调用函数accumulateWeighted,根据权重参数可以调整背景更新的速度,将当前图片更新到背景中,这里巧妙利用得到的前景提取结果作为mask,在更新背景图片的过程中避免了前景的干扰。程序效果如图,代码下载地址为http://download.csdn.net/detail/yang_xian521/3814878虽然可以调整阈值参数和权重更新速度调节前景提取的结果,但从测试视频可以发现,树叶的运动对结果的干扰还是不小的,特别对于第一帧出现前景的情况,由于后续更新背景都是对前景mask后对背景进行更新的,所以第一帧的前景部分对背景的影响因子很难被更新掉。这里提出一种改进的办法——混合高斯模型。可以使一个像素具有更多的信息,这样可以有效的减少类似树叶的不停飘动,水波的不停荡漾这种对前景的干扰。这个精密的算法比之前我所介绍的简单方法要复杂很多,不易实现。还好,OpenCV已经为我们做好了相关工作,将其封装在类BackgroundSubtractorMOG,使用起来非常方便。实现代码如下:view plainMat frame Mat foreground// 前景图片 namedWindow("Extracted Foreground") // 混合高斯物体 BackgroundSubtractorMOG mog bool stop(false) while (!stop) { if (!capture.read(frame)) { break } // 更新背景图片并且输出前景 mog(frame, foreground, 0.01) // 输出的前景图片并不是2值图片,要处理一下显示threshold(foreground, foreground, 128, 255, THRESH_BINARY_INV)


欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/tougao/12208168.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-05-21
下一篇2023-05-21

发表评论

登录后才能评论

评论列表(0条)

    保存