图像识别之初探SVM分类器

SVM的中文名为支持向量机,是一种非常经典的有监督数据分类算法,也即该算法首先需要训练,训练得到分类模型之后,再使用分类模型对待分类数据进行分类。有监督数据分类算法的大致过程如下图所示:

图像识别之初探SVM分类器_第1张图片

上图中,训练数据与待分类数据通常为n维向量,n可以是1,2,3,4,5,......

对于图像,一般有两种方法把其所有像素点的像素值转换为n维向量:

方法一:图像数据属于二维矩阵,可以直接把二维矩阵的多行数据按行进行首尾拼接,组成只有一行的n维向量。这种做法通常是在图像尺寸很小的情况,如果图像很大,那么得到的n维向量长度会很长,不仅严重影响训练和分类的速度,有时候数据量过多反而对分类起干扰作用,影响分类的准确性。

图像识别之初探SVM分类器_第2张图片

方法二:提取图像的特征,包括Sift、Surf特征和Hog特征等。

(1) 检测Hog特征,对于相同尺寸的图像和相同的输入参数,检测得到Hog特征的维数是一样的, 因此Hog特征可直接作为n维向量输入分类器。

(2) 检测Sift特征,Sift特征是对图像上特殊点(可以理解为具有明显特征的角点)的向量描述,每个特殊点的Sift特征为一个128维向量。然而使用Sift算法在不同图像上检测到的特殊点的个数往往是不一样的,此时不同图像的128维向量的个数也不一样,因此不同图像中所有128维向量首尾拼接组成的n维向量的长度也不一样了。如果向量长度不一样,是不能输入分类器的,为了保证每张图像的特征向量长度一致,PCA降维就派上用场了,通常使用PCA降维算法把多个128维向量降维成1个128维向量。

图像识别之初探SVM分类器_第3张图片

(3) 检测Surf特征,Surf算法是由Sift算法改进和演变而来的,Surf算法主要比Sift算法更快,一个Surf特征为一个64维向量,检测到多个Surf特征之后,也需要像检测Sift特征那样,进行PCA降维,确保每张图像的特征向量维度一致。

最近本人一直在琢磨SVM的数学原理,还有不少地方没能理解,比如求最优解的数学原理、怎么做到多类的分类、核函数等。下面首先以二维向量为例,来讲讲我对SVN分类器的初步理解(扩展到n维也是一样的原理),然后再讲解怎么使用Opencv的SVM模块对手写数字图片和Cifar-10数据集进行分类。

假设有多个二维向量如下:

图像识别之初探SVM分类器_第4张图片

以上的每个二维向量在x1-x2平面坐标系中表现为一个点,我们的目标是使用一条直线把这些点分成两类,两类中距离最近的点分别为Xi和Xj,我们要寻找的直线在Xi和Xj的中间,也即Xi和Xj到直线的距离都为d,当d取得最大值的时候,这条直线就是我们要找的分类界限啦~

图像识别之初探SVM分类器_第5张图片

在我们最常见的x-y坐标系中,直线的一般形式为Ax+By+c=0。同理,在x1-x2坐标系中,我们分别使用w1代替A,w2代替B,x1代替x,x2代替y,b代替C,得到直线的一般表达式为w1x1+w2x2+b=0,写成向量形式:

图像识别之初探SVM分类器_第6张图片

根据点到直线的距离公式,有d的计算式:

图像识别之初探SVM分类器_第7张图片

由于d是最短距离,对于所有点均满足:

也即有:

图像识别之初探SVM分类器_第8张图片

对上式,不等号两边都除以d,则有:

图像识别之初探SVM分类器_第9张图片

我们记:

于是有:

图像识别之初探SVM分类器_第10张图片

为了方便分类,我们通常给每个点(二维向量)贴上一个标签y,属于红点则y=1,属于蓝点则y=-1,从几何原理来看,有:

1. 当WTX+b>0时点位于直线上方,属于红点类型,此时y=1;

2. 当WTX+b<0时点位于直线下方,属于蓝点类型,此时y=-1。

所以上式又可以转换为:

y的绝对值为1,因此y相当于正负号的作用,由此上式可以合并为:

我们注意到,在x1-x2坐标系中,由于d|W|是一个大于零的实数,以下两个解析式表示的是同一条直线:

图像识别之初探SVM分类器_第11张图片

因此,问题可以等效转换为求使d取得最大值的以上的解析式二。

图像识别之初探SVM分类器_第12张图片

此时同样由点到直线的距离公式可得d的计算式为:

图像识别之初探SVM分类器_第13张图片

又由以上的(1)式可得:

图像识别之初探SVM分类器_第14张图片

由上式可知当|Wd|取得最小值时,d取得最大值,因此问题又可以等效转换为求下式的最小值,之所以这样转换,使为了后续方便通过求导来求得最优解:

推导到这里,由以上的(2)式和(3)式,我们就可以得到SVM最优化问题的数学表达了:

(1) 约束条件:对x1-x2坐标系中所有的点Xk (k=1,2,3,4,5,......),均满足:

(2) 在满足以上约束条件的前提下,求解(3)式的Wd

