最近在尝试运用opencv的svm训练器来实现图片分类,选用的是hog特征和sift特征两种,过程大致相同的,都是要把每一个样品提取出来的特征化成同样维度的一维向量,因为opencv的svm是以一个行向量作为一个训练数据。
sift特征每一幅图提取的特征向量的个数是不同的,在网上搜索后选择使用pca降维的方式来归一化特征向量的维度。参考文章
hog特征在统一图像尺寸以及hog特征提取的检测窗口大小,得出的特征向量的维度是一定的。
使用svm检测时要按照训练的方式来对待检测样品进行处理,即将图像提取成与训练模型同样的维度的特征向量。
例如hog特征向量是1*588维,sift特征经过pca方式降维后是1*128维的。
#include
#include //头文件
#include
#include
#include
#include
#include
#include
#include
#include //查找文件相关函数
using namespace cv::ml;
using namespace cv; //包含cv命名空间
using namespace std;
void getFiles(string path, vector& files);
void getBubble(Mat& trainingImages, vector& trainingLabels);
void getNoBubble(Mat& trainingImages, vector& trainingLabels);
Mat detector_HOG(Mat image) {
HOGDescriptor hog(Size(128, 128), Size(16, 16), Size(8, 8), Size(8, 8), 3);
Mat featureMat;
vector descriptors;
hog.compute(image, descriptors);
featureMat = Mat::zeros(1, descriptors.size(), CV_32FC1);
for (int j = 0; j(0, j) = descriptors[j];
}
return featureMat;
}
int main() {
Mat classes;
Mat trainingData;
Mat trainingImages;
vector trainingLabels;
getPositive(trainingImages, trainingLabels);
getNegative(trainingImages, trainingLabels);
Mat(trainingImages).copyTo(trainingData);
trainingData.convertTo(trainingData, CV_32FC1);
Mat(trainingLabels).copyTo(classes);
// 创建分类器并设置参数
Ptr SVM_params = SVM::create();
SVM_params->setType(SVM::C_SVC);
SVM_params->setKernel(SVM::LINEAR); //核函数
SVM_params->setDegree(0);
SVM_params->setGamma(1);
SVM_params->setCoef0(0);
SVM_params->setC(1);
SVM_params->setNu(0);
SVM_params->setP(0);
SVM_params->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 1000, 0.01));
cout << "start" << endl;
Ptr tData = TrainData::create(trainingData, ROW_SAMPLE, classes);
// 训练分类器
clock_t start, end;
start = clock();
SVM_params->train(tData);
end = clock();
cout << (float)(end - start) / CLOCKS_PER_SEC << "s" << endl;
//保存模型
SVM_params->save("F:\\testdata\\svm_hog_726_2.xml"); //svm模型存放路径
cout << "训练好了!!!" << endl;
getchar();
return 0;
}
void getFiles(string path, vector& files)
{
//文件句柄
long long hFile = 0;
//文件信息
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);
}
}
//获取正样本
void getPositive(Mat& trainingImages, vector& trainingLabels)
{
string filePath = "F:\\picture\\positive"; //正样本路径
vector files;
getFiles(filePath, files);
int number = files.size();
for (int i = 0; i < number; i++)
{
Mat image = imread(files[i], 0);//读入灰度图
equalizeHist(image, SrcImage);//直方图均衡
resize(image, image, Size(128, 128));
Mat hogFeactureMat = detector_HOG(image);
if (!hogFeactureMat.empty()) {
trainingImages.push_back(hogFeactureMat);
trainingLabels.push_back(1);
}
cout << "remain " << (number - i) << " count: " << i << endl;
}
cout << "positive ok" << endl;
}
//获取负样本
void getNegative(Mat& trainingImages, vector& trainingLabels)
{
string filePath = "F:\\picture\\negative"; //负样本路径
vector files;
getFiles(filePath, files);
int number = files.size();
for (int i = 0; i < number; i++)
{
Mat image = imread(files[i], 0);
equalizeHist(image, image);
resize(image, image, Size(128, 128));
Mat hogFeactureMat = detector_HOG(SrcImage);
if (!hogFeactureMat.empty()) {
trainingImages.push_back(hogFeactureMat);
trainingLabels.push_back(0);
}
cout << "remain " << (number - i) << " count: " << i << endl;
}
cout << "negative ok" << endl;
}
sift特征分类仅在特征处理上有不同
Mat detector_SIFI(Mat image) {
//Create SIFT class pointer
Ptr f2d = xfeatures2d::SIFT::create(1000,3,0.04,10);//sift的参数,会影响一幅图提取出的特征点的个数
vector keypoints_1, keypoints_2;
f2d->detect(image, keypoints_1);
Mat descriptors_1;
f2d->compute(image, keypoints_1, descriptors_1);
if (keypoints_1.size() == 0) {
return Mat();
}
PCA pca(descriptors_1, noArray(), CV_PCA_DATA_AS_ROW);
Mat eigenvalues;//特征值
Mat eigenvectors;//特征向量
DoPca(descriptors_1, 3, eigenvalues, eigenvectors);
eigenvectors = eigenvectors.reshape(0, 1);
//cout << eigenvalues.rows << " " << eigenvectors.cols << endl;
return eigenvectors;
}
//这个函数在前文的资料有提及,降维特征向量
void DoPca(const Mat &_data, int dim, Mat &eigenvalues, Mat &eigenvectors)
{
assert(dim>0);
Mat data = cv::Mat_(_data);
int R = data.rows;
int C = data.cols;
if (dim>C)
dim = C;
//计算均值
Mat m = Mat::zeros(1, C, data.type());
for (int j = 0; j(0, j) += data.at(i, j);
}
}
m = m / R;
//求取6列数据对应的均值存放在m矩阵中,均值: [1.67、2.01、1.67、2.01、1.67、2.01]
//计算协方差矩阵
Mat S = Mat::zeros(R, C, data.type());
for (int i = 0; i(i, j) = data.at(i, j) - m.at(0, j); // 数据矩阵的值减去对应列的均值
}
}
Mat Average = S.t() * S / (R);
//计算协方差矩阵的方式----(S矩阵的转置 * S矩阵)/行数
//使用opencv提供的eigen函数求特征值以及特征向量
eigen(Average, eigenvalues, eigenvectors);
}
sift特征点构造方法可以参考opencv 中sift特征提取的参数介绍
训练完成后就是测试结果
void svm_pridect_HOG(Ptr
&model, Mat test)
{
Mat tmp;
float validity = 0;
float rst;
tmp = detector_HOG(test);tmp.convertTo(tmp, CV_32F);
rst = model->predict(tmp);
cout << rst << " ";
}
读入svm训练模型
Ptrsvm = StatModel::load("...your xml file path");
opencv下svm训练数据要存入一个矩阵中,一行为一个样品,对于每一种特征都应该尽可能将其转换成一个行向量,因为一副图像中特征点之间应该是有关联的,不能单纯将一个特征点作为一个训练样品。
同时这个矩阵不能过大,个人尝试过大概14000*50000左右的矩阵是会报错,没有了解到是否有扩大数据容量的方法。
参考资料
opencv下svm参数介绍
opencv3与2.x svm参数介绍
opencv下实现简单的图像分类器
sift特征点与特征向量的转换