OpenCV SVM Gives Strange Prediction Results - c++

I am quite new to both OpenCV and Support Vector Machines. I want to use SVM to train a dataset with two labels and then predict the label of a given set. My current set contains about 600 rows with equal class distributions (300 for 1 and 300 for -1) containing 34 columns.
This is my current code for setting up OpenCV's SVM. I am using OpenCV 3.0.0
// trainingData is an int array with size 600x34
// labels is an int array with size 600, they're the labels corresponding to the trainingData rows
cv::Mat trainingDataMat(600, 34, CV_32FC1, trainingData);
cv::Mat labelsMat(600, 1, CV_32SC1, labels);
cv::Ptr<cv::ml::SVM> svm = cv::ml::SVM::create();
cv::Ptr<cv::ml::TrainData> tempData = cv::ml::TrainData::create(trainingDataMat, cv::ml::ROW_SAMPLE, labelsMat);
svm->setType(cv::ml::SVM::C_SVC);
svm->setKernel(cv::ml::SVM::RBF);
svm->setTermCriteria(cv::TermCriteria(cv::TermCriteria::MAX_ITER, 100, 0.001));
// Assign the SVM parameters to the most accurate result
svm->trainAuto(tempData);
// Train the SVM
svm->train(trainingDataMat, cv::ml::ROW_SAMPLE, labelsMat);
// predictRow contains a row of data with 34 columns to predict against the SVM Model
cv::Mat sampleMat(1, 34, CV_32FC1, predictRow);
// Prediction
float response = svm->predict(sampleMat);
std::cout << response << std::endl;
The SVM training seems to work fine. But when I predict a row, response always shows "1" no matter how the input looks like. Even when I try to predict using training rows with "-1" label I used earlier, the response is still "1".
I tried to increase the max iteration parameter for the termination criteria to a large number. The training process takes more time but the results are still the same.
I tried to use the libsvm library (https://www.csie.ntu.edu.tw/~cjlin/libsvm/) to see if the same behavior occurs. Interestingly, it worked well. I use the Windows "svm-train.exe" and "svm-predict.exe" command to validate it and the responses are accurate.
I even tried to run the executables on the OpenCV program by using some dirty system calls and file I/O. The resulting responses using the training rows are correct.
I suspect there is something wrong with the my SVM parameters. Even by using train_auto function, the SVM model still shows strange behaviour. I wonder if anyone can help me setting the SVM parameters correctly in OpenCV 3.0?

Related

Opencv 3.0 SVM train classification issues

Im new in openCV SVM. Im running in Xcode 7.0, openCV 3.0, Below is my code
MatMat labels(0,1,CV_32FC1);
//Mat labels(0,1,CV_32S); //I also try this when i saw some posting, But error too.
...
Mat samples_32f; samples.convertTo(samples_32f, CV_32F);
//Mat samples_32f; samples.convertTo(samples_32f, CV_32FC1); //tried!
Ptr<SVM> classifier = SVM::create();
classifier->SVM::train(samples_32f, 0, labels); <- Here the Error
The OpenCV Error: Bad argument (in the case of classification problem the responses must be categorical; either specify varType when creating TrainData, or pass integer responses) in train.
When I search around some solutions, the error message seem was came from labels that define not integer value. So i had try to changed to Mat labels(0,1,CV_32S), but the issues error still the same.
So i have no idea what going wrong with the code..is anyone can help?
The errors is because labels does not content any value as defined as 0 rows with 1 cols. Therefore, it is correct to make sure labels has holding numbers of rows records for SVM training.
My Solutions:
Mat labels(0,1,CV_32S);
/*
for loop to use pushback added value into lables
{...}
*/
/*
or
define another Mat labeled(labels.rows, 1, CV_32S); after the for loop
and use it in the SVM.train
*/
Mat samples_32f; samples.convertTo(samples_32f, CV_32F);
Ptr<SVM> classifier = SVM::create();
classifier->SVM::train(samples_32f, 0, labels); <-change the labels names if you define new labels after the for loop.
Thanks, that's what i can share.

OpenCV Clustering Bag Of Words K-Means

Using SIFT Detector and Extractor, with FlannBased Matcher, and the Dictionary set up for the BOWKMeansTrainer like this:
TermCriteria termCrit(CV_TERMCRIT_ITER, 100, 0.001);
int dictionarySize = 15; // -- Same as number of images given in
int retries = 1;
int flags = KMEANS_PP_CENTERS;
BOWKMeansTrainer trainBowTrainer(dictionarySize, termCrit, retries, flags);
the array size of the Clustered Extracted Keypoints will come out as [128 x 15].
Then when using the BOWImgDescriptorExtractor as the Extractor on a different set of 15 Images, with the previously extracted array as its Vocabulary, the array comes out at [15 x 15].
Why?
I can't find that much on how this all actually works, rather just where to put it and what values to give.
The result should always be [n x 15] if you have n images and k=15.
But in the first run, you looked at the vocabular, not the feature representation of the first images. The 128 you are seeing there is the SIFT dimensionality; these are 15 "typical" SIFT vectors; they are not descriptions of your images.
You need to read up on the BoW model, and why the outcome should ways be a vector of length k (potentially sparse, i.e. with many 0s) for each image. I have the impression you expect this approach to produce one 128-dimensional feature vector for each image. Also, k=15 is probably too small; and the training data set is too small as well.

OpenCV Face Recognition strange result

I have been using OpenCV's SVM and RF for a multi-class face recognition problem with 11 classes and only 5 images per class. I used two kinds of features - initially a toy intensity image feature (just each image resized to 32x32 grayscale) and then the second feature was simply another toy feature using Tan Triggs preprocessing(link). Here is the feature code:
void Feature::makeFeature(cv::Mat &image, cv::Mat &result)
{
cv::resize( image, image, cv::Size(32, 32), 0, 0, cv::INTER_CUBIC );
cv::equalizeHist(image, image);
// Images must be aligned - Only pitch executed, yaw and roll assumed negligible
algmt->getAlignedImage( image, image ); // image alignment
// tan triggs
{
tan_triggs_preprocessing(image, result);
result = result.reshape(0, 1); // make a single row vector, needed for the training samples matrix
}
// if plain intensity
{
// image.copyTo(result);
// result.convertTo(result, CV_32F, 1.0f/255.0f);
// result = result.reshape(0, 1); // make a single row vector, needed for the training samples matrix
}
}
Where the tan_triggs_preprocessing function is the same as the Tan Triggs preprocessing function given in the link. I added one step - i normalized the result between 0 and 1.
The results on test for both were not very good, as expected, but then I made a silly mistake and discovered something strange: When I accidentally gave the training directory as input for both training and test, I get 100% results on the plain intensity feature, but the Tan Triggs feature gives the following as result:
SVM Training Complete
Total number of correct: 51 and accuracy: 92.7273
RF Training Complete
Total number of correct: 53 and accuracy: 96.3636
I do know however much you overfit the result should be perfect when the training set is input to test. Everything else is standard, both SVM and RF are standard as in the OpenCV examples. Besides I get 100% for plain intensity feature so of course I am mucking something up here when using Tan Triggs. Anyone has any idea what mistake I am making?
I have used other complex features like LTPs and LQPs without issue, but this preprocessing method is something I want to use. I use the Jain-Learned Miller congealing algorithm for alignment as I assume frontals for face recognition, no pose correction.

Hu moments and SVM does not work

I have come across one problem when trying to train data with SVM.
I get some different regions (set of connected pixels) from face images, and regions from eyes are very similar, so I want to use Hu moments for shape description and SVM for training.
But SVM does not work properly, method svm.predict evaluates afterwards everything as non-eye, moreover the same regions which were labeled and used in traning phase as eye, are evaluated as non-eye.
Feature data consists only of 7 Hu moments. I will post here some samples of source code in a moment, thanks in advance :)
Additional info:
input image:
http://i.stack.imgur.com/GyLO0.png
Setting up basic svm for 1 image:
int image_regions = 10;
Mat training_mat(image_regions ,7,CV_32FC1); // 7 hu moments
Mat labels(image_regions ,1,CV_32FC1); // for labels 1 (eye) and -1 (non eye)
// computing hu moments
Moments moments2=moments(croppedImage,false);
double hu[7];
HuMoments(moments2,hu);
// putting them into svm traning mat
for (int k=0;k<huCounter;k++)
training_mat.at<float>(counter,k) = hu[k]; // counter is current number of region
if (isEye(...))
{
labels.at<float>(counter,0)=1.0;
}
else
{
labels.at<float>(counter,0)=-1.0;
}
//I use the following:
CvSVM svm;
CvSVMParams params;
params.svm_type = CvSVM::C_SVC;
params.kernel_type = CvSVM::LINEAR;
params.term_crit = cvTermCriteria(CV_TERMCRIT_ITER, 1000, 1e-6);
// ... do the above mentioned phase, and then:
svm.train(training_mat, labels, Mat(), Mat(), params);
I hope the following suggestions can help you…..
The simplest task is to use a clustering algorithm and try to cluster the data into two classes. If an algorithm like ‘k-means’ can do the job why make things complex by using SVM and Neural Nets. I suggest you use this technique because your feature vector dimension is of a very small size (7 Hu Moments) as well as your number of samples.
Perform feature Normalization (specified in point 4) to make sure the values fall in a limited range.
Check out “is your data really separable?” As your data is small, take a few samples from positive images and a few samples from negative images and plot the feature vectors. If you can visually see the difference surely any learning algorithm can do the job for you. As I said earlier simple tricks can do better than complex math.
Only if you then decide to use SVM you should know the following:
• As I can see from your code you are using a Linear SVM, may be your data is non-separable by a linear kernel. Try using some polynomial kernel or other kernels. There is one option bool CvSVM::train_auto in openCV just have a look.
• Try to check whether the feature vector values you are getting are proper values or not (make sure that they are not some garbage values).
• Also you can perform feature normalization “ZERO MEAN and UNIT VARIENCE” before you use it for training.
• Most importantly increase the number of images for training, both positively and negatively labeled.
• Last but not least SVM is not magic, at the end of the day it is just drawing a line between two sets of points. So don’t expect it to classify anything you give it as input.
If nothing works “Just improve your feature extraction technique”