本文的原理就讲到这里,关于如何求得以上数学问题的最优解,我们后续再继续研究和讲解。下面我们来讲讲如何使用Opencv得SVM模块来对手写数字图像和Cifar-10图像进行分类。关于如何获取手写数字图像和Cifar-10图像,此处不再重复,读者如果感兴趣可以参考我之前的博文:

https://blog.csdn.net/shandianfengfan/article/details/109665320

https://blog.csdn.net/shandianfengfan/article/details/109882511

首先是对手写数字图像进行训练和分类,由于手写数字图像特征相对简单,且尺寸为较小的20*20,因此我们使用以上提到的方法一把每张图像数据转换为一个n维向量,再把n维向量输入SVM模块中。代码如下:

void SVM_Hand_Digital_test(void)
{
  char ad[128] = { 0 };
  int testnum = 0, truenum = 0;


  Ptr model = SVM::create();   //创建一个SVM分类器
  model->setType(SVM::C_SVC);     //设置SVM类型
  model->setKernel(SVM::LINEAR);  //设置核函数,这里使用线性核
  model->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 1000, 1e-6));   //设置求SVM最优解的最大迭代次数和精度


  Mat traindata, trainlabel;


  for (int i = 0; i < 10; i++)
  {
    for (int j = 0; j < 400; j++)
    {
      sprintf_s(ad, "%d/%d.jpg", i, j);
      Mat srcimage = imread(ad);
      srcimage = srcimage.reshape(1, 1);    //将多行数据转换为一行的向量
      traindata.push_back(srcimage);   //将向量输入到训练矩阵中
      trainlabel.push_back(i);      //将标签输入到标签矩阵中
    }
  }


  traindata.convertTo(traindata, CV_32F);   //将训练数据转换为浮点型数据
  model->train(traindata, ROW_SAMPLE, trainlabel);   //输入训练数据和标签,开始训练分类模型


  for (int i = 0; i < 10; i++)
  {
    for (int j = 400; j < 500; j++)
    {
      testnum++;
      sprintf_s(ad, "%d/%d.jpg", i, j);
      Mat testdata = imread(ad);
      testdata = testdata.reshape(1, 1);   //将多行数据转换为一行的向量
      testdata.convertTo(testdata, CV_32F);  //将待分类数据转换为浮点型数据


      Mat result;
      int  response = model->predict(testdata);   //使用训练得到的分类模型对待分类数据进行预测,得到分类结果
      if (response == i)   //如果预测的分类结果与标签一致,则认为分类成功
      {
        truenum++;
      }
    }
  }
  cout << "测试总数" << testnum << endl;
  cout << "正确分类数" << truenum << endl;
  cout << "准确率:" << (float)truenum / testnum * 100 << "%" << endl;
}

运行以上代码,得到结果如下。训练之后,对1000张手写数字图像进行预测分类,准确率达到90.5%,这个分类结果还是比较理想的。

图像识别之初探SVM分类器_第15张图片

接下来,我们再使用Opencv的SVM模块对Cifar-10数据集进行分类。我们使用以上提到的方法二把每张图像转换为一个n维向量:提取每张图像的Hog特征,并把Hog特征输入SVM模块。首先在data_batch_1.bin~data_batch_5.bin这个5个文件中都取前900张图像对SVM模型进行训练,然后再对test_batch.bin中的前800张图像进行预测分类。代码如下:

