Using HOGDescriptor with C++. First of all, how can I identify class of detected object? There is no information in doc and example. I wrote simple program (according to train_HOG.cpp) and trained with vehicles and non-vehicles. So its "one-class"? How descriptor know it found vehicle or non-vehicle?
I did like in example, but it seems to not work (I hoped t see maybe higher density around cars - but it looks completely random!). One thing different is class name - I used 1 and 2 (for every class folder) but similar results are with classes like in example (-1 and 1). Tried different svm params (default and as in example...). The only thing I can do is show you my code :|
#include <HogClassifier.h>
#include <iostream>
#include <boost/filesystem.hpp>
#include <boost/range/iterator_range.hpp>
#include <boost/range/adaptor/indexed.hpp>
#include <boost/assert.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/core/ocl.hpp>
#include <opencv2/highgui.hpp>
namespace bfs = boost::filesystem;
HogClassifier::HogClassifier(cv::Size winSize)
{
mHogs.winSize = winSize;
//cv::ocl::setUseOpenCL(false);
}
auto HogClassifier::GetImages(std::vector<std::string> dirPaths, std::vector<int>& labels)
{
int currClassDir = 0;
std::vector<cv::Mat> images;
for(auto& root: dirPaths)
{
if(bfs::is_directory(root))
{
for(const auto& record: boost::make_iterator_range(bfs::recursive_directory_iterator(root, {})))
if(!bfs::is_directory(record))
{
images.push_back(cv::imread(record.path().string()));
labels.push_back(currClassDir);
}
}
else
{
std::cout << root << " is not a directory!" << std::endl;
}
++currClassDir;
}
return images;
}
auto HogClassifier::GetDescriptors(const std::vector<cv::Mat> &images)
{
std::vector<cv::Mat> allDdescriptors;
std::vector<float> singleDescriptors;
cv::Mat grayImage;
for(const auto& img: images)
{
if(img.cols >= mHogs.winSize.width && img.rows >= mHogs.winSize.height)
{
cv::Rect extracted{(img.cols - mHogs.winSize.width) / 2,
(img.rows - mHogs.winSize.height) / 2,
mHogs.winSize.width, mHogs.winSize.height};
cv::cvtColor(img(extracted), grayImage, cv::COLOR_BGR2GRAY);
if(grayImage.data)
{
mHogs.compute(grayImage, singleDescriptors, cv::Size(4, 4), cv::Size(0, 0));
allDdescriptors.push_back(cv::Mat{singleDescriptors}.clone());
}
}
}
return allDdescriptors;
}
auto HogClassifier::CumulateData(const std::vector<cv::Mat> &uncumulatedData)
{
using namespace boost::adaptors;
int columns = std::max(uncumulatedData.at(0).rows, uncumulatedData.at(0).cols);
int rows = uncumulatedData.size();
cv::Mat cumulatedData(rows, columns, CV_32F);
cv::Mat temp(1, columns, CV_32F);
for(const auto& sample: uncumulatedData | indexed(0))
{
if(sample.value().cols == 1)
{
cv::transpose(sample.value(), temp);
temp.copyTo(cumulatedData.row(sample.index()));
}
else if(sample.value().rows == 1)
sample.value().copyTo(cumulatedData.row(sample.index()));
else
std::cout << "Wrng data provided for cumulation" << std::endl;
}
return cumulatedData;
}
auto HogClassifier::GetSVM(SVMParams svmParams)
{
auto svm = cv::ml::SVM::create();
svm->setType(svmParams.type);
svm->setKernel(svmParams.kernel);
svm->setTermCriteria(svmParams.termCriteria);
svm->setDegree(svmParams.degree);
svm->setCoef0(svmParams.coef0);
svm->setNu(svmParams.ny);
svm->setP(svmParams.epsilon);
svm->setC(svmParams.C);
return svm;
}
auto HogClassifier::PrepateTrainingData(std::vector<std::string> dirPaths)
{
std::vector<int> labels;
auto images = GetImages(dirPaths, labels);
auto uncumulatedData = GetDescriptors(images);
auto trainingData = CumulateData(uncumulatedData);
return std::make_tuple(trainingData, labels);
}
auto HogClassifier::TrainClassifier(cv::Ptr<cv::ml::SVM> svm, cv::Mat& data, std::vector<int>& labels)
{
BOOST_ASSERT_MSG(data.rows == labels.size(), "Number of samples doesn't match numer of labels");
BOOST_ASSERT(svm);
if(svm)
{
svm->train(data, cv::ml::ROW_SAMPLE, labels);
cv::Mat sv = svm->getSupportVectors();
cv::Mat alpha, svidx;
double rho = svm->getDecisionFunction( 0, alpha, svidx );
CV_Assert( alpha.total() == 1 && svidx.total() == 1 && sv.rows == 1 );
CV_Assert( (alpha.type() == CV_64F && alpha.at<double>(0) == 1.) ||
(alpha.type() == CV_32F && alpha.at<float>(0) == 1.f) );
CV_Assert( sv.type() == CV_32F );
std::vector<float> hogDetector(sv.cols + 1);
memcpy(&hogDetector[0], sv.ptr(), sv.cols*sizeof(hogDetector[0]));
hogDetector[sv.cols] = (float)-rho;
mHogs.setSVMDetector(hogDetector);
mTrained = true;
}
}
auto HogClassifier::Train(std::vector<std::string> dirPaths, SVMParams svmParams)
{
auto [trainingData, labels] = PrepateTrainingData(dirPaths);
auto svm = GetSVM(svmParams);
TrainClassifier(svm, trainingData, labels);
}
auto HogClassifier::Save(std::string destFile)
{
mHogs.save(destFile);
}
auto HogClassifier::Load(std::string srcFile)
{
if(mHogs.load(srcFile))
mTrained = true;
else
{
mTrained = false;
std::cout << "Problem reading classifier file." << std::endl;
}
return mTrained;
}
auto HogClassifier::FindObjects(cv::Mat& image)
{
std::vector<cv::Rect> detections;
std::vector<double> weights;
if(mTrained && image.data)
{
cv::Mat grayImg;
cv::cvtColor(image, grayImg, cv::COLOR_BGR2GRAY);
mHogs.detectMultiScale(grayImg, detections, weights);
//mHogs.detectMultiScale(image, detections, weights);
for(const auto& occurence: detections | boost::adaptors::indexed(0))
{
cv::Scalar color{0, weights[occurence.index()] * weights[occurence.index()] * 200, 0};
cv::rectangle(image, occurence.value(), color, image.cols/400 + 1);
}
}
return detections.size();
}
/*=========================================*/
//#define DOCTEST_CONFIG_DISABLE
#include <doctest.h>
TEST_CASE("Train and check")
{
HogClassifier classifier{cv::Size{64, 64}};
cv::Mat image = cv::imread("../cars-on-road2.jpg");
classifier.Train({"../dataset/vehicles", "../dataset/non-vehicles"});
int found = classifier.FindObjects(image);
if(found)
{
cv::imshow("found cars", image);
cv::waitKey();
}
CHECK(found > 0);
}
//header
struct SVMParams
{
using SVM = cv::ml::SVM;
SVM::Types type = SVM::C_SVC;
SVM::KernelTypes kernel = SVM::LINEAR;
cv::TermCriteria termCriteria = {cv::TermCriteria::MAX_ITER, 100, 1e-4};
int degree = 0;
double coef0 = 0;
double ny = 0;
double epsilon = 0;
double C = 0.01;
};
class HogClassifier
{
public:
HogClassifier(cv::Size winSize);
auto Train(std::vector<std::string> dirPaths, SVMParams svmParams = SVMParams{});
auto Save(std::string destFile);
auto Load(std::string srcFile);
auto Test(std::vector<std::string> dirPaths);
auto FindObjects(cv::Mat &image);
private:
auto PrepateTrainingData(std::vector<std::string> dirPaths);
auto GetImages(std::vector<std::string> dirPaths, std::vector<int>& labels);
auto GetDescriptors(const std::vector<cv::Mat>& images);
auto CumulateData(const std::vector<cv::Mat>& uncumulatedData);
auto GetSVM(SVMParams svmParams);
auto TrainClassifier(cv::Ptr<cv::ml::SVM>svm, cv::Mat& data, std::vector<int>& labels);
cv::HOGDescriptor mHogs;
bool mTrained{false};
};