RansacHomographyEstimator.cs 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. namespace VisualMath.Accord.Imaging
  2. {
  3. using System;
  4. using System.Drawing;
  5. using VisualMath.Accord.MachineLearning;
  6. using VisualMath.Accord.Math;
  7. using AForge;
  8. /// <summary>
  9. /// RANSAC Robust Homography Matrix Estimator.
  10. /// </summary>
  11. ///
  12. /// <remarks>
  13. /// <para>
  14. /// Fitting a homography using RANSAC is pretty straightforward. Being a iterative method,
  15. /// in a single iteration a random sample of four correspondences is selected from the
  16. /// given correspondence points and a homography H is then computed from those points.</para>
  17. /// <para>
  18. /// The original points are then transformed using this homography and their distances to
  19. /// where those transforms should be is then computed and matching points can classified
  20. /// as inliers and non-matching points as outliers.</para>
  21. /// <para>
  22. /// After a given number of iterations, the iteration which produced the largest number
  23. /// of inliers is then selected as the best estimation for H.</para>
  24. ///
  25. /// <para>
  26. /// References:
  27. /// <list type="bullet">
  28. /// <item><description>
  29. /// http://www.cs.ubc.ca/grads/resources/thesis/May09/Dubrofsky_Elan.pdf </description></item>
  30. /// <item><description>
  31. /// http://www.cc.gatech.edu/classes/AY2005/cs4495_fall/assignment4.pdf </description></item>
  32. /// </list></para>
  33. /// </remarks>
  34. ///
  35. public class RansacHomographyEstimator
  36. {
  37. private RANSAC<MatrixH> ransac;
  38. private int[] inliers;
  39. private PointF[] pointSet1;
  40. private PointF[] pointSet2;
  41. /// <summary>
  42. /// Gets the RANSAC estimator used.
  43. /// </summary>
  44. public RANSAC<MatrixH> Ransac
  45. {
  46. get { return this.ransac; }
  47. }
  48. /// <summary>
  49. /// Gets the final set of inliers detected by RANSAC.
  50. /// </summary>
  51. public int[] Inliers
  52. {
  53. get { return inliers; }
  54. }
  55. /// <summary>
  56. /// Creates a new RANSAC homography estimator.
  57. /// </summary>
  58. /// <param name="threshold">Inlier threshold.</param>
  59. /// <param name="probability">Inlier probability.</param>
  60. public RansacHomographyEstimator(double threshold, double probability)
  61. {
  62. // Create a new RANSAC with the selected threshold
  63. ransac = new RANSAC<MatrixH>(4, threshold, probability);
  64. // Set RANSAC functions
  65. ransac.Fitting = homography;
  66. ransac.Degenerate = degenerate;
  67. ransac.Distances = distance;
  68. }
  69. /// <summary>
  70. /// Matches two sets of points using RANSAC.
  71. /// </summary>
  72. /// <returns>The homography matrix matching x1 and x2.</returns>
  73. public MatrixH Estimate(Point[] points1, Point[] points2)
  74. {
  75. // Initial argument checkings
  76. if (points1.Length != points2.Length)
  77. throw new ArgumentException("The number of points should be equal.");
  78. if (points1.Length < 4)
  79. throw new ArgumentException("At least four points are required to fit an homography");
  80. PointF[] p1 = new PointF[points1.Length];
  81. PointF[] p2 = new PointF[points2.Length];
  82. for (int i = 0; i < points1.Length; i++)
  83. {
  84. p1[i] = new PointF(points1[i].X, points1[i].Y);
  85. p2[i] = new PointF(points2[i].X, points2[i].Y);
  86. }
  87. return Estimate(p1, p2);
  88. }
  89. /// <summary>
  90. /// Matches two sets of points using RANSAC.
  91. /// </summary>
  92. /// <returns>The homography matrix matching x1 and x2.</returns>
  93. public MatrixH Estimate(IntPoint[] points1, IntPoint[] points2)
  94. {
  95. // Initial argument checkings
  96. if (points1.Length != points2.Length)
  97. throw new ArgumentException("The number of points should be equal.");
  98. if (points1.Length < 4)
  99. throw new ArgumentException("At least four points are required to fit an homography");
  100. PointF[] p1 = new PointF[points1.Length];
  101. PointF[] p2 = new PointF[points2.Length];
  102. for (int i = 0; i < points1.Length; i++)
  103. {
  104. p1[i] = new PointF(points1[i].X, points1[i].Y);
  105. p2[i] = new PointF(points2[i].X, points2[i].Y);
  106. }
  107. return Estimate(p1, p2);
  108. }
  109. /// <summary>
  110. /// Matches two sets of points using RANSAC.
  111. /// </summary>
  112. /// <returns>The homography matrix matching x1 and x2.</returns>
  113. public MatrixH Estimate(PointF[] points1, PointF[] points2)
  114. {
  115. // Initial argument checkings
  116. if (points1.Length != points2.Length)
  117. throw new ArgumentException("The number of points should be equal.");
  118. if (points1.Length < 4)
  119. throw new ArgumentException("At least four points are required to fit an homography");
  120. // Normalize each set of points so that the origin is
  121. // at centroid and mean distance from origin is sqrt(2).
  122. MatrixH T1, T2;
  123. this.pointSet1 = Tools.Normalize(points1, out T1);
  124. this.pointSet2 = Tools.Normalize(points2, out T2);
  125. // Compute RANSAC and find the inlier points
  126. MatrixH H = ransac.Compute(points1.Length, out inliers);
  127. if (inliers == null || inliers.Length < 4)
  128. //throw new Exception("RANSAC could not find enough points to fit an homography.");
  129. return null;
  130. // Compute the final homography considering all inliers
  131. H = homography(inliers);
  132. // Denormalise
  133. H = T2.Inverse() * (H * T1);
  134. return H;
  135. }
  136. /// <summary>
  137. /// Estimates a homography with the given points.
  138. /// </summary>
  139. private MatrixH homography(int[] points)
  140. {
  141. // Retrieve the original points
  142. PointF[] x1 = this.pointSet1.Submatrix(points);
  143. PointF[] x2 = this.pointSet2.Submatrix(points);
  144. // Compute the homography
  145. return Tools.Homography(x1, x2);
  146. }
  147. /// <summary>
  148. /// Compute inliers using the Symmetric Transfer Error,
  149. /// </summary>
  150. private int[] distance(MatrixH H, double t)
  151. {
  152. int n = pointSet1.Length;
  153. // Compute the projections (both directions)
  154. PointF[] p1 = H.TransformPoints(pointSet1);
  155. PointF[] p2 = H.Inverse().TransformPoints(pointSet2);
  156. // Compute the distances
  157. double[] d2 = new double[n];
  158. for (int i = 0; i < n; i++)
  159. {
  160. // Compute the distance as
  161. float ax = pointSet1[i].X - p2[i].X;
  162. float ay = pointSet1[i].Y - p2[i].Y;
  163. float bx = pointSet2[i].X - p1[i].X;
  164. float by = pointSet2[i].Y - p1[i].Y;
  165. d2[i] = (ax * ax) + (ay * ay) + (bx * bx) + (by * by);
  166. }
  167. // Find and return the inliers
  168. return Matrix.Find(d2, z => z < t);
  169. }
  170. /// <summary>
  171. /// Checks if the selected points will result in a degenerate homography.
  172. /// </summary>
  173. private bool degenerate(int[] points)
  174. {
  175. PointF[] x1 = this.pointSet1.Submatrix(points);
  176. PointF[] x2 = this.pointSet2.Submatrix(points);
  177. // If any three of the four points in each set is colinear,
  178. // the resulting homography matrix will be degenerate.
  179. return Tools.Colinear(x1[0], x1[1], x1[2]) ||
  180. Tools.Colinear(x1[0], x1[1], x1[3]) ||
  181. Tools.Colinear(x1[0], x1[2], x1[3]) ||
  182. Tools.Colinear(x1[1], x1[2], x1[3]) ||
  183. Tools.Colinear(x2[0], x2[1], x2[2]) ||
  184. Tools.Colinear(x2[0], x2[1], x2[3]) ||
  185. Tools.Colinear(x2[0], x2[2], x2[3]) ||
  186. Tools.Colinear(x2[1], x2[2], x2[3]);
  187. }
  188. }
  189. }