PageRenderTime 43ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/Branches/v1.0/SharpMap/Utilities/LeastSquaresTransform.cs

#
C# | 317 lines | 189 code | 25 blank | 103 comment | 12 complexity | fb8181d9140abbf60b07dbb507eb5559 MD5 | raw file
Possible License(s): LGPL-2.1
  1. // Copyright 2005, 2006 - Morten Nielsen (www.iter.dk)
  2. //
  3. // This file is part of SharpMap.
  4. // SharpMap is free software; you can redistribute it and/or modify
  5. // it under the terms of the GNU Lesser General Public License as published by
  6. // the Free Software Foundation; either version 2 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // SharpMap is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Lesser General Public License for more details.
  13. // You should have received a copy of the GNU Lesser General Public License
  14. // along with SharpMap; if not, write to the Free Software
  15. // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  16. using System;
  17. using System.Collections.Generic;
  18. using SharpMap.Geometries;
  19. namespace SharpMap.Utilities
  20. {
  21. using GeoAPI.Geometries;
  22. /// <summary>
  23. /// Calculates Affine and Helmert transformation using Least-Squares Regression of input and output points
  24. /// </summary>
  25. public class LeastSquaresTransform
  26. {
  27. private readonly List<Coordinate> inputs;
  28. private readonly List<Coordinate> outputs;
  29. /// <summary>
  30. /// Initialize Least Squares transformations
  31. /// </summary>
  32. public LeastSquaresTransform()
  33. {
  34. inputs = new List<Coordinate>();
  35. outputs = new List<Coordinate>();
  36. }
  37. /// <summary>
  38. /// Adds an input and output value pair to the collection
  39. /// </summary>
  40. /// <param name="input"></param>
  41. /// <param name="output"></param>
  42. public void AddInputOutputPoint(Coordinate input, Coordinate output)
  43. {
  44. inputs.Add(input);
  45. outputs.Add(output);
  46. }
  47. /// <summary>
  48. /// Removes input and output value pair at the specified index
  49. /// </summary>
  50. /// <param name="i"></param>
  51. public void RemoveInputOutputPointAt(int i)
  52. {
  53. inputs.RemoveAt(i);
  54. outputs.RemoveAt(i);
  55. }
  56. /// <summary>
  57. /// Gets the input point value at the specified index
  58. /// </summary>
  59. /// <param name="i">index</param>
  60. /// <returns>Input point value a index 'i'</returns>
  61. public Coordinate GetInputPoint(int i)
  62. {
  63. return inputs[i];
  64. }
  65. /// <summary>
  66. /// Sets the input point value at the specified index
  67. /// </summary>
  68. /// <param name="p">Point value</param>
  69. /// <param name="i">index</param>
  70. public void SetInputPointAt(Coordinate p, int i)
  71. {
  72. inputs[i] = p;
  73. }
  74. /// <summary>
  75. /// Gets the output point value at the specified index
  76. /// </summary>
  77. /// <param name="i">index</param>
  78. /// <returns>Output point value a index 'i'</returns>
  79. public Coordinate GetOutputPoint(int i)
  80. {
  81. return outputs[i];
  82. }
  83. /// <summary>
  84. /// Sets the output point value at the specified index
  85. /// </summary>
  86. /// <param name="p">Point value</param>
  87. /// <param name="i">index</param>
  88. public void SetOutputPointAt(Coordinate p, int i)
  89. {
  90. outputs[i] = p;
  91. }
  92. /// <summary>
  93. /// Return an array with the six affine transformation parameters {a,b,c,d,e,f} and the sum of the squares of the residuals (s0)
  94. /// </summary>
  95. /// <remarks>
  96. /// a,b defines scale vector 1 of coordinate system, d,e scale vector 2. c,f defines offset.
  97. /// <para>
  98. /// Converting from input (X,Y) to output coordinate system (X',Y') is done by:
  99. /// X' = a*X + b*Y + c, Y' = d*X + e*Y + f
  100. /// </para>
  101. /// <para>
  102. /// Transformation based on Mikhail "Introduction to Modern Photogrammetry" p. 399-300.
  103. /// Extended to arbitrary number of measurements by M. Nielsen
  104. /// </para>
  105. /// </remarks>
  106. /// <returns>Array with the six transformation parameters and sum of squared residuals: a,b,c,d,e,f,s0</returns>
  107. public double[] GetAffineTransformation()
  108. {
  109. if (inputs.Count < 3)
  110. throw (new Exception("At least 3 measurements required to calculate affine transformation"));
  111. //double precision isn't always enough when transforming large numbers.
  112. //Lets subtract some mean values and add them later again:
  113. //Find approximate center values:
  114. Coordinate meanInput = new Coordinate(0, 0);
  115. Coordinate meanOutput = new Coordinate(0, 0);
  116. for (int i = 0; i < inputs.Count; i++)
  117. {
  118. meanInput.X += inputs[i].X;
  119. meanInput.Y += inputs[i].Y;
  120. meanOutput.X += outputs[i].X;
  121. meanOutput.Y += outputs[i].Y;
  122. }
  123. meanInput.X = Math.Round(meanInput.X/inputs.Count);
  124. meanInput.Y = Math.Round(meanInput.Y/inputs.Count);
  125. meanOutput.X = Math.Round(meanOutput.X/inputs.Count);
  126. meanOutput.Y = Math.Round(meanOutput.Y/inputs.Count);
  127. double[][] N = CreateMatrix(3, 3);
  128. //Create normal equation: transpose(B)*B
  129. //B: matrix of calibrated values. Example of row in B: [x , y , -1]
  130. for (int i = 0; i < inputs.Count; i++)
  131. {
  132. //Subtract mean values
  133. inputs[i].X -= meanInput.X;
  134. inputs[i].Y -= meanInput.Y;
  135. outputs[i].X -= meanOutput.X;
  136. outputs[i].Y -= meanOutput.Y;
  137. //Calculate summed values
  138. N[0][0] += Math.Pow(inputs[i].X, 2);
  139. N[0][1] += inputs[i].X*inputs[i].Y;
  140. N[0][2] += -inputs[i].X;
  141. N[1][1] += Math.Pow(inputs[i].Y, 2);
  142. N[1][2] += -inputs[i].Y;
  143. }
  144. N[2][2] = inputs.Count;
  145. double[] t1 = new double[3];
  146. double[] t2 = new double[3];
  147. for (int i = 0; i < inputs.Count; i++)
  148. {
  149. t1[0] += inputs[i].X*outputs[i].X;
  150. t1[1] += inputs[i].Y*outputs[i].X;
  151. t1[2] += -outputs[i].X;
  152. t2[0] += inputs[i].X*outputs[i].Y;
  153. t2[1] += inputs[i].Y*outputs[i].Y;
  154. t2[2] += -outputs[i].Y;
  155. }
  156. double[] trans = new double[7];
  157. // Solve equation N = transpose(B)*t1
  158. double frac = 1/
  159. (-N[0][0]*N[1][1]*N[2][2] + N[0][0]*Math.Pow(N[1][2], 2) + Math.Pow(N[0][1], 2)*N[2][2] -
  160. 2*N[1][2]*N[0][1]*N[0][2] + N[1][1]*Math.Pow(N[0][2], 2));
  161. trans[0] = (-N[0][1]*N[1][2]*t1[2] + N[0][1]*t1[1]*N[2][2] - N[0][2]*N[1][2]*t1[1] + N[0][2]*N[1][1]*t1[2] -
  162. t1[0]*N[1][1]*N[2][2] + t1[0]*Math.Pow(N[1][2], 2))*frac;
  163. trans[1] = (-N[0][1]*N[0][2]*t1[2] + N[0][1]*t1[0]*N[2][2] + N[0][0]*N[1][2]*t1[2] - N[0][0]*t1[1]*N[2][2] -
  164. N[0][2]*N[1][2]*t1[0] + Math.Pow(N[0][2], 2)*t1[1])*frac;
  165. trans[2] =
  166. -(-N[1][2]*N[0][1]*t1[0] + Math.Pow(N[0][1], 2)*t1[2] + N[0][0]*N[1][2]*t1[1] - N[0][0]*N[1][1]*t1[2] -
  167. N[0][2]*N[0][1]*t1[1] + N[1][1]*N[0][2]*t1[0])*frac;
  168. trans[2] += - meanOutput.X + meanInput.X;
  169. // Solve equation N = transpose(B)*t2
  170. trans[3] = (-N[0][1]*N[1][2]*t2[2] + N[0][1]*t2[1]*N[2][2] - N[0][2]*N[1][2]*t2[1] + N[0][2]*N[1][1]*t2[2] -
  171. t2[0]*N[1][1]*N[2][2] + t2[0]*Math.Pow(N[1][2], 2))*frac;
  172. trans[4] = (-N[0][1]*N[0][2]*t2[2] + N[0][1]*t2[0]*N[2][2] + N[0][0]*N[1][2]*t2[2] - N[0][0]*t2[1]*N[2][2] -
  173. N[0][2]*N[1][2]*t2[0] + Math.Pow(N[0][2], 2)*t2[1])*frac;
  174. trans[5] =
  175. -(-N[1][2]*N[0][1]*t2[0] + Math.Pow(N[0][1], 2)*t2[2] + N[0][0]*N[1][2]*t2[1] - N[0][0]*N[1][1]*t2[2] -
  176. N[0][2]*N[0][1]*t2[1] + N[1][1]*N[0][2]*t2[0])*frac;
  177. trans[5] += - meanOutput.Y + meanInput.Y;
  178. //Restore values
  179. for (int i = 0; i < inputs.Count; i++)
  180. {
  181. inputs[i].X += meanInput.X;
  182. inputs[i].Y += meanInput.Y;
  183. outputs[i].X += meanOutput.X;
  184. outputs[i].Y += meanOutput.Y;
  185. }
  186. //Calculate s0
  187. double s0 = 0;
  188. for (int i = 0; i < inputs.Count; i++)
  189. {
  190. double x = inputs[i].X*trans[0] + inputs[i].Y*trans[1] + trans[2];
  191. double y = inputs[i].X*trans[3] + inputs[i].Y*trans[4] + trans[5];
  192. s0 += Math.Pow(x - outputs[i].X, 2) + Math.Pow(y - outputs[i].Y, 2);
  193. }
  194. trans[6] = Math.Sqrt(s0)/(inputs.Count);
  195. return trans;
  196. }
  197. /// <summary>
  198. /// Calculates the four helmert transformation parameters {a,b,c,d} and the sum of the squares of the residuals (s0)
  199. /// </summary>
  200. /// <remarks>
  201. /// <para>
  202. /// a,b defines scale vector 1 of coordinate system, d,e scale vector 2.
  203. /// c,f defines offset.
  204. /// </para>
  205. /// <para>
  206. /// Converting from input (X,Y) to output coordinate system (X',Y') is done by:
  207. /// X' = a*X + b*Y + c, Y' = -b*X + a*Y + d
  208. /// </para>
  209. /// <para>This is a transformation initially based on the affine transformation but slightly simpler.</para>
  210. /// </remarks>
  211. /// <returns>Array with the four transformation parameters, and sum of squared residuals: a,b,c,d,s0</returns>
  212. public double[] GetHelmertTransformation()
  213. {
  214. if (inputs.Count < 2)
  215. throw (new Exception("At least 2 measurements required to calculate helmert transformation"));
  216. //double precision isn't always enough. Lets subtract some mean values and add them later again:
  217. //Find approximate center values:
  218. Coordinate meanInput = new Coordinate(0, 0);
  219. Coordinate meanOutput = new Coordinate(0, 0);
  220. for (int i = 0; i < inputs.Count; i++)
  221. {
  222. meanInput.X += inputs[i].X;
  223. meanInput.Y += inputs[i].Y;
  224. meanOutput.X += outputs[i].X;
  225. meanOutput.Y += outputs[i].Y;
  226. }
  227. meanInput.X = Math.Round(meanInput.X/inputs.Count);
  228. meanInput.Y = Math.Round(meanInput.Y/inputs.Count);
  229. meanOutput.X = Math.Round(meanOutput.X/inputs.Count);
  230. meanOutput.Y = Math.Round(meanOutput.Y/inputs.Count);
  231. double b00 = 0;
  232. double b02 = 0;
  233. double b03 = 0;
  234. double[] t = new double[4];
  235. for (int i = 0; i < inputs.Count; i++)
  236. {
  237. //Subtract mean values
  238. inputs[i].X -= meanInput.X;
  239. inputs[i].Y -= meanInput.Y;
  240. outputs[i].X -= meanOutput.X;
  241. outputs[i].Y -= meanOutput.Y;
  242. //Calculate summed values
  243. b00 += Math.Pow(inputs[i].X, 2) + Math.Pow(inputs[i].Y, 2);
  244. b02 -= inputs[i].X;
  245. b03 -= inputs[i].Y;
  246. t[0] += -(inputs[i].X*outputs[i].X) - (inputs[i].Y*outputs[i].Y);
  247. t[1] += -(inputs[i].Y*outputs[i].X) + (inputs[i].X*outputs[i].Y);
  248. t[2] += outputs[i].X;
  249. t[3] += outputs[i].Y;
  250. }
  251. double frac = 1/(-inputs.Count*b00 + Math.Pow(b02, 2) + Math.Pow(b03, 2));
  252. double[] result = new double[5];
  253. result[0] = (-inputs.Count*t[0] + b02*t[2] + b03*t[3])*frac;
  254. result[1] = (-inputs.Count*t[1] + b03*t[2] - b02*t[3])*frac;
  255. result[2] = (b02*t[0] + b03*t[1] - t[2]*b00)*frac + meanOutput.X;
  256. result[3] = (b03*t[0] - b02*t[1] - t[3]*b00)*frac + meanOutput.Y;
  257. //Restore values
  258. for (int i = 0; i < inputs.Count; i++)
  259. {
  260. inputs[i].X += meanInput.X;
  261. inputs[i].Y += meanInput.Y;
  262. outputs[i].X += meanOutput.X;
  263. outputs[i].Y += meanOutput.Y;
  264. }
  265. //Calculate s0
  266. double s0 = 0;
  267. for (int i = 0; i < inputs.Count; i++)
  268. {
  269. double x = inputs[i].X*result[0] + inputs[i].Y*result[1] + result[2];
  270. double y = -inputs[i].X*result[1] + inputs[i].Y*result[0] + result[3];
  271. s0 += Math.Pow(x - outputs[i].X, 2) + Math.Pow(y - outputs[i].Y, 2);
  272. }
  273. result[4] = Math.Sqrt(s0)/(inputs.Count);
  274. return result;
  275. }
  276. /// <summary>
  277. /// Creates an n x m matrix of doubles
  278. /// </summary>
  279. /// <param name="n">width of matrix</param>
  280. /// <param name="m">height of matrix</param>
  281. /// <returns>n*m matrix</returns>
  282. private double[][] CreateMatrix(int n, int m)
  283. {
  284. double[][] N = new double[n][];
  285. for (int i = 0; i < n; i++)
  286. {
  287. N[i] = new double[m];
  288. }
  289. return N;
  290. }
  291. }
  292. }