/* * Software License Agreement (BSD License) * * Point Cloud Library (PCL) - www.pointclouds.org * Copyright (c) 2010-2012, Willow Garage, Inc. * Copyright (c) 2000-2012 Chih-Chung Chang and Chih-Jen Lin * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * Neither the name of copyright holders nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ #pragma once #include // for PCL_ERROR #include #include // for assert #include #include #include // for numeric_limits #include // for string #include #define Malloc(type, n) static_cast(malloc((n) * sizeof(type))) namespace pcl { /** The structure stores the parameters for the classificationa nd must be initialized * and passed to the training method pcl::SVMTrain. * * \param svm_type {C_SVC, NU_SVC, ONE_CLASS, EPSILON_SVR, NU_SVR} * \param kernel_type {LINEAR, POLY, RBF, SIGMOID, PRECOMPUTED} * \param probability sets the probability estimates */ struct SVMParam : svm_parameter { SVMParam() { svm_type = C_SVC; // C_SVC, NU_SVC, ONE_CLASS, EPSILON_SVR, NU_SVR kernel_type = RBF; // LINEAR, POLY, RBF, SIGMOID, PRECOMPUTED degree = 3; // for poly gamma = 0; // 1/num_features {for poly/rbf/sigmoid} coef0 = 0; // for poly/sigmoid nu = 0.5; // for NU_SVC, ONE_CLASS, and NU_SVR cache_size = 100; // in MB C = 1; // for C_SVC, EPSILON_SVR and NU_SVR eps = 1e-3; // stopping criteria p = 0.1; // for EPSILON_SVR shrinking = 0; // use the shrinking heuristics probability = 0; // do probability estimates nr_weight = 0; // for C_SVC weight_label = nullptr; // for C_SVC weight = nullptr; // for C_SVC } }; /** The structure initialize a model created by the SVM (Support Vector Machines) * classifier (pcl::SVMTrain). */ struct SVMModel : svm_model { SVMModel() { l = 0; probA = nullptr; probB = nullptr; } }; /** The structure initialize a single feature value for the classification using * SVM (Support Vector Machines). */ struct SVMDataPoint { /// It's the feature index. It has to be an integer number greater or equal to zero int idx; /// The value assigned to the correspondent feature. float value; SVMDataPoint() : idx(-1), value(0) {} }; /** The structure stores the features and the label of a single sample which has to be * used for the training or the classification of the SVM (Support Vector Machines). */ struct SVMData { /// Pointer to the label value. It is a mandatory to train the classifier double label; /// Vector of features for the specific sample. std::vector SV; SVMData() : label(std::numeric_limits::signaling_NaN()) {} }; /** Base class for SVM SVM (Support Vector Machines). */ class SVM { protected: std::vector training_set_; // Basic training set svm_problem prob_; // contains the problem (vector of samples with their features) SVMModel model_; // model of the classifier svm_scaling scaling_; // for the best model training, the input dataset is scaled and // the scaling factors are stored here SVMParam param_; // it stores the training parameters std::string class_name_; // The SVM class name. char* line_; // buffer for line reading int max_line_len_; // max line length in the input file bool labelled_training_set_; // it stores whether the input set of samples is labelled /** Set for output printings during classification. */ static void printNull(const char*){}; /** To read a line from the input file. Stored in "line_". */ char* readline(FILE* input); /** Outputs an error in file reading. */ void exitInputError(int line_num) { fprintf(stderr, "Wrong input format at line %d\n", line_num); exit(1); } /** Get a string representation of the name of this class. */ inline const std::string& getClassName() const { return (class_name_); } /** Convert the input format (vector of SVMData) into a readable format for libSVM. */ void adaptInputToLibSVM(std::vector training_set, svm_problem& prob); /** Convert the libSVM format (svm_problem) into a easier output format. */ void adaptLibSVMToInput(std::vector& training_set, svm_problem prob) const; /** Load a problem from an extern file. */ bool loadProblem(const char* filename, svm_problem& prob); /** Save the raw problem in an extern file.*/ bool saveProblem(const char* filename, bool labelled); /** Save the problem (with normalized values) in an extern file.*/ bool saveProblemNorm(const char* filename, svm_problem prob_, bool labelled); public: /** Constructor. */ SVM() : prob_(), line_(nullptr), max_line_len_(10000), labelled_training_set_(true) {} /** Destructor. */ ~SVM() { svm_destroy_param(¶m_); // delete parameters if (scaling_.max > 0) free(scaling_.obj); // delete scaling factors // delete the problem if (prob_.l > 0) { free(prob_.x); free(prob_.y); } } /** Return the labels order from the classifier model. */ void getLabel(std::vector& labels) { int nr_class = svm_get_nr_class(&model_); int* labels_ = static_cast(malloc(nr_class * sizeof(int))); svm_get_labels(&model_, labels_); for (int j = 0; j < nr_class; j++) labels.push_back(labels_[j]); free(labels_); }; /** Save the classifier model in an extern file (in svmlight format). */ void saveClassifierModel(const char* filename) { // exit if model has no data if (model_.l == 0) return; if (svm_save_model(filename, &model_)) { fprintf(stderr, "can't save model to file %s\n", filename); exit(1); } }; }; /** SVM (Support Vector Machines) training class for the SVM machine learning. * * It creates a model for the classifier from a labelled input dataset. * * OPTIONAL: pcl::SVMParam has to be given as input to vary the default training method * and parameters. */ class SVMTrain : public SVM { protected: using SVM::class_name_; using SVM::labelled_training_set_; using SVM::line_; using SVM::max_line_len_; using SVM::model_; using SVM::param_; using SVM::prob_; using SVM::scaling_; using SVM::training_set_; /// Set to 1 to see the training output bool debug_; /// Set too 1 for cross validating the classifier int cross_validation_; /// Number of folds to be used during cross validation. It indicates in how many parts /// is split the input training set. int nr_fold_; /** To cross validate the classifier. It is automatic for probability estimate. */ void doCrossValidation(); /** It extracts scaling factors from the input training_set. * * The scaling of the training_set is a mandatory for a good training of the * classifier. */ void scaleFactors(std::vector training_set, svm_scaling& scaling); public: /** Constructor. */ SVMTrain() : debug_(false), cross_validation_(0), nr_fold_(0) { class_name_ = "SVMTrain"; svm_set_print_string_function( &printNull); // Default to NULL to not print debugging info } /** Destructor. */ ~SVMTrain() { if (model_.l > 0) svm_free_model_content(&model_); } /** Change default training parameters (pcl::SVMParam). */ void setParameters(SVMParam param) { param_ = param; } /** Return the current training parameters. */ SVMParam getParameters() { return param_; } /** Return the result of the training. */ SVMModel getClassifierModel() { return model_; } /** It adds/store the training set with labelled data. */ void setInputTrainingSet(std::vector training_set) { training_set_.insert(training_set_.end(), training_set.begin(), training_set.end()); } /** Return the current training set. */ std::vector getInputTrainingSet() { return training_set_; } /** Reset the training set. */ void resetTrainingSet() { training_set_.clear(); } /** Start the training of the SVM classifier. * * \return false if fails */ bool trainClassifier(); /** Read in a problem (in svmlight format). * * \return false if fails */ bool loadProblem(const char* filename) { return SVM::loadProblem(filename, prob_); }; /** Set to 1 for debugging info. */ void setDebugMode(bool in) { debug_ = in; if (in) svm_set_print_string_function(nullptr); else svm_set_print_string_function(&printNull); }; /** Save the raw training set in a file (in svmlight format). * * \return false if fails */ bool saveTrainingSet(const char* filename) { return SVM::saveProblem(filename, true); }; /** Save the normalized training set in a file (in svmlight format). * * \return false if fails */ bool saveNormTrainingSet(const char* filename) { return SVM::saveProblemNorm(filename, prob_, true); }; }; /** SVM (Support Vector Machines) classification of a dataset. * * It can be used both for testing a classifier model and for classify of new data. */ class SVMClassify : public SVM { protected: using SVM::class_name_; using SVM::labelled_training_set_; using SVM::line_; using SVM::max_line_len_; using SVM::model_; using SVM::param_; using SVM::prob_; using SVM::scaling_; using SVM::training_set_; bool model_extern_copied_; // Set to 0 if the model is loaded from an extern file. bool predict_probability_; // Set to 1 to predict probabilities. std::vector> prediction_; // It stores the resulting prediction. /** It scales the input dataset using the model information. */ void scaleProblem(svm_problem& input, svm_scaling scaling); public: /** Constructor. */ SVMClassify() : model_extern_copied_(false), predict_probability_(false) { class_name_ = "SvmClassify"; } /** Destructor. */ ~SVMClassify() { if (!model_extern_copied_ && model_.l > 0) svm_free_model_content(&model_); } /** It adds/store the training set with labelled data. */ void setInputTrainingSet(std::vector training_set) { assert(training_set.size() > 0); if (scaling_.max == 0) { // to be sure to have loaded the scaling PCL_ERROR("[pcl::%s::setInputTrainingSet] Classifier model not loaded!\n", getClassName().c_str()); return; } training_set_.insert(training_set_.end(), training_set.begin(), training_set.end()); SVM::adaptInputToLibSVM(training_set_, prob_); } /** Return the current training set. */ std::vector getInputTrainingSet() { return training_set_; } /** Reset the training set. */ void resetTrainingSet() { training_set_.clear(); } /** Read in a classifier model (in svmlight format). * * \return false if fails */ bool loadClassifierModel(const char* filename); /** Get the result of the classification. */ void getClassificationResult(std::vector>& out) { out.clear(); out.insert(out.begin(), prediction_.begin(), prediction_.end()); } /** Save the classification result in an extern file. */ void saveClassificationResult(const char* filename); /** Set the classifier model. */ void setClassifierModel(SVMModel model) { // model (inner pointers are references) model_ = model; int i = 0; while (model_.scaling[i].index != -1) i++; scaling_.max = i; scaling_.obj = Malloc(struct svm_node, i + 1); scaling_.obj[i].index = -1; // Performing full scaling copy for (int j = 0; j < i; j++) { scaling_.obj[j] = model_.scaling[j]; } model_extern_copied_ = true; }; /** Read in a raw classification problem (in svmlight format). * * The values are normalized using the classifier model information. * * \return false if fails */ bool loadClassProblem(const char* filename) { assert(model_.l != 0); bool out = SVM::loadProblem(filename, prob_); SVM::adaptLibSVMToInput(training_set_, prob_); scaleProblem(prob_, scaling_); return out; }; /** Read in a normalized classification problem (in svmlight format). * * The data are kept whitout normalizing. * * \return false if fails */ bool loadNormClassProblem(const char* filename) { bool out = SVM::loadProblem(filename, prob_); SVM::adaptLibSVMToInput(training_set_, prob_); return out; }; /** Set whether the classification has to be done with the probability estimate. (The * classifier model has to support it). */ void setProbabilityEstimates(bool set) { predict_probability_ = set; }; /** Start the classification on labelled input dataset. * * It returns the accuracy percentage. To get the classification result, use * getClassificationResult(). * * \return false if fails */ bool classificationTest(); /** Start the classification on un-labelled input dataset. * * To get the classification result, use getClassificationResult(). * * \return false if fails */ bool classification(); /** Start the classification on a single set. */ std::vector classification(SVMData in); /** Save the raw classification problem in a file (in svmlight format). * * \return false if fails */ bool saveClassProblem(const char* filename) { return SVM::saveProblem(filename, false); }; /** Save the normalized classification problem in a file (in svmlight format). * * \return false if fails */ bool saveNormClassProblem(const char* filename) { return SVM::saveProblemNorm(filename, prob_, false); }; }; } // namespace pcl