Saturday, 26 October 2013

openCV : Construct the images suitable for SVM

  The example from document show us some basic about the SVM, but it don't tell us how to incorporate with image. I had a hard time to figure out how to do it correctly until I study the boo--mastering openCV with practical computer vision projects.

  In a nutshell, the data of SVM needed are training data and labels.

Training data

rows :  corresponding to specific samples(ex : images)

columns :  corresponding to the features(ex : pixels)

In the case of image, all of the pixels values would be considered as features.


cv::Mat trainingData;
for(size_t i = 0; i != numTrainData; ++i){
  std::stringstream name;
  name<<leaf<<i<<".png";
  cv::Mat samples = cv::imread(name.str());
  //reshape it to 1 channel and 1 row
  samples.reshape(1, 1);
  trainingData.push_back(samples);
}
//convert all of the image to floating point values
trainingData.convertTo(trainingData, CV_32F); 

That is, if the original samples are look like a 4X4, after transform, it would become a 1X16 matrix.




Although this is an example for image, but it could apply to everything, the most important part is transform the samples into  a 1 row and 1 channel cv::Mat, put all of the features(data) of the samples into the column.

The function of reshape is very convenient, but it could not tackle with non-continuous image, we have to transform them into a suitable cv::Mat by ourselves.Here is a generic transform function design for images.


template<typename T = uchar, typename U = float>
void transform_to_svm_training_data(cv::Mat &input)
{
    if(input.isContinuous()){
        input = input.reshape(1, 1);
        input.convertTo(input, cv::DataType<U>().depth);        
        return;
    }

    cv::Mat output(1, input.total() * input.channels(), 
                   cv::DataType<U>().depth);
    auto output_ptr = output.ptr<U>(0);
    OCV::for_each_channels<T>(input, [&](T a)
    {
        *output_ptr = a;
        ++output_ptr;
    });

    input = output;

}

  There are one more thing to keep in mind, all of the samples of the training data should have same numbers of features, all of the features should have the same type.The codes of OCV::for_each_channels can found on github, the principal of it could refer to opencv and generic programming.

 Labels

rows : same as the number of rows of the training data, the samples and the rows of the labels are mapping to each other.

columns : specify what are the samples belong to? For example if you were classifying leafs and non- leafts, you would need to specify which one is leaf and which one is non-leaf in the training data(ex : 1 == leaf; -1 == non-leaf).

Examples


cv::Mat trainingImages;
std::vector<int> trainingLabels;
for(int i = 0; i != numPlates; ++i)
{
   std::stringstream ss;
   ss << path_Plates << i << ".jpg";
   cv::Mat img = cv::imread(ss.str(), 0);   
   transform_to_svm_training_data(img);
   //every labels are associated to the training data
   trainingImages.push_back(img);
   trainingLabels.emplace_back(1);
}

for(int i = 0; i != numNoPlates; ++i)
{
   std::stringstream ss;
   ss << path_NoPlates << i << ".jpg";
   cv::Mat img = cv::imread(ss.str(), 0);
   transform_to_svm_training_data(img);
   trainingImages.push_back(img);
   trainingLabels.emplace_back(0);
}

cv::Mat classes;
cv::Mat trainingData = trainingImages;
cv::Mat(trainingLabels).copyTo(classes);
trainingData.convertTo(trainingData, CV_32FC1);
cv::FileStorage fs("SVM.xml", cv::FileStorage::WRITE);
fs << "TrainingData" << trainingData;
fs << "classes" << classes;