OpenCV 在很久以前就集成了SVM的功能,现在OpenCV升级到了3.0和3.1了,很多人都不习惯了怎么调用OpenCV中的SVM功能了。在之前OpenCV的SVM调用一直有个案例:首先,给定几组训练数据,并且给了label所对应的值。然后经过训练之后,对图像的各个位置进行预测是1还是-1。如果是1的话,用绿色来表示,如果是-1呢,用蓝色表示。并且还画出几个支持向量。
下面给了OpenCV2.0 的SVM代码(勿喷,直接从OpenCV官方网址复制下来的)
#include
#include
#include
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(j, i) = green;
else if (response == -1)
image.at(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);
}
下面给出了正确的,记住是正确的代码:
#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 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(i, j) = green;
else if (respose == -1)
image.at(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);
。当然,这个只是一处地方,其他的请自己阅读。
虽然,大家的目标很明确:导入训练数据+label –> Mat, 训练,预测,显示这些目标。但是仍然避免不了代码错误。下面就是我遇到的代码的问题。
其实,在本代码中出现了一种情况就是数据类型,下面均以标签为例:
这个是OpenCV3.0、OpenCV3.1正确的代码:
int labels[4] = { 1, -1, -1, -1 };
cv::Mat labelsMat(4, 1, CV_32SC1, labels);
下面是OpenCV 2.X 正确代码
float labels[4] = {1.0, -1.0, -1.0, -1.0};
Mat labelsMat(3, 1, CV_32FC1, labels);
如果将OpenCV 2.X的代码换到OpenCV 3.1、OpenCV3.0代码会有什么样子的结果呢?
这个很熟悉吧所以,如果傻乎乎的换,这个是行不通的。
下面对于label
和labelMat
按照不同的情况进行分析。
首先,这个labelMat
和trainingMat
到底能取哪几种类型?
下面,请看OpenCV3.1 源码中的一个部分:
void setData(InputArray _samples, int _layout, InputArray _responses,
InputArray _varIdx, InputArray _sampleIdx, InputArray _sampleWeights,
InputArray _varType, InputArray _missing)
{
samples = _samples.getMat();
responses = _responses.getMat();
CV_Assert( samples.type() == CV_32F || samples.type() == CV_32S );
if( !responses.empty() )
{
CV_Assert( responses.type() == CV_32F || responses.type() == CV_32S );
}
}
为了方便起见,将这个函数的代码的其他部分删除了。首先解释一下:samples
就是训练的数据。response
就是标签。通过上面,我们知道再来用Mat的时候,只能用CV_32F
和CV_32S
。所以说,如果这些矩阵不能写什么CV_8UC1
之类的了,这个是错误的。
假设我们写其他的情况,比如
这个是正确的。
这个会出现错误,这个我还没有分析出来是什么原因。错误的截图如下:。
下面的错误均表示为截图所示的错误。
同样的,这个是错误。当我们用Imagewatch插件去观察labelsMat的值的时候发现这个labelsMat的值为。好吧,这个很明显了。
这个是可以运行的,但是结果肯定是错误的。同样的,值不对。