OpenCV Human Detection SVM Training on Raspberry Pi with Qt

Next

OpenCV Human Detection SVM Training on Raspberry Pi with Qt


Here is a simple tutorial on how to train an SVM for human detection. We will be using OpenCV (http://opencv.org/). I will demonstrate the trainins and classification steps with Qt ( C++ ) linked against the OpenCV Library. The following code examples were run on my Raspberry Pi (http://www.raspberrypi.org/ ).

I owe ALOT of what I write below to this blog:

http://feelmare.blogspot.kr/2014/04/example-source-code-of-extract-hog.html

That was extremely helpful in getting the classifier working properly.

Here is some information on what SVMs are:

http://docs.opencv.org/doc/tutorials/ml/introduction_to_svm/introduction_to_svm.html

Basically, we will be taking a bunch of positive and negative images and training a classifier that will be used to later predict for us if a new image should be classified as a positive or negative type. Here a “positive” image is one that contains a Human, and a “negative” image is one that does not contain a human. OpenCV’s HoG related classes implement the “Histogram of Oriented Gradients ( HoG ) algorithm. That algorithm is described here:

http://en.wikipedia.org/wiki/Histogram_of_oriented_gradients

The first thing to do is create a collection of images, grayscale and 64x128 pixels, located in 2 directories named “pos” and “neg”, for each type respectively.

I will include source code below that trains the SVM that ships with OpenCV, called CvSVM.

Below is Main.cpp. It basically creates Positive and Negative XML type descriptors from the images in the pos/ and neg/ directories, trains the SVM, and tests the classifier.


#include <QCoreApplication>


/// OpenCV

#include <stdio.h>

#include <opencv2/opencv.hpp>


using namespace cv;

using namespace std;


/// HoG classes

#include "hogdescriptorextractor.h"

#include "hogtrainer.h"

#include "testsvm.h"


/// Qt includes

#include <QDebug>


int main(int argc, char *argv[])

{

QCoreApplication a(argc, argv);


// Create extractor

HoGDescriptorExtractor extractor;

qDebug() << "Creating Positive XML Descriptor for XML...";

Mat positiveFeatures = extractor.createXmlDescriptor("pos","pos",2400);

qDebug() << "Done, Positive feature size is: " << positiveFeatures.size;


qDebug() << "Creating Negative XML Descriptor for XML...";

Mat negativeFeatures = extractor.createXmlDescriptor("neg","neg",24000);

qDebug() << "Done, Positive feature size is: " << negativeFeatures.size;


// Train the SVM

qDebug() << "Creating SVM Trainer...";

HoGTrainer svmTrainer;

svmTrainer.trainOpenCvSVM();

qDebug() << "Done training SVM...";


/////////////////////////////////////////////////////////////////////////////////////


TestSVM tester1(1);

TestSVM tester2(2);

TestSVM tester3(3);


return a.exec();


}

Here is the extractor implementation:


#include "hogdescriptorextractor.h"


/// Debug

#include <QDebug>


HoGDescriptorExtractor::HoGDescriptorExtractor(QObject *parent) :

QObject(parent)

{

}


/// Give this a path to the images to describe with HoG algorithm

Mat HoGDescriptorExtractor::createXmlDescriptor(QString path, QString posOrNeg, int numberImagesToTrain) {


// The vectors to store the descriptors

vector< vector <float> > v_descriptorsValues;

vector< vector <Point> > v_locations;


// Get a directory iterator

QDir dir(path);

QFileInfoList fileList = dir.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot);

int count = 0;


qDebug() << "Looking in directory: " << path;


// Loop for the number of images that we have

foreach(QFileInfo files, fileList){


if (count >= numberImagesToTrain) {

break;

};


// Get the image file

QString imageName = files.filePath();

qDebug() << "Image is: " << imageName;

if ( imageName.contains("png") ) {


//qDebug() << "Opening file: " << imageName;


// Read image file

Mat img, img_gray;

img = imread(imageName.toStdString().c_str());


// Ensure 64x128

resize(img, img, Size(64,128) ); //Size(64,48) ); //Size(32*2,16*2)); //Size(80,72) );


// Grayscale

cvtColor(img, img_gray, CV_RGB2GRAY);


/*

HOGDescriptor(Size win_size=Size(64, 128), Size block_size=Size(16, 16),

Size block_stride=Size(8, 8), Size cell_size=Size(8, 8),

int nbins=9, double win_sigma=DEFAULT_WIN_SIGMA,

double threshold_L2hys=0.2, bool gamma_correction=true,

int nlevels=DEFAULT_NLEVELS);

*/


//win_size=Size(64, 128) block_size=Size(16, 16) block_stride=Size(8, 8) cell_size=Size(8, 8) nbins=9


// Extract features

HOGDescriptor d( Size(64,128), Size(16,16), Size(8,8), Size(8,8), 9);

vector< float> descriptorsValues;

vector< Point> locations;

d.compute( img_gray, descriptorsValues, Size(0,0), Size(0,0), locations);


// Store the desctriptors

v_descriptorsValues.push_back( descriptorsValues );

v_locations.push_back( locations );


// Show image

imshow("Original image", img);

waitKey(10);


// Increment counter

count++;


}


qDebug() << count << fileList.size();


}


// Destroy the original image window

destroyWindow("Original image");


qDebug() << "Created this many desctipors: " << v_descriptorsValues.size();


// Figure out the name

QString saveXmlName;

if ( posOrNeg == "pos" ) {

saveXmlName = "Positive.xml";

}

else {

saveXmlName = "Negative.xml";

}


// Save to xml

FileStorage hogXml(saveXmlName.toStdString().c_str(), FileStorage::WRITE);


// 2d vector to Mat

int row = v_descriptorsValues.size(), col=v_descriptorsValues[0].size();

printf("col=%d, row=%d\n", row, col);

Mat result(row,col,CV_32F);


qDebug() << "Mem copy prior to saving to XML...";


// Save Mat to XML

for( int i=0; i < row; ++i ) {

memcpy( &(result.data[col * i * sizeof(float) ]) ,v_descriptorsValues[i].data(),col*sizeof(float));

}


qDebug() << "Writing XML now...";


// Write xml


if ( posOrNeg == "pos" ) {

saveXmlName = "Positive";

}

else {

saveXmlName = "Negative";

}

write(hogXml, "Descriptor_of_images", result);

//write(hogXml, "Descriptor", v_descriptorsValues );

//write(hogXml, "locations", v_locations );


qDebug() << "Done writing XML...";


// Release it

hogXml.release();


// Return the Mat

return result;


}


