namespace VisualMath.Accord.Imaging
{
using System;
using System.Drawing;
using VisualMath.Accord.MachineLearning;
using VisualMath.Accord.Math;
using AForge;
///
/// RANSAC Robust Homography Matrix Estimator.
///
///
///
///
/// Fitting a homography using RANSAC is pretty straightforward. Being a iterative method,
/// in a single iteration a random sample of four correspondences is selected from the
/// given correspondence points and a homography H is then computed from those points.
///
/// The original points are then transformed using this homography and their distances to
/// where those transforms should be is then computed and matching points can classified
/// as inliers and non-matching points as outliers.
///
/// After a given number of iterations, the iteration which produced the largest number
/// of inliers is then selected as the best estimation for H.
///
///
/// References:
///
/// -
/// http://www.cs.ubc.ca/grads/resources/thesis/May09/Dubrofsky_Elan.pdf
/// -
/// http://www.cc.gatech.edu/classes/AY2005/cs4495_fall/assignment4.pdf
///
///
///
public class RansacHomographyEstimator
{
private RANSAC ransac;
private int[] inliers;
private PointF[] pointSet1;
private PointF[] pointSet2;
///
/// Gets the RANSAC estimator used.
///
public RANSAC Ransac
{
get { return this.ransac; }
}
///
/// Gets the final set of inliers detected by RANSAC.
///
public int[] Inliers
{
get { return inliers; }
}
///
/// Creates a new RANSAC homography estimator.
///
/// Inlier threshold.
/// Inlier probability.
public RansacHomographyEstimator(double threshold, double probability)
{
// Create a new RANSAC with the selected threshold
ransac = new RANSAC(4, threshold, probability);
// Set RANSAC functions
ransac.Fitting = homography;
ransac.Degenerate = degenerate;
ransac.Distances = distance;
}
///
/// Matches two sets of points using RANSAC.
///
/// The homography matrix matching x1 and x2.
public MatrixH Estimate(Point[] points1, Point[] points2)
{
// Initial argument checkings
if (points1.Length != points2.Length)
throw new ArgumentException("The number of points should be equal.");
if (points1.Length < 4)
throw new ArgumentException("At least four points are required to fit an homography");
PointF[] p1 = new PointF[points1.Length];
PointF[] p2 = new PointF[points2.Length];
for (int i = 0; i < points1.Length; i++)
{
p1[i] = new PointF(points1[i].X, points1[i].Y);
p2[i] = new PointF(points2[i].X, points2[i].Y);
}
return Estimate(p1, p2);
}
///
/// Matches two sets of points using RANSAC.
///
/// The homography matrix matching x1 and x2.
public MatrixH Estimate(IntPoint[] points1, IntPoint[] points2)
{
// Initial argument checkings
if (points1.Length != points2.Length)
throw new ArgumentException("The number of points should be equal.");
if (points1.Length < 4)
throw new ArgumentException("At least four points are required to fit an homography");
PointF[] p1 = new PointF[points1.Length];
PointF[] p2 = new PointF[points2.Length];
for (int i = 0; i < points1.Length; i++)
{
p1[i] = new PointF(points1[i].X, points1[i].Y);
p2[i] = new PointF(points2[i].X, points2[i].Y);
}
return Estimate(p1, p2);
}
///
/// Matches two sets of points using RANSAC.
///
/// The homography matrix matching x1 and x2.
public MatrixH Estimate(PointF[] points1, PointF[] points2)
{
// Initial argument checkings
if (points1.Length != points2.Length)
throw new ArgumentException("The number of points should be equal.");
if (points1.Length < 4)
throw new ArgumentException("At least four points are required to fit an homography");
// Normalize each set of points so that the origin is
// at centroid and mean distance from origin is sqrt(2).
MatrixH T1, T2;
this.pointSet1 = Tools.Normalize(points1, out T1);
this.pointSet2 = Tools.Normalize(points2, out T2);
// Compute RANSAC and find the inlier points
MatrixH H = ransac.Compute(points1.Length, out inliers);
if (inliers == null || inliers.Length < 4)
//throw new Exception("RANSAC could not find enough points to fit an homography.");
return null;
// Compute the final homography considering all inliers
H = homography(inliers);
// Denormalise
H = T2.Inverse() * (H * T1);
return H;
}
///
/// Estimates a homography with the given points.
///
private MatrixH homography(int[] points)
{
// Retrieve the original points
PointF[] x1 = this.pointSet1.Submatrix(points);
PointF[] x2 = this.pointSet2.Submatrix(points);
// Compute the homography
return Tools.Homography(x1, x2);
}
///
/// Compute inliers using the Symmetric Transfer Error,
///
private int[] distance(MatrixH H, double t)
{
int n = pointSet1.Length;
// Compute the projections (both directions)
PointF[] p1 = H.TransformPoints(pointSet1);
PointF[] p2 = H.Inverse().TransformPoints(pointSet2);
// Compute the distances
double[] d2 = new double[n];
for (int i = 0; i < n; i++)
{
// Compute the distance as
float ax = pointSet1[i].X - p2[i].X;
float ay = pointSet1[i].Y - p2[i].Y;
float bx = pointSet2[i].X - p1[i].X;
float by = pointSet2[i].Y - p1[i].Y;
d2[i] = (ax * ax) + (ay * ay) + (bx * bx) + (by * by);
}
// Find and return the inliers
return Matrix.Find(d2, z => z < t);
}
///
/// Checks if the selected points will result in a degenerate homography.
///
private bool degenerate(int[] points)
{
PointF[] x1 = this.pointSet1.Submatrix(points);
PointF[] x2 = this.pointSet2.Submatrix(points);
// If any three of the four points in each set is colinear,
// the resulting homography matrix will be degenerate.
return Tools.Colinear(x1[0], x1[1], x1[2]) ||
Tools.Colinear(x1[0], x1[1], x1[3]) ||
Tools.Colinear(x1[0], x1[2], x1[3]) ||
Tools.Colinear(x1[1], x1[2], x1[3]) ||
Tools.Colinear(x2[0], x2[1], x2[2]) ||
Tools.Colinear(x2[0], x2[1], x2[3]) ||
Tools.Colinear(x2[0], x2[2], x2[3]) ||
Tools.Colinear(x2[1], x2[2], x2[3]);
}
}
}