OpenCV SVM throwing exception on train, "Bad argument (There is only a single class)"

I'm stuck on this one.
I am trying to do some object classification through OpenCV feature 2d framework, but am running into troubles on training my SVM.
I am able to extract vocabularies and cluster them using BowKMeansTrainer, but after I extract features from training data to add to the trainer and run SVM.train method, I get the following exception.
OpenCV Error: Bad argument (There is only a single class) in cvPreprocessCategoricalResponses, file /home/tbu/prog/OpenCV-2.4.2/modules/ml/src /inner_functions.cpp, line 729
terminate called after throwing an instance of 'cv::Exception'
what(): /home/tbuchy/prog/OpenCV-2.4.2/modules/ml/src/inner_functions.cpp:729: error: (-5) There is only a single class in function cvPreprocessCategoricalResponses
I have tried modifying dictionary size, using different trainers, ensuring my matrix types are correct (to the best of my ability, still new to opencv).
Has any seen this error or have any insight on how to fix it?
My code looks like this:
trainingPaths = getFilePaths();
extractTrainingVocab(trainingPaths);
cout<<"Clustering..."<<endl;
Mat dictionary = bowTrainer.cluster();
bowDE.setVocabulary(dictionary);
Mat trainingData(0, dictionarySize, CV_32FC1);
Mat labels(0, 1, CV_32FC1);
extractBOWDescriptor(trainingPaths, trainingData, labels);
//making the classifier
CvSVM classifier;
CvSVMParams params;
params.svm_type = CvSVM::C_SVC;
params.kernel_type = CvSVM::LINEAR;
params.term_crit = cvTermCriteria(CV_TERMCRIT_ITER, 100, 1e-6);
classifier.train(trainingData, labels, Mat(), Mat(), params);
Based on the error, it looks like your labels only contains one category of data. That is, all of the features in your trainingData have the same label.
For example, say you're trying to use the SVM to determine whether an image contains a cat or not. If every entry in the labels is the same, then either...
all your training images are labeled as "yes this is a cat"
or, all your training images are labeled as "no, this is not a cat."
SVMs try to separate two (or sometimes more) classes of data, so the SVM library complains if you only only provide one class of data.
To see if this is the problem, I recommend adding a print statement to check whether labels only contains one category. Here's some code to do this:
//check: are the printouts all the same?
for(int i=0; i<labels.rows; i++)
for(int j=0; j<labels.cols; j++)
printf("labels(%d, %d) = %f \n", i, j, labels.at<float>(i,j));
Once your extractBOWDescriptor() loads data into labels, I'm assuming that labels is of size (trainingData.rows, trainingData.cols). If not, this could be a problem.