Created
March 10, 2013 21:11
-
-
Save joshuajnoble/5130437 to your computer and use it in GitHub Desktop.
Cinder based Histogram of Oriented Gradients creation using SVMLight (svmlight.joachims.org) requires lots of images to work properly
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "cinder/app/AppBasic.h" | |
#include "cinder/gl/gl.h" | |
#include "cinder/gl/Texture.h" | |
#include "cinder/Capture.h" | |
#include <boost/filesystem/operations.hpp> | |
#include <boost/filesystem/fstream.hpp> | |
#include <boost/foreach.hpp> | |
#include <stdio.h> | |
#include <dirent.h> | |
#include <ios> | |
#include <fstream> | |
#include <stdexcept> | |
#include "CinderOpenCV.h" | |
#include "svmlight/svmlight.h" | |
using namespace cv; | |
using namespace ci; | |
using namespace ci::app; | |
using namespace std; | |
class HOGTrainApp : public AppBasic { | |
public: | |
HOGTrainApp(); | |
void setup(); | |
void mouseDown( MouseEvent event ); | |
void update(); | |
void draw(); | |
// generate the descriptor | |
int createHOG(); | |
// write out data | |
void saveDescriptorVectorToFile(vector<float>& descriptorVector, vector<unsigned int>& _vectorIndices, string fileName); | |
// utility | |
void getFilesInDirectory(const string& dirName, vector<string>& fileNames, const vector<string>& validExtensions); | |
// pass in the descriptors in case we want to make multiple | |
void calculateFeaturesFromInput(const string& imageFilename, vector<float>& featureVector, shared_ptr<HOGDescriptor> hog); | |
// test the detection | |
void detectTest(Mat& imageData, vector<cv::Rect>& found); | |
shared_ptr<HOGDescriptor> hogDesc; | |
// Directory containing positive sample images | |
string posSamplesDirectory; | |
// Directory containing negative sample images | |
string negSamplesDirectory; | |
// Set the file to write the features to | |
string featuresFile; | |
// Set the file to write the SVM model to | |
string svmModelFile; | |
// Set the file to write the resulting detecting descriptor vector to | |
string descriptorVectorFile; | |
const cv::Size trainingPadding; | |
const cv::Size winStride; | |
Capture cap; | |
bool doDetection; | |
gl::Texture captureTexture; | |
vector<ci::Rectf> detectedLocations; | |
}; | |
HOGTrainApp::HOGTrainApp():trainingPadding(0, 0), winStride(8,8) | |
{ | |
posSamplesDirectory = "pos/"; | |
negSamplesDirectory = "neg/"; | |
featuresFile = "genfiles/features.dat"; | |
svmModelFile = "genfiles/svmlightmodel.dat"; | |
descriptorVectorFile = "genfiles/descriptorvector.dat"; | |
} | |
void HOGTrainApp::setup() | |
{ | |
createHOG(); // make a HOG descriptor based on our pos & neg samples | |
captureTexture = gl::Texture(640, 480); | |
doDetection = false; | |
cap = Capture(640, 480); | |
cap.start(); | |
} | |
void HOGTrainApp::mouseDown( MouseEvent event ) | |
{ | |
doDetection = true; | |
} | |
void HOGTrainApp::update() | |
{ | |
if(cap.checkNewFrame()) { | |
captureTexture = gl::Texture(cap.getSurface()); | |
if(doDetection) | |
{ | |
cv::Mat m = toOcv(cap.getSurface()); | |
vector<cv::Rect> found; | |
detectTest(m, found); | |
doDetection = false; | |
detectedLocations.clear(); | |
for ( int i = 0; i < found.size(); i++) { | |
detectedLocations.push_back(fromOcv(found[i])); | |
} | |
} | |
} | |
} | |
void HOGTrainApp::draw() | |
{ | |
// clear out the window with black | |
gl::clear( Color( 0, 0, 0 ) ); | |
gl::color(1, 1, 1); | |
gl::draw(captureTexture); | |
gl::color(0, 1, 0); | |
for ( int i = 0; i < detectedLocations.size(); i++) { | |
gl::drawStrokedRect(detectedLocations[i]); | |
} | |
} | |
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
// HOG training | |
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
void HOGTrainApp::saveDescriptorVectorToFile(vector<float>& descriptorVector, vector<unsigned int>& _vectorIndices, string fileName) { | |
console() << "Saving descriptor vector to file " << fileName << endl; | |
string separator = " "; // Use blank as default separator between single features | |
fstream File; | |
float percent; | |
File.open(fileName.c_str(), ios::out); | |
if (File.good() && File.is_open()) { | |
for (int feature = 0; feature < descriptorVector.size(); ++feature) { | |
if ((feature % 10 == 0) || (feature == (descriptorVector.size()-1)) ) { | |
percent = ((1 + feature) * 100 / descriptorVector.size()); | |
} | |
File << descriptorVector.at(feature) << separator; | |
} | |
File << endl; | |
File.flush(); | |
File.close(); | |
} | |
} | |
void HOGTrainApp::getFilesInDirectory(const string& dirName, vector<string>& fileNames, const vector<string>& validExtensions) { | |
fs::path targetDir(dirName); | |
fs::directory_iterator it(targetDir), eod; | |
BOOST_FOREACH(fs::path const &p, std::make_pair(it, eod)) | |
{ | |
if(is_regular_file(p)) | |
{ | |
string filename = p.string(); | |
// is it a hidden file? | |
if(filename.find(".") == 0) | |
continue; | |
int extensionLocation = string(filename).find_last_of("."); | |
string tempExt = filename.substr(extensionLocation + 1); | |
std::transform(tempExt.begin(), tempExt.end(), tempExt.begin(), ::tolower); | |
if (find(validExtensions.begin(), validExtensions.end(), tempExt) != validExtensions.end()) { | |
fileNames.push_back(filename); | |
} | |
} | |
} | |
} | |
void HOGTrainApp::calculateFeaturesFromInput(const string& imageFilename, vector<float>& featureVector, shared_ptr<HOGDescriptor> hog) { | |
Mat imageData = imread(imageFilename, 0); | |
if (imageData.empty()) { | |
featureVector.clear(); | |
console() << "Error: imge is empty " << imageFilename << endl; | |
return; | |
} | |
// all dimensions need to match | |
if (imageData.cols != hog->winSize.width || imageData.rows != hog->winSize.height) { | |
featureVector.clear(); | |
console() << "image dimesions need to match HOG window size " << endl; | |
return; | |
} | |
vector<cv::Point> locations; | |
hog->compute(imageData, featureVector, winStride, trainingPadding, locations); | |
imageData.release(); // Release the image again after features are extracted | |
} | |
void HOGTrainApp::detectTest(Mat& imageData, vector<cv::Rect>& found) { | |
//vector<cv::Rect> found; | |
int groupThreshold = 0; | |
cv::Size padding(cv::Size(0,0)); | |
cv::Size winStride(cv::Size(8, 8)); | |
double hitThreshold = 0; // tolerance | |
hogDesc->detectMultiScale(imageData, found, hitThreshold, winStride, padding, 1.05, groupThreshold); | |
} | |
int HOGTrainApp::createHOG() | |
{ | |
cv::Size win(96,160); | |
cv::Size blockSize(16, 16); | |
cv::Size blockStride(8,8); | |
cv::Size cellSize(8,8); | |
int nbins = 9; | |
hogDesc.reset( new HOGDescriptor(win, blockSize, blockStride, cellSize, nbins) ); // Use standard parameters here | |
// Get the files to train from somewhere | |
static vector<string> positiveTrainingImages; | |
static vector<string> negativeTrainingImages; | |
static vector<string> validExtensions; | |
validExtensions.push_back("jpg"); | |
validExtensions.push_back("jpeg"); | |
validExtensions.push_back("png"); | |
getFilesInDirectory(posSamplesDirectory, positiveTrainingImages, validExtensions); | |
getFilesInDirectory(negSamplesDirectory, negativeTrainingImages, validExtensions); | |
/// Retrieve the descriptor vectors from the samples | |
unsigned long overallSamples = positiveTrainingImages.size() + negativeTrainingImages.size(); | |
// Make sure there are actually samples to train | |
if (overallSamples == 0) { | |
console() << "No training sample files found, nothing to do! " << endl;; | |
return 1; | |
} | |
setlocale(LC_ALL, "C"); // Do not use the system locale | |
setlocale(LC_NUMERIC,"C"); | |
setlocale(LC_ALL, "POSIX"); | |
console() << "Reading files, generating HOG features nd saving them. Go enjoy some coffee or tea, this is gonna take a while" << endl; | |
float percent; | |
fstream File; | |
File.open(featuresFile.c_str(), ios::out); | |
if (File.good() && File.is_open()) { | |
File << "# Use this file to train, e.g. SVMlight by issuing $ svm_learn -i 1 -a weights.txt " << featuresFile.c_str() << endl; // Remove this line for libsvm which does not support comments | |
// Iterate over sample images | |
for (unsigned long currentFile = 0; currentFile < overallSamples; ++currentFile) { | |
vector<float> featureVector; | |
// Get positive or negative sample image file path | |
const string currentImageFile = (currentFile < positiveTrainingImages.size() ? positiveTrainingImages.at(currentFile) : negativeTrainingImages.at(currentFile - positiveTrainingImages.size())); | |
// Output progress | |
if ( (currentFile+1) % 10 == 0 || (currentFile+1) == overallSamples ) { | |
percent = ((currentFile+1) * 100 / overallSamples); | |
console() << currentFile+1 << " " << percent << "% " << currentImageFile << endl; | |
} | |
// Calculate feature vector from current image file | |
calculateFeaturesFromInput(currentImageFile, featureVector, hogDesc); | |
if (!featureVector.empty()) { | |
// positive class to +1 and negative class to -1 for SVMlight processing | |
File << ((currentFile < positiveTrainingImages.size()) ? "+1" : "-1"); | |
// Save feature vector components | |
for (unsigned int feature = 0; feature < featureVector.size(); ++feature) { | |
File << " " << (feature + 1) << ":" << featureVector.at(feature); | |
} | |
File << endl; | |
} | |
} | |
File.flush(); | |
File.close(); | |
} else { | |
console() << "Error opening file " << featuresFile << endl; | |
return 0; | |
} | |
// </editor-fold> | |
/// Read in and train the calculated feature vectors | |
console() << "Calling SVMlight " << endl; | |
SVMlight::getInstance()->read_problem(const_cast<char*> (featuresFile.c_str())); | |
SVMlight::getInstance()->train(); // Call the core libsvm training procedure | |
console() <<"Training done, saving model " << endl; | |
SVMlight::getInstance()->saveModelToFile(svmModelFile); | |
//Generate single detecting feature vector from calculated SVM support vectors and SVM model | |
console() << "Generating HOG feature vector w/ svmlight" << endl; | |
vector<float> descriptorVector; | |
vector<unsigned int> descriptorVectorIndices; | |
// Generate a single detecting feature vector (v1 | b) from the trained support vectors, for use e.g. with the HOG algorithm | |
SVMlight::getInstance()->getSingleDetectingVector(descriptorVector, descriptorVectorIndices); | |
// save to file | |
saveDescriptorVectorToFile(descriptorVector, descriptorVectorIndices, descriptorVectorFile); | |
console() <<"Testing custom detection using camera " << endl; | |
hogDesc->setSVMDetector(descriptorVector); // Set our custom detecting vector | |
return 1; | |
} | |
CINDER_APP_BASIC( HOGTrainApp, RendererGl ) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment