小言_互联网的博客

图像识别之初探SVM分类器

415人阅读  评论(0)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

也即有:

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

我们记:

于是有:

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

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

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

所以上式又可以转换为:

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

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

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

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

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

由上式可知当|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模块中。代码如下:


   
  1. void SVM_Hand_Digital_test(void)
  2. {
  3. char ad[ 128] = { 0 };
  4. int testnum = 0, truenum = 0;
  5. Ptr<SVM> model = SVM::create(); //创建一个SVM分类器
  6. model->setType(SVM::C_SVC); //设置SVM类型
  7. model->setKernel(SVM::LINEAR); //设置核函数,这里使用线性核
  8. model->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 1000, 1e-6)); //设置求SVM最优解的最大迭代次数和精度
  9. Mat traindata, trainlabel;
  10. for ( int i = 0; i < 10; i++)
  11. {
  12. for ( int j = 0; j < 400; j++)
  13. {
  14. sprintf_s(ad, "%d/%d.jpg", i, j);
  15. Mat srcimage = imread(ad);
  16. srcimage = srcimage.reshape( 1, 1); //将多行数据转换为一行的向量
  17. traindata.push_back(srcimage); //将向量输入到训练矩阵中
  18. trainlabel.push_back(i); //将标签输入到标签矩阵中
  19. }
  20. }
  21. traindata.convertTo(traindata, CV_32F); //将训练数据转换为浮点型数据
  22.   model->train(traindata, ROW_SAMPLE, trainlabel);    //输入训练数据和标签,开始训练分类模型
  23. for ( int i = 0; i < 10; i++)
  24. {
  25. for ( int j = 400; j < 500; j++)
  26. {
  27. testnum++;
  28. sprintf_s(ad, "%d/%d.jpg", i, j);
  29. Mat testdata = imread(ad);
  30. testdata = testdata.reshape( 1, 1); //将多行数据转换为一行的向量
  31. testdata.convertTo(testdata, CV_32F); //将待分类数据转换为浮点型数据
  32. Mat result;
  33. int response = model->predict(testdata); //使用训练得到的分类模型对待分类数据进行预测,得到分类结果
  34. if (response == i) //如果预测的分类结果与标签一致,则认为分类成功
  35. {
  36. truenum++;
  37. }
  38. }
  39. }
  40. cout << "测试总数" << testnum << endl;
  41. cout << "正确分类数" << truenum << endl;
  42. cout << "准确率:" << ( float)truenum / testnum * 100 << "%" << endl;
  43. }

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

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


   
  1. void SVM_cifar_test_hog(void)
  2. {
  3. char ad[ 128] = { 0 };
  4. int testnum = 0, truenum = 0;
  5. Ptr<SVM> model = SVM::create(); //创建一个SVM分类器
  6. model->setType(SVM::C_SVC); //设置SVM类型
  7. model->setKernel(SVM::RBF); //设置核函数,这里使用非线性核RBF
  8. model->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 1000, 1e-6)); //设置求SVM最优解的最大迭代次数和精度
  9. Mat traindata, trainlabel;
  10. const int trainnum = 900;
  11. //定义HOG检测器
  12. HOGDescriptor *hog = new HOGDescriptor(cvSize( 16, 16), cvSize( 8, 8), cvSize( 8, 8), cvSize( 2, 2), 9); //特征提取滑动窗口, 块大小, 块滑动步长, 胞元(cell)大小
  13. vector< float> descriptors; //定义HOG特征数组
  14. const int win_step = 16;
  15. //加载data_batch_1.bin的训练数据
  16. for ( int i = 0; i < 10; i++)
  17. {
  18. for ( int j = 0; j < trainnum; j++)
  19. {
  20. printf( "i=%d, j=%d\n", i, j);
  21. sprintf_s(ad, "cifar/batch1/%d/%d.tif", i, j);
  22. Mat srcimage = imread(ad, CV_LOAD_IMAGE_GRAYSCALE);
  23. hog->compute(srcimage, descriptors, Size(win_step, win_step), Size( 0, 0)); //计算图像的HOG特征
  24. Mat hogg(descriptors); //将特征数组存入Mat矩阵中
  25. srcimage = hogg.reshape( 1, 1); //将多行数据转换为一行的向量
  26. traindata.push_back(srcimage); //将HOG特征向量加载到训练矩阵中
  27. trainlabel.push_back(i); //将标签输入到标签矩阵中
  28. }
  29. }
  30. //加载data_batch_2.bin的训练数据
  31. for ( int i = 0; i < 10; i++)
  32. {
  33. for ( int j = 0; j < trainnum; j++)
  34. {
  35. printf( "i=%d, j=%d\n", i, j);
  36. sprintf_s(ad, "cifar/batch2/%d/%d.tif", i, j);
  37. Mat srcimage = imread(ad, CV_LOAD_IMAGE_GRAYSCALE);
  38. hog->compute(srcimage, descriptors, Size(win_step, win_step), Size( 0, 0));
  39. Mat hogg(descriptors);
  40. srcimage = hogg.reshape( 1, 1); //Mat Mat::reshape(int cn, int rows = 0),cn表示通道数,为0则通道不变,rows表示矩阵行数
  41. traindata.push_back(srcimage);
  42. trainlabel.push_back(i);
  43. }
  44. }
  45. //加载data_batch_3.bin的训练数据
  46. for ( int i = 0; i < 10; i++)
  47. {
  48. for ( int j = 0; j < trainnum; j++)
  49. {
  50. printf( "i=%d, j=%d\n", i, j);
  51. sprintf_s(ad, "cifar/batch3/%d/%d.tif", i, j);
  52. Mat srcimage = imread(ad, CV_LOAD_IMAGE_GRAYSCALE);
  53. hog->compute(srcimage, descriptors, Size(win_step, win_step), Size( 0, 0));
  54. Mat hogg(descriptors);
  55. srcimage = hogg.reshape( 1, 1); //Mat Mat::reshape(int cn, int rows = 0),cn表示通道数,为0则通道不变,rows表示矩阵行数
  56. traindata.push_back(srcimage);
  57. trainlabel.push_back(i);
  58. }
  59. }
  60. //加载data_batch_4.bin的训练数据
  61. for ( int i = 0; i < 10; i++)
  62. {
  63. for ( int j = 0; j < trainnum; j++)
  64. {
  65. printf( "i=%d, j=%d\n", i, j);
  66. sprintf_s(ad, "cifar/batch4/%d/%d.tif", i, j);
  67. Mat srcimage = imread(ad, CV_LOAD_IMAGE_GRAYSCALE);
  68. hog->compute(srcimage, descriptors, Size(win_step, win_step), Size( 0, 0));
  69. Mat hogg(descriptors);
  70. srcimage = hogg.reshape( 1, 1); //Mat Mat::reshape(int cn, int rows = 0),cn表示通道数,为0则通道不变,rows表示矩阵行数
  71. traindata.push_back(srcimage);
  72. trainlabel.push_back(i);
  73. }
  74. }
  75. //加载data_batch_5.bin的训练数据
  76. for ( int i = 0; i < 10; i++)
  77. {
  78. for ( int j = 0; j < trainnum; j++)
  79. {
  80. printf( "i=%d, j=%d\n", i, j);
  81. sprintf_s(ad, "cifar/batch5/%d/%d.tif", i, j);
  82. Mat srcimage = imread(ad, CV_LOAD_IMAGE_GRAYSCALE);
  83. hog->compute(srcimage, descriptors, Size(win_step, win_step), Size( 0, 0));
  84. Mat hogg(descriptors);
  85. srcimage = hogg.reshape( 1, 1); //Mat Mat::reshape(int cn, int rows = 0),cn表示通道数,为0则通道不变,rows表示矩阵行数
  86. traindata.push_back(srcimage);
  87. trainlabel.push_back(i);
  88. }
  89. }
  90. traindata.convertTo(traindata, CV_32F); //将训练数据转换为浮点型数据
  91. model->train(traindata, ROW_SAMPLE, trainlabel); //输入训练数据和标签,开始训练分类模型
  92. //对test_batch.bin中的前800张图像进行预测分类
  93. for ( int i = 0; i < 10; i++)
  94. {
  95. for ( int j = 0; j < 800; j++)
  96. {
  97. testnum++;
  98. sprintf_s(ad, "cifar/test_batch/%d/%d.tif", i, j);
  99. Mat testdata = imread(ad, CV_LOAD_IMAGE_GRAYSCALE);
  100. hog->compute(testdata, descriptors, Size(win_step, win_step), Size( 0, 0)); //计算待分类图像的HOG特征
  101. Mat hogg(descriptors);
  102. testdata = hogg.reshape( 1, 1);
  103. testdata.convertTo(testdata, CV_32F);
  104. Mat result;
  105. int response = model->predict(testdata); //对Hog特征进行预测分类
  106. if (response == i) //如果预测的分类结果与标签一致,则认为分类成功
  107. {
  108. truenum++;
  109. }
  110. }
  111. }
  112. cout << "测试总数" << testnum << endl;
  113. cout << "正确分类数" << truenum << endl;
  114. cout << "准确率:" << ( float)truenum / testnum * 100 << "%" << endl;
  115. }

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

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

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


转载:https://blog.csdn.net/shandianfengfan/article/details/112505042
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场