SVM实现分类识别及参数调优(一)

前言

 项目有一个模块需要将不同类别的图片进行分类,共有三个类别,使用SVM实现分类。

实现步骤:

1.创建训练样本库;

2.训练、测试SVM模型;

3.SVM的数据要求;

实现系统:

windows_x64、opencv2.4.10、 VS2013

实现过程:

1.创建训练样本库;

1)将图片以包含类别的名称进行命名,比如0(1).jpg等等;

2)将所有已命名正确的训练样本保存在同一个文件夹中;

3)在训练样本库的文件夹目录下创建python源文件;

python代码:

import sys
import os
import string
import re

if __name__=='__main__':
    print('Begin generate path and label.')
    path_file=open('train_path.txt','w')
    path='E:/carriage_recognition/redplate_detection/svm_train_test/data/train/model'
    pic_type='.png'
    pat=re.compile(r'^(\d+)')
    files=os.listdir(path)
    files_tmp=[]
    for i in files:
        if pic_type in i and not os.path.isdir(path+'/'+i):
            files_tmp.append(i)
    files=files_tmp
    for file in range(len(files)):
        ret=pat.match(files[file])
        path_file.write(path+'/'+files[file]+'\n')
        if file<len(files)-1:           
            path_file.write(ret.group(1)+'\n')
        else:           
            path_file.write(ret.group(1))
    path_file.close()
    print('finish......')
View Code

4)运行代码,即可,生成包含图片名称和类别的文本文件,用于SVM训练过程中读物图片获取相应的类别标签;

E:/carriage_recognition/redplate_detection/svm_train_test/data/train/model/0 (1).png
0
E:/carriage_recognition/redplate_detection/svm_train_test/data/train/model/0 (10).png
0

奇数行表示训练样本图片的路径名称;偶数行表示该图片的类别标签;

2.训练、测试SVM模型;

1)image.h,主要实现过程的代码;

#include <fstream>
#include <vector>
#include<direct.h>
#include <opencv2\core\core.hpp>     //红牌事件检测头文件 
#include <opencv2\opencv.hpp>    

using namespace std;
using namespace cv;

#define ON_STUDY 0
#define Num 3      //类别数目
#define STANDARD_ROW 65
#define STANDARD_COL 85

#define STANDARD_ROW_CHOOSE 65
#define STANDARD_COL_CHOOSE 85

#define CHANELS 1
class NumTrainData
{
public:
    NumTrainData()
    {
        memset(data, 0, sizeof(data));
        result = -1;
    }
public:
    float data[CHANELS*STANDARD_COL_CHOOSE*STANDARD_ROW_CHOOSE];
    int result;
};

vector<string> img_path;//输入文件名变量
vector<string> img_test_path;//输入文件名变量 
vector<int> img_catg;
vector<int> img_test_catg;
int nLine = 0;
string buf;

unsigned long n;
vector<NumTrainData> buffer;
int featureLen = CHANELS*STANDARD_COL_CHOOSE*STANDARD_ROW_CHOOSE;

char* test_path = "./test_path.txt";
char* train_path = "./train_path.txt";
//存放输出结果
char* save_path = "./SVM_DATA_train_0.5_0.2.xml";
ofstream matrix_config("./fusion_matrix_0.5_0.2.txt");   //存放混淆矩阵
string save_wrong_results = "./wrong_0.5_0.2";         //存放识别错误的结果


void ReadTrainData()
{
    ifstream svm_data(train_path);//训练样本图片的路径都写在这个txt文件中,使用python可以得到这个txt文件
    while (svm_data)//将训练样本文件依次读取进来    
    {
        if (getline(svm_data, buf))
        {
            nLine++;
            if (nLine % 2 == 0)//注:奇数行是图片全路径,偶数行是标签 
            {
                img_catg.push_back(atoi(buf.c_str()));//atoi将字符串转换成整型,标志(0,1,2,...,9),注意这里至少要有两个类别,否则会出错    
            }
            else
            {
                img_path.push_back(buf);//图像路径    
            }
        }
    }
    svm_data.close();//关闭文件 
}

void ReadTestData()
{
    ifstream svm_data(test_path);//训练样本图片的路径都写在这个txt文件中,使用python可以得到这个txt文件
    while (svm_data)//将训练样本文件依次读取进来    
    {
        if (getline(svm_data, buf))
        {
            nLine++;
            if (nLine % 2 == 0)//注:奇数行是图片全路径,偶数行是标签 
            {
                img_test_catg.push_back(atoi(buf.c_str()));//atoi将字符串转换成整型,标志(0,1,2,...,9),注意这里至少要有两个类别,否则会出错    
            }
            else
            {
                img_test_path.push_back(buf);//图像路径    
            }
        }
    }
    svm_data.close();//关闭文件 
}

