Training images using SVM on OpenCV

2020-07-18 03:53发布

问题:

I am trying to do classification with images (next step I'll classify based on features but now just want to try whether I am doing it right or not)

here is my code.

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/ml/ml.hpp>

using namespace cv;
using namespace std;



int main(){


    Mat image[2];
    image[0]= imread("image.jpg",0);

    image[1]= imread("wrongimage.jpg",0);

    Mat rotated = imread("image.jpg",0);


    image[0] = image[0].reshape(0, 1); //SINGLE LINE
    image[1] = image[1].reshape(0, 1); //SINGLE LINE


    //  image[0].convertTo(image[0], CV_32FC1); //CONVERT TO 32FC1
    //  image[1].convertTo(image[1], CV_32FC1); //CONVERT TO 32FC1


    Mat new_image(2,1,CV_32FC1,image); //CONVERT TO 32FC1



    float labels[2] = {1.0, -1.0};
    Mat labelsmat(2,1,CV_32FC1,labels); //correct labels 1

    labelsmat.convertTo(labelsmat, CV_32FC1);



    CvSVMParams params;
    params.svm_type = CvSVM::C_SVC;
    params.kernel_type = CvSVM::LINEAR;
    params.gamma = 3;
    params.degree = 3;
    CvSVM svm;
    svm.train(new_image, labelsmat, Mat(),Mat(),params);

    //    svm.train(training_mat2, labelsmat, Mat(),Mat(),params);

    // svm.train(training_mat2, labelsmat, Mat(), Mat(), params);
    svm.save("svm.xml"); // saving


    svm.load("svm.xml"); // loading


    rotated = rotated.reshape(0,1);
    rotated.convertTo(rotated, CV_32FC1);


     svm.predict(rotated);
}

since training images with opencv svm is lack of documented I tried to manage something by reading using OpenCV and SVM with images and http://docs.opencv.org/doc/tutorials/ml/introduction_to_svm/introduction_to_svm.html

somehow I manage to train my images but I stron I this train xml file is not correct because I didn't point out which image is correct (1) or false (-1)

and also when I try to predict with the image I've trained to svm is gives me error

OpenCV Error: Sizes of input arguments do not match (The sample size is different from what has been used for training) in cvPreparePredictData, file /tmp/opencv-DXLLi8/opencv-2.4.9/modules/ml/src/inner_functions.cpp, line 1114 libc++abi.dylib: terminating with uncaught exception of type cv::Exception: /tmp/opencv-DXLLi8/opencv-2.4.9/modules/ml/src/inner_functions.cpp:1114: error: (-209) The sample size is different from what has been used for training in function cvPreparePredictData

also here xml generated by SVM.

<?xml version="1.0"?>
<opencv_storage>
<my_svm type_id="opencv-ml-svm">
  <svm_type>C_SVC</svm_type>
  <kernel><type>LINEAR</type></kernel>
  <C>1.</C>
  <term_criteria><epsilon>1.1920928955078125e-07</epsilon>
    <iterations>1000</iterations></term_criteria>
  <var_all>1</var_all>
  <var_count>1</var_count>
  <class_count>2</class_count>
  <class_labels type_id="opencv-matrix">
    <rows>1</rows>
    <cols>2</cols>
    <dt>i</dt>
    <data>
      -1 1</data></class_labels>
  <sv_total>1</sv_total>
  <support_vectors>
    <_>
      -1.56709105e-02</_></support_vectors>
  <decision_functions>
    <_>
      <sv_count>1</sv_count>
      <rho>-1.</rho>
      <alpha>
        1.</alpha>
      <index>
        0</index></_></decision_functions></my_svm>
</opencv_storage>

UPDATE

I've changed my code with the suggestions by guneykayim but now I'm getting EXC_BAD_ACCESS (code=1 address=...) error. My updated code is below.

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/ml/ml.hpp>

using namespace cv;
using namespace std;



int main(){


    Mat image[2];
    image[0]= imread("image.jpg",0);

    image[1]= imread("wrongimage.jpg",0);

    Mat rotated = imread("image.jpg",0);


   image[0] = image[0].reshape(0, 1); //SINGLE LINE
   image[1] = image[1].reshape(0, 1); //SINGLE LINE

 //   int size = sizeof(image)/sizeof(Mat);
    //  image[0].convertTo(image[0], CV_32FC1); //CONVERT TO 32FC1
    //  image[1].convertTo(image[1], CV_32FC1); //CONVERT TO 32FC1

    Mat new_image(2,341318,CV_32FC1,image); //CONVERT TO 32FC1

    float labels[2] = {1.0, -1.0};
    Mat labelsmat(2,1,CV_32FC1,labels); //correct labels 1

    labelsmat.convertTo(labelsmat, CV_32FC1);


    cout<<image[0].size()<<endl;
    cout<<new_image.size()<<endl;



    CvSVMParams params;
    params.svm_type = CvSVM::C_SVC;
    params.kernel_type = CvSVM::LINEAR;
    params.gamma = 3;
    params.degree = 3;
    CvSVM svm;
    svm.train_auto(new_image, labelsmat,Mat(),Mat(),params);

    //  svm.train_(new_image, labelsmat, Mat(),Mat(),params);
    //    svm.train(training_mat2, labelsmat, Mat(),Mat(),params);

    // svm.train(training_mat2, labelsmat, Mat(), Mat(), params);
    svm.save("svm.xml"); // saving


    svm.load("svm.xml"); // loading


    rotated = rotated.reshape(0,1);
    rotated.convertTo(rotated, CV_32FC1);

    cout<<svm.predict(rotated)<<endl;

}

my image size is : [170569 x 1] and new_image size is [341318 x 2]

回答1:

There are couple things that you are doing wrong or you are not aware that you are doing.

  1. You say you didn't point out which image is correct (1) or false (-1), but you did with the following line: float labels[2] = {1.0, -1.0};
  2. You are creating your training set wrong. Suppose your images are 640x480 sized. Then when you reshape them, they will be 307200 sized vectors, which is fine. So, your training set, which you call new_image, should be 2x307200 sized and each row should represent an image, but instead you are creating it 2x1 sized training set. That's why when you try to predict you get an error which says Sizes of input arguments do not match. You trained SVM with 2x1 sized training set and tried to predict with 1x307200 vector.

In addition to these, you should not set SVM parameters by using magic numbers, you need to optimize them with cross validation. In this toy example of course you can't do parameter optimization, what I'm trying to say is that you should be aware that setting SVM parameters is a very crucial task.

There are several question that I've answered about SVM, you can go check them from my profile.

Hope this helps.

UPDATE

You can check if you are creatnig new_image correct by following code:

Mat image[2];
image[0]= imread("image.jpg",0);
image[1]= imread("wrongimage.jpg",0);

// dont reshape them for debugging purposes...
//image[0] = image[0].reshape(0, 1); //SINGLE LINE
//image[1] = image[1].reshape(0, 1); //SINGLE LINE

// I assume that images are 640x480, change the value accordig to the image sizes, or directly use values in the size parameter
Mat new_image(1280, 480, CV_32FC1, image); //CONVERT TO 32FC1

// visualize the image see if the previous line of code does its job correctly.
imshow("new_image", new_image);

UPDATE 2

You need to create single cv::Mat object from your cv::Mat array. The following line, apparently, can't do it properly.

Mat new_image(2,341318,CV_32FC1,image); 

Find a proper way to do it, currently I don't have OpenCV environment installed, you can basicly ask another question about it.

Remember, your new_image variable should be [image_count * (image_width * image_height)] sized, and each row should represent image vector as 1 * (image_width * image_height) sized.