Program Listing for File ransac.h

Return to documentation for file (include/swri_math_util/ransac.h)

// *****************************************************************************
//
// Copyright (c) 2014, Southwest Research Institute® (SwRI®)
// 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 Southwest Research Institute® (SwRI®) 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 <COPYRIGHT HOLDER> 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.
//
// *****************************************************************************

#ifndef MATH_UTIL_RANSAC_H_
#define MATH_UTIL_RANSAC_H_

#include <cmath>
#include <limits>
#include <vector>

#include <boost/make_shared.hpp>
#include <swri_math_util/random.h>

namespace swri_math_util
{
  template <class Model>
  class Ransac
  {
  public:
    typedef typename Model::M ModelType;
    typedef typename Model::T DataType;

    explicit Ransac(RandomGeneratorPtr rng = RandomGeneratorPtr()) : rng_(rng) {}

    ModelType FitModel(
      Model& model,
      double max_error,
      double confidence,
      int32_t min_iterations,
      int32_t max_iterations,
      std::vector<uint32_t>& inliers,
      int32_t& iterations)
    {
      int32_t breakout = std::numeric_limits<int32_t>::max();
      ModelType best_fit;
      inliers.clear();
      int32_t max_inliers = 0;

      if (!model.ValidData())
      {
        return best_fit;
      }

      if (!rng_)
      {
        rng_ = boost::make_shared<RandomGenerator>();
      }

      std::vector<int32_t> indices;

      ModelType hypothesis;
      for (iterations = 0; (iterations < max_iterations && iterations < breakout) || iterations < min_iterations; iterations++)
      {
        indices.clear();
        rng_->GetUniformRandomSample(0, model.Size() - 1, Model::MIN_SIZE, indices);

        // Generate a hypothesis model from the random sample.
        // If the sample is not degenerate, calculate the number of inliers.
        if (model.GetModel(indices, hypothesis, max_error))
        {
          int32_t inlier_count = model.GetInlierCount(hypothesis, max_error);

          // Update the best fit hypothesis and inliers if this hypothesis has
          // the most inliers so far.
          if (inlier_count > max_inliers)
          {
            max_inliers = inlier_count;
            Model::CopyTo(hypothesis, best_fit);

            // Recalculate breakout threshold to see if the fit is good enough.
            double ratio = inlier_count / static_cast<double>(model.Size());
            double p_no_outliers = 1.0 - std::pow(ratio, Model::MIN_SIZE);
            if (p_no_outliers == 0)
            {
              breakout = 0;
            }
            else if (p_no_outliers < .9999)
            {
              breakout = std::log(1 - confidence) / std::log(p_no_outliers);
            }
          }
        }
      }

      if (max_inliers > 0)
      {
        model.GetInliers(best_fit, max_error, inliers);
      }
      return best_fit;
    }

  private:
    RandomGeneratorPtr rng_;
  };

  template <class Model>
  class RansacBatch
  {
  public:
    typedef typename Model::M ModelType;
    typedef typename Model::T DataType;

    explicit RansacBatch(RandomGeneratorPtr rng = RandomGeneratorPtr()) : rng_(rng) {}

    ModelType FitModel(
      const DataType& data,
      double max_error,
      double confidence,
      int32_t max_iterations,
      int32_t batch_size,
      std::vector<uint32_t>& inliers,
      int32_t& iterations)
    {
      Model model(data, batch_size);
      iterations = 0;
      int32_t breakout = std::numeric_limits<int32_t>::max();
      ModelType best_fit;
      inliers.clear();
      int32_t max_inliers = 0;

      if (!model.ValidData())
      {
        return best_fit;
      }

      if (!rng_)
      {
        rng_ = boost::make_shared<RandomGenerator>();
      }

      std::vector<int32_t> indices;

      ModelType hypothesis;
      while (iterations < max_iterations && iterations < breakout)
      {
        int32_t valid = 0;
        model.ClearSamples();
        while (iterations < max_iterations && iterations < breakout && model.Samples() < batch_size)
        {
          iterations++;
          indices.clear();
          rng_->GetUniformRandomSample(0, model.Size() - 1, Model::MIN_SIZE, indices);
          model.AddSample(indices, max_error);
        }

        if (model.Samples() > 0)
        {
          int32_t inlier_count = model.ProcessSamples(hypothesis, max_error);
          if (inlier_count > 0 && inlier_count > max_inliers)
          {
            max_inliers = inlier_count;
            Model::CopyTo(hypothesis, best_fit);

            // Recalculate breakout threshold to see if the fit is good enough.
            double ratio = inlier_count / static_cast<double>(model.Size());
            double p_no_outliers = 1.0 - std::pow(ratio, Model::MIN_SIZE);
            if (p_no_outliers == 0)
            {
              breakout = 0;
            }
            else if (p_no_outliers < .9999)
            {
              breakout = std::log(1 - confidence) / std::log(p_no_outliers);
            }
          }
        }
      }

      if (max_inliers > 0)
      {
        model.GetInliers(best_fit, max_error, inliers);
      }
      return best_fit;
    }

  private:
    RandomGeneratorPtr rng_;
  };
}

#endif  // MATH_UTIL_RANSAC_H_