void LoadTrainData()
{
    Mat src;      //= Mat::zeros(rows, cols, CV_8UC1);
    Mat dst;
    NumTrainData rtd;
    cout << "Begin load training data...." << endl;
    for (int i = 0; i < img_path.size(); i++)
    {
        rtd.result = img_catg[i];

        int k = 0;
        if (CHANELS == 1) // gray image
        {
            src = imread(img_path[i].c_str(), 0);
            dst = src;

            Mat temp = Mat::zeros(STANDARD_ROW, STANDARD_COL, CV_8UC1); 

            //尺寸归一化
            resize(dst, temp, temp.size());

            float m[CHANELS*STANDARD_COL_CHOOSE*STANDARD_ROW_CHOOSE];
            for (int i = 0; i<STANDARD_ROW; i++)
            {
                for (int j = 0; j<STANDARD_COL; j++)
                {
                    rtd.data[i * STANDARD_COL + j] = temp.at<uchar>(i, j);
                }
            }
        }
        else if (CHANELS == 3) // 3-channel image
        {
            src = imread(img_path[i].c_str(), 1);
            dst = src;

            Mat temp = Mat::zeros(STANDARD_ROW, STANDARD_COL, CV_8UC1);    //大小归一化
            resize(dst, temp, temp.size());
            //cout << temp.channels() << endl;

            for (int i = 0; i < STANDARD_ROW_CHOOSE; i++)
            {
                for (int j = 0; j < STANDARD_COL_CHOOSE; j++)
                {
                    Vec3b& mp = temp.at<Vec3b>(i, j);
                    float B = mp.val[0];
                    //cout << "B=" << B << endl;
                    float G = mp.val[1];
                    //cout << "G=" << B << endl;
                    float R = mp.val[2];
                    //cout << "R=" << B << endl;

                    rtd.data[k++] = B;  //R
                    rtd.data[k++] = G;  //G
                    rtd.data[k++] = R;  //B
                }
            }
        }
        buffer.push_back(rtd);
        //cout << i << "th Image is loaded!" << endl;
    }
    cout << "Loading image finished!" << endl;
}

void SVMPredict()
{
    int x = 0;
    //_mkdir(save_test_preprocess.c_str());
    _mkdir(save_wrong_results.c_str());
    int fusion_matrix[Num][Num] = { 0 };

    CvSVM svm;
    svm.load(save_path);
    Mat src,dst;
    Mat m = Mat::zeros(1, featureLen, CV_32FC1);

    NumTrainData rtd;
    int label = -1;
    int right = 0, error = 0;
    save_wrong_results += "/%d_true_%d_false_%d.png";
    //
    double ptrue_rtrue = 0;
    double ptrue = 0;
    double rtrue = 0;
    //
    for (int i = 0; i < img_test_path.size(); i++)
    {
        label = img_test_catg[i];
        rtd.result = label;

        if (CHANELS == 1)
        {
            src = imread(img_test_path[i].c_str(), 0);
            dst = src;

            Mat temp = Mat::zeros(STANDARD_ROW, STANDARD_COL, CV_8UC1);    //大小归一化
            resize(dst, temp, temp.size());

            for (int i = 0; i<STANDARD_ROW; i++)
            {
                for (int j = 0; j<STANDARD_COL; j++)
                {
                    m.at<float>(0, j + i * STANDARD_COL) = temp.at<uchar>(i, j);
                }
            }
            normalize(m, m);
        }
        else if (CHANELS == 3) // 3-channel image
        {
            src = imread(img_test_path[i].c_str(), 1);
            dst = src;

            Mat temp = Mat::zeros(STANDARD_ROW, STANDARD_COL, CV_8UC1);    //大小归一化
            resize(dst, temp, temp.size());

            int k = 0;
            for (int i = 0; i < STANDARD_ROW_CHOOSE; i++)
            {
                for (int j = 0; j < STANDARD_COL_CHOOSE; j++)
                {
                    Vec3b& mp = temp.at<Vec3b>(i, j);
                    float B = mp.val[0];
                    float G = mp.val[1];
                    float R = mp.val[2];

                    m.at<float>(0, k++) = B;  //R
                    m.at<float>(0, k++) = G;  //G
                    m.at<float>(0, k++) = R;  //B
                }
            }
        }
        
        int ret = svm.predict(m);
        //if (ret == 3)
        //    ret = 1;
        cout << "Picture->" << img_test_path[i].c_str() << " : \nTrue label is [" << label << "] Predicted label is [" << ret << "]" << endl;
        //
        //计算FSCORE指标各个参数
        if (label == 0 && ret == 0) ptrue_rtrue++;//识别为红牌且实际为红牌;
        if (ret == 0) ptrue++;//识别为红牌的个数
        if (label == 0) rtrue++;//实际为红牌的个数
        //
        //存储错误图片
        if (label != ret)
        {
            x++;
            char filename[200];
            src = imread(img_test_path[i].c_str(), 1);
            sprintf(filename, save_wrong_results.c_str(), x, label, ret);
            imwrite(filename, src);
        }
        //计算混淆矩阵
        //fusion_matrix[label][ret] = fusion_matrix[label][ret] + 1;
    }
    //
    //FSCORE
    std::cout << "count_all: " << img_test_path.size() << std::endl;
    std::cout << "ptrue_rtrue: " << ptrue_rtrue << std::endl;
    std::cout << "ptrue: " << ptrue << std::endl;
    std::cout << "rtrue: " << rtrue << std::endl;
    //precise
    double precise = 0;
    if (ptrue != 0)
    {
        precise = ptrue_rtrue / ptrue;
        std::cout << "precise: " << precise << std::endl;
    }
    else
    {
        std::cout << "precise: " << "NA" << std::endl;
    }
    //recall
    double recall = 0;
    if (rtrue != 0)
    {
        recall = ptrue_rtrue / rtrue;
        std::cout << "recall: " << recall << std::endl;
    }
    else
    {
        std::cout << "recall: " << "NA" << std::endl;
    }
    //FSCORE
    double FScore = 0;
    if (precise + recall != 0)
    {
        FScore = 2 * (precise * recall) / (precise + recall);
        std::cout << "FScore: " << FScore << std::endl;
    }
    else
    {
        std::cout << "FScore: " << "NA" << std::endl;
    }
    //
    //for (size_t i = 0; i < Num; i++)
    //{
    //    for (size_t j = 0; j < Num; j++)
    //    {
    //        matrix_config << fusion_matrix[i][j] << " ";
    //    }
    //    matrix_config << endl;
    //}
    //matrix_config.close();
    cout << "Task finished!output_matix" << endl;
    getchar();
}

