Hello Friends,
Have you ever wondered how can you create your own OpenCV 3 Support Vector Machines(SVM) + HOG Classifier? Did you want to create your own classifier that actually works? Are you tried of searching google for a good SVM tutorial but all you find it bunch of example SVM Tutorials that does nothing but show a test image with useless circles?
If the answer is Yes! , you have come to the right place my friend.
I have myself struggled a lot to get this working all because there wasn’t a good tutorial anywhere that existed which would teach you the SVM.
In this tutorial, we’ll create a simple Car Detector using SVM aka Support Vector Machines.
So, before we get started, let’s look at the pre-requisites.
1) Visual Studio 2015
2) Windows 10 x64 (works best)
3) OpenCV 3.0, 3.1, 3.2, 3.3 and above
4) Patience !!
If you are someone who doesn’t really bother what’s going on behind the scene and would want to straight out implement this goddamn classifier, then without further due, let’s look at the code. (Although, i would prefer that you understand what’s what before actually using it, but I know you always don’t have that options 🙂 !!!)
Special Note: A lot of things have changed between OpenCV 2.x and OpenCV 3.x builds with respect to SVM. I mean if you are trying to port the code or maybe using the existing classifier that you created in OpenCV 2.x in OpenCV 3.x, man you better off start fresh because things have changed like hell.
Step by Step for Training Phase:
1) Create a new ’empty’ project in Visual Studio 2015
2) Link the OpenCV Includes and Libs with the project (Follow this link on how to install OpenCV 3.3 with Visual Studio and run a test Solution)
3) For Training, Create 3 files namely:
a) Train.cpp
b) Datamanager.cpp
c) Datamanager.h
4) Name your classifier file under “svm->save(“firetrain.yml”);” to your desired filename
5) Create a folder called “Positive” inside the project and add all the positive images to it. (Size 64×64)
6) Create a folder called “Negative” inside the project and add all the Negative images to it. (Size 64×64)
7) (Optional) Use svm->trainAuto(td); instead of svm->train(td) to set value of C and gamma, but it takes a lot of time
Note: Shortly, after this post, i’ll create a crop_image software to assist you with the cropping of images.
Step by Step for Detection Phase:
1) Create a new ’empty’ project in Visual Studio 2015
2) Link the OpenCV Includes and Libs with the project (Follow this link on how to install OpenCV 3.3 with Visual Studio and run a test Solution)
3) For Detection, Create 1 file called “DetectCode.cpp” by excluding the above 3 training files and also referencing the classifier file in “#define TRAINED_SVM “firetrain.yml” that you created in training phase.
Note: There are hog parameters that you need to tune accordingly. I have plans to create a HOG based UI software, so that you can easily get all those parameters from your referenced image. Stay tuned for updates for it.
Algorithm pipeline(Training svm):
– > From the image dataset that you have created, randomly separate it into training and test set(80%, 20% split)
– > Set the hog windowSize(64×64) and Compute the HOG features for the training image set containing vehicles and non-vehicles.
– > Train a SVM using these HOG features. (Use linear or RBF SVM)
– > Test the trained SVM on the separate test imageset and evaluate the results and performance.
– > For the real world test, run a sliding window and use the SVM from above to predict vehicle/non-vehicle.
Algorithm pipeline(Detectting car in video):
– > Extract individual frame of the video frames.
– > Convert each captured frame into grayscale image.
– > Discard the upper half containing the sky part.
– > Choose a sliding window size, and resize to the trained size; Use SVM to predict
– > Filter the detect points for false positive(use template matching only for the subsection of detected)
Codes :
#Train.cpp
//train.cpp
#include
//#include
#include
#include
//#include "util/imageutils.h"
#include "DataSetManager.h"
using namespace std;
using namespace cv;
using namespace cv::ml;
HOGDescriptor hog(
Size(64,64), //winSize
Size(8,8), //blocksize
Size(8,8), //blockStride,
Size(8,8), //cellSize,
9, //nbins,
1, //derivAper,
-1, //winSigma,
0, //histogramNormType,
0.2, //L2HysThresh,
0,//gammal correction,
64,//nlevels=64
1);
void getSVMParams(SVM *svm)
{
cout getKernelType() getType() getC() getDegree() getNu() getGamma() &trainLabels, Mat &testResponse, Mat &testMat) {
Ptr svm = SVM::create();
svm->setGamma(0.50625);
svm->setC(100);
svm->setKernel(SVM::RBF);
svm->setType(SVM::C_SVC);
Ptr td = TrainData::create(trainMat, ROW_SAMPLE, trainLabels);
svm->train(td);
//svm->trainAuto(td);
svm->save("firetrain.yml");
svm->predict(testMat, testResponse);
getSVMParams(svm);
/*
To acheive 100% rate.
Descriptor Size : 576
Kernel type : 2
Type : 100
C : 2.5
Degree : 0
Nu : 0
Gamma : 0.03375
the accuracy is :100
*/
}
void SVMevaluate(Mat &testResponse, float &count, float &accuracy, vector &testLabels) {
for (int i = 0; i(i,0) (i, 0) == testLabels[i]) {
count = count + 1;
}
}
accuracy = (count / testResponse.rows) * 100;
}
void computeHOG(vector &inputCells, vector > &outputHOG) {
for (int y = 0; y descriptors;
hog.compute(inputCells[y], descriptors);
outputHOG.push_back(descriptors);
}
}
void ConvertVectortoMatrix(vector > &ipHOG, Mat & opMat)
{
int descriptor_size = ipHOG[0].size();
for (int i = 0; i(i, j) = ipHOG[i][j];
}
}
}
int main(int argc, char ** argv)
{
/**************** user code starts *******************/
cout trainCells;
vector testCells;
vector trainLabels;
vector testLabels;
for (int i = 0; i > trainHOG;
std::vector<:vector> > testHOG;
//compute_hog(trainCells, gradient_lst);
computeHOG(trainCells, trainHOG);
computeHOG(testCells, testHOG);
int descriptor_size = trainHOG[0].size();
cout > ch;
return 0;
}
#DataSetManager.cpp
//DataSetManager.cpp
#include
#include
#include
#include "DataSetManager.h"
#include
using namespace cv;
using std::cout;
using std::endl;
using std::string;
#define EN_DEBUG
//constructor
DataSetManager::DataSetManager() :testDataPercent(20), validationDataPercent(0), totalDataNum(0), totalTrainDataNum(0), totalTestDataNum(0) {
// default parameter initialization here
}
// setter and getter methods
void DataSetManager::setTestDataPercent(float num) { testDataPercent = num; }
void DataSetManager::setValidationDataPercent(float num) { validationDataPercent = num; }
int DataSetManager::getTotalDataNum() { return totalDataNum; }
int DataSetManager::getTotalTrainDataNum() { return totalTrainDataNum; }
int DataSetManager::getTotalTestDataNum() { return totalTestDataNum; }
//main functions
void DataSetManager::addData(std::string folderName, int classlabel) {
// notice here that we are using the Opencv's embedded "String" class
std::vector<:string> filenames;
cv::String folder = folderName.c_str();// converting from std::string->cv::String
cv::glob(folder, filenames);
// for each of these fileNames append them into the DataSet structure with labels
for (size_t i = 0; i(filenames[i]);
tempDataset.label = classlabel;
dataList.push_back(tempDataset);
}
totalDataNum = totalDataNum + filenames.size();
}
// the distribute functions distributes the whole data into training data and test data.
void DataSetManager::distribute() {
int n_test_valid = static_cast (
(validationDataPercent*totalDataNum / 100) + (testDataPercent*totalDataNum / 100));
//cout rndIndex;
std::vector::iterator it;
DataSet tempDataset;
while (counter(dataList[num].filename);
tempDataset.label = dataList[num].label;
TestData.push_back(tempDataset);
counter++;
}
std::sort(rndIndex.begin(), rndIndex.end());//sort in ascending order
#ifdef EN_DEBUG
cout ::iterator it = rndIndex.begin(); it != rndIndex.end(); ++it)
cout (dataList[i].filename);
tempDataset.label = dataList[i].label;
TrainData.push_back(tempDataset);
}
else if ((current == i) && (curIdx#DataSetManager.h
#ifndef _DATASETMANAGER_H
#define _DATASETMANAGER_H
#include
struct DataSet {
std::string filename;
float label;
};
class DataSetManager
{
private:
// user defined data member
float testDataPercent;
float validationDataPercent;
// derrived or internally calculated
int totalDataNum;
int totalTrainDataNum;
int totalTestDataNum;
int totalValidationDataNum;
public:
//constructor
DataSetManager();
// setter and getter methods
void setTestDataPercent(float num);
void setValidationDataPercent(float num);
int getTotalDataNum();
int getTotalTrainDataNum();
int getTotalTestDataNum();
int getTotalValidationDataNum();
// primary functions of the class
void addData(std::string folderName, int classlabel);
void read();
void display();// displays the read file names for debugging
void distribute();
// ideally these are private; need to update
std::vector dataList;
std::vector TrainData;
std::vector TestData;
std::vector ValidationData;
};
#endif
DetectCode.cpp
//detectcar.cpp
#include
#include
#include
#include
#include
#include
#include
#include
#define WINDOW_NAME "WINDOW"
#define TRAFFIC_VIDEO_FILE "7.avi"
#define TRAINED_SVM "firetrain.yml"
#define IMAGE_SIZE Size(40,40)
#define save_video true
#define OUT_Video_File "march323230_project_video.avi"
using namespace cv;
using namespace cv::ml;
using namespace std;
bool file_exists(const string &file);
void draw_locations(Mat & img, const vector & locations, const Scalar & color);
void readImage(string imgname, Mat & im);
//void test_image(Mat & img, const Size & size);
void test_video(const Size & size);
bool checkIfpatchIsVehicle(Mat & img2check);
void printHOGParams(HOGDescriptor &hog)
{
cout & svm, vector & hog_detector)
{
// get the support vectors
Mat sv = svm->getSupportVectors();
const int sv_total = sv.rows;
// get the decision function
Mat alpha, svidx;
double rho = svm->getDecisionFunction(0, alpha, svidx);
cout (0) == 1.) ||
//(alpha.type() == CV_32F && alpha.at(0) == 1.f));
//CV_Assert(sv.type() == CV_32F);
hog_detector.clear();
hog_detector.resize(sv.cols + 1);
memcpy(&hog_detector[0], sv.ptr(), sv.cols * sizeof(hog_detector[0]));
hog_detector[sv.cols] = (float)-rho;
}
void draw_locations(Mat & img, const vector & locations, const Scalar & color)
{
if (!locations.empty())
{
vector::const_iterator loc = locations.begin();
vector::const_iterator end = locations.end();
for (; loc != end; ++loc)
{
rectangle(img, *loc, color, 2);
}
}
}
void test_video(const Size & size)
{
char key = 27;
Mat img, draw;
Ptr svm;
HOGDescriptor hog;
hog.winSize = size;
vector locations;
vector found_filtered;
// Load the trained SVM.
svm = StatModel::load(TRAINED_SVM);
// Set the trained svm to my_hog
vector hog_detector;
get_svm_detector(svm, hog_detector);
hog.setSVMDetector(hog_detector);
printHOGParams(hog);
VideoCapture video;
// Open the video file.
video.open(TRAFFIC_VIDEO_FILE);
if (!video.isOpened())
{
cerr (video.get(CV_CAP_PROP_FOURCC));
//int codec = CV_FOURCC('M', 'J', 'P', 'G');
bool isWriterInitialized = false;
int num_of_vehicles = 0;
bool end_of_process = false;
while (!end_of_process)
{
video >> img;
if (img.empty())
break;
draw = img.clone();
Mat cropped;
cv::resize(draw, cropped, Size(720, 560));
Mat temp, temp3;
cvtColor(cropped, temp, COLOR_BGR2GRAY);
/*Mat bgr[3]; //destination array
split(temp3,bgr);//split source
temp = bgr[0]+bgr[2];
*/
if (isWriterInitialized) {
//execute only once
isWriterInitialized = true;
/*writer.open(outputFile,
capture.get(CV_CAP_PROP_FOURCC),
capture.get(CV_CAP_PROP_FPS),
Size(capture.get(CV_CAP_PROP_FRAME_WIDTH),capture.get(CV_CAP_PROP_FRAME_HEIGHT)),
true);*/
writer.open(outputFile, codec, rate, cropped.size(), true);
}
locations.clear();
// Rect(x,y,w,h) w->width=cols;h->rows
// first remove the upper 50% from height Original Cropped =size(720,560)=(cols,rows)
Mat roi = temp(Rect(0, temp.rows*0.5, temp.cols, temp.rows - temp.rows*0.5));
//size(roi) = size(720,280)
//cout remove false positives
roi = roi(Rect(0, 0, roi.cols, roi.rows - 100));
//cout::iterator it = locations.begin();
std::vector::iterator itend = locations.end();
vector actuallocations;
bool isVehicle = false;
for (; it != itend; it++)
{
Rect current = *it;
//cout templateList;
templateList.push_back("temp1.png"); templateList.push_back("temp2.png");
templateList.push_back("temp3.png"); templateList.push_back("temp4.png");
templateList.push_back("temp5.png"); templateList.push_back("temp6.png");
templateList.push_back("temp7.png");
//templateList.push_back("temp8.png");
//templateList.push_back("temp9.png");templateList.push_back("temp10.png");
Mat matchScore = Mat::zeros(7, 1, CV_32FC1);
for (int ii = 0; ii(ii, 0) = maxVal;
if (maxVal>0.15)
isVehicle = true;
}
//cout0.13*templateList.size())
isVehicle = true;
//waitKey();
return isVehicle;
}
Files for Download
Test Images containing Positive and Negative Dataset
1) testdatafolder
Hi, where is the Traffic video you used for testing? Detection of my test videos is very slow.