/// Count the number of files in a directory

int HoGDescriptorExtractor::countFiles(QString path) {


int suma = 0;

QDir dir(path);

dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot);

if(!dir.exists()) {

return 1;

}

QFileInfoList sList = dir.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot);

foreach(QFileInfo ruta, sList){

if(ruta.isDir()){

suma += countFiles(ruta.path() + "/" + ruta.completeBaseName()+"/");

}

suma++;

}

return suma;


}

Here is the trainer class:


#include "hogtrainer.h"


HoGTrainer::HoGTrainer(QObject *parent) :

QObject(parent)

{

}


/// Train the SVM

void HoGTrainer::trainOpenCvSVM() {


// Read Hog feature from XML file

qDebug() << "Reading XML for pos/neg...";


// Create xml to read

qDebug() << "Reading XML file for positive features...";

FileStorage read_PositiveXml("Positive.xml", FileStorage::READ);

qDebug() << "Reading XML file for negative features...";

FileStorage read_NegativeXml("Negative.xml", FileStorage::READ);


// Positive Mat

Mat pMat;

read_PositiveXml["Descriptor_of_images"] >> pMat;

// Read Row, Cols

int pRow,pCol;

pRow = pMat.rows; pCol = pMat.cols;


// Negative Mat

Mat nMat;

read_NegativeXml["Descriptor_of_images"] >> nMat;

// Read Row, Cols

int nRow,nCol;

nRow = nMat.rows; nCol = nMat.cols;


// Rows, Cols printf

printf(" pRow=%d pCol=%d, nRow=%d nCol=%d\n", pRow, pCol, nRow, nCol );

read_PositiveXml.release();

read_NegativeXml.release();


// Make training data for SVM

qDebug() << "Making training data for SVM...";


// Descriptor data set

Mat PN_Descriptor_mtx( pRow + nRow, pCol, CV_32FC1 ); // in here pCol and nCol is descriptor number, so two value must be same;

memcpy(PN_Descriptor_mtx.data, pMat.data, sizeof(float) * pMat.cols * pMat.rows );

int startP = sizeof(float) * pMat.cols * pMat.rows;

memcpy(&(PN_Descriptor_mtx.data[ startP ]), nMat.data, sizeof(float) * nMat.cols * nMat.rows );


//PN_Descriptor_mtx.convertTo(PN_Descriptor_mtx, CV_64FC1);


// Data labeling

Mat labels( pRow + nRow, 1, CV_32FC1, Scalar(-1.0) );

labels.rowRange( 0, pRow ) = Scalar( 1.0 );


// Set svm parameters

qDebug() << "SVM Parameter setting...";

CvSVM svm;

CvSVMParams params;

params.svm_type = CvSVM::C_SVC;

params.kernel_type = CvSVM::LINEAR;

params.term_crit = cvTermCriteria( CV_TERMCRIT_ITER, 10000, 1e-6 );


// Training

qDebug() << "Training SVM Now...";

svm.train(PN_Descriptor_mtx, labels, Mat(), Mat(), params);


// Trained data save

qDebug() << "Saving trained data...";

svm.save( "trainedSVM.xml" );


}

Here is the linear SVM extractor helper class that helps extract the “primal” descriptor type for the classifier:


#include "linearsvm.h"