void SVMTrain(vector<NumTrainData>& trainData)
{
    int testCount = trainData.size();

    Mat m = Mat::zeros(1, featureLen, CV_32FC1);
    Mat data = Mat::zeros(testCount, featureLen, CV_32FC1);
    //Mat res = Mat::zeros(testCount, 1, CV_32SC1);
    Mat res = Mat::zeros(testCount, 1, CV_32SC1);

    for (int i = 0; i< testCount; i++)
    {

        NumTrainData td = trainData.at(i);
        memcpy(m.data, td.data, featureLen * sizeof(float));
        normalize(m, m);
        memcpy(data.data + i*featureLen * sizeof(float), m.data, featureLen * sizeof(float));
        cout << td.result << endl;
        res.at<int>(i, 0) = td.result;
        
    }

    /////////////START SVM TRAINNING//////////////////
    //CvSVM svm = CvSVM();
    CvSVM svm;
    CvSVMParams param;
    CvTermCriteria criteria;

    criteria = cvTermCriteria(CV_TERMCRIT_EPS, 1000, FLT_EPSILON);
    param = CvSVMParams(CvSVM::C_SVC, CvSVM::RBF, 10.0, 0.5, 1.0, 0.2, 0.5, 0.1, NULL, criteria);  //gamma=2;C=3
    cout << "Begin to train model using given train data.....\n Total training sample count is " << testCount << endl;
    svm.train(data, res, Mat(), Mat(), param);
    svm.save(save_path);
    cout << "Finish" << endl;
}

  

2)主要函数说明;

2.1)SVMTrain函数主要实现模型的训练,其中训练参数使用RBF核,主要调整gamma和C这两个参数,固定一个参数调整另一个参数,最后确定模型参数分别为0.5/0.2;

2.2)SVMPredict函数主要实现对测试样本库的测试,并使用FScore指标测试SVM模型的性能;也可以使用混淆矩阵测试性能;

2.3)ReadTrainData/ ReadTestData函数分别用于获取训练和测试样本库图片的名称和类别标签;

2.4)LoadTrainData函数用于读取训练数据,并进行图像处理;

2.5)代码中使用整张图片的信息进行归一化之后作为特征;

3)主函数入口

#include "image.h"

int main(int argc, char *argv[])
{
#if (ON_STUDY)
    ReadTrainData();
    LoadTrainData();
    SVMTrain(buffer);
#else
    ReadTestData();
    SVMPredict();
#endif

    getchar();
}

参数ON_STUDY表示选择进行训练或者测试的标志位;

3.SVM的数据要求;

需要说明的是就是SVM对于输入的数据类型是有要求的,即mTrainData(训练数据矩阵)以及mFlagPosNeg(标签矩阵)都必须为CV_32FC1类型(我的环境标签矩阵是CV_32SC1类型的),因此需要进行类型转换,而且必须保证转换完之后数值都不能大于1,这就给我们了两点启示:1)不能直接用下采样后的图像像素作为训练数据的输入,需要进行类型的归一化。2)类型转换时要使用normlize函数,保证其数值范围不大于1,而不能简单的使用Mat的成员函数coverto,只变类型不变数值范围。( 需要注意!)

问题:

该实现过程需要人工调整参数,比较繁琐,可以思考一下,是否还存在其他问题;

参考:

1.http://blog.csdn.net/firefight/article/details/6452188

2.opencv中SVM的那些事儿

 

posted on 2017-08-11 10:13  鹅要长大  阅读(1047)  评论(0编辑  收藏  举报

导航