文章目录
- 一、代码示例
- 二、SVM训练过程
- 三、显示支持向量机分类区域
- 四、opencv3 SVM trainAuto()使用规则
- 五、opencv3 svm参数详解
- 六、opencv svm常见错误
- 6.1error: unknown type name ‘CvSVMParams‘解决方案
- 6.2 Error: Assertion failed (samples.cols == var_count && samples.type() == 5) in cv::ml::SVMImpl::predict
- 6.3 Assertion failed (samples.cols == var_count && samples.type() == CV_32F)
- 6.4 OpenCV(3.4.14) Error: Assertion failed (nsamples == 1) in cv::ml::SVMImpl::predict, file C:\build\3_4_winpack-build-win64-vc15\opencv\modules\ml\src\svm.cpp, line 2022
- 6.5 OpenCV Error: Assertion failed (samples.cols == var_count && samples.type()== 5) in cv::ml::SVMImpl::predict, file C:\build\master_winpack-build-win64-vc14\opencv\modules\ml\src\svm.cpp, line 2005
- 七、OpenCV2与OpenCV3的SVM操作
- 八、OpenCV3版本SVM参数的设定详解
- 九、C++中如何记录程序运行时间
- 十、c++ vector容器的嵌套使用
- 十一、OPenCV中的Mat类中rowRange和colRange基本使用
- 十二、opencv3多分类代码
- 十三、C++ std::ostringstream 是什么 怎么用
- 十四、c++中二维Vector的初始化(vector subscript out of range)
- 十五、OpenCV学习心得:vector<Mat>数据存储问题
- 十六、学会封装train\precdit
一、代码示例
参考
OpenCV之机器学习:利用svm(支持向量机)分类
http://t.csdn.cn/jeLw4
OpneCV——svm数字识别
http://t.csdn.cn/HlnUS
OpenCV——SVM学习(一)
http://t.csdn.cn/wCNCF
基于SVM+HOG的手写体数字识别
http://t.csdn.cn/U7XuH
Opencv3.0 手写数字识别(Hog特征+SVM分类器)
http://t.csdn.cn/nMgVT
opencv——基于KNN的数字识别
http://t.csdn.cn/uYidI
1.1普通版train
`话不多说,直接上代码:
#include <opencv2/opencv.hpp>
#include <opencv2/ml.hpp>
#include<iostream>
using namespace std;
using namespace cv;
using namespace cv::ml;
int main(int, char**)
{
//视觉表示的数据
int width = 512, height = 512;
Mat image = Mat::zeros(height, width, CV_8UC3);
//设置训练数据
int labels[4] = { 1, -1, -1, -1 };
float trainingData[4][2] = { {501, 10}, {255, 10}, {501, 255}, {10, 501} };
Mat trainingDataMat(4, 2, CV_32FC1, trainingData);
Mat labelsMat(4, 1, CV_32SC1, labels);//4行1列
//训练SVM
Ptr<SVM> svm = SVM::create();//创建一个svm对象
svm->setType(SVM::C_SVC); //设置SVM公式类型
svm->setKernel(SVM::LINEAR);//设置SVM核函数类型
svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 100, 1e-6));//设置SVM训练时迭代终止条件
Ptr<TrainData> train_data = TrainData::create(trainingDataMat, ROW_SAMPLE, labelsMat); //创建训练集
svm->train(train_data); //参数默认
//svm->train(trainingDataMat, ROW_SAMPLE, labelsMat);//训练数据
//显示SVM的决策区域
Vec3b green(0, 255, 0), blue(255, 0, 0);
for (int i = 0; i < image.rows; ++i)
for (int j = 0; j < image.cols; ++j)
{
Mat sampleMat = (Mat_<float>(1, 2) << j, i);//蓝绿赋值
float response = svm->predict(sampleMat);
if (response == 1)
image.at<Vec3b>(i, j) = green;
else if (response == -1)
image.at<Vec3b>(i, j) = blue;
}
//显示训练数据
int thickness = -1;//-1表示实心
int lineType = 8;
circle(image, Point(501, 10), 5, Scalar(0, 0, 0), thickness, lineType);//半径为5
circle(image, Point(255, 10), 5, Scalar(255, 255, 255), thickness, lineType);
circle(image, Point(501, 255), 5, Scalar(255, 255, 255), thickness, lineType);
circle(image, Point(10, 501), 5, Scalar(255, 255, 255), thickness, lineType);
//显示支持向量
thickness = 2;
lineType = 8;
Mat sv = svm->getUncompressedSupportVectors();
//cout << sv << endl;//输出结果:[501,10; 255,10; 501,255]为什么???
for (int i = 0; i < sv.rows; ++i)
{
const float* v = sv.ptr<float>(i);//指向矩阵sv的第i行
circle(image, Point((int)v[0], (int)v[1]), 6, Scalar(128, 128, 128), thickness, lineType);//灰色,半径为6
}
imwrite("result.png", image); //保存图像
imshow("SVM Simple Example", image); //显示图像
waitKey(0);
}
输出如下:
SVM训练的两种方式
Ptr<TrainData> train_data = TrainData::create(trainingDataMat, ROW_SAMPLE, labelsMat); //创建训练集
svm->train(train_data); //参数默认
//svm->train(trainingDataMat, ROW_SAMPLE, labelsMat);//训练数据
注意代码中这几行:
if (0)
svm->setKernel(ml::SVM::LINEAR);
else{
svm->setKernel(ml::SVM::POLY);
svm->setDegree(1.0);
}
如果核函数用ml::SVM::LINEAR,下边Mat SupportVectorsMat = svm->getSupportVectors();将无法正常输出支持向量。
1.2升级版加入trainAuto
#include <opencv2/opencv.hpp>
#include <opencv2/ml.hpp>
#include<iostream>
using namespace std;
using namespace cv;
using namespace cv::ml;
int main(int, char**)
{
//视觉表示的数据
int width = 512, height = 512;
Mat image = Mat::zeros(height, width, CV_8UC3);
//设置训练数据
int labels[4] = { 1, -1, -1, -1 };
float trainingData[4][2] = { {501, 10}, {255, 10}, {501, 255}, {10, 501} };
Mat trainingDataMat(4, 2, CV_32FC1, trainingData);
Mat labelsMat(4, 1, CV_32SC1, labels);//4行1列
//训练SVM
Ptr<SVM> svm = SVM::create();//创建一个svm对象
svm->setType(SVM::C_SVC); //设置SVM公式类型
svm->setKernel(SVM::LINEAR);//设置SVM核函数类型
svm->setC(0.1);//必须初始化
svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 100, 1e-6));//设置SVM训练时迭代终止条件
ParamGrid c_grid(0.0001, 1000, 10);//若·不设置使用默认值范围
Ptr<TrainData> train_data = TrainData::create(trainingDataMat, ROW_SAMPLE, labelsMat); //创建训练集
svm->trainAuto(train_data, 10, c_grid);//10 ---kFold
cout << "训练完成" << endl;
/***************方法1**********************/
//Ptr<SVM> svm = SVM::create();//创建一个svm对象
//svm->setType(SVM::C_SVC); //设置SVM公式类型
//svm->setKernel(SVM::LINEAR);//设置SVM核函数类型
//svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 100, 1e-6));//设置SVM训练时迭代终止条件
//Ptr<TrainData> train_data = TrainData::create(trainingDataMat, ROW_SAMPLE, labelsMat); //创建训练集
//svm->train(train_data); //参数默认
/***************方法2**********************/
Ptr<SVM> svm = SVM::create();//创建一个svm对象
svm->setType(SVM::C_SVC); //设置SVM公式类型
svm->setKernel(SVM::LINEAR);//设置SVM核函数类型
svm->setC(0.1);//必须初始化
svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 100, 1e-6));//设置SVM训练时迭代终止条件
ParamGrid c_grid(0.0001, 1000, 10);//若·不设置使用默认值范围
Ptr<TrainData> train_data = TrainData::create(trainingDataMat, ROW_SAMPLE, labelsMat); //创建训练集
svm->trainAuto(train_data, 10, c_grid);//10 ---kFold
//Ptr<TrainData> train_data = TrainData::create(trainingDataMat, ROW_SAMPLE, labelsMat);
//svm->train(trainingDataMat, ROW_SAMPLE, labelsMat);//训练数据
/*************方法3******************/ //此方法暂时表现不太好
//Ptr<SVM> svm = SVM::create();//创建一个svm对象
//svm->setType(SVM::C_SVC); //设置SVM公式类型
//svm->setKernel(SVM::LINEAR);//设置SVM核函数类型
//svm->setC(0.1);//必须初始化
//svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 100, 1e-6));//设置SVM训练时迭代终止条件
//ParamGrid c_grid(0.0001, 1000, 10);//若·不设置使用默认值范围
//Ptr<TrainData> train_data = TrainData::create(trainingDataMat, ROW_SAMPLE, labelsMat); //创建训练集
/* svm->trainAuto(train_data, 10, svm->getDefaultGrid(svm->getC()),
svm -> getDefaultGrid(svm->getGamma()),
svm->getDefaultGrid(svm->getP()),
svm->getDefaultGrid(svm->getNu()),
svm->getDefaultGrid(svm->getCoef0()),
svm->getDefaultGrid(svm->getDegree()), true);*/
//显示SVM的决策区域
Vec3b green(0, 255, 0), blue(255, 0, 0);
for (int i = 0; i < image.rows; ++i)
for (int j = 0; j < image.cols; ++j)
{
Mat sampleMat = (Mat_<float>(1, 2) << j, i);//蓝绿赋值
float response = svm->predict(sampleMat);
if (response == 1)
image.at<Vec3b>(i, j) = green;
else if (response == -1)
image.at<Vec3b>(i, j) = blue;
}
//显示训练数据
int thickness = -1;//-1表示实心
int lineType = 8;
circle(image, Point(501, 10), 5, Scalar(0, 0, 0), thickness, lineType);//半径为5
circle(image, Point(255, 10), 5, Scalar(255, 255, 255), thickness, lineType);
circle(image, Point(501, 255), 5, Scalar(255, 255, 255), thickness, lineType);
circle(image, Point(10, 501), 5, Scalar(255, 255, 255), thickness, lineType);
//显示支持向量
thickness = 2;
lineType = 8;
Mat sv = svm->getUncompressedSupportVectors();
//cout << sv << endl;//输出结果:[501,10; 255,10; 501,255]为什么???
for (int i = 0; i < sv.rows; ++i)
{
const float* v = sv.ptr<float>(i);//指向矩阵sv的第i行
circle(image, Point((int)v[0], (int)v[1]), 6, Scalar(128, 128, 128), thickness, lineType);//灰色,半径为6
}
imwrite("result.png", image); //保存图像
imshow("SVM Simple Example", image); //显示图像
waitKey(0);//注意:imshow之后必须加waitKey,否则无法显示图像
}
1.4 测试自己写入数据
#include <opencv2/opencv.hpp>
#include <opencv2/ml.hpp>
#include<iostream>
using namespace std;
using namespace cv;
using namespace cv::ml;
int main(int, char**)
{
cv::Mat mo = cv::Mat::ones(cv::Size(1, 1), CV_32SC1); // 全1矩阵
cout << mo << endl;
cv::Mat mo1 = cv::Mat::ones(cv::Size(1, 3), CV_32SC1)*-1; // 全1矩阵
cout << mo1 << endl;
cv::Mat A;
cv::vconcat(mo, mo1,A);
cout << A << endl;
//视觉表示的数据
int width = 512, height = 512;
Mat image = Mat::zeros(height, width, CV_8UC3);
//设置训练数据
int labels[4] = { 1, -1, -1, -1 };
float trainingData[4][2] = { {501, 10}, {255, 10}, {501, 255}, {10, 501} };
Mat trainingDataMat(4, 2, CV_32FC1, trainingData);
Mat labelsMat(4, 1, CV_32SC1, labels);//4行1列
//Mat labelsMat = A.clone();
//训练SVM
Ptr<SVM> svm = SVM::create();//创建一个svm对象
svm->setType(SVM::C_SVC); //设置SVM公式类型
svm->setKernel(SVM::LINEAR);//设置SVM核函数类型
svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 100, 1e-6));//设置SVM训练时迭代终止条件
Ptr<TrainData> train_data = TrainData::create(trainingDataMat, ROW_SAMPLE, A); //创建训练集
svm->train(train_data); //参数默认
//svm->train(trainingDataMat, ROW_SAMPLE, labelsMat);//训练数据
//显示SVM的决策区域
Vec3b green(0, 255, 0), blue(255, 0, 0);
for (int i = 0; i < image.rows; ++i)
for (int j = 0; j < image.cols; ++j)
{
Mat sampleMat = (Mat_<float>(1, 2) << j, i);//蓝绿赋值
float response = svm->predict(sampleMat);
if (response == 1)
image.at<Vec3b>(i, j) = green;
else if (response == -1)
image.at<Vec3b>(i, j) = blue;
}
//显示训练数据
int thickness = -1;//-1表示实心
int lineType = 8;
circle(image, Point(501, 10), 5, Scalar(0, 0, 0), thickness, lineType);//半径为5
circle(image, Point(255, 10), 5, Scalar(255, 255, 255), thickness, lineType);
circle(image, Point(501, 255), 5, Scalar(255, 255, 255), thickness, lineType);
circle(image, Point(10, 501), 5, Scalar(255, 255, 255), thickness, lineType);
//显示支持向量
thickness = 2;
lineType = 8;
Mat sv = svm->getUncompressedSupportVectors();
//cout << sv << endl;//输出结果:[501,10; 255,10; 501,255]为什么???
for (int i = 0; i < sv.rows; ++i)
{
const float* v = sv.ptr<float>(i);//指向矩阵sv的第i行
circle(image, Point((int)v[0], (int)v[1]), 6, Scalar(128, 128, 128), thickness, lineType);//灰色,半径为6
}
imwrite("result1.png", image); //保存图像
imshow("SVM Simple Example", image); //显示图像
//system("pause");
waitKey(0);
}
二、SVM训练过程
支持向量机SVM的训练过程主要包括以下四个方面:
2.1 数据准备
本案例中的训练数据如下:
//设置训练数据
int labels[4] = { 1, -1, -1, -1 };
float trainingData[4][2] = { {501, 10}, {255, 10}, {501, 255}, {10, 501} };
Mat trainingDataMat(4, 2, CV_32FC1, trainingData);
Mat labelsMat(4, 1, CV_32SC1, labels);
这里主要设置了四个坐标点,其中第一个坐标点的标签为1,后三个坐标点的标签为-1。最后将其转化为SVM能识别的所需的类型。
【注】:
①样本数据必须是CV_32FC1类型。这是由opencv3版本决定的;
②样本标签必须是CV_32SC1,opencv3后从int数组转换为CV_32SC1类型,而opencv2是从float数据转换。
2.2 初始化SVM参数
初始化参数程序如下:
Ptr<SVM> svm = SVM::create();//创建一个svm对象
svm->setType(SVM::C_SVC); //设置SVM公式类型
svm->setKernel(SVM::LINEAR);//设置SVM核函数类型
svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 100, 1e-6));//设置SVM训练时迭代终止条件
SVM::create() ——创建一个SVM对象
svm->setType(SVM::C_SVC); ——设置SVM公式类型,包括C_SVC、NU_SVC、ONE_CLASS、EPS_SVR、NU_SVR,用于指定分类、回归等,默认为C_SVC;
svm->setKernel(SVM::LINEAR); ——设置SVM核函数类型,包括CUSTOM、LINEAR、POLY、RBF、SIGMOID、CHI2、INTER,默认值为RBF;
非线性分类中常见的核函数包括:齐次多项式、非齐次多项式、双曲正切、高斯核(Gaussiankernel)、线性核、径向基函数(radialbasis function, RBF)核和、Sigmoid核。
svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 100, 1e-6)); ——设置SVM训练时迭代终止条件,默认值是cv::TermCriteria(cv::TermCriteria::MAX_ITER + TermCriteria::EPS,1000, FLT_EPSILON);
2.3 训练SVM
程序如下:
svm->train(trainingDataMat, ROW_SAMPLE, labelsMat);//训练数据
2.4 保存数据
当遇到较大数据量时,一般需要将数据保存为xml文件,以便在其他程序中得到调用。程序如下:
//保存模型
svm->save("svm.xml");
2.5 加载保存的数据
当需要加载2.4中保存的xml文件时,代码为:
Ptr<SVM> svm = SVM::load("svm.xml");//加载svm对象
2.6 测试数据
测试数据代码如下:
int response = (int)svm->predict(p);
其中,p为CV_32FC1类型数据,response输出SVM预测的数据类型。
三、显示支持向量机分类区域
本例中,由于数据量较小,因此我们可以显示SVM的决策区域,以便更好地了解SVM算法的实现过程。主要步骤如下:
3.1 初始化图像显示区域
//视觉表示的数据
int width = 512, height = 512;
Mat image = Mat::zeros(height, width, CV_8UC3);
将图像显示区域设置为512*512像素大小。
3.2 显示SVM决策区域
//显示SVM的决策区域
Vec3b green(0, 255, 0), blue(255, 0, 0);
for (int i = 0; i < image.rows; ++i)
for (int j = 0; j < image.cols; ++j)
{
Mat sampleMat = (Mat_<float>(1, 2) << j, i);//蓝绿赋值
float response = svm->predict(sampleMat);
if (response == 1)
image.at<Vec3b>(i, j) = green;
else if (response == -1)
image.at<Vec3b>(i, j) = blue;
}
遍历图像上所有点,如果该点预测为1,则赋值为绿色;否则如果预测为-1,则赋值为蓝色。
3.3 显示训练数据
//显示训练数据
int thickness = -1;//-1表示实心
int lineType = 8;
circle(image, Point(501, 10), 5, Scalar(0, 0, 0), thickness, lineType);//半径为5
circle(image, Point(255, 10), 5, Scalar(255, 255, 255), thickness, lineType);
circle(image, Point(501, 255), 5, Scalar(255, 255, 255), thickness, lineType);
circle(image, Point(10, 501), 5, Scalar(255, 255, 255), thickness, lineType);
将训练数据标签为1的坐标点显示为半径为5的黑色实心圆圈,标签为-1的坐标点显示为半径为5的白色实心圆圈。
3.4 显示支持向量
那什么是支持向量呢?看一幅图:
样本中距离超平面最近的一些点,这些点叫做支持向量。
代码如下:
//显示支持向量
thickness = 2;
lineType = 8;
Mat sv = svm->getUncompressedSupportVectors();
//cout << sv << endl;//输出结果:[501,10; 255,10; 501,255]为什么???
for (int i = 0; i < sv.rows; ++i)
{
const float* v = sv.ptr<float>(i);//指向矩阵sv的第i行
circle(image, Point((int)v[0], (int)v[1]), 6, Scalar(128, 128, 128), thickness, lineType);//灰色,半径为6
}
将所有训练得到的支持向量绘制为半径为6的空心灰色圆圈。
3.5 输出结果
输出图像为:
在上图中可以发现,被灰色圆圈包围的三个数据才决定了最终的决策边界,所以这三个数据被称之为支持向量。
四、opencv3 SVM trainAuto()使用规则
4.1 trainAuto简介
bool trainAuto(
const Ptr<TrainData>& data,
int kFold = 10, //交叉验证次数,默认值为10
ParamGrid Cgrid = getDefaultGrid(C),//默认值为0.1~500 步进 5
ParamGrid gammaCgrid =getDefaultGrid(GAMMA),//默认值为1e-5~0.6 步进 15
ParamGrid pGrid = getDefaultGrid(P),//默认值为0.01~100 步进 7
ParamGrid nuGrid = getDefaultGrid(NU),//默认值为0.01~0.2 步进 3
ParamGrid coeffGrid = getDefaultGrid(COEF),//默认值为0.1~300 步进 14
ParamGrid degreeGrid = getDefaultGrid(DEGREE),//默认值为0.01~4 步进 7
bool balanced = false
);
使用trainAuTo的例子如下
Ptr<SVM> svm = SVM::create();
svm->setType(SVM::Types::C_SVC);
svm->setKernel(SVM::KernelTypes::RBF);
svm->setC(0.1);//必须初始化
Ptr<TrainData> td = TrainData::create(sampleFeatureMat, SampleTypes::ROW_SAMPLE, sampleLabelMat);
//训练分类器
//svm->train(td);
ParamGrid c_grid(0.0001, 1000, 10);//若·不设置使用默认值范围
svm->trainAuto(td,10,c_grid);
cout << "训练完成" << endl;
//将训练好的SVM模型保存为xml文件
svm->save("SVM_HOG1.xml");
这里trainAuto使用了默认的参数, 比如对于C, 它会在 0.1 ~ 500寻找最优值,
如果需要自定义参数范围, 可以按下面代码, 比如 C [0.0001,1000], logStep =10
ParamGrid c_grid(0.0001, 1000, 10);//若·不设置使用默认值范围
4.1SVM的使用trainAuto(),K折交叉验证优化参数
trainAuto()函数中,使用了K折交叉验证来优化参数,会自动寻找最优参数。
两种用法:标黄的等效
virtual bool trainAuto( const Ptr<TrainData>& data,
int kFold = 10,
ParamGrid Cgrid = getDefaultGrid(C),
ParamGrid gammaGrid = getDefaultGrid(GAMMA),
ParamGrid pGrid = getDefaultGrid(P),
ParamGrid nuGrid = getDefaultGrid(NU),
ParamGrid coeffGrid = getDefaultGrid(COEF),
ParamGrid degreeGrid = getDefaultGrid(DEGREE),
bool balanced=false) = 0;
bool trainAuto(InputArray samples,int layout,InputArray responses,
int kFold = 10,
Ptr<ParamGrid> Cgrid = SVM::getDefaultGridPtr(SVM::C),
Ptr<ParamGrid> gammaGrid = SVM::getDefaultGridPtr(SVM::GAMMA),
Ptr<ParamGrid> pGrid = SVM::getDefaultGridPtr(SVM::P),
Ptr<ParamGrid> nuGrid = SVM::getDefaultGridPtr(SVM::NU),
Ptr<ParamGrid> coeffGrid = SVM::getDefaultGridPtr(SVM::COEF),
Ptr<ParamGrid> degreeGrid = SVM::getDefaultGridPtr(SVM::DEGREE),
bool balanced=false);
第一种使用方式:
Ptr<TrainData> train_data= TrainData::create(InputArray samples, int layout, InputArray responses); //创建训练集
svm->trainAuto(train_data); //参数默认
第二种使用方式:
svm->trainAuto(InputArray samples, int layout, InputArray responses);//直接用
例如
svm->trainAuto(train_data,ROW_SAMPLE,labels);
注意:无论哪种方式,samples必须行为样本,列为特征。responses标签1行或1列都可以,但是必须与样本类别对应。
4.2 train()
CvSVM::train(const CvMat* trainData,
const CvMat* responses,
const CvMat* varIdx=0,
const CvMat* sampleIdx=0,
CvSVMParams params=CvSVMParams()
)
其中,参数信息:
1.trainData: 练习数据,必须是CV_32FC1(32位浮点类型,单通道)。数据必须是CV_ROW_SAMPLE的,即特点向量以行来存储。
2.responses: 响应数据,凡是1D向量存储在CV_32SC1(仅仅用在分类题目上)或者CV_32FC1格局。
3.varIdx: 指定感爱好的特点。可所以整数(32sC1)向量,例如以0为开端的索引,或者8位(8uC1)的应用的特点或者样本的掩码。用户也可以传入NULL指针,用来默示练习中应用所有变量/样本。
4.sampleIdx: 指定感爱好的样本。描述同上。
5.params: SVM参数。
可以看出,练习数据trainData就是已经存储的数组Datetrain,而相应数据responses就相对于数组label,为了达到SVM训练相对应的数据类型,可以进行转化:
Mat trainingDataMat(n, 2, CV_32FC1, Datatrain); Mat labelsMat(n,1,
CV_32FC1, label);
4.3 train_auto
建议:先用opencv3训练模型,得到hog_descriptor.xml文件,然后再使用opencv2。由于opencv2的SVM没有trainAuto这样的接口,需要自己设置参数,可以先用opencv3训练好SVM模型,再根据其参数设置opencv2下SVM的C和gamma,然后再训练。
其实opencv中SVM类是提供了优化参数值功能的,瞬间感觉世界美好了。
trainAuto()函数中,使用了K折交叉验证来优化参数,会自动寻找最优参数。
下面讲具体的做法:
要让svm自动优化参数,那么训练时就不能再用train函数了,而应该用train_auto函数。下面是train_auto的函数原型
bool CvSVM::train_auto(const Mat& trainData,
const Mat& responses,
const Mat& varIdx,
const Mat& sampleIdx,
CvSVMParams params,
int k_fold=10,
CvParamGrid Cgrid=CvSVM::get_default_grid(CvSVM::C), CvParamGrid gammaGrid=CvSVM::get_default_grid(CvSVM::GAMMA), CvParamGrid pGrid=CvSVM::get_default_grid(CvSVM::P), CvParamGrid nuGrid=CvSVM::get_default_grid(CvSVM::NU), CvParamGrid coeffGrid=CvSVM::get_default_grid(CvSVM::COEF), CvParamGrid degreeGrid=CvSVM::get_default_grid(CvSVM::DEGREE),
bool balanced=false
)
自动训练函数的参数注释(13个) 前5个参数参考构造函数的参数注释。 k_fold:
交叉验证参数。训练集被分成k_fold的自子集。其中一个子集是用来测试模型,其他子集则成为训练集。所以,SVM算法复杂度是执行k_fold的次数。
*Grid: (6个)对应的SVM迭代网格参数。 balanced: 如果是true则这是一个2类分类问题。这将会创建更多的平衡交叉验证子集。
自动训练函数的使用说明
这个方法根据CvSVMParams中的最佳参数C, gamma, p, nu, coef0, degree自动训练SVM模型。
参数被认为是最佳的交叉验证,其测试集预估错误最小。
如果没有需要优化的参数,相应的网格步骤应该被设置为小于或等于1的值。例如,为了避免gamma的优化,设置gamma_grid.step =
0,gamma_grid.min_val, gamma_grid.max_val为任意数值。所以params.gamma 由gamma得出。
最后,如果参数优化是必需的,但是相应的网格却不确定,你可能需要调用函数CvSVM::get_default_grid(),创建一个网格。例如,对于gamma,调用CvSVM::get_default_grid(CvSVM::GAMMA)。
该函数为分类运行 (params.svm_type=CvSVM::C_SVC 或者
params.svm_type=CvSVM::NU_SVC) 和为回归运行 (params.svm_type=CvSVM::EPS_SVR
或者params.svm_type=CvSVM::NU_SVR)效果一样好。如果params.svm_type=CvSVM::ONE_CLASS,没有优化,并指定执行一般的SVM。
这里需要注意的是,对于需要的优化的参数虽然train_auto可以自动选择最优值,但在代码中也要先赋初始值,要不然编译能通过,但运行时会报错。下面是示例代码
CvSVMParams param;
param.svm_type = CvSVM::EPS_SVR;
param.kernel_type = CvSVM::RBF;
param.C = 1; //给参数赋初始值
param.p = 5e-3; //给参数赋初始值
param.gamma = 0.01; //给参数赋初始值
param.term_crit = cvTermCriteria(CV_TERMCRIT_EPS, 100, 5e-3);
//对不用的参数step设为0
CvParamGrid nuGrid = CvParamGrid(1,1,0.0);
CvParamGrid coeffGrid = CvParamGrid(1,1,0.0);
CvParamGrid degreeGrid = CvParamGrid(1,1,0.0);
CvSVM regressor;
regressor.train_auto(PCA_training,tr_label,NULL,NULL,param,
10,
regressor.get_default_grid(CvSVM::C),
regressor.get_default_grid(CvSVM::GAMMA),
regressor.get_default_grid(CvSVM::P),
nuGrid,
coeffGrid,
degreeGrid);
用上面的代码的就可以自动训练并优化参数。最后,若想查看优化后的参数值,可以使用CvSVM::get_params()函数来获得优化后的CvSVMParams。下面是示例代码:
CvSVMParams params_re = regressor.get_params();
regressor.save("training_srv.xml");
float C = params_re.C;
float P = params_re.p;
float gamma = params_re.gamma;
printf("\nParms: C = %f, P = %f,gamma = %f \n",C,P,gamma);
参数配置CvSVMParams是SVM的核心部分,在Opencv中它被定义成一个结构体类型,如下:
struct CV_EXPORTS_W_MAP CvSVMParams
{
CvSVMParams();
CvSVMParams( int svm_type,
int kernel_type,
double degree,
double coef0,
double Cvalue,
double p,
CvMat* class_weights,
CvTermCriteria term_crit
);
CV_PROP_RW int svm_type;
CV_PROP_RW int kernel_type;
CV_PROP_RW double degree; // for poly
CV_PROP_RW double gamma; // for poly/rbf/sigmoid
CV_PROP_RW double coef0; // for poly/sigmoid
CV_PROP_RW double C; // for CV_SVM_C_SVC, CV_SVM_EPS_SVR and CV_SVM_NU_SVR
CV_PROP_RW double nu; // for CV_SVM_NU_SVC, CV_SVM_ONE_CLASS, and CV_SVM_NU_SVR
CV_PROP_RW double p; // for CV_SVM_EPS_SVR
CvMat* class_weights; // for CV_SVM_C_SVC
CV_PROP_RW CvTermCriteria term_crit; // termination criteria
};
第一个参数:svm_type,SVM的类型
作用主要在于使用SVM处理每一个数据点时的分类问题,包括处理意外的点,如果需要拟合高维度的图像时的拟合距离等。
CvSVM::C_SVC:表示在不能精确地使用线性曲线来分割时,可以忽略某些点,但是需要保持尽量最好地线性分类
CvSVM::NU_SVC:n类似然不完全分类的分类器。参数nu取代了c,其值在区间【0,1】中,nu越大,决策边界越平滑。
CvSVM::ONE_CLASS: 单分类器,把当前需要分类的所有数据归为一个类,线性分界线用于区分当前类和另外一个类
CvSVM::EPS_SVR :回归。 训练集中的特征向量和拟合出来的超平面的距离需要小于p。异常值惩罚因子C被采用。
CvSVM::NU_SVR :回归;nu 代替了p
第二个参数:kernel_type,核类型:
核函数的基本作用就是接受两个低维空间里的向量,能够计算出经过某个变换后在高维空间里的向量内积值。因此,核类型的作用在于选择哪种方法进行多维度的参数拟合。
CvSVM::LINEAR :线性核函数,当前维度内进行分界,最快的选择
CvSVM::POLY :多项式核
CvSVM::RBF:径向基,对于大多数情况都是一个较好的选择
CvSVM::SIGMOID: sigmoid函数被用作核函数
SVM_params.degree:核函数中的参数degree,针对多项式核函数;
SVM_params.gama:核函数中的参数gamma,针对多项式/RBF/SIGMOID核函数;
SVM_params.coef0:核函数中的参数,针对多项式/SIGMOID核函数;
SVM_params.C:SVM最优问题参数,设置C-SVC,EPS_SVR和NU_SVR的参数;
SVM_params.nu:SVM最优问题参数,设置NU_SVC,ONE_CLASS 和NU_SVR的参数;
SVM_params.p:SVM最优问题参数,设置EPS_SVR中损失函数p的值.
class_weights:可选权重,赋给指定的类别。一般乘以C以后去影响不同类别的错误分类惩罚项。权重越大,某一类别的误分类数据的惩罚项就越大。
term_crit:SVM的迭代训练过程的中止。(解决了部分受约束二次最优问题),迭代中止的方法有两种:
1.迭代到了一定的次数而中止
2.迭代到了指定的阙值后中止
这部分在学习特征检测时就有涉及,今天再次回顾:
TermCriteria(int type, int maxcount, double epsilon)
其中type取TermCriteria::MAX_ITER/TerCriteria::COUNT时表示迭代到最大次数中止,这时参数maxcount起作用;
type取TermCriteria::EPS时表示迭代到制定阙值后中止,这时参数epsilon起作用;
type取TermCriteria::EPS+TermCriteria::MAX_ITER时表示出现以上任意一种情况后都中止,这时两个参数都起作用。
到这里,已经可以对参数进行训练了,训练后得到的分界线可以由SVM.predict(Mat sampleMat)预测函数来判断,判断的结果则为label中的设定1,-1来进行标记,依次对每一个坐标进行预测都能得到该坐标的分类状况,坐标应化为Mat型的SampleMat来进行预判。
配置SVM训练器参数示例:
CvSVMParams SVM_params;
SVM_params.svm_type = CvSVM::C_SVC;
SVM_params.kernel_type = CvSVM::LINEAR;
SVM_params.degree = 0;
SVM_params.gamma = 1;
SVM_params.coef0 = 0;
SVM_params.C = 1;
SVM_params.nu = 0;
SVM_params.p = 0;
SVM_params.term_crit = cvTermCriteria(CV_TERMCRIT_ITER, 1000, 0.01);
//训练
//CvSVM svm;
//svm.train(trainingData, classes, Mat(), Mat(), SVM_params);
//保存模型
//svm.save("svm.xml");
opencv3
virtual bool cv::ml::SVM::trainAuto ( const Ptr< TrainData > & data,
int kFold = 10,
ParamGrid Cgrid = getDefaultGrid(C),
ParamGrid gammaGrid = getDefaultGrid(GAMMA),
ParamGrid pGrid = getDefaultGrid(P),
ParamGrid nuGrid = getDefaultGrid(NU),
ParamGrid coeffGrid = getDefaultGrid(COEF),
ParamGrid degreeGrid = getDefaultGrid(DEGREE),
bool balanced = false
)
bool cv::ml::SVM::trainAuto ( InputArray samples,
int layout,
InputArray responses,
int kFold = 10,
Ptr< ParamGrid > Cgrid = SVM::getDefaultGridPtr(SVM::C),
Ptr< ParamGrid > gammaGrid = SVM::getDefaultGridPtr(SVM::GAMMA),
Ptr< ParamGrid > pGrid = SVM::getDefaultGridPtr(SVM::P),
Ptr< ParamGrid > nuGrid = SVM::getDefaultGridPtr(SVM::NU),
Ptr< ParamGrid > coeffGrid = SVM::getDefaultGridPtr(SVM::COEF),
Ptr< ParamGrid > degreeGrid = SVM::getDefaultGridPtr(SVM::DEGREE),
bool balanced = false
)
等价
const Ptr< TrainData > & data
InputArray samples,int layout, InputArray responses
五、opencv3 svm参数详解
OpenCV 3.3中给出了支持向量机(Support Vector Machines)的实现,即cv::ml::SVM类,此类的声明在include/opencv2/ml.hpp文件中,实现在modules/ml/src/svm.cpp文件中,它既支持两分类,也支持多分类,还支持回归等,OpenCV中SVM的实现源自libsvm库。其中:
(1)、cv::ml::SVM类:继承自cv::ml::StateModel,而cv::ml::StateModel又继承自cv::Algorithm;
(2)、create函数:为static,new一个SVMImpl用来创建一个SVM对象;
(3)、setType/getType函数:设置/获取SVM公式类型,包括C_SVC、NU_SVC、ONE_CLASS、EPS_SVR、NU_SVR,用于指定分类、回归等,默认为C_SVC;
(4)、setGamma/getGamma函数:设置/获取核函数的γ参数,默认值为1;
(5)、setCoef0/getCoef0函数:设置/获取核函数的coef0参数,默认值为0;
(6)、setDegree/getDegree函数:设置/获取核函数的degreee参数,默认值为0;
(7)、setC/getC函数:设置/获取SVM优化问题的C参数,默认值为0;
(8)、setNu/getNu函数:设置/获取SVM优化问题的υ参数,默认值为0;
(9)、setP/getP函数:设置/获取SVM优化问题的ε参数,默认值为0;
(10)、setClassWeights/getClassWeights函数:应用在SVM::C_SVC,设置/获取weights,默认值是空cv::Mat;
(11)、setTermCriteria/getTermCriteria函数:设置/获取SVM训练时迭代终止条件,默认值是cv::TermCriteria(cv::TermCriteria::MAX_ITER + TermCriteria::EPS,1000, FLT_EPSILON);
(12)、setKernel/getKernelType函数:设置/获取SVM核函数类型,包括CUSTOM、LINEAR、POLY、RBF、SIGMOID、CHI2、INTER,默认值为RBF;
(13)、setCustomKernel函数:初始化CUSTOM核函数;
(14)、trainAuto函数:用最优参数训练SVM;
(15)、getSupportVectors/getUncompressedSupportVectors函数:获取所有的支持向量;
(16)、getDecisionFunction函数:决策函数;
(17)、getDefaultGrid/getDefaultGridPtr函数:生成SVM参数网格;
(18)、save/load函数:保存/载入已训练好的model,支持xml,yaml,json格式;
(19)、train/predict函数:用于训练/预测,均使用基类StatModel中的。
opencv2参数
由于3.0不存在为机器学习定义的类,因此不能用调用函数的方法对分类器进行操作,而代之使用:
svm->save("....");//文件形式为xml,可以保存在txt或者xml文件中
svm->getVarCount();//获取SVM支持向量的维数
svm->getSupportVectors;//返回Mat类型,获取SVM的支持向量
六、opencv svm常见错误
6.1error: unknown type name ‘CvSVMParams‘解决方案
//配置SVM训练器参数
opencv2:
CvSVMParams SVM_params;
SVM_params.svm_type = CvSVM::C_SVC;
SVM_params.kernel_type = CvSVM::LINEAR;
SVM_params.degree = 0;
SVM_params.gamma = 1;
SVM_params.coef0 = 0;
SVM_params.C = 1;
SVM_params.nu = 0;
SVM_params.p = 0;
这个是有问题的代码,然后改进的方法如下:opencv3
Ptr svm = SVM::create();
svm->setType(SVM::C_SVC);
svm->setKernel(SVM::LINEAR);
svm->setGamma(0.01);
svm->setC(10.0);
svm->setDegree(0);
svm->setGamma(1);
svm->setCoef0(0);
svm->setC(1);
svm->setNu(0);
svm->setP(0);
6.2 Error: Assertion failed (samples.cols == var_count && samples.type() == 5) in cv::ml::SVMImpl::predict
【1】【错误】在完成分类器的训练,直接predict是没有问题的,但是保存xml文件后,再用以下的语句加载训练好的xml模型,predict出现了问题。
svm->save("svm.xml")
svm->load<SVM>("trained-svm.xml");
【解决】
SVM::load() is a static function, which returns a new instance.
you have to use it like:
svm = Algorithm::load<SVM>("svm.xml");
cv::Ptr<cv::ml::SVM> svm = cv::ml::StatModel::load<cv::ml::SVM>("C:\\Users\\Administrator\\Desktop\\小李文献\\data\\svm.xml");
【2】【错误】
程序如下:
Ptrml::SVM svm = ml::SVM::create();
svm->load(“svm.xml”);
【解决】
Ptr<ml::SVM> svm = Algorithm::load<ml::SVM>("svm.xml");
这里需要一个未经初始化的实例来载入模型
【3】OpenCV调用SVM时,要对训练数据和测试数据进行相同的预处理。
6.3 Assertion failed (samples.cols == var_count && samples.type() == CV_32F)
VS2019+OpenCV4.3.0
我做的是用SVM + 傅里叶算子识别静态手势的项目
在调试中,我发现第一次 predict函数 可以运行,第二次就会报如下错误:Assertion failed (samples.cols == var_count && samples.type() == CV_32F)
for (int i = 0; i < 50; i++) {
for (int j = 0; j < 5; j++) {
String str = loadimg_path + to_string(j) + "/" + to_string(i) + ".jpg";
Mat src = imread(str);
calcute_fft(src); //计算傅里叶算子
for (int i = 0; i < 15; i++){
ffsFeature.push_back(fd[i]);
}
Mat sample(1, ffsFeature.size(), CV_32FC1, ffsFeature.data());
float r = psvm->predict(sample);//直接在 for循环中使用,只有第一次正常返回
//第二次便会报异常错误
r = abs(r - j) <= FLT_EPSILON ? 1.f : 0.f;
prec_num += r; //累计正确数
}
}
cout << "识别率:" << (prec_num / templete_sum) * 100 << "%" << endl;
我想到或许是因为上次的循环结果对这次 predict 造成影响,但是不知道要清理哪个结果,函数是什么。
解决方法(正确代码)如下:
把预测部分封装成一个函数,在 for 循环中调用:float r = predictSVM();
for (int i = 0; i < templete_num; i++) { //每一类的模板数
for (int j = 1; j <= class_num; j++) { //类别
// if(i != 1 || j != 2)
// continue;
String str = loadimg_path + to_string(j) + "/" + to_string(i) + ".jpg";
Mat src = imread(str);
if (src.empty())
cout << "not load image..." << endl;
calcute_fft(src);
float r = predictSVM(); //当函数调用时才没有报错
//直接在 for 循环中使用 match_number = psvm->predict(sample);
//一定会报错,具体原因不清楚
r = abs(r - j) <= FLT_EPSILON ? 1.f : 0.f;
prec_num += r;
}
cout << endl;
}
cout << "识别率:" << (prec_num / templete_sum) * 100 << "%" << endl;
6.4 OpenCV(3.4.14) Error: Assertion failed (nsamples == 1) in cv::ml::SVMImpl::predict, file C:\build\3_4_winpack-build-win64-vc15\opencv\modules\ml\src\svm.cpp, line 2022
说明C++版本的SVM算法predict每次只能识别一组数据
6.5 OpenCV Error: Assertion failed (samples.cols == var_count && samples.type()== 5) in cv::ml::SVMImpl::predict, file C:\build\master_winpack-build-win64-vc14\opencv\modules\ml\src\svm.cpp, line 2005
似乎像这样加载经过训练的文件会起作用:
Ptr<SVM> svm = SVM::create();
svm = SVM::load("Images/trainedImages.xml");
七、OpenCV2与OpenCV3的SVM操作
下面给了OpenCV2.0 的SVM代码(勿喷,直接从OpenCV官方网址复制下来的)
7.1opencv2的SVM训练代码
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/ml/ml.hpp>
using namespace cv;
int main()
{
// Data for visual representation
int width = 512, height = 512;
Mat image = Mat::zeros(height, width, CV_8UC3);
// Set up training data
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);
// Set up SVM's parameters
CvSVMParams params;
params.svm_type = CvSVM::C_SVC;
params.kernel_type = CvSVM::LINEAR;
params.term_crit = cvTermCriteria(CV_TERMCRIT_ITER, 100, 1e-6);
// Train the SVM
CvSVM SVM;
SVM.train(trainingDataMat, labelsMat, Mat(), Mat(), params);
Vec3b green(0,255,0), blue (255,0,0);
// Show the decision regions given by the SVM
for (int i = 0; i < image.rows; ++i)
for (int j = 0; j < image.cols; ++j)
{
Mat sampleMat = (Mat_<float>(1,2) << i,j);
float response = SVM.predict(sampleMat);
if (response == 1)
image.at<Vec3b>(j, i) = green;
else if (response == -1)
image.at<Vec3b>(j, i) = blue;
}
// Show the training data
int thickness = -1;
int lineType = 8;
circle( image, Point(501, 10), 5, Scalar( 0, 0, 0), thickness, lineType);
circle( image, Point(255, 10), 5, Scalar(255, 255, 255), thickness, lineType);
circle( image, Point(501, 255), 5, Scalar(255, 255, 255), thickness, lineType);
circle( image, Point( 10, 501), 5, Scalar(255, 255, 255), thickness, lineType);
// Show support vectors
thickness = 2;
lineType = 8;
int c = SVM.get_support_vector_count();
for (int i = 0; i < c; ++i)
{
const float* v = SVM.get_support_vector(i);
circle( image, Point( (int) v[0], (int) v[1]), 6, Scalar(128, 128, 128), thickness, lineType);
}
imwrite("result.png", image); // save the image
imshow("SVM Simple Example", image); // show it to the user
waitKey(0);
}
7.2OpenCV3的SVM训练代码
#include "opencv2/opencv.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/ml.hpp"
//using namespace cv;
//using namespace cv::ml;
int main(int argc, char** argv)
{
// visual representation
int width = 512;
int height = 512;
cv::Mat image = cv::Mat::zeros(height, width, CV_8UC3);
// training data
int labels[4] = { 1, -1, -1, -1 };
float trainingData[4][2] = { { 501, 10 }, { 255, 10 }, { 501, 255 }, { 10, 501 } };
cv::Mat trainingDataMat(4, 2, CV_32FC1, trainingData);
cv::Mat labelsMat(4, 1, CV_32SC1, labels);
// initial SVM
cv::Ptr<cv::ml::SVM> svm = cv::ml::SVM::create();
svm->setType(cv::ml::SVM::Types::C_SVC);
svm->setKernel(cv::ml::SVM::KernelTypes::LINEAR);
svm->setTermCriteria(cv::TermCriteria(cv::TermCriteria::MAX_ITER, 100, 1e-6));
// train operation
svm->train(trainingDataMat, cv::ml::SampleTypes::ROW_SAMPLE, labelsMat);
// prediction
cv::Vec3b green(0, 255, 0);
cv::Vec3b blue(255, 0, 0);
for (int i = 0; i < image.rows; i++)
{
for (int j = 0; j < image.cols; j++)
{
cv::Mat sampleMat = (cv::Mat_<float>(1, 2) << j, i);
float respose = svm->predict(sampleMat);
if (respose == 1)
image.at<cv::Vec3b>(i, j) = green;
else if (respose == -1)
image.at<cv::Vec3b>(i, j) = blue;
}
}
int thickness = -1;
int lineType = cv::LineTypes::LINE_8;
cv::circle(image, cv::Point(501, 10), 5, cv::Scalar(0, 0, 0), thickness, lineType);
cv::circle(image, cv::Point(255, 10), 5, cv::Scalar(255, 255, 255), thickness, lineType);
cv::circle(image, cv::Point(501, 255), 5, cv::Scalar(255, 255, 255), thickness, lineType);
cv::circle(image, cv::Point(10, 501), 5, cv::Scalar(255, 255, 255), thickness, lineType);
thickness = 2;
lineType = cv::LineTypes::LINE_8;
cv::Mat sv = svm->getSupportVectors();
for (int i = 0; i < sv.rows; i++)
{
const float* v = sv.ptr<float>(i);
cv::circle(image, cv::Point((int)v[0], (int)v[1]), 6, cv::Scalar(128, 128, 128), thickness, lineType);
}
cv::imshow("SVM Simple Example", image);
cv::waitKey(0);
return 0;
}
为了保证代码可读性,代码没有用using namespace cv或using namespace cv::ml;之类的代码,全部都写完整的名称,命名空间+类名。
比如设置SVM的核类型为线性,写成svm->setKernel(cv::ml::SVM::KernelTypes::LINEAR);。当然,这个只是一处地方,其他的请自己阅读。
// // 使用SVM分类器训练样本~~(opencv3.1以后使用的基本上都是采用模板来完成相应的功能)
Train 使用方法:
vector<float> featureVector;
vector<int> imageClass;
Ptr<ml::SVM> svm = ml::SVM::create();
svm->setType(ml::SVM::C_SVC);
svm->setKernel(ml::SVM::RBF);
//svm->setC(10);
//svm->setGamma(0.1);//caffe -gamma
svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 8000, 1e-8));
// svm->train(featureVectorOfSample, ml::ROW_SAMPLE, classOfSample);
Trainauto 使用方法:
vector<float> featureVector;
vector<int> imageClass;
Ptr<ml::TrainData>traindata = ml::TrainData::create(featureVectorOfSample, ml::ROW_SAMPLE, classOfSample);
Ptr<ml::SVM> svm = ml::SVM::create();
svm->setType(ml::SVM::C_SVC);
svm->setKernel(ml::SVM::RBF);
svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 6000, 1e-8));
svm->trainAuto(traindata, 10, svm->getDefaultGrid(svm->getC()), svm->getDefaultGrid(svm->getGamma()),
svm->getDefaultGrid(svm->getP()),
svm->getDefaultGrid(svm->getNu()),
svm->getDefaultGrid(svm->getCoef0()),
svm->getDefaultGrid(svm->getDegree()), true);
八、OpenCV3版本SVM参数的设定详解
opencv3.0和2.4的SVM接口有不同,基本可以按照以下的格式来执行:
ml::SVM::Params params;
params.svmType = ml::SVM::C_SVC;
params.kernelType = ml::SVM::POLY;
params.gamma = 3;
Ptr<ml::SVM> svm = ml::SVM::create(params);
Mat trainData; // 每行为一个样本
Mat labels;
svm->train( trainData , ml::ROW_SAMPLE , labels );
// ...
svm->save("....");//文件形式为xml,可以保存在txt或者xml文件中
Ptr<SVM> svm=statModel::load<SVM>("....");
Mat query; // 输入, 1个通道
Mat res; // 输出
svm->predict(query, res);
但是要注意,如果报错的话最好去看opencv3.0的文档,里面有函数原型和解释,我在实际操作的过程中,也做了一些改动
1)设置参数
SVM的参数有很多,但是与C_SVC和RBF有关的就只有gamma和C,所以设置这两个就好,终止条件设置和默认一样,由经验可得(其实是查阅了很多的资料,把gamma设置成0.01,这样训练收敛速度会快很多)
Ptr<SVM> svm = SVM::create();
svm->setType(SVM::C_SVC);
svm->setKernel(SVM::RBF);
svm->setGamma(0.01);
svm->setC(10.0);
svm->setTermCriteria(TermCriteria(CV_TERMCRIT_EPS, 1000,FLT_EPSILON));
svm_type –指定SVM的类型,下面是可能的取值:
CvSVM::C_SVC C类支持向量分类机。 n类分组 (n \geq 2),允许用异常值惩罚因子C进行不完全分类。
CvSVM::NU_SVC \nu类支持向量分类机。n类似然不完全分类的分类器。参数为 \nu 取代C(其值在区间【0,1】中,nu越大,决策边界越平滑)。
CvSVM::ONE_CLASS 单分类器,所有的训练数据提取自同一个类里,然后SVM建立了一个分界线以分割该类在特征空间中所占区域和其它类在特征空间中所占区域。
CvSVM::EPS_SVR \epsilon类支持向量回归机。训练集中的特征向量和拟合出来的超平面的距离需要小于p。异常值惩罚因子C被采用。
CvSVM::NU_SVR \nu类支持向量回归机。 \nu 代替了 p。
kernel_type –SVM的内核类型,下面是可能的取值:
CvSVM::LINEAR 线性内核。没有任何向映射至高维空间,线性区分(或回归)在原始特征空间中被完成,这是最快的选择。K(x_i, x_j) = x_i^T x_j.
CvSVM::POLY 多项式内核: K(x_i, x_j) = (\gamma x_i^T x_j + coef0)^{degree}, \gamma > 0.
CvSVM::RBF 基于径向的函数,对于大多数情况都是一个较好的选择: K(x_i, x_j) = e^{-\gamma ||x_i - x_j||^2}, \gamma > 0.
CvSVM::SIGMOID Sigmoid函数内核:K(x_i, x_j) = \tanh(\gamma x_i^T x_j + coef0).
degree – 内核函数(POLY)的参数degree。
gamma – 内核函数(POLY/ RBF/ SIGMOID)的参数\gamma。
coef0 – 内核函数(POLY/ SIGMOID)的参数coef0。
Cvalue – SVM类型(C_SVC/ EPS_SVR/ NU_SVR)的参数C。
nu – SVM类型(NU_SVC/ ONE_CLASS/ NU_SVR)的参数 \nu。
p – SVM类型(EPS_SVR)的参数 \epsilon。
class_weights – C_SVC中的可选权重,赋给指定的类,乘以C以后变成 class\_weights_i * C。所以这些权重影响不同类别的错误分类惩罚项。权重越大,某一类别的误分类数据的惩罚项就越大。
term_crit – SVM的迭代训练过程的中止条件,解决部分受约束二次最优问题。您可以指定的公差和/或最大迭代次数。
2)训练
Mat trainData;
Mat labels;
trainData = read_mnist_image(trainImage);
labels = read_mnist_label(trainLabel);
svm->train(trainData, ROW_SAMPLE, labels);
3)保存
svm->save("mnist_dataset/mnist_svm.xml");
- 测试,比对结果
(此处的FLT_EPSILON是一个极小的数,1.0 - FLT_EPSILON != 1.0)
Mat testData;
Mat tLabel;
testData = read_mnist_image(testImage);
tLabel = read_mnist_label(testLabel);
float count = 0;
for (int i = 0; i < testData.rows; i++) {
Mat sample = testData.row(i);
float res = svm1->predict(sample);
res = std::abs(res - tLabel.at<unsigned int>(i, 0)) <= FLT_EPSILON ? 1.f : 0.f;
count += res;
}
cout << "正确的识别个数 count = " << count << endl;
cout << "错误率为..." << (10000 - count + 0.0) / 10000 * 100.0 << "%....\n";
这里没有使用svm->predict(query, res);
然后就查看了opencv的文档,当传入数据是Mat 而不是cvMat时,可以利用predict的返回值(float)来判断预测是否正确。
九、C++中如何记录程序运行时间
9.1clock()计时函数
clock()是C/C++中的计时函数,而与其相关的数据类型是clock_t。在MSDN中,查得对clock函数定义如下:
clock_t clock(void) ;
简单而言,就是该程序从启动到函数调用占用CPU的时间。这个函数返回从“开启这个程序进程”到“程序中调用clock()函数”时之间的CPU时钟计时单元(clock tick)数,在MSDN中称之为挂钟时间(wal-clock);若挂钟时间不可取,则返回-1。其中clock_t是用来保存时间的数据类型。
C/C++头文件:#include
下面来看测试代码:
1 //计算一段程序运行的时间
2 #include<iostream>
3 #include<ctime>
4 using namespace std;
5 int main()
6 {
7 clock_t startTime,endTime;
8 startTime = clock();//计时开始
9 for (int i = 0; i < 2147483640; i++)
10 {
11 i++;
12 }
13 endTime = clock();//计时结束
14 cout << "The run time is: " <<(double)(endTime - startTime) / CLOCKS_PER_SEC << "s" << endl;
15 system("pause");
16 return 0;
17 }
18 //注释在:VC++6.0中可以用CLK_TCK替换CLOCKS_PER_SEC。
1 //计算整个程序运行的时间
2 #include<iostream>
3 #include<time.h>
4 using namespace std;
5 int main()
6 {
7 for (int i = 0; i < 2147483640; i++)
8 {
9 i++;
10 }
11 cout << "The run time is:" << (double)clock() /CLOCKS_PER_SEC<< "s" << endl;
12 system("pause");
13 return 0;
14 }
两个写在一起
1 #include<iostream>
2 #include<ctime>
3 using namespace std;
4 int main()
5 {
6 clock_t startTime,endTime;
7 for (int i = 0; i < 2147483640; i++)
8 {
9 i++;
10 }
11 startTime = clock();//计时开始
12 for ( i = 0; i < 2147483640; i++)
13 {
14 i++;
15 }
16 endTime = clock();//计时结束
17 cout << "The run time is:" <<(double)(endTime - startTime) / CLOCKS_PER_SEC << "s" << endl;
18 cout << "The run time is:" << (double)clock() /CLOCKS_PER_SEC<< "s" << endl;
19 system("pause");
20 return 0;
21 }
我用了两个一样的for循环,可以看出程序种的运行时间差不多是一个循环的两倍。
9.2GetTickCount()函数
GetTickCount是一种函数。GetTickCount返回(retrieve)从操作系统启动所经过(elapsed)的毫秒数,它的返回值是DWORD。
函数原型:
DWORD GetTickCount(void);
头文件:
C/C++头文件:winbase.h
windows程序设计中可以使用头文件windows.h
下面来看测试代码:
1 #include<iostream>
2 #include<Windows.h>
3 using namespace std;
4 int main()
5 {
6 DWORD startTime = GetTickCount();//计时开始
7 for (int i = 0; i < 2147483640; i++)
8 {
9 i++;
10 }
11 DWORD endTime = GetTickCount();//计时结束
12 cout << "The run time is:" << endTime - startTime << "ms" << endl;
13 system("pause");
14 return 0;
15 }
特别注意:这个函数并非实时发送,而是由系统每18ms发送一次,因此其最小精度为18ms。当需要有小于18ms的精度计算时,应使用StopWatch方法进行。用clock()函数计算运行时间,表示范围一定大于GetTickCount()函数,所以,建议使用clock()函数。两种函数功能一样,这就看自己的取舍。
十、c++ vector容器的嵌套使用
10.1定义
vector<vector<int>> M;
10.2添加元素
这里是vector的嵌套使用,本质是vector元素里的每个元素也是vector类型,所以抓住本质来添加元素就比较容易理解。
我们假设外层的vector的对象为M,为外层vector对象,则M中的每一个元素也是vector类型,记为N1,N2,N3……,为内层对象
则,我们得先形成一个个的N1,N2等的vector对象,然后再将这些vector对象添加进入外层vector对象M中
这样就比较容易理解向vector<vector>对象添加元素的原理了,实现如下:
如M=[[1 2 3], [4 5 6]],添加方式如下:
vector<vector<int>> M; //外层vector对象M
vector<int> N; //内层vector对象
N.push_back(1);
N.push_back(2);
N.push_back(3); //已经形成第一个内层vector对象N1
M.push_back(N); //将形第一个内层vector对象N添加到外层vector对象M中
N.clear(); //清楚N中的元素,可以继续存放后续vector对象
N.push_back(4);
N.push_back(5);
N.push_back(6); //已经形成第一个内层vector对象N2
M.push_back(N); //将形第一个内层vector对象N添加到外层vector对象M中
N.clear(); //清楚N中的元素,可以继续存放后续vector对象
10.3访问元素
访问元素和二维数组相同,M[0][0],访问M中第一个vector对象的第一个元素,值为1;
10.4长度
(1)M中vector的个数:M.size();
(2)M中第i个vector元素的长度:M[i].size();
一、vector 的初始化:
vector a(10); //定义10个整型元素的向量,没有给出初值,值不确定。
vector a(10,1); //定义10个整型元素的向量,每个元素的初值为1。
这是基础的用法
二、vector的嵌套:
比较常用的:
vector vc(3,vector(4,’\0’));
相当于定义3行4列的矩阵,每个一维数组初始化为4个’\0’
具体操作如下:
此处小tips: vector::iterator 可以直接写成auto,减少输入。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<vector<char>> vc(3,vector<char>(4,'\0'));
for(int i = 0;i<4;i++){
vc[0][i]='A'+i;
vc[1][i]='E'+i;
vc[2][i]='I'+i;
}
for(auto it=vc.begin();it!=vc.end();it++)
{
//(*it)是个vector<int>型的小容器
//故要再创建相应的迭代器才能访问其中元素
for(auto vit=(*it).begin();vit!=(*it).end();vit++)
cout<<*vit<<" ";
cout<<endl;
}
}
结果如下
十一、OPenCV中的Mat类中rowRange和colRange基本使用
rowRange为指定的行span创建一个新的矩阵头,可取指定行区间元素
colRange为指定的列span创建一个新的矩阵头,可取指定列区间元素
Mat Test = (Mat_<double>(3,3) << 0,1,2, 3,4,5, 6,7,8);
cout << "Total matrix:" << endl;
cout << Test << endl;
Mat testrow = Test.rowRange(0,1).clone();
cout << "Row range:" << endl;
cout << testrow << endl;
cout << "Test 1 row:" << endl;
cout << Test.row(0) << endl;
Mat testcol = Test.colRange(0,1).clone();
cout << "Col range:" << endl;
cout << testcol << endl;
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc, char** argv) {
cout << "OpenCV rowRang() Test!" << endl << endl;
//定义病初始化5x5矩阵
Mat Martrix55 = (Mat_<int>(5, 5) << 1,2,3,4,5,
11,12,13,14,15,
21,22,23,24,25,
31,32,33,34,35,
41,42,43,44,45);
cout << "Martrix55 = " << endl << Martrix55 << endl << endl;
Mat mchunk1 = Martrix55.rowRange(0,1);
cout << "mchunk1 = Martrix55.rowRange(0,1)" << endl << "mchunk1 = " << endl << mchunk1 << endl << endl;
Mat mchunk2 = Martrix55.rowRange(0, 2);
cout << "mchunk2 = Martrix55.rowRange(0, 2)" << endl << "mchunk2 = " << endl << mchunk2 << endl << endl;
Mat mchunk3 = Martrix55.rowRange(0, 3);
cout << "mchunk3 = Martrix55.rowRange(0, 3)" << endl << "mchunk3 = " << endl << mchunk3 << endl << endl;
Mat mchunk4 = Martrix55.rowRange(3, 5);
cout << "mchunk4 = Martrix55.rowRange(3, 5)" << endl << "mchunk4 = " << endl << mchunk4 << endl << endl;
Mat mchunk5 = Martrix55.rowRange(3, 4);
cout << "mchunk5 = Martrix55.rowRange(3, 4)" << endl << "mchunk5 = " << endl << mchunk5 << endl << endl;
Mat mchunk6 = Martrix55.rowRange(3, 3);
cout << "mchunk6 = Martrix55.rowRange(3, 3)" << endl << "mchunk6 = " << endl << mchunk6 << endl << endl;
system("pause");
return 0;
}
十二、opencv3多分类代码
12.1训练代码未验证
#include
#include
#include
#include
#include
using namespace std;
using namespace cv;
void get_0(Mat& trainingImages, vector& trainingLabels);
void get_1(Mat& trainingImages, vector& trainingLabels);
void get_2(Mat& trainingImages, vector& trainingLabels);
void get_3(Mat& trainingImages, vector& trainingLabels);
void get_4(Mat& trainingImages, vector& trainingLabels);
void get_5(Mat& trainingImages, vector& trainingLabels);
void get_6(Mat& trainingImages, vector& trainingLabels);
void get_7(Mat& trainingImages, vector& trainingLabels);
void get_8(Mat& trainingImages, vector& trainingLabels);
void get_9(Mat& trainingImages, vector& trainingLabels);
int main()
{
//获取训练数据
Mat classes;
Mat trainingData;
Mat trainingImages;
vector trainingLabels;
get_0(trainingImages, trainingLabels);
get_1(trainingImages, trainingLabels);
get_2(trainingImages, trainingLabels);
get_3(trainingImages, trainingLabels);
get_4(trainingImages, trainingLabels);
get_5(trainingImages, trainingLabels);
get_6(trainingImages, trainingLabels);
get_7(trainingImages, trainingLabels);
get_8(trainingImages, trainingLabels);
get_9(trainingImages, trainingLabels);
Mat(trainingImages).copyTo(trainingData);
trainingData.convertTo(trainingData, CV_32FC1);
Mat(trainingLabels).copyTo(classes);
//配置SVM训练器参数
CvSVMParams params;
params.svm_type = SVM::C_SVC;
params.kernel_type = SVM::LINEAR;//RBF效果不好
params.term_crit = cvTermCriteria(CV_TERMCRIT_ITER, 10000, 1e-6);
//训练
CvSVM svm;
cout << "训练中..." << endl;
svm.train_auto(trainingData, classes, Mat(), Mat(), params);
//保存模型
svm.save("E:\\VSpro\\vsm\\svm.xml");
cout << "训练好了!!!" << endl;
return 0;
}
void get_0(Mat& trainingImages, vector& trainingLabels)
{
for (int i = 0; i < 408; i++)
{
Mat SrcImage = imread("E:\\VSpro\\svm\\0\\" + to_string(i) + ".jpg", 0);
threshold(SrcImage, SrcImage, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
SrcImage = SrcImage.reshape(0, 1);
trainingImages.push_back(SrcImage);
trainingLabels.push_back(0);
}
}
void get_1(Mat& trainingImages, vector& trainingLabels)
{
for (int i = 0; i < 1127; i++)
{
Mat SrcImage = imread("E:\\VSpro\\svm\\1\\" + to_string(i) + ".jpg", 0);
threshold(SrcImage, SrcImage, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
SrcImage = SrcImage.reshape(0, 1);
trainingImages.push_back(SrcImage);
trainingLabels.push_back(1);
}
}
void get_2(Mat& trainingImages, vector& trainingLabels)
{
for (int i = 0; i < 1218; i++)
{
Mat SrcImage = imread("E:\\VSpro\\svm\\2\\" + to_string(i) + ".jpg", 0);
threshold(SrcImage, SrcImage, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
SrcImage = SrcImage.reshape(0, 1);
trainingImages.push_back(SrcImage);
trainingLabels.push_back(2);
}
}
void get_3(Mat& trainingImages, vector& trainingLabels)
{
for (int i = 0; i < 1188; i++)
{
Mat SrcImage = imread("E:\\VSpro\\svm\\3\\" + to_string(i) + ".jpg", 0);
threshold(SrcImage, SrcImage, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
SrcImage = SrcImage.reshape(0, 1);
trainingImages.push_back(SrcImage);
trainingLabels.push_back(3);
}
}
void get_4(Mat& trainingImages, vector& trainingLabels)
{
for (int i = 0; i < 1133; i++)
{
Mat SrcImage = imread("E:\\VSpro\\svm\\4\\" + to_string(i) + ".jpg", 0);
threshold(SrcImage, SrcImage, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
SrcImage = SrcImage.reshape(0, 1);
trainingImages.push_back(SrcImage);
trainingLabels.push_back(4);
}
}
void get_5(Mat& trainingImages, vector& trainingLabels)
{
for (int i = 0; i < 1109; i++)
{
Mat SrcImage = imread("E:\\VSpro\\svm\\5\\" + to_string(i) + ".jpg", 0);
threshold(SrcImage, SrcImage, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
SrcImage = SrcImage.reshape(0, 1);
trainingImages.push_back(SrcImage);
trainingLabels.push_back(5);
}
}
void get_6(Mat& trainingImages, vector& trainingLabels)
{
for (int i = 0; i < 584; i++)
{
Mat SrcImage = imread("E:E:\\VSpro\\svm\\6\\" + to_string(i) + ".jpg", 0);
threshold(SrcImage, SrcImage, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
SrcImage = SrcImage.reshape(0, 1);
trainingImages.push_back(SrcImage);
trainingLabels.push_back(6);
}
}
void get_7(Mat& trainingImages, vector& trainingLabels)
{
for (int i = 0; i < 416; i++)
{
Mat SrcImage = imread("E:E:\\VSpro\\svm\\7\\" + to_string(i) + ".jpg", 0);
threshold(SrcImage, SrcImage, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
SrcImage = SrcImage.reshape(0, 1);
trainingImages.push_back(SrcImage);
trainingLabels.push_back(7);
}
}
void get_8(Mat& trainingImages, vector& trainingLabels)
{
for (int i = 0; i < 374; i++)
{
Mat SrcImage = imread("E:\\VSpro\\svm\\8\\" + to_string(i) + ".jpg", 0);
threshold(SrcImage, SrcImage, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
SrcImage = SrcImage.reshape(0, 1);
trainingImages.push_back(SrcImage);
trainingLabels.push_back(8);
}
}
void get_9(Mat& trainingImages, vector& trainingLabels)
{
for (int i = 0; i < 421; i++)
{
Mat SrcImage = imread("E:\\VSpro\\svm\\9\\" + to_string(i) + ".jpg", 0);
threshold(SrcImage, SrcImage, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
SrcImage = SrcImage.reshape(0, 1);
trainingImages.push_back(SrcImage);
trainingLabels.push_back(9);
}
}
12.2训练代码验证未成功
// svm_test.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <iostream>
#include <fstream>
#include <vector>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/ml/ml.hpp>
using namespace cv;
using namespace std;
#define skyface_API extern __declspec(dllexport)
skyface_API int sex_detect(vector<float> &feats, const char* modpth);
Mat traindata(string path, int num)
{
vector<vector<float>> data(num, vector<float>(2048, 0));
ifstream ifs;
ifs.open(path);
for (int i = 0; i < num; i++)
{
for (int j = 0; j < 2048; j++)
{
ifs >> data[i][j];
}
}
ifs.close();
Mat class_n_data(data.size(), data.at(0).size(), CV_32FC1);
for (int i = 0; i < data.size(); i++)
for (int j = 0; j < data.at(0).size(); j++)
class_n_data.at<float>(i, j) = data.at(i).at(j);
return class_n_data;
}
Mat get_traindata3(Mat class1, Mat class2, Mat class3)
{
Mat traindata(class1.rows + class2.rows + class3.rows , 2048, CV_32FC1);
Mat tmp = traindata.rowRange(0, class1.rows);
class1.copyTo(tmp);
tmp = traindata.rowRange(class1.rows, class1.rows + class2.rows);
class2.copyTo(tmp);
tmp = traindata.rowRange(class1.rows + class2.rows, class1.rows + class2.rows + class3.rows);
class3.copyTo(tmp);
cout << "获取到训练数据!" << endl;
return traindata;
}
Mat get_labels3(Mat class1, Mat class2, Mat class3)
{
Mat labels(class1.rows + class2.rows + class3.rows , 1, CV_32FC1);
labels.rowRange(0, class1.rows).setTo(1);
labels.rowRange(class1.rows, class1.rows + class2.rows).setTo(2);
labels.rowRange(class1.rows + class2.rows, class1.rows + class2.rows + class3.rows).setTo(3);
return labels;
}
void trainSVM(Mat traindata, Mat labels, string modelpth)
{
//------------------------ 2. Set up the support vector machines parameters --------------------
CvSVMParams params;
params.svm_type = SVM::C_SVC;
params.C = 0.1;
params.kernel_type = SVM::LINEAR;
params.term_crit = TermCriteria(CV_TERMCRIT_ITER, (int)1e7, 1e-6);
//------------------------ 3. Train the svm ----------------------------------------------------
cout << "Starting training process" << endl;
CvSVM svm;
svm.train(traindata, labels, Mat(), Mat(), params);
cout << "Finished training process" << endl;
svm.save("../data/model_AGE.txt");
}
int sex_detect(vector<float> &feats, const char* modpth)
{
CvSVM SVM;
SVM.load(modpth);
int i;
float* testdata = new float[2048];
for (int i = 0; i < 2048; i++)
{
testdata[i] = feats[i];
}
Mat test = Mat(1, 2048, CV_32FC1, testdata);
float result = SVM.predict(test);
delete[] testdata;
return result;
}
int main()
{
//int labels[3]=[class1,class2,class3];
Mat class1 = traindata("../data/feats_left.txt",40);
Mat class2 = traindata("../data/feats_right.txt",36);
Mat class3 = traindata("../data/feats_pos.txt",48);
//Mat traindata = get_traindata(class1, class2);
//Mat labels = get_labels(class1, class2);
Mat traindata = get_traindata3(class1, class2, class3);
Mat labels = get_labels3(class1, class2, class3);
trainSVM(traindata, labels, "*");
CvSVM SVM;
SVM.load("../data/model_AGE.txt");
ifstream ifs;
float testdata[2048];
ifs.open("../data/feats_test.txt");
for (int i = 0; i < 2048; i++)
{
ifs >> testdata[i];
}
Mat test = Mat(1, 2048, CV_32FC1, testdata);
float result = SVM.predict(test);
if (result == 1)
cout << "左偏30度" << endl;
else if (result == 2)
cout<< "右偏30度" <<endl;
else if (result == 3)
cout<< "正脸" <<endl;
ifs.close();
system("pause");
}
12.3验证正确(10个不同样本,识别一张照片)
#include <stdio.h>
#include <time.h>
#include <opencv2/opencv.hpp>
#include <opencv/cv.h>
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/ml/ml.hpp>
#include <io.h> //查找文件相关函数
using namespace std;
using namespace cv;
using namespace ml;
ostringstream oss;
int num = -1;
Mat dealimage;
Mat src;
Mat yangben_gray;
Mat yangben_thresh;
int main()
{
//核心思路://获取一张图片后会将图片特征写入到容器中,
//紧接着会将标签写入另一个容器中,这样就保证了特征
// 和标签是一一对应的关系。
/*============================== = 读取训练数据============================== =*/
const int classsum = 10;//图片共有10类,可修改
const int imagesSum = 400;//每类有张图片,可修改
//训了样本图片与测试图片的尺寸应该一样
const int imageRows = 20;//图片尺寸
const int imageCols = 20;
//训练数据,每一行一个训练图片
Mat trainingData;
//训练样本标签
Mat labels;
//最终的训练样本标签
Mat clas;
//最终的训练数据
Mat traindata;
cout << "读取数据中...." << endl;
//从指定文件夹下提取图片//
for (int p = 0; p < classsum; p++)//依次提取0到9文件夹中的图片
{
oss << "H:\\opencv\\多分类_glob\\data_test\\train_image\\";
num += 1;//num从0到9
int label = num;
oss << num << "/*.jpg";//图片名字后缀,oss可以结合数字与字符串
string pattern = oss.str();//oss.str()输出oss字符串,并且赋给pattern
oss.str("");//每次循环后把oss字符串清空
vector<Mat> input_images;
vector<String> input_images_name;
glob(pattern, input_images_name, false);
//为false时,仅仅遍历指定文件夹内符合模式的文件,当为true时,会同时遍历指定文件夹的子文件夹
//此时input_images_name存放符合条件的图片地址
int all_num =(int) input_images_name.size();
//文件下总共有几个图片
//cout << num << ":总共有" << all_num << "个图片待测试" << endl;
for (int i = 0; i < imagesSum; i++)//依次循环遍历每个文件夹中的图片
{
cvtColor(imread(input_images_name[i]), yangben_gray, COLOR_BGR2GRAY);//灰度变换
threshold(yangben_gray, yangben_thresh, 0, 255, THRESH_OTSU);//二值化
//循环读取每张图片并且依次放在vector<Mat> input_images内
input_images.push_back(yangben_thresh);
dealimage = input_images[i];
//注意:我们简单粗暴将整个图的所有像素作为了特征,因为我们关注更多的是整个的训练过程
//,所以选择了最简单的方式完成特征提取工作,除此中外,
//特征提取的方式有很多,比如LBP,HOG等等
//我们利用reshape()函数完成特征提取,
//eshape(1, 1)的结果就是原图像对应的矩阵将被拉伸成一个一行的向量,作为特征向量。
dealimage = dealimage.reshape(1, 1);//图片序列化
trainingData.push_back(dealimage);//序列化后的图片依次存入
labels.push_back(label);//把每个图片对应的标签依次存入
}
}
cout << "读取数据完毕...." << endl;
//图片数据和标签转变下
Mat(trainingData).copyTo(traindata);//复制
traindata.convertTo(traindata, CV_32FC1);//更改图片数据的类型,必要,不然会出错
Mat(labels).copyTo(clas);//复制
/*============================== = 创建SVM模型============================== =*/
// 创建分类器并设置参数
Ptr<SVM> SVM_params = SVM::create();
SVM_params->setType(SVM::C_SVC);//C_SVC用于分类,C_SVR用于回归
SVM_params->setKernel(SVM::LINEAR); //LINEAR线性核函数。SIGMOID为高斯核函数
SVM_params->setDegree(0);//核函数中的参数degree,针对多项式核函数;
SVM_params->setGamma(1);//核函数中的参数gamma,针对多项式/RBF/SIGMOID核函数;
SVM_params->setCoef0(0);//核函数中的参数,针对多项式/SIGMOID核函数;
SVM_params->setC(1);//SVM最优问题参数,设置C-SVC,EPS_SVR和NU_SVR的参数;
SVM_params->setNu(0);//SVM最优问题参数,设置NU_SVC, ONE_CLASS 和NU_SVR的参数;
SVM_params->setP(0);//SVM最优问题参数,设置EPS_SVR 中损失函数p的值.
//结束条件,即训练1000次或者误差小于0.01结束
SVM_params->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 1000, 0.01));
cout << "数据训练中...." << endl;
//训练数据和标签的结合
Ptr<TrainData> tData = TrainData::create(traindata, ROW_SAMPLE, clas);
// 训练分类器
SVM_params->train(tData);//训练
cout << "训练好了!!!" << endl;
//保存模型
cout << "保存模型中...." << endl;
//SVM_params->save("H:\\opencv\\多分类_glob\\data_test\\svm_LINEAR.xml");
cout << "保存好了!!!" << endl;
/*============================== = 预测部分============================== =*/
Mat src = imread("H:\\opencv\\多分类_glob\\data_test\\train\\1.jpg");
cvtColor(src, src, COLOR_BGR2GRAY);
threshold(src, src, 0, 255, CV_THRESH_OTSU);
//imshow("原图像", src);
Mat input;
src = src.reshape(1, 1);//输入图片序列化
input.push_back(src);
input.convertTo(input, CV_32FC1);//更改图片数据的类型,必要,不然会出错
float r = SVM_params->predict(input); //对所有行进行预测
cout << r << endl;
waitKey(0);
system("pause");
return 0;
}
升级版,加入了.clone深拷贝
#include <stdio.h>
#include <time.h>
#include <opencv2/opencv.hpp>
#include <opencv/cv.h>
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/ml/ml.hpp>
#include <io.h> //查找文件相关函数
using namespace std;
using namespace cv;
using namespace ml;
ostringstream oss;
int num = -1;
Mat dealimage;
Mat src;
Mat yangben_gray;
Mat yangben_thresh;
int main()
{
//核心思路://获取一张图片后会将图片特征写入到容器中,
//紧接着会将标签写入另一个容器中,这样就保证了特征
// 和标签是一一对应的关系。
/*============================== = 读取训练数据============================== =*/
const int classsum = 10;//图片共有10类,可修改
const int imagesSum = 400;//每类有张图片,可修改
//训了样本图片与测试图片的尺寸应该一样
const int imageRows = 20;//图片尺寸
const int imageCols = 20;
//训练数据,每一行一个训练图片
Mat trainingData;
//训练样本标签
Mat labels;
//最终的训练样本标签
Mat clas;
//最终的训练数据
Mat traindata;
cout << "读取数据中...." << endl;
//从指定文件夹下提取图片//
for (int p = 0; p < classsum; p++)//依次提取0到9文件夹中的图片
{
oss << "H:\\opencv\\多分类_glob\\data_test\\train_image\\";
num += 1;//num从0到9
int label = num;
oss << num << "/*.jpg";//图片名字后缀,oss可以结合数字与字符串
string pattern = oss.str();//oss.str()输出oss字符串,并且赋给pattern
oss.str("");//每次循环后把oss字符串清空
vector<Mat> input_images;
vector<String> input_images_name;
glob(pattern, input_images_name, false);
//为false时,仅仅遍历指定文件夹内符合模式的文件,当为true时,会同时遍历指定文件夹的子文件夹
//此时input_images_name存放符合条件的图片地址
int all_num = (int)input_images_name.size();
//文件下总共有几个图片
//cout << num << ":总共有" << all_num << "个图片待测试" << endl;
for (int i = 0; i < imagesSum; i++)//依次循环遍历每个文件夹中的图片
{
cvtColor(imread(input_images_name[i]), yangben_gray, COLOR_BGR2GRAY);//灰度变换
threshold(yangben_gray, yangben_thresh, 0, 255, THRESH_OTSU);//二值化
//循环读取每张图片并且依次放在vector<Mat> input_images内
input_images.push_back(yangben_thresh.clone());
dealimage = input_images[i];
//注意:我们简单粗暴将整个图的所有像素作为了特征,因为我们关注更多的是整个的训练过程
//,所以选择了最简单的方式完成特征提取工作,除此中外,
//特征提取的方式有很多,比如LBP,HOG等等
//我们利用reshape()函数完成特征提取,
//eshape(1, 1)的结果就是原图像对应的矩阵将被拉伸成一个一行的向量,作为特征向量。
dealimage = dealimage.reshape(1, 1);//图片序列化
trainingData.push_back(dealimage.clone());//序列化后的图片依次存入
labels.push_back(label);//把每个图片对应的标签依次存入
}
}
cout << "读取数据完毕...." << endl;
//图片数据和标签转变下
Mat(trainingData).copyTo(traindata);//复制
traindata.convertTo(traindata, CV_32FC1);//更改图片数据的类型,必要,不然会出错
Mat(labels).copyTo(clas);//复制
/*============================== = 创建SVM模型============================== =*/
// 创建分类器并设置参数
Ptr<SVM> SVM_params = SVM::create();
SVM_params->setType(SVM::C_SVC);//C_SVC用于分类,C_SVR用于回归
SVM_params->setKernel(SVM::LINEAR); //LINEAR线性核函数。SIGMOID为高斯核函数
SVM_params->setDegree(0);//核函数中的参数degree,针对多项式核函数;
SVM_params->setGamma(1);//核函数中的参数gamma,针对多项式/RBF/SIGMOID核函数;
SVM_params->setCoef0(0);//核函数中的参数,针对多项式/SIGMOID核函数;
SVM_params->setC(1);//SVM最优问题参数,设置C-SVC,EPS_SVR和NU_SVR的参数;
SVM_params->setNu(0);//SVM最优问题参数,设置NU_SVC, ONE_CLASS 和NU_SVR的参数;
SVM_params->setP(0);//SVM最优问题参数,设置EPS_SVR 中损失函数p的值.
//结束条件,即训练1000次或者误差小于0.01结束
SVM_params->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 1000, 0.01));
cout << "数据训练中...." << endl;
//训练数据和标签的结合
Ptr<TrainData> tData = TrainData::create(traindata, ROW_SAMPLE, clas);
// 训练分类器
SVM_params->train(tData);//训练
cout << "训练好了!!!" << endl;
//保存模型
cout << "保存模型中...." << endl;
//SVM_params->save("H:\\opencv\\多分类_glob\\data_test\\svm_LINEAR.xml");
cout << "保存好了!!!" << endl;
/*============================== = 预测部分============================== =*/
Mat src = imread("H:\\opencv\\多分类_glob\\data_test\\train\\1.jpg");
cvtColor(src, src, COLOR_BGR2GRAY);
threshold(src, src, 0, 255, CV_THRESH_OTSU);
//imshow("原图像", src);
Mat input;
src = src.reshape(1, 1);//输入图片序列化
input.push_back(src);
input.convertTo(input, CV_32FC1);//更改图片数据的类型,必要,不然会出错
float r = SVM_params->predict(input); //对所有行进行预测
cout << r << endl;
waitKey(0);
system("pause");
return 0;
}
12.4 SVM+HOG特征对数字进行识别
这篇文章主要介绍用SVM+HOG特征对数字进行识别。
详细请看上篇文章,它们主要区别在于训练样本HOG特征的提取,其他基本一样,所以我直接附上代码。
下面代码是opencv3和C++
可以根据自己需要修改训练样本类别,数目,尺寸。oss的训练样本路径,src的检测图片路径。
#include <stdio.h>
#include <time.h>
#include <opencv2/opencv.hpp>
#include <opencv/cv.h>
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/ml/ml.hpp>
#include <io.h> //查找文件相关函数
using namespace std;
using namespace cv;
using namespace ml;
ostringstream oss;//结合字符串和数字
int num = -1;
Mat dealimage;
Mat src;
Mat yangben_gray;
Mat yangben_thresh;
void feature(Mat dealimage);
/*============================== = 读取训练数据============================== =*/
const int classsum = 10;//训练图片共有10类,可修改
const int imagesSum = 400;//每类有500张图片,可修改
int yangben_data_position = -1;
Mat data_mat = Mat(classsum*imagesSum, 8100, CV_32FC1);
//data_mat用来保存所有训练样本的HOG特征,每一列为一副图像的HOG特征
//每一行为每一幅训练图像
//必须为CV_32FC1的类型
//行、列、类型;第二个参数,即矩阵的列是由下面的descriptors的大小决定的,
//可以由descriptors.size()得到,且对于不同大小的输入训练图片,这个值是不同的
//descriptors为提取到的HOG特征
int main()
{
//训练数据,每一行一个训练图片
Mat trainingData;
//训练样本标签
Mat labels;
//最终的训练样本标签
Mat clas;
//最终的训练数据
Mat traindata;
//从指定文件夹下提取图片//
for (int p = 0; p < classsum; p++)//依次提取0到9文件夹中的图片
{
oss << "H:\\opencv\\多分类_glob\\data_test\\train_image\\";
num += 1;//num从0到9
int label = num;
oss << num << "/*.jpg";//图片名字后缀,oss可以结合数字与字符串
string pattern = oss.str();//oss.str()输出oss字符串,并且赋给pattern
oss.str("");//每次循环后把oss字符串清空
vector<Mat> input_images;
vector<String> input_images_name;
glob(pattern, input_images_name, false);
//为false时,仅仅遍历指定文件夹内符合模式的文件,当为true时,会同时遍历指定文件夹的子文件夹
//此时input_images_name存放符合条件的图片地址
int all_num = input_images_name.size();
//文件下总共有几个图片
//cout << num << ":总共有" << all_num << "个图片待测试" << endl;
for (int i = 0; i < imagesSum; i++)//依次循环遍历每个文件夹中的图片
{
cvtColor(imread(input_images_name[i]), yangben_gray, COLOR_BGR2GRAY);//灰度变换
threshold(yangben_gray, yangben_thresh, 0, 255, THRESH_OTSU);//二值化
//循环读取每张图片并且依次放在vector<Mat> input_images内
input_images.push_back(yangben_thresh.clone());
dealimage = input_images[i];
//选择了HOG的方式完成特征提取工作
yangben_data_position += 1;//代表为第几幅图像
feature(dealimage);//图片特征提取
labels.push_back(label);//把每个图片对应的标签依次存入
cout << "第" << yangben_data_position << "样本正在提取HOG特征" << endl;
}
}
cout << "样本特征提取完毕,等待创建SVM模型" << endl;
/*============================== = 创建SVM模型============================== =*/
// 创建分类器并设置参数
Ptr<SVM> SVM_params = SVM::create();
SVM_params->setType(SVM::C_SVC);//C_SVC用于分类,C_SVR用于回归
SVM_params->setKernel(SVM::LINEAR); //LINEAR线性核函数。SIGMOID为高斯核函数
SVM_params->setDegree(0);//核函数中的参数degree,针对多项式核函数;
SVM_params->setGamma(1);//核函数中的参数gamma,针对多项式/RBF/SIGMOID核函数;
SVM_params->setCoef0(0);//核函数中的参数,针对多项式/SIGMOID核函数;
SVM_params->setC(1);//SVM最优问题参数,设置C-SVC,EPS_SVR和NU_SVR的参数;
SVM_params->setNu(0);//SVM最优问题参数,设置NU_SVC, ONE_CLASS 和NU_SVR的参数;
SVM_params->setP(0);//SVM最优问题参数,设置EPS_SVR 中损失函数p的值.
//结束条件,即训练1000次或者误差小于0.01结束
SVM_params->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 1000, 0.01));
//训练数据和标签的结合
Ptr<TrainData> tData = TrainData::create(data_mat, ROW_SAMPLE, labels);
// 训练分类器
SVM_params->train(tData);//训练
//保存模型
//SVM_params->save("C:/Users/zhang/Desktop/opencv——实例/小案例/车牌检测/基于机器学习/字符识别svm.xml");
cout << "训练好了!!!" << endl;
cout << "等待识别" << endl;
/*============================== = 预测部分============================== =*/
Mat src = imread("H:\\opencv\\多分类_glob\\data_test\\train\\1.jpg");
cvtColor(src, src, COLOR_BGR2GRAY);
threshold(src, src, 0, 255, CV_THRESH_OTSU);
imshow("原图像", src);
//输入图像取特征点
Mat trainTempImg = Mat::zeros(Size(128, 128), CV_8UC1);
resize(src, trainTempImg, trainTempImg.size());
HOGDescriptor *hog = new HOGDescriptor(Size(128, 128), Size(16, 16), Size(8, 8), Size(8, 8), 9);
vector<float>descriptors;//结果数组
hog->compute(trainTempImg, descriptors, Size(1, 1), Size(0, 0));
//cout << "HOG描述子向量维数 " << descriptors.size() << endl;
Mat SVMtrainMat = Mat(1, descriptors.size(), CV_32FC1);
int number1 = descriptors.size();
//将计算好的HOG描述子复制到样本特征矩阵SVMtrainMat
for (int i = 0; i < number1; i++)
{
//把一幅图像的HOG描述子向量依次存入data_mat矩阵的同一列
//因为输入图像只有一个,即SVMtrainMat只有一列,则为0
SVMtrainMat.at<float>(0, i) = descriptors[i]; // n++;
}
SVMtrainMat.convertTo(SVMtrainMat, CV_32FC1);//更改图片数据的类型,必要,不然会出错
int ret = (int)SVM_params->predict(SVMtrainMat);//检测结果
cout << "识别的数字为:" << ret << endl;
waitKey(0);
return 0;
}
void feature(Mat dealimage)
{
//把训练样本放大到128,128。便于HOG提取特征
Mat trainImg = Mat(Size(128, 128), CV_8UC1);
resize(dealimage, trainImg, trainImg.size());
//处理HOG特征
//检测窗口(64,128),块尺寸(16,16),块步长(8,8),cell尺寸(8,8),直方图bin个数9 ,需要修改
HOGDescriptor *hog = new HOGDescriptor(Size(128, 128), Size(16, 16), Size(8, 8), Size(8, 8), 9);
vector<float>descriptors;//存放结果 为HOG描述子向量
hog->compute(trainImg, descriptors, Size(1, 1), Size(0, 0)); //Hog特征计算,检测窗口移动步长(1,1)
//cout << "HOG描述子向量维数 : " << descriptors.size() << endl;
for (vector<float>::size_type j = 0; j < descriptors.size(); j++)
{
//把一幅图像的HOG描述子向量依次存入data_mat矩阵的同一列
data_mat.at<float>(yangben_data_position, j) = descriptors[j];
}
}
12.5opencv——基于KNN的数字识别
KNN即K个最近邻,网上有很多关于KNN的文章。我大概总结下核心:假设有A图片,让A与训练样本依次计算相似度(可用欧式距离),挑选出K个与A图片相似度最大的图片,这K个图片中,哪种类型最多那么定义A图片也属于该类型。
首先,需要有数字的训练样本
KNN的数字识别代码与基于SVM的数字识别大体一致
核心思路:
1:获取一张训练图片后会将图片特征写入到容器中,紧接着会将标签写入另一个容器中,这样就保证了特征和标签是一一对应的关系。
2:特征可用LBP,HOG等提取,但是我们这里主要说KNN训练过程,所以用最简单的方法,即把训练图片的全部像素序列成一行像素作为特征,用reshape(1,1)。
3:图片特征数据得转换成CV_32FC1的数据格式。
4:所有训练样本与测试样本的尺寸都应该一样(这里我都选择20*20)。
下面代码是opencv3和C++
可以根据自己需要修改训练样本类别,数目,尺寸。oss的训练样本路径,src的检测图片路径。
/// 字符识别——基于模版匹配.cpp: 定义控制台应用程序的入口点。
//
//#include "stdafx.h"
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;
using namespace ml;
ostringstream oss;
int num = -1;
Mat dealimage;
Mat src;
int k = 0;
Mat yangben_gray;
Mat yangben_thresh;
int main()
{
/*============================== = 读取训练数据============================== =*/
const int classsum = 10;//图片共有10类
const int imagesSum = 400;//每类有500张图片
const int imageRows = 20;//图片尺寸
const int imageCols = 20;
//训练数据,每一行一个训练图片
Mat trainingData;
//训练样本标签
Mat labels;
//最终的训练样本标签
Mat clas;
//最终的训练数据
Mat traindata;
//从指定文件夹下提取图片//
for (int p = 0; p < classsum; p++)
{
oss << "H:\\opencv\\多分类_glob\\data_test\\train_image\\";
num += 1;//num从0到9
int label = num;
oss << num << "/*.jpg";//图片名字后缀,oss可以结合数字与字符串
string pattern = oss.str();//oss.str()输出oss字符串,并且赋给pattern
oss.str("");//每次循环后把oss字符串清空
vector<Mat> input_images;
vector<String> input_images_name;
glob(pattern, input_images_name, false);
//为false时,仅仅遍历指定文件夹内符合模式的文件,当为true时,会同时遍历指定文件夹的子文件夹
//此时input_images_name存放符合条件的图片地址
int all_num = input_images_name.size();//文件下总共有几个图片
//cout << num << ":总共有" << all_num << "个图片待测试" << endl;
for (int i = 0; i < imagesSum; i++)
{
cvtColor(imread(input_images_name[i]), yangben_gray, COLOR_BGR2GRAY);
threshold(yangben_gray, yangben_thresh, 0, 255, THRESH_OTSU);
input_images.push_back(yangben_thresh.clone());
//循环读取每张图片并且依次放在vector<Mat> input_images内
dealimage = input_images[i];
//注意:我们简单粗暴将整个图的所有像素作为了特征,因为我们关注更多的是整个的训练过程
//,所以选择了最简单的方式完成特征提取工作,除此中外,
//特征提取的方式有很多,比如LBP,HOG等等
//我们利用reshape()函数完成特征提取,
//reshape(1, 1)的结果就是原图像对应的矩阵将被拉伸成一个一行的向量,作为特征向量。
dealimage = dealimage.reshape(1, 1);//图片序列化
trainingData.push_back(dealimage.clone());//序列化后的图片依次存入
labels.push_back(label);//把每个图片对应的标签依次存入
}
}
//图片数据和标签转变下
Mat(trainingData).copyTo(traindata);//复制
traindata.convertTo(traindata, CV_32FC1);//更改图片数据的类型,必要,不然会出错
Mat(labels).copyTo(clas);//复制
/*============================== = 创建KNN模型============================== =*/
Ptr<KNearest>knn = KNearest::create();
knn->setDefaultK(10);//k个最近领
knn->setIsClassifier(true);//true为分类,false为回归
//训练数据和标签的结合
cout << "训练中...." << endl;
Ptr<TrainData>trainData = TrainData::create(traindata, ROW_SAMPLE, clas);
//训练
knn->train(trainData);
cout << "训练结束...." << endl;
//model->save("H:\\opencv\\多分类_glob\\data_test\\KNNsvm.xml");
/*============================== = 预测部分============================== =*/
//预测分类
Mat src = imread("H:\\opencv\\多分类_glob\\data_test\\train\\1.jpg");
cvtColor(src, src, COLOR_BGR2GRAY);
threshold(src, src, 0, 255, CV_THRESH_OTSU);
imshow("原图像", src);
Mat input;
src = src.reshape(1, 1);//输入图片序列化
input.push_back(src);
input.convertTo(input, CV_32FC1);//更改图片数据的类型,必要,不然会出错
float r = knn->predict(input); //对所有行进行预测
cout << r << endl;
waitKey(0);
return 0;
}
训练速度特别快
12.6模型评估
这篇文章主要是关于模型评估,即识别数字的正确率
下面代码是opencv3 c++
加载的XML文件是之前代码训练好的。
测试集是我的“”数字检测样本“”文件夹下的0-9个文件夹所包含的检测样本
在这里插入代码片
结果:
12.5 验证正确(10个不同样本,识别多张照片,没有办法判断具体分类率)
#include <stdio.h>
#include <time.h>
#include <opencv2/opencv.hpp>
#include <opencv/cv.h>
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/ml/ml.hpp>
#include <io.h> //查找文件相关函数
using namespace std;
using namespace cv;
using namespace ml;
ostringstream oss;
int num = -1;
Mat dealimage;
Mat src;
Mat yangben_gray;
Mat yangben_thresh;
void getFiles(string path, vector<string>& files)
{
long long hFile = 0; //long long = intptr_t
struct _finddata_t fileinfo;
string p;
if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1)
{
do
{
if ((fileinfo.attrib & _A_SUBDIR))
{
if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
getFiles(p.assign(path).append("\\").append(fileinfo.name), files);
}
else
{
files.push_back(p.assign(path).append("\\").append(fileinfo.name));
}
} while (_findnext(hFile, &fileinfo) == 0);
_findclose(hFile);
}
}
int main()
{
//核心思路://获取一张图片后会将图片特征写入到容器中,
//紧接着会将标签写入另一个容器中,这样就保证了特征
// 和标签是一一对应的关系。
/*============================== = 读取训练数据============================== =*/
const int classsum = 10;//图片共有10类,可修改
const int imagesSum = 400;//每类有张图片,可修改
//训了样本图片与测试图片的尺寸应该一样
const int imageRows = 20;//图片尺寸
const int imageCols = 20;
//训练数据,每一行一个训练图片
Mat trainingData;
//训练样本标签
Mat labels;
//最终的训练样本标签
Mat clas;
//最终的训练数据
Mat traindata;
cout << "读取数据中...." << endl;
//从指定文件夹下提取图片//
for (int p = 0; p < classsum; p++)//依次提取0到9文件夹中的图片
{
oss << "H:\\opencv\\多分类_glob\\data_test\\train_image\\";
num += 1;//num从0到9
int label = num;
oss << num << "/*.jpg";//图片名字后缀,oss可以结合数字与字符串
string pattern = oss.str();//oss.str()输出oss字符串,并且赋给pattern
oss.str("");//每次循环后把oss字符串清空
vector<Mat> input_images;
vector<String> input_images_name;
glob(pattern, input_images_name, false);
//为false时,仅仅遍历指定文件夹内符合模式的文件,当为true时,会同时遍历指定文件夹的子文件夹
//此时input_images_name存放符合条件的图片地址
int all_num = (int)input_images_name.size();
//文件下总共有几个图片
//cout << num << ":总共有" << all_num << "个图片待测试" << endl;
for (int i = 0; i < imagesSum; i++)//依次循环遍历每个文件夹中的图片
{
cvtColor(imread(input_images_name[i]), yangben_gray, COLOR_BGR2GRAY);//灰度变换
threshold(yangben_gray, yangben_thresh, 0, 255, THRESH_OTSU);//二值化
//循环读取每张图片并且依次放在vector<Mat> input_images内
input_images.push_back(yangben_thresh);
dealimage = input_images[i];
//注意:我们简单粗暴将整个图的所有像素作为了特征,因为我们关注更多的是整个的训练过程
//,所以选择了最简单的方式完成特征提取工作,除此中外,
//特征提取的方式有很多,比如LBP,HOG等等
//我们利用reshape()函数完成特征提取,
//eshape(1, 1)的结果就是原图像对应的矩阵将被拉伸成一个一行的向量,作为特征向量。
dealimage = dealimage.reshape(1, 1);//图片序列化
trainingData.push_back(dealimage);//序列化后的图片依次存入
labels.push_back(label);//把每个图片对应的标签依次存入
}
}
cout << "读取数据完毕...." << endl;
//图片数据和标签转变下
Mat(trainingData).copyTo(traindata);//复制
traindata.convertTo(traindata, CV_32FC1);//更改图片数据的类型,必要,不然会出错
Mat(labels).copyTo(clas);//复制
/*============================== = 创建SVM模型============================== =*/
// 创建分类器并设置参数
Ptr<SVM> SVM_params = SVM::create();
SVM_params->setType(SVM::C_SVC);//C_SVC用于分类,C_SVR用于回归
SVM_params->setKernel(SVM::LINEAR); //LINEAR线性核函数。SIGMOID为高斯核函数
SVM_params->setDegree(0);//核函数中的参数degree,针对多项式核函数;
SVM_params->setGamma(1);//核函数中的参数gamma,针对多项式/RBF/SIGMOID核函数;
SVM_params->setCoef0(0);//核函数中的参数,针对多项式/SIGMOID核函数;
SVM_params->setC(1);//SVM最优问题参数,设置C-SVC,EPS_SVR和NU_SVR的参数;
SVM_params->setNu(0);//SVM最优问题参数,设置NU_SVC, ONE_CLASS 和NU_SVR的参数;
SVM_params->setP(0);//SVM最优问题参数,设置EPS_SVR 中损失函数p的值.
//结束条件,即训练1000次或者误差小于0.01结束
SVM_params->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 1000, 0.01));
cout << "数据训练中...." << endl;
//训练数据和标签的结合
Ptr<TrainData> tData = TrainData::create(traindata, ROW_SAMPLE, clas);
// 训练分类器
SVM_params->train(tData);//训练
cout << "训练好了!!!" << endl;
//保存模型
cout << "保存模型中...." << endl;
//SVM_params->save("H:\\opencv\\多分类_glob\\data_test\\svm_LINEAR.xml");
cout << "保存好了!!!" << endl;
char * filePath = (char *)"H:\\opencv\\多分类_glob\\data_test\\test_image\\";
vector<string> files;
getFiles(filePath, files);
//char * filePath1 = (char *)"C:\\Users\\Administrator\\Desktop\\小李文献\\data\\test_image\\1";
//vector<string> files1;
int number = (int)files.size();
//int number1 = (int)files1.size();
cout <<"number" << number << endl;
int xuan = 0;
for (int i = 0; i < number; i++)
{
/*============================== = 预测部分============================== =*/
Mat src = imread(files[i].c_str());
cvtColor(src, src, COLOR_BGR2GRAY);
threshold(src, src, 0, 255, CV_THRESH_OTSU);
//imshow("原图像", src);
Mat input;
src = src.reshape(1, 1);//输入图片序列化
input.push_back(src);
input.convertTo(input, CV_32FC1);//更改图片数据的类型,必要,不然会出错
float r = SVM_params->predict(input); //对所有行进行预测
xuan++;
cout << "0:" << r << endl;
}
cout << "xuan:" << xuan << endl;
waitKey(0);
system("pause");
return 0;
}
测试代码正确(仅一个文件下)
#include <stdio.h>
#include <time.h>
#include <opencv2/opencv.hpp>
#include <opencv/cv.h>
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/ml/ml.hpp>
#include <io.h> //查找文件相关函数
using namespace std;
using namespace cv;
using namespace ml;
Ptr<SVM> SVM_params;
void getFiles(string path, vector<string>& files)
{
long long hFile = 0; //long long = intptr_t
struct _finddata_t fileinfo;
string p;
if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1)
{
do
{
if ((fileinfo.attrib & _A_SUBDIR))
{
if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
getFiles(p.assign(path).append("\\").append(fileinfo.name), files);
}
else
{
files.push_back(p.assign(path).append("\\").append(fileinfo.name));
}
} while (_findnext(hFile, &fileinfo) == 0);
_findclose(hFile);
}
}
int main()
{
/*SVM_params = SVM::load("H:/opencv/多分类_glob/data_test/svm_LINEAR.xml");
Mat src = imread("H:\\opencv\\多分类_glob\\data_test\\train\\1.jpg");
cvtColor(src, src, COLOR_BGR2GRAY);
threshold(src, src, 0, 255, CV_THRESH_OTSU);
imshow("原图像", src);
Mat input;
src = src.reshape(1, 1);//输入图片序列化
input.push_back(src);
input.convertTo(input, CV_32FC1);//更改图片数据的类型,必要,不然会出错
float r = SVM_params->predict(input); //对所有行进行预测
cout << r << endl;*/
SVM_params = SVM::load("H:\\opencv\\多分类_glob\\data_test\\svm_LINEAR1.xml");
char * filePath = (char *)"H:\\opencv\\多分类_glob\\data_test\\test_image\\4";
vector<string> files;
getFiles(filePath, files);
//char * filePath1 = (char *)"C:\\Users\\Administrator\\Desktop\\小李文献\\data\\test_image\\1";
//vector<string> files1;
int number = (int)files.size();
//int number1 = (int)files1.size();
cout << "number=" << number << endl;
int xuan = 0;
for (int i = 0; i < number; i++)
{
/*============================== = 预测部分============================== =*/
Mat src = imread(files[i].c_str());
cvtColor(src, src, COLOR_BGR2GRAY);
threshold(src, src, 0, 255, CV_THRESH_OTSU);
//imshow("原图像", src);
Mat input;
src = src.reshape(1, 1);//输入图片序列化
input.push_back(src);
input.convertTo(input, CV_32FC1);//更改图片数据的类型,必要,不然会出错
float r = SVM_params->predict(input); //对所有行进行预测
xuan++;
cout << "0:" << r << endl;
}
cout << "xuan:" << xuan << endl;
waitKey(0);
system("pause");
return 0;
}
12.6SVM+HOG特征对数字进行识别
#include <stdio.h>
#include <time.h>
#include <opencv2/opencv.hpp>
#include <opencv/cv.h>
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/ml/ml.hpp>
#include <io.h> //查找文件相关函数
using namespace std;
using namespace cv;
using namespace ml;
ostringstream oss;//结合字符串和数字
int num = -1;
Mat dealimage;
Mat src;
Mat yangben_gray;
Mat yangben_thresh;
void feature(Mat dealimage);
/*============================== = 读取训练数据============================== =*/
const int classsum = 10;//训练图片共有10类,可修改
const int imagesSum = 400;//每类有500张图片,可修改
int yangben_data_position = -1;
Mat data_mat = Mat(classsum*imagesSum, 8100, CV_32FC1);
//data_mat用来保存所有训练样本的HOG特征,每一列为一副图像的HOG特征
//每一行为每一幅训练图像
//必须为CV_32FC1的类型
//行、列、类型;第二个参数,即矩阵的列是由下面的descriptors的大小决定的,
//可以由descriptors.size()得到,且对于不同大小的输入训练图片,这个值是不同的
//descriptors为提取到的HOG特征
int main()
{
//训练数据,每一行一个训练图片
Mat trainingData;
//训练样本标签
Mat labels;
//最终的训练样本标签
Mat clas;
//最终的训练数据
Mat traindata;
//从指定文件夹下提取图片//
for (int p = 0; p < classsum; p++)//依次提取0到9文件夹中的图片
{
oss << "H:\\opencv\\多分类_glob\\data_test\\train_image\\";
num += 1;//num从0到9
int label = num;
oss << num << "/*.jpg";//图片名字后缀,oss可以结合数字与字符串
string pattern = oss.str();//oss.str()输出oss字符串,并且赋给pattern
oss.str("");//每次循环后把oss字符串清空
vector<Mat> input_images;
vector<String> input_images_name;
glob(pattern, input_images_name, false);
//为false时,仅仅遍历指定文件夹内符合模式的文件,当为true时,会同时遍历指定文件夹的子文件夹
//此时input_images_name存放符合条件的图片地址
int all_num = input_images_name.size();
//文件下总共有几个图片
//cout << num << ":总共有" << all_num << "个图片待测试" << endl;
for (int i = 0; i < imagesSum; i++)//依次循环遍历每个文件夹中的图片
{
cvtColor(imread(input_images_name[i]), yangben_gray, COLOR_BGR2GRAY);//灰度变换
threshold(yangben_gray, yangben_thresh, 0, 255, THRESH_OTSU);//二值化
//循环读取每张图片并且依次放在vector<Mat> input_images内
input_images.push_back(yangben_thresh.clone());
dealimage = input_images[i];
//选择了HOG的方式完成特征提取工作
yangben_data_position += 1;//代表为第几幅图像
feature(dealimage);//图片特征提取
labels.push_back(label);//把每个图片对应的标签依次存入
cout << "第" << yangben_data_position << "样本正在提取HOG特征" << endl;
}
}
cout << "样本特征提取完毕,等待创建SVM模型" << endl;
/*============================== = 创建SVM模型============================== =*/
// 创建分类器并设置参数
Ptr<SVM> SVM_params = SVM::create();
SVM_params->setType(SVM::C_SVC);//C_SVC用于分类,C_SVR用于回归
SVM_params->setKernel(SVM::LINEAR); //LINEAR线性核函数。SIGMOID为高斯核函数
SVM_params->setDegree(0);//核函数中的参数degree,针对多项式核函数;
SVM_params->setGamma(1);//核函数中的参数gamma,针对多项式/RBF/SIGMOID核函数;
SVM_params->setCoef0(0);//核函数中的参数,针对多项式/SIGMOID核函数;
SVM_params->setC(1);//SVM最优问题参数,设置C-SVC,EPS_SVR和NU_SVR的参数;
SVM_params->setNu(0);//SVM最优问题参数,设置NU_SVC, ONE_CLASS 和NU_SVR的参数;
SVM_params->setP(0);//SVM最优问题参数,设置EPS_SVR 中损失函数p的值.
//结束条件,即训练1000次或者误差小于0.01结束
SVM_params->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 1000, 0.01));
//训练数据和标签的结合
Ptr<TrainData> tData = TrainData::create(data_mat, ROW_SAMPLE, labels);
// 训练分类器方法1
SVM_params->train(tData);//训练
// 训练分类器方法2
//ParamGrid c_grid(1, 20, 10);//
//SVM_params->trainAuto(tData, 10, c_grid);//10 ---kFold
//SVM_params->trainAuto(tData);
//保存模型
//SVM_params->save("C:/Users/zhang/Desktop/opencv——实例/小案例/车牌检测/基于机器学习/字符识别svm.xml");
cout << "训练好了!!!" << endl;
cout << "等待识别" << endl;
/*============================== = 预测部分============================== =*/
Mat src = imread("H:\\opencv\\多分类_glob\\data_test\\train\\1.jpg");
cvtColor(src, src, COLOR_BGR2GRAY);
threshold(src, src, 0, 255, CV_THRESH_OTSU);
imshow("原图像", src);
//输入图像取特征点
Mat trainTempImg = Mat::zeros(Size(128, 128), CV_8UC1);
resize(src, trainTempImg, trainTempImg.size());
HOGDescriptor *hog = new HOGDescriptor(Size(128, 128), Size(16, 16), Size(8, 8), Size(8, 8), 9);
vector<float>descriptors;//结果数组
hog->compute(trainTempImg, descriptors, Size(1, 1), Size(0, 0));
//cout << "HOG描述子向量维数 " << descriptors.size() << endl;
Mat SVMtrainMat = Mat(1, descriptors.size(), CV_32FC1);
int number1 = descriptors.size();
//将计算好的HOG描述子复制到样本特征矩阵SVMtrainMat
for (int i = 0; i < number1; i++)
{
//把一幅图像的HOG描述子向量依次存入data_mat矩阵的同一列
//因为输入图像只有一个,即SVMtrainMat只有一列,则为0
SVMtrainMat.at<float>(0, i) = descriptors[i]; // n++;
}
SVMtrainMat.convertTo(SVMtrainMat, CV_32FC1);//更改图片数据的类型,必要,不然会出错
int ret = (int)SVM_params->predict(SVMtrainMat);//检测结果
cout << "识别的数字为:" << ret << endl;
waitKey(0);
return 0;
}
void feature(Mat dealimage)
{
//把训练样本放大到128,128。便于HOG提取特征
Mat trainImg = Mat(Size(128, 128), CV_8UC1);
resize(dealimage, trainImg, trainImg.size());
//处理HOG特征
//检测窗口(64,128),块尺寸(16,16),块步长(8,8),cell尺寸(8,8),直方图bin个数9 ,需要修改
HOGDescriptor *hog = new HOGDescriptor(Size(128, 128), Size(16, 16), Size(8, 8), Size(8, 8), 9);
vector<float>descriptors;//存放结果 为HOG描述子向量
hog->compute(trainImg, descriptors, Size(1, 1), Size(0, 0)); //Hog特征计算,检测窗口移动步长(1,1)
//cout << "HOG描述子向量维数 : " << descriptors.size() << endl;
for (vector<float>::size_type j = 0; j < descriptors.size(); j++)
{
//把一幅图像的HOG描述子向量依次存入data_mat矩阵的同一列
data_mat.at<float>(yangben_data_position, j) = descriptors[j];
}
}
十三、C++ std::ostringstream 是什么 怎么用
13.1 简单介绍
ostringstream是C++的一个字符集操作模板类,定义在sstream.h头文件中。ostringstream类通常用于执行C风格的串流的输出操作,格式化字符串,避免申请大量的缓冲区,替代sprintf。
13.2ostringstream的基本使用
ostringstream的构造函数形式:
explicit ostringstream ( openmode which = ios_base::out );
explicit ostringstream ( const string & str, openmode which = ios_base::out );
有时候,我们需要格式化一个字符串,但通常并不知道需要多大的缓冲区。为了保险常常申请大量的缓冲区以防止缓冲区过小造成字符串无法全部存储。这时我们可以考虑使用ostringstream类,该类能够根据内容自动分配内存,并且其对内存的管理也是相当的到位。取得std::ostringstream里的内容可以通过str()和str(string&)成员函数。
13.3std::ostringstream::str()返回的是临时对象,不能对其直接操作。
例如会有如下误用:
const char * pBuffer = oss.str().c_str();
注意pBuffer指向的内存已被析构!!
#include <sstream>
#include <string>
#include <iostream>
using namespace std;
void main()
{
ostringstream ostr1; // 构造方式1
ostringstream ostr2("abc"); // 构造方式2
/*----------------------------------------------------------------------------
*** 方法str()将缓冲区的内容复制到一个string对象中,并返回
----------------------------------------------------------------------------*/
ostr1 << "ostr1 " << 2012 << endl; // 格式化,此处endl也将格式化进ostr1中
cout << ostr1.str();
/*----------------------------------------------------------------------------
*** 建议:在用put()方法时,先查看当前put pointer的值,防止误写
----------------------------------------------------------------------------*/
long curPos = ostr2.tellp(); //返回当前插入的索引位置(即put pointer的值),从0开始
cout << "curPos = " << curPos << endl;
ostr2.seekp(2); // 手动设置put pointer的值
ostr2.put('g'); // 在put pointer的位置上写入'g',并将put pointer指向下一个字符位置
cout << ostr2.str() << endl;
/*----------------------------------------------------------------------------
*** 重复使用同一个ostringstream对象时,建议:
*** 1:调用clear()清除当前错误控制状态,其原型为 void clear (iostate state=goodbit);
*** 2:调用str("")将缓冲区清零,清除脏数据
----------------------------------------------------------------------------*/
ostr2.clear();
ostr2.str("");
cout << ostr2.str() << endl;
ostr2.str("_def");
cout << ostr2.str() << endl;
ostr2 << "gggghh"; // 覆盖原有的数据,并自动增加缓冲区
cout << ostr2.str() << endl;
ostr2.str(""); // 若不加这句则运行时错误,因为_df所用空间小于gggghh,导致读取脏数据
ostr2.str("_df");
cout << ostr2.str() << endl;
// 输出随机内存值,危险
const char* buf = ostr2.str().c_str();
cout << buf << endl;
// 正确输出_df
string ss = ostr2.str();
const char *buffer = ss.c_str();
cout << buffer << endl;
}
十四、c++中二维Vector的初始化(vector subscript out of range)
(0)常见的错误使用方式
vector<vector<int> > result;//int可以换为其他数据类型处理方法相同
//如果不初始化,直接使用
i=0;
result[i].push_back(0); //想把0放入第一行中
//即为result[0][0] = 0;
报错:vector subscript out of range
因为该二维向量为空,寻下标i索引第一行找不到,故而报错
(1)初始化时遇到下表越界问题
在行和列的维数都确定的情况下采用如下操作
vector<vector<cv::Point2f>> Point;
vector<cv::Point2f> a;
a.push_back(1,1);
a.push_back(2,2);
a.push_back(3,3);
vector<cv::Point2f> b;
b.push_back(4,4);
b.push_back(5,5);
b.push_back(6,6);
Point.push_back(a);
Point.push_back(b);
(2)行数确定列数不确定是可以使用resize()函数进行初始化
//得到一个10行5列的数组
//由vector实现的二维数组,可以通过resize()的形式改变行、列值
vector<vector<int>> vec(10);
//或者vector<vector<int> > vec(10, vector<int> (0));
for (int i = 0; i < vec.size(); i++)
vec[i].resize(5);//调用resize()函数确定列数
for(int i = 0; i < array.size(); i++)
{
for (int j = 0; j < array[0].size();j++)
{
array[i][j] = 1 ;
}
}
(3)如果刚开始行数列数都不确定
vector<vector<int> > result(0, vector<int> (0)); //行列都不确定
int num =10;//数值自己确定
int cols=7;
for(int i = 1; i < num; i++)
{
result.resize(i); //修改二维向量行数为i
result[i].resize(cols); //修改向量的列数为cols
result[i].push_back(0);
result[i].push_back(1);//个数小于等于cols的个数
}
十五、OpenCV学习心得:vector<Mat>数据存储问题
一、推荐存储方式
vector<Mat> masks;
Mat img;
masks.pushback(img.clone()); //必须使用clone或者copyto
二、Mat的深拷贝,浅拷贝
1、浅拷贝
不复制数据只创建矩阵头,数据共享(更改a,b,c的任意一个都会对另外2个产生同样的作用)
Mat a;
Mat b = a; //a "copy" to b
Mat c(a); //a "copy" to c
2、深拷贝
Mat a;
Mat b = a.clone(); //a copy to b
Mat c;
a.copyTo(c); //a copy to c
三、vector的pushback()
如果直接使用变量名,调用的是浅拷贝,
若后续img重新赋值,指向不同的数据,则masks中存储的数据全部改变
vector《Mat> masks;
Mat img;
masks.pushback(img);
十六、学会封装train\precdit
/*
* 把图片从vector<Mat>格式转换成SVM的RAW_SAMPLE格式
*/
void transform(const vector<Mat> &split, Mat &testData)
{
for (auto it = split.begin(); it != split.end(); it++){
Mat tmp;
resize(*it, tmp, Size(28, 28));
testData.push_back(tmp.reshape(0, 1));
}
testData.convertTo(testData, CV_32F);
}
/*
* 从文件list.txt中读取测试数据和标签,输出SVM的Mat格式
*/
void get_data(string path, Mat &trainData, Mat &trainLabels)
{
fstream io(path, ios::in);
if (!io.is_open()){
cout << "file open error in path : " << path << endl;
exit(0);
}
while (!io.eof())
{
string msg;
io >> msg;
trainData.push_back(imread(msg, 0).reshape(0, 1));
io >> msg;
int idx = msg[0] - '0';
//trainLabels.push_back(Mat_<int>(1, 1) << idx); //用这种方式会报错,原因尚且不明
trainLabels.push_back(Mat(1, 1, CV_32S, &idx));
}
trainData.convertTo(trainData, CV_32F);
}
/*
* 训练SVM
*/
void svm_train(Ptr<SVM> &model, Mat &trainData, Mat &trainLabels)
{
model->setType(SVM::C_SVC); //SVM类型
model->setKernel(SVM::LINEAR); //核函数,这里使用线性核
Ptr<TrainData> tData = TrainData::create(trainData, ROW_SAMPLE, trainLabels);
cout << "SVM: start train ..." << endl;
model->trainAuto(tData);
cout << "SVM: train success ..." << endl;
}
/*
* 利用训练好的SVM预测
*/
void svm_pridect(Ptr<SVM> &model, Mat test)
{
Mat result;
float rst = model->predict(test, result);
for (auto i = 0; i < result.rows; i++){
cout << result.at<float>(i, 0);
}
}
int main(int argc, const char** argv)
{
fstream io;
io.open("test_list.txt", ios::in);
string train_path = "train_list.txt";
vector<Mat> test_set;
get_test(io, test_set);
Ptr<SVM> model = SVM::create();
Mat trainData, trainLabels;
get_data(train_path, trainData, trainLabels);
svm_train(model, trainData, trainLabels);
Mat testData;
transform(test_set, testData);
svm_pridect(model, testData);
}
在OpenCV 3.3中取消了CvSVM类的定义,结构变成了这样的了:具体可参考文档:
http://docs.opencv.org/3.3.0/d1/d2d/classcv_1_1ml_1_1SVM.html#a77d9a35898cae44ac9071c4b35bc96a8
matlab的.mat数据转txt
clear
clc
load Feature1
load Feature2
load Feature3
load Feature4
load labels
% save('pqfile.txt', 'data', '-ASCII')
featureall=[Feature1;Feature2;Feature3;Feature4];
back1 = mat2txt( 'labels.txt', labels );
% 把矩阵 matrix 保存成任意后缀的文件
% 转换成 .txt 举例:mat2txt( 'filename.txt', data );
% 转换成 .corr 举例:mat2txt( 'filename.corr',data );
function back = mat2txt( file_Name, matrix )
fop = fopen( file_Name, 'wt' );
[M,N] = size(matrix);
for m = 1:M
for n = 1:N
fprintf( fop, ' %s', mat2str( matrix(m,n) ) );
end
fprintf(fop, '\n' );
end
back = fclose( fop ) ;
end