void SVM_cifar_test_hog(void)
{
  char ad[128] = { 0 };
  int testnum = 0, truenum = 0;


  Ptr model = SVM::create();  //创建一个SVM分类器
  model->setType(SVM::C_SVC);     //设置SVM类型
  model->setKernel(SVM::RBF);  //设置核函数,这里使用非线性核RBF
  model->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 1000, 1e-6));  //设置求SVM最优解的最大迭代次数和精度


  Mat traindata, trainlabel;
  const int trainnum = 900;


  //定义HOG检测器
  HOGDescriptor *hog = new HOGDescriptor(cvSize(16, 16), cvSize(8, 8), cvSize(8, 8), cvSize(2, 2), 9);   //特征提取滑动窗口, 块大小, 块滑动步长, 胞元(cell)大小
  vector descriptors;  //定义HOG特征数组


  const int win_step = 16;
  
  //加载data_batch_1.bin的训练数据
  for (int i = 0; i < 10; i++)
  {
    for (int j = 0; j < trainnum; j++)
    {
      printf("i=%d, j=%d\n", i, j);
      sprintf_s(ad, "cifar/batch1/%d/%d.tif", i, j);
      Mat srcimage = imread(ad, CV_LOAD_IMAGE_GRAYSCALE);  
                          
      hog->compute(srcimage, descriptors, Size(win_step, win_step), Size(0, 0));  //计算图像的HOG特征
      Mat hogg(descriptors); //将特征数组存入Mat矩阵中


      srcimage = hogg.reshape(1, 1);    //将多行数据转换为一行的向量


      traindata.push_back(srcimage);   //将HOG特征向量加载到训练矩阵中
      trainlabel.push_back(i);      //将标签输入到标签矩阵中
    }
  }


  //加载data_batch_2.bin的训练数据
  for (int i = 0; i < 10; i++)
  {
    for (int j = 0; j < trainnum; j++)
    {
      printf("i=%d, j=%d\n", i, j);
      sprintf_s(ad, "cifar/batch2/%d/%d.tif", i, j);
      Mat srcimage = imread(ad, CV_LOAD_IMAGE_GRAYSCALE);




      hog->compute(srcimage, descriptors, Size(win_step, win_step), Size(0, 0));
      Mat hogg(descriptors);




      srcimage = hogg.reshape(1, 1);    //Mat Mat::reshape(int cn, int rows = 0),cn表示通道数,为0则通道不变,rows表示矩阵行数
      traindata.push_back(srcimage);
      trainlabel.push_back(i);
    }
  }


  //加载data_batch_3.bin的训练数据
  for (int i = 0; i < 10; i++)
  {
    for (int j = 0; j < trainnum; j++)
    {
      printf("i=%d, j=%d\n", i, j);
      sprintf_s(ad, "cifar/batch3/%d/%d.tif", i, j);
      Mat srcimage = imread(ad, CV_LOAD_IMAGE_GRAYSCALE);




      hog->compute(srcimage, descriptors, Size(win_step, win_step), Size(0, 0));
      Mat hogg(descriptors);




      srcimage = hogg.reshape(1, 1);    //Mat Mat::reshape(int cn, int rows = 0),cn表示通道数,为0则通道不变,rows表示矩阵行数
      traindata.push_back(srcimage);
      trainlabel.push_back(i);
    }
  }


  //加载data_batch_4.bin的训练数据
  for (int i = 0; i < 10; i++)
  {
    for (int j = 0; j < trainnum; j++)
    {
      printf("i=%d, j=%d\n", i, j);
      sprintf_s(ad, "cifar/batch4/%d/%d.tif", i, j);
      Mat srcimage = imread(ad, CV_LOAD_IMAGE_GRAYSCALE);




      hog->compute(srcimage, descriptors, Size(win_step, win_step), Size(0, 0));
      Mat hogg(descriptors);




      srcimage = hogg.reshape(1, 1);    //Mat Mat::reshape(int cn, int rows = 0),cn表示通道数,为0则通道不变,rows表示矩阵行数
      traindata.push_back(srcimage);
      trainlabel.push_back(i);
    }
  }


  //加载data_batch_5.bin的训练数据
  for (int i = 0; i < 10; i++)
  {
    for (int j = 0; j < trainnum; j++)
    {
      printf("i=%d, j=%d\n", i, j);
      sprintf_s(ad, "cifar/batch5/%d/%d.tif", i, j);
      Mat srcimage = imread(ad, CV_LOAD_IMAGE_GRAYSCALE);




      hog->compute(srcimage, descriptors, Size(win_step, win_step), Size(0, 0));
      Mat hogg(descriptors);




      srcimage = hogg.reshape(1, 1);    //Mat Mat::reshape(int cn, int rows = 0),cn表示通道数,为0则通道不变,rows表示矩阵行数
      traindata.push_back(srcimage);
      trainlabel.push_back(i);
    }
  }




  traindata.convertTo(traindata, CV_32F);   //将训练数据转换为浮点型数据
  model->train(traindata, ROW_SAMPLE, trainlabel);   //输入训练数据和标签,开始训练分类模型


  //对test_batch.bin中的前800张图像进行预测分类
  for (int i = 0; i < 10; i++)
  {
    for (int j = 0; j < 800; j++)
    {
      testnum++;
      sprintf_s(ad, "cifar/test_batch/%d/%d.tif", i, j);
      Mat testdata = imread(ad, CV_LOAD_IMAGE_GRAYSCALE);




      hog->compute(testdata, descriptors, Size(win_step, win_step), Size(0, 0));   //计算待分类图像的HOG特征
      Mat hogg(descriptors);




      testdata = hogg.reshape(1, 1);
      testdata.convertTo(testdata, CV_32F);




      Mat result;
      int  response = model->predict(testdata);   //对Hog特征进行预测分类
      if (response == i)  //如果预测的分类结果与标签一致,则认为分类成功
      {
        truenum++;
      }
    }
  }


  cout << "测试总数" << testnum << endl;
  cout << "正确分类数" << truenum << endl;
  cout << "准确率:" << (float)truenum / testnum * 100 << "%" << endl;
}

运行以上代码,得到结果如下。对8000张图像进行分类,准确率只有55.2375%,分类的准确率与前文中我们使用KNN算法的分类结果相比,并不没有提升多少。当图像复杂之后,这些传统的数据分类算法就显得有些无力了。因此在以后的文章中,我们将尝试卷积神经网络与深度学习来进行分类。

以上只是总结了一下本人对SVM算法的初步理解,本人远没有达到理解透的境界,但是学无止境,让我们继续加油吧~~

微信公众号如下,欢迎扫码关注,欢迎私信技术交流:

图像识别之初探SVM分类器_第16张图片

你可能感兴趣的:(Opencv,C++编程,算法,神经网络,计算机视觉,机器学习,人工智能)