1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package com.irurueta.geometry.refiners;
17
18 import com.irurueta.algebra.Matrix;
19 import com.irurueta.geometry.CoordinatesType;
20 import com.irurueta.geometry.EuclideanTransformation3D;
21 import com.irurueta.geometry.Point3D;
22 import com.irurueta.geometry.Quaternion;
23 import com.irurueta.geometry.Rotation3DType;
24 import com.irurueta.geometry.estimators.LockedException;
25 import com.irurueta.geometry.estimators.NotReadyException;
26 import com.irurueta.numerical.EvaluationException;
27 import com.irurueta.numerical.GradientEstimator;
28 import com.irurueta.numerical.fitting.LevenbergMarquardtMultiDimensionFitter;
29 import com.irurueta.numerical.fitting.LevenbergMarquardtMultiDimensionFunctionEvaluator;
30 import com.irurueta.numerical.robust.InliersData;
31
32 import java.util.BitSet;
33 import java.util.List;
34
35
36
37
38
39
40
41
42
43 @SuppressWarnings("DuplicatedCode")
44 public class EuclideanTransformation3DRefiner extends
45 PairMatchesAndInliersDataRefiner<EuclideanTransformation3D, Point3D, Point3D> {
46
47
48
49
50 private final Point3D residualTestPoint = Point3D.create(CoordinatesType.HOMOGENEOUS_COORDINATES);
51
52
53
54
55 private Quaternion quaternion = new Quaternion();
56
57
58
59
60
61
62
63
64
65
66 private double refinementStandardDeviation;
67
68
69
70
71 public EuclideanTransformation3DRefiner() {
72 }
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88 public EuclideanTransformation3DRefiner(
89 final EuclideanTransformation3D initialEstimation, final boolean keepCovariance, final BitSet inliers,
90 final double[] residuals, final int numInliers, final List<Point3D> samples1, final List<Point3D> samples2,
91 final double refinementStandardDeviation) {
92 super(initialEstimation, keepCovariance, inliers, residuals, numInliers, samples1, samples2);
93 this.refinementStandardDeviation = refinementStandardDeviation;
94 }
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109 public EuclideanTransformation3DRefiner(
110 final EuclideanTransformation3D initialEstimation, final boolean keepCovariance,
111 final InliersData inliersData, final List<Point3D> samples1,
112 final List<Point3D> samples2, final double refinementStandardDeviation) {
113 super(initialEstimation, keepCovariance, inliersData, samples1, samples2);
114 this.refinementStandardDeviation = refinementStandardDeviation;
115 }
116
117
118
119
120
121
122
123
124
125
126
127
128 public double getRefinementStandardDeviation() {
129 return refinementStandardDeviation;
130 }
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145 public void setRefinementStandardDeviation(final double refinementStandardDeviation) throws LockedException {
146 if (isLocked()) {
147 throw new LockedException();
148 }
149 this.refinementStandardDeviation = refinementStandardDeviation;
150 }
151
152
153
154
155
156
157
158
159
160
161
162 @Override
163 public EuclideanTransformation3D refine() throws NotReadyException, LockedException, RefinerException {
164 final var result = new EuclideanTransformation3D();
165 refine(result);
166 return result;
167 }
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183 @Override
184 public boolean refine(final EuclideanTransformation3D result) throws NotReadyException, LockedException,
185 RefinerException {
186 if (isLocked()) {
187 throw new LockedException();
188 }
189 if (!isReady()) {
190 throw new NotReadyException();
191 }
192
193 locked = true;
194
195 if (listener != null) {
196 listener.onRefineStart(this, initialEstimation);
197 }
198
199 final var initialTotalResidual = totalResidual(initialEstimation);
200
201 try {
202
203 final var initParams = new double[Quaternion.N_PARAMS + EuclideanTransformation3D.NUM_TRANSLATION_COORDS];
204
205 if (initialEstimation.getRotation().getType() == Rotation3DType.QUATERNION) {
206 quaternion = (Quaternion) initialEstimation.getRotation();
207 } else {
208 quaternion = initialEstimation.getRotation().toQuaternion();
209 }
210 quaternion.normalize();
211
212
213 initParams[0] = quaternion.getA();
214 initParams[1] = quaternion.getB();
215 initParams[2] = quaternion.getC();
216 initParams[3] = quaternion.getD();
217
218 System.arraycopy(initialEstimation.getTranslation(), 0, initParams, Quaternion.N_PARAMS,
219 EuclideanTransformation3D.NUM_TRANSLATION_COORDS);
220
221
222 final var y = new double[numInliers];
223
224 final var nDims = 2 * Point3D.POINT3D_HOMOGENEOUS_COORDINATES_LENGTH;
225 final var x = new Matrix(numInliers, nDims);
226 final var nSamples = inliers.length();
227 var pos = 0;
228 for (var i = 0; i < nSamples; i++) {
229 if (inliers.get(i)) {
230
231 final var inputPoint = samples1.get(i);
232 final var outputPoint = samples2.get(i);
233 inputPoint.normalize();
234 outputPoint.normalize();
235 x.setElementAt(pos, 0, inputPoint.getHomX());
236 x.setElementAt(pos, 1, inputPoint.getHomY());
237 x.setElementAt(pos, 2, inputPoint.getHomZ());
238 x.setElementAt(pos, 3, inputPoint.getHomW());
239 x.setElementAt(pos, 4, outputPoint.getHomX());
240 x.setElementAt(pos, 5, outputPoint.getHomY());
241 x.setElementAt(pos, 6, outputPoint.getHomZ());
242 x.setElementAt(pos, 7, outputPoint.getHomW());
243
244 y[pos] = residuals[i];
245 pos++;
246 }
247 }
248
249 final var evaluator = new LevenbergMarquardtMultiDimensionFunctionEvaluator() {
250
251 private final Point3D inputPoint = Point3D.create(CoordinatesType.HOMOGENEOUS_COORDINATES);
252
253 private final Point3D outputPoint = Point3D.create(CoordinatesType.HOMOGENEOUS_COORDINATES);
254
255 private final EuclideanTransformation3D transformation = new EuclideanTransformation3D();
256
257 private final GradientEstimator gradientEstimator = new GradientEstimator(params -> {
258
259 quaternion.setA(params[0]);
260 quaternion.setB(params[1]);
261 quaternion.setC(params[2]);
262 quaternion.setD(params[3]);
263 transformation.setRotation(quaternion);
264
265 System.arraycopy(params, Quaternion.N_PARAMS, transformation.getTranslation(), 0,
266 EuclideanTransformation3D.NUM_TRANSLATION_COORDS);
267
268 return residual(transformation, inputPoint, outputPoint);
269 });
270
271 @Override
272 public int getNumberOfDimensions() {
273 return nDims;
274 }
275
276 @Override
277 public double[] createInitialParametersArray() {
278 return initParams;
279 }
280
281 @Override
282 public double evaluate(final int i, final double[] point, final double[] params,
283 final double[] derivatives) throws EvaluationException {
284 inputPoint.setHomogeneousCoordinates(point[0], point[1], point[2], point[3]);
285 outputPoint.setHomogeneousCoordinates(point[4], point[5], point[6], point[7]);
286
287
288 quaternion.setA(params[0]);
289 quaternion.setB(params[1]);
290 quaternion.setC(params[2]);
291 quaternion.setD(params[3]);
292 transformation.setRotation(quaternion);
293
294 System.arraycopy(params, Quaternion.N_PARAMS, transformation.getTranslation(), 0,
295 EuclideanTransformation3D.NUM_TRANSLATION_COORDS);
296
297 final var y = residual(transformation, inputPoint, outputPoint);
298 gradientEstimator.gradient(params, derivatives);
299
300 return y;
301 }
302 };
303
304 final LevenbergMarquardtMultiDimensionFitter fitter = new LevenbergMarquardtMultiDimensionFitter(evaluator,
305 x, y, getRefinementStandardDeviation());
306
307 fitter.fit();
308
309
310 final var params = fitter.getA();
311
312
313 quaternion.setA(params[0]);
314 quaternion.setB(params[1]);
315 quaternion.setC(params[2]);
316 quaternion.setD(params[3]);
317 result.setRotation(quaternion);
318 System.arraycopy(params, Quaternion.N_PARAMS, result.getTranslation(), 0,
319 EuclideanTransformation3D.NUM_TRANSLATION_COORDS);
320
321 if (keepCovariance) {
322
323 covariance = fitter.getCovar();
324 }
325
326 final var finalTotalResidual = totalResidual(result);
327 final var errorDecreased = finalTotalResidual < initialTotalResidual;
328
329 if (listener != null) {
330 listener.onRefineEnd(this, initialEstimation, result, errorDecreased);
331 }
332
333 return errorDecreased;
334
335 } catch (final Exception e) {
336 throw new RefinerException(e);
337 } finally {
338 locked = false;
339 }
340 }
341
342
343
344
345
346
347
348
349
350
351 private double residual(final EuclideanTransformation3D transformation, final Point3D inputPoint,
352 final Point3D outputPoint) {
353 inputPoint.normalize();
354 outputPoint.normalize();
355
356 transformation.transform(inputPoint, residualTestPoint);
357 return residualTestPoint.distanceTo(outputPoint);
358 }
359
360
361
362
363
364
365
366 private double totalResidual(final EuclideanTransformation3D transformation) {
367 var result = 0.0;
368
369 final var nSamples = inliers.length();
370 for (var i = 0; i < nSamples; i++) {
371 if (inliers.get(i)) {
372
373 final var inputPoint = samples1.get(i);
374 final var outputPoint = samples2.get(i);
375 inputPoint.normalize();
376 outputPoint.normalize();
377 result += residual(transformation, inputPoint, outputPoint);
378 }
379 }
380
381 return result;
382 }
383 }