LinearSVM::LinearSVM() {


//qDebug() << "Creating SVM and loading trained data...";


load("trainedSVM.xml");


}


std::vector<float> LinearSVM::getPrimalForm() const

{

std::vector<float> support_vector;


int sv_count = get_support_vector_count();


const CvSVMDecisionFunc* df = decision_func;

const double* alphas = df[0].alpha;

double rho = df[0].rho;

int var_count = get_var_count();


support_vector.resize(var_count, 0);


for (unsigned int r = 0; r < (unsigned)sv_count; r++)

{

float myalpha = alphas[r];

const float* v = get_support_vector(r);

for (int j = 0; j < var_count; j++,v++)

{

support_vector[j] += (-myalpha) * (*v);

}

}


support_vector.push_back(rho);


return support_vector;

}

And finally here is the tester class:


#include "testsvm.h"


TestSVM::TestSVM(int type)

{


// Load trained SVM xml data

CvSVM svm;

svm.load("trainedSVM.xml");


//count variable

int nnn = 0, ppp = 0;


// Get a directory iterator

QDir dir;

if ( type == 1 ) {

qDebug() << "\n\n Testing images in Test Directory!";

dir = QDir("test");

}

else if ( type == 2 ) {

qDebug() << "\n\n Testing images in Pos Directory!";

dir = QDir("pos");

}

else {

qDebug() << "\n\n Testing images in Neg Directory!";

dir = QDir("neg");

}

QFileInfoList fileList = dir.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot);


// Loop for the number of images that we have

foreach(QFileInfo files, fileList){


// Read image file

Mat img, img_gray;

img = imread(files.filePath().toStdString().c_str());


// Gray

cvtColor(img, img_gray, CV_RGB2GRAY);


// Extract HogFeature

HOGDescriptor d( Size(64,128), Size(16,16), Size(8,8), Size(8,8), 9);

vector< float> descriptorsValues;

vector< Point> locations;

d.compute( img_gray, descriptorsValues, Size(0,0), Size(0,0), locations);

// Vector to Mat

Mat fm = Mat(descriptorsValues);


// Classification whether data is positive or negative

int result = svm.predict(fm);

//printf("%s - > %d\n", (files.filePath().toStdString().c_str()), result);


//qDebug() << (files.filePath().toStdString().c_str()) << result;


//qDebug() << "Descriptor size is: " << descriptorsValues.size();


// Count data

if(result == 1)

ppp++;

else

nnn++;


// // Show image

// imshow("origin", img);


// waitKey(1);


///////////////

// DetectMultiScale


// Descriptor

HOGDescriptor hog;


// Primal for of cvsvm descriptor

LinearSVM getPrimals;

vector<float> primalVector = getPrimals.getPrimalForm();


// Set hog detection window size

// win_size=Size(64, 128) block_size=Size(16, 16) block_stride=Size(8, 8) cell_size=Size(8, 8) nbins=9

hog.winSize = Size(64, 128);

hog.blockSize = Size(16,16);

hog.blockStride = Size(8,8);

hog.cellSize = Size(8,8);

hog.nbins = 9;


//qDebug() << "Detector vector size is: " << primalVector.size();


// Set the SVM Detector - custom trained HoG Detector

hog.setSVMDetector(primalVector);


// Look for Humans

vector<Rect> found,found_filtered;

int groupThreshold = 2;

Size padding(Size(8, 8));

Size winStride(Size(8, 8));

double hitThreshold = 0.; // tolerance


// Detect in multi scale

hog.detectMultiScale(img_gray, found, hitThreshold, winStride, padding, 1.05, groupThreshold);


if ( found.size() > 0 ) {

//qDebug() << "DetectMultiScale Found this many people: " << found.size();

}


// Draw boxes

size_t i, j;

for (i = 0; i < found.size(); i++) {

Rect r = found[i];

for (j=0; j<found.size(); j++) {

if (j!=i && (r & found[j])==r) {

break;

}

}

if (j==found.size()) {

found_filtered.push_back(r);

}

}

for (i = 0; i < found_filtered.size(); i++) {


Rect r = found_filtered[i];

r.x += cvRound(r.width*0.1);

r.width = cvRound(r.width*0.8);

r.y += cvRound(r.height*0.06);

r.height = cvRound(r.height*0.9);

rectangle(img, r.tl(), r.br(), cv::Scalar(0,0,255), 2);


// Show image

imshow("origin", img);


waitKey(1);


}


}


if ( type == 1 || type == 2 ) {

qDebug() << "positive/negative = " << ppp << "/" << nnn << " Percent Correct: " << float(ppp)/float(fileList.count())*100.0f << "%";

}

else {

qDebug() << "positive/negative = " << ppp << "/" << nnn << " Percent Correct: " << float(nnn)/float(fileList.count())*100.0f << "%";

}


// Done

qDebug() << "\nAll Done!";


}

Hope this helps someone, Happy Coding!!


ClassyBits 2016