1

I'm trying to use google's ceres solver (http://ceres-solver.org/) to calculate non-linear least squares trilateration (goal is indoor positioning with BLE beacons). My problem is that CERES gives results which have a significant error and comparing another solution which also uses the Levenberg-Marquardt algorithm it is evident that something is wrong with my ceres setup.

My starting point is this: https://nrr.mit.edu/sites/default/files/documents/Lab_11_Localization.html B part of the document. At first the library I used was this JAVA project: https://github.com/lemmingapex/Trilateration which uses the above mentioned LM algorithm to solve trilateration problems. The derivatives and jacobi matrices are precalculated/coded (I don't (yet) understand these, in order to move forward quickly unfortunately had to skip deeper understanding). With CERES (my 1st time using it) the AutoDiffCostFunction seemed a really easy way for me to define the problem which is like this:

Coordinate TrilaterationCalculator::ComputePosition2D(
    const std::list<std::pair<Coordinate, double>> &measurementPoints) {
  Problem problem;
  double x = 1.0;
  double y = 1.0;

  for (const auto &measurementPoint : measurementPoints) {
    problem.AddResidualBlock(new AutoDiffCostFunction<BeaconCostFunctor2D, 1, 1, 1>(new BeaconCostFunctor2D(measurementPoint)), nullptr, &x, &y);
  }

  Solver::Options options;
  options.minimizer_progress_to_stdout = false;
  options.minimizer_type = ceres::TRUST_REGION;
  // options.minimizer_type = ceres::LINE_SEARCH; // TODO
  //options.linear_solver_type = ceres::SPARSE_NORMAL_CHOLESKY;
  options.linear_solver_type = ceres::DENSE_QR;
  options.trust_region_strategy_type = ceres::LEVENBERG_MARQUARDT;
  options.logging_type = ceres::SILENT;
  options.minimizer_progress_to_stdout = false;

  options.function_tolerance = 1e-12;
  options.gradient_tolerance = 1e-12;
  options.parameter_tolerance = 1e-12;
  options.max_num_iterations = 1000;
  //options.max_solver_time_in_seconds = 1;
  //options.num_threads = 4;

  Solver::Summary summary;
  Solve(options, &problem, &summary);

  auto result = Coordinate(x, y, 1.4); //TODO proper handling of z coordinate...
  return result;
}

struct BeaconCostFunctor2D {

public:
  BeaconCostFunctor2D(const std::pair<Coordinate, double> &measurementPoint_)
      : measurementPoint(measurementPoint_) {}

  template <typename T>
  bool operator()(const T *const x, const T *const y, T *residual) const {
    //r^2-(x-x1)^2-(y-y1)^2 
    residual[0] = pow(measurementPoint.second, 2) - pow(x[0]-measurementPoint.first.getX(), 2) - pow(y[0]-measurementPoint.first.getY(), 2);
    return true;
  }

private:
  const std::pair<Coordinate, double> &measurementPoint;
};

Comparing some examples from the 2 solution:

Input (from JAVA code, C++ has the same values but this is shorter; numbers are in millimeter):

double[][] positions = new double[][]{{-24223,26072}, {-13446,16859}, {-20860,15693}, {-21019,25807}, {-17037,21467}, {-11449,15837}, {-3980,24447}, {-16639,15693}, {-21019,17803}, {-4439,21155}, {-12503,20343}, {-16878,24891}, {-24364,22343}, {-20979,21985}, {-17157,17883}, {-7836,16369}, {-12498,24971}, {-160,24931}, {-8860,24514}, {-8825,21002}, {-8769,18404}, };
double[] distances = new double[]{0.001,7874.97,4182.28,0.001,4382.07,3027.21,4380.63,5801.38,3222.07,6158.16,2676.96,2984.05,0.001,2388.27,3359.42,4153.79,2105.41,6676.31,2981.94,2385.64,2417.16,};
 double[] expectedPosition = new double[]{-24706.0, 26754.0};

The java trilateration library's solution: -23085.6 24505.1 (close to expected position)

Ceres solution: -13891.2, 22133.1 (way off)

(Also compared these 2 with other tests, in many both give the same (good) results. But these real-life data seem to "confuse" ceres and gives wrong results.)

I can think of 3 possible things where the problem lies:

1) ceres's automatic differentiation is not working properly (less likely I guess)

2) my problem setup in Ceres is wrong (most likely)

3) (something very stupid coding mistake somewhere?)

Could you please help me with what am I missing? (We moved on to use C++ because of technical requirements so that's why we need to replace this already working version of non-linear trilaterion in JAVA)

By the way isn't this solution calculating the derivatives each time this trilateration calculation is called? So then this introduces a significant delay compared to if I didn't use the autocostfunctor, right?

Thanks for any insight! (I tried joining ceres google groups but haven't been approved yet, hence asking here, as there were also some ceres-solver related questions)

bartfer
  • 111
  • 1
  • 8
  • Once you get the math right, be careful to test in a very small space, preferably outdoors with few obstacles for reflection or obstruction. Otherwise, you won't know if the problem is the math or signal noise. My experience is that trilateration based on BLE beacon RSSI gives very large errors on position estimates if the beacons are placed more than 2 meters from the receiver or if there are obstacles that cause reflection or signal attenuation. (Even the human body will do this, so for best results, put the receiver on a plastic tripod in a space within 2 meters of all transmitters.) – davidgyoung Nov 27 '20 at 16:00
  • I've read lot of your posts/comments regarding beacon positioning which were really helpful, so thanks for those as well! My problem with this approach is that you will have to examine and adapt the positioning system for each new location - similalry to fingerprinting. In which case any new source of signal noise can throw us off. So that's why I'm approaching this in a more general way.It may be that this way I'll not go below a certain precision.If that is not acceptable then I have to adapt to each new location. I can check the math's correctness by looking at (proper)input / output. – bartfer Dec 01 '20 at 09:59

1 Answers1

2

ceres optimize the sum of squared residuals. Instead, you provide squared value itself, so you optimize the 4th power of distances.

residual[0] = measurementPoint.second- sqrt(pow(x[0]-measurementPoint.first.getX(), 2) - pow(y[0]-measurementPoint.first.getY(), 2)); should work

Solon
  • 362
  • 1
  • 13
  • 1
    Thanks for your answer! Yes you are right regarding this, but it still doesn't give as good result as the JAVA lib. Since - I tried eigen library and using this eqution 1 - sqrt(pow(x - x0, 2) + pow(y - y0, 2)) / distance; and it gives even better result than the JAVA lib, but basically the same. I don't understand why this form of the equation is different than like this: sqrt(pow(x - x0, 2) + pow(y - y0, 2)) - distance; I will ask and try to clear this on math stackexchange and will get back with an explanation. Of course meanwhile if anyone knows, please answer. – bartfer Jan 04 '21 at 14:13