C++ 基于 HOG 特征的 SVM 分类器用于 OpenCV 中的“对象检测”

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10769519/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 14:26:17  来源:igfitidea点击:

SVM classifier based on HOG features for "object detection" in OpenCV

c++opencvsvmobject-recognitiontraining-data

提问by Mario

I have a project, which I want to detect objects in the images; my aim is to use HOG features. By using OpenCV SVM implementation , I could find the code for detecting people, and I read some papers about tuning the parameters in order to detect object instead of people. Unfortunately, I couldn't do that for a few reasons; first of all, I am probably tuning the parameters incorrectly, second of all, I am not a good programmer in C++ but I have to do it with C++/OpenCV... hereyou can find the code for detecting HOG features for people by using C++/OpenCV.

我有一个项目,我想检测图像中的对象;我的目标是使用 HOG 功能。通过使用 OpenCV SVM 实现,我可以找到检测人的代码,并且我阅读了一些关于调整参数以检测对象而不是人的论文。不幸的是,由于一些原因我不能这样做;首先,我可能没有正确地调整参数,其次,我不是一个好的 C++ 程序员,但我必须用 C++/OpenCV 来做......在这里你可以找到为人们检测 HOG 特征的代码使用 C++/OpenCV。

Let's say that I want to detect the object in this image. Now, I will show you what I have tried to change in the code but it didn't work out with me.

假设我想检测此图像中的对象。现在,我将向您展示我尝试在代码中更改但对我不起作用的内容。

The code that I tried to change:

我试图改变的代码:

HOGDescriptor hog;
hog.setSVMDetector(HOGDescriptor::getDefaultPeopleDetector());

I tried to change getDefaultPeopleDetector()with the following parameters, but it didn't work:

我尝试getDefaultPeopleDetector()使用以下参数进行更改,但没有奏效:

(Size(64, 128), Size(16, 16), Size(8, 8), Size(8, 8), 9, 0,-1, 0, 0.2, true, cv::HOGDescriptor::DEFAULT_NLEVELS)

I then tried to make a vector, but when I wanted to print the results, it seems to be empty.

然后我尝试制作一个矢量,但是当我想打印结果时,它似乎是空的。

vector<float> detector;

HOGDescriptor hog(Size(64, 128), Size(16, 16), Size(8, 8), Size(8, 8), 9, 0,-1, 0, 0.2, true, cv::HOGDescriptor::DEFAULT_NLEVELS);

hog.setSVMDetector(detector);

Please, I need help solving this problem.

拜托,我需要帮助解决这个问题。

回答by Hakan Serce

In order to detect arbitrary objects with using opencv HOG descriptors and SVM classifier, you need to first train the classifier. Playing with the parameters will not help here, sorry :( .

为了使用 opencv HOG 描述符和 SVM 分类器检测任意对象,您需要首先训练分类器。在这里玩参数无济于事,抱歉:(。

In broad terms, you will need to complete the following steps:

从广义上讲,您需要完成以下步骤:

Step 1)Prepare some training images of the objects you want to detect (positive samples). Also you will need to prepare some images with no objects of interest (negative samples).

步骤 1)准备一些要检测的对象的训练图像(正样本)。您还需要准备一些没有感兴趣对象的图像(负样本)。

Step 2)Detect HOG features of the training sample and use this features to train an SVM classifier (also provided in OpenCV).

步骤 2)检测训练样本的 HOG 特征并使用该特征来训练 SVM 分类器(OpenCV 中也提供)。

Step 3)Use the coefficients of the trained SVM classifier in HOGDescriptor::setSVMDetector() method.

步骤 3)在 HOGDescriptor::setSVMDetector() 方法中使用经过训练的 SVM 分类器的系数。

Only then, you can use the peopledetector.cpp sample code, to detect the objects you want to detect.

只有这样,您才能使用 peopledetector.cpp 示例代码来检测您要检测的对象。

回答by LihO

I've been dealing with the same problem and surprised with the lack of some clean C++ solutions I have create ~> this wrapper of SVMLight <~, which is a static library that provides classes SVMTrainerand SVMClassifierthat simplify the training to something like:

我一直在处理同样的问题,并且对我创建的缺少一些干净的 C++ 解决方案感到惊讶~> 这个 SVMLight <~ 的包装器,它是一个静态库,提供类SVMTrainer并将SVMClassifier训练简化为类似的东西:

// we are going to use HOG to obtain feature vectors:
HOGDescriptor hog;
hog.winSize = Size(32,48);

// and feed SVM with them:
SVMLight::SVMTrainer svm("features.dat");

then for each training sample:

然后对于每个训练样本:

// obtain feature vector describing sample image:
vector<float> featureVector;
hog.compute(img, featureVector, Size(8, 8), Size(0, 0));

// and write feature vector to the file:
svm.writeFeatureVectorToFile(featureVector, true);      // true = positive sample

till the features.datfile contains feature vectors for all samples and at the end you just call:

直到features.dat文件包含所有样本的特征向量,最后你只需调用:

std::string modelName("classifier.dat");
svm.trainAndSaveModel(modelName);


Once you have a file with model (or features.datthat you can just train the classifier with):

一旦你有一个带有模型的文件(或者features.dat你可以用它训练分类器):

SVMLight::SVMClassifier c(classifierModelName);
vector<float> descriptorVector = c.getDescriptorVector();
hog.setSVMDetector(descriptorVector);
...
vector<Rect> found;
Size padding(Size(0, 0));
Size winStride(Size(8, 8));
hog.detectMultiScale(segment, found, 0.0, winStride, padding, 1.01, 0.1);

just check the documentation of HOGDescriptorfor more info :)

只需查看HOGDescriptor的文档以获取更多信息:)

回答by bonchenko

I have done similar things as you did: collect samples of positive and negative images using HOG to extract features of car, train the feature set using linear SVM (I use SVM light), then use the model to detect car using HOG multidetect function.

我和你做过类似的事情:使用 HOG 收集正负图像样本以提取汽车特征,使用线性 SVM(我使用 SVM 光)训练特征集,然后使用模型使用 HOG 多重检测功能检测汽车。

I get lot of false positives, then I retrain the data using positive samples and false positive+negative samples. The resulting model is then tested again. The resulting detection improves (less false positives) but the result is not satisfying (average 50% hit rate and 50% false positives). Tuning up multidetect parameters improve the result but not much (10% less false positives and increase in hit rate).

我得到了很多误报,然后我使用正样本和假正+负样本重新训练数据。然后再次测试生成的模型。结果检测得到改善(误报减少),但结果并不令人满意(平均 50% 的命中率和 50% 的误报)。调整多重检测参数会改善结果,但效果不大(误报减少 10%,命中率增加)。

EditI can share you the source code if you'd like, and I am very open for discussion as I have not get satisfactory results using HOG. Anyway, I think the code can be good starting point on using HOG for training and detection

编辑如果您愿意,我可以与您分享源代码,并且我非常愿意讨论,因为我没有使用 HOG 获得令人满意的结果。无论如何,我认为代码可以作为使用 HOG 进行训练和检测的良好起点

Edit: adding code

编辑:添加代码

static void calculateFeaturesFromInput(const string& imageFilename, vector<float>& featureVector, HOGDescriptor& hog) 
{
    Mat imageData = imread(imageFilename, 1);
    if (imageData.empty()) {
        featureVector.clear();
        printf("Error: HOG image '%s' is empty, features calculation skipped!\n", imageFilename.c_str());
        return;
    }
    // Check for mismatching dimensions
    if (imageData.cols != hog.winSize.width || imageData.rows != hog.winSize.height) {
       featureVector.clear();
       printf("Error: Image '%s' dimensions (%u x %u) do not match HOG window size (%u x %u)!\n", imageFilename.c_str(), imageData.cols, imageData.rows, hog.winSize.width, hog.winSize.height);
        return;
    }
    vector<Point> locations;
    hog.compute(imageData, featureVector, winStride, trainingPadding, locations);
    imageData.release(); // Release the image again after features are extracted
}

...

...

int main(int argc, char** argv) {

    // <editor-fold defaultstate="collapsed" desc="Init">
    HOGDescriptor hog; // Use standard parameters here
    hog.winSize.height = 128;
    hog.winSize.width = 64;

    // Get the files to train from somewhere
    static vector<string> tesImages;
    static vector<string> positiveTrainingImages;
    static vector<string> negativeTrainingImages;
    static vector<string> validExtensions;
    validExtensions.push_back("jpg");
    validExtensions.push_back("png");
    validExtensions.push_back("ppm");
    validExtensions.push_back("pgm");
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Read image files">
    getFilesInDirectory(posSamplesDir, positiveTrainingImages, validExtensions);
    getFilesInDirectory(negSamplesDir, negativeTrainingImages, validExtensions);
    /// Retrieve the descriptor vectors from the samples
    unsigned long overallSamples = positiveTrainingImages.size() + negativeTrainingImages.size();
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Calculate HOG features and save to file">
    // Make sure there are actually samples to train
    if (overallSamples == 0) {
        printf("No training sample files found, nothing to do!\n");
        return EXIT_SUCCESS;
    }

    /// @WARNING: This is really important, some libraries (e.g. ROS) seems to set the system locale which takes decimal commata instead of points which causes the file input parsing to fail
    setlocale(LC_ALL, "C"); // Do not use the system locale
    setlocale(LC_NUMERIC,"C");
    setlocale(LC_ALL, "POSIX");

    printf("Reading files, generating HOG features and save them to file '%s':\n", featuresFile.c_str());
    float percent;
    /**
     * Save the calculated descriptor vectors to a file in a format that can be used by SVMlight for training
     * @NOTE: If you split these steps into separate steps: 
     * 1. calculating features into memory (e.g. into a cv::Mat or vector< vector<float> >), 
     * 2. saving features to file / directly inject from memory to machine learning algorithm,
     * the program may consume a considerable amount of main memory
     */ 
    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) {
            storeCursor();
            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);
                printf("%5lu (%3.0f%%):\tFile '%s'", (currentFile+1), percent, currentImageFile.c_str());
                fflush(stdout);
                resetCursor();
            }
            // Calculate feature vector from current image file
            calculateFeaturesFromInput(currentImageFile, featureVector, hog);
            if (!featureVector.empty()) {
                /* Put positive or negative sample class to file, 
                 * true=positive, false=negative, 
                 * and convert positive class to +1 and negative class to -1 for SVMlight
                 */
                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;
            }
        }
        printf("\n");
        File.flush();
        File.close();
    } else {
        printf("Error opening file '%s'!\n", featuresFile.c_str());
        return EXIT_FAILURE;
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Pass features to machine learning algorithm">
    /// Read in and train the calculated feature vectors
    printf("Calling SVMlight\n");
    SVMlight::getInstance()->read_problem(const_cast<char*> (featuresFile.c_str()));
    SVMlight::getInstance()->train(); // Call the core libsvm training procedure
    printf("Training done, saving model file!\n");
    SVMlight::getInstance()->saveModelToFile(svmModelFile);
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Generate single detecting feature vector from calculated SVM support vectors and SVM model">
    printf("Generating representative single HOG feature vector using svmlight!\n");
    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);
    // And save the precious to file system
    saveDescriptorVectorToFile(descriptorVector, descriptorVectorIndices, descriptorVectorFile);
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Test detecting vector">

    cout << "Test Detecting Vector" << endl;
    hog.setSVMDetector(descriptorVector); // Set our custom detecting vector
    cout << "descriptorVector size: " << sizeof(descriptorVector) << endl;

    getFilesInDirectory(tesSamplesDir, tesImages, validExtensions);
    namedWindow("Test Detector", 1);

    for( size_t it = 0; it < tesImages.size(); it++ )
    {
        cout << "Process image " << tesImages[it] << endl;
        Mat image = imread( tesImages[it], 1 );
        detectAndDrawObjects(image, hog);

        for(;;)
        {
            int c = waitKey();
            if( (char)c == 'n')
                break;
            else if( (char)c == '\x1b' )
                exit(0);
        }
    }
    // </editor-fold>
    return EXIT_SUCCESS;
}