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.AffineTransformation3D;
20 import com.irurueta.geometry.CoordinatesType;
21 import com.irurueta.geometry.Point3D;
22 import com.irurueta.geometry.estimators.LockedException;
23 import com.irurueta.geometry.estimators.NotReadyException;
24 import com.irurueta.numerical.EvaluationException;
25 import com.irurueta.numerical.GradientEstimator;
26 import com.irurueta.numerical.fitting.LevenbergMarquardtMultiDimensionFitter;
27 import com.irurueta.numerical.fitting.LevenbergMarquardtMultiDimensionFunctionEvaluator;
28 import com.irurueta.numerical.robust.InliersData;
29
30 import java.util.BitSet;
31 import java.util.List;
32
33
34
35
36
37
38
39
40
41 @SuppressWarnings("DuplicatedCode")
42 public class PointCorrespondenceAffineTransformation3DRefiner extends AffineTransformation3DRefiner<Point3D, Point3D> {
43
44
45
46
47 private final Point3D residualTestPoint = Point3D.create(CoordinatesType.HOMOGENEOUS_COORDINATES);
48
49
50
51
52 public PointCorrespondenceAffineTransformation3DRefiner() {
53 }
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69 public PointCorrespondenceAffineTransformation3DRefiner(
70 final AffineTransformation3D initialEstimation, final boolean keepCovariance, final BitSet inliers,
71 final double[] residuals, final int numInliers, final List<Point3D> samples1, final List<Point3D> samples2,
72 final double refinementStandardDeviation) {
73 super(initialEstimation, keepCovariance, inliers, residuals, numInliers, samples1, samples2,
74 refinementStandardDeviation);
75 }
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90 public PointCorrespondenceAffineTransformation3DRefiner(
91 final AffineTransformation3D initialEstimation, final boolean keepCovariance, final InliersData inliersData,
92 final List<Point3D> samples1, final List<Point3D> samples2, final double refinementStandardDeviation) {
93 super(initialEstimation, keepCovariance, inliersData, samples1, samples2, refinementStandardDeviation);
94 }
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110 @Override
111 public boolean refine(final AffineTransformation3D result) throws NotReadyException, LockedException,
112 RefinerException {
113 if (isLocked()) {
114 throw new LockedException();
115 }
116 if (!isReady()) {
117 throw new NotReadyException();
118 }
119
120 locked = true;
121
122 if (listener != null) {
123 listener.onRefineStart(this, initialEstimation);
124 }
125
126 final var initialTotalResidual = totalResidual(initialEstimation);
127
128 try {
129 final var initParams = new double[AffineTransformation3D.INHOM_COORDS * AffineTransformation3D.INHOM_COORDS
130 + AffineTransformation3D.NUM_TRANSLATION_COORDS];
131
132 System.arraycopy(initialEstimation.getA().getBuffer(), 0, initParams, 0,
133 AffineTransformation3D.INHOM_COORDS * AffineTransformation3D.INHOM_COORDS);
134
135 System.arraycopy(initialEstimation.getTranslation(), 0, initParams,
136 AffineTransformation3D.INHOM_COORDS * AffineTransformation3D.INHOM_COORDS,
137 AffineTransformation3D.NUM_TRANSLATION_COORDS);
138
139
140 final var y = new double[numInliers];
141
142 final var nDims = 2 * Point3D.POINT3D_HOMOGENEOUS_COORDINATES_LENGTH;
143 final var x = new Matrix(numInliers, nDims);
144 final var nSamples = inliers.length();
145 var pos = 0;
146 for (var i = 0; i < nSamples; i++) {
147 if (inliers.get(i)) {
148
149 final var inputPoint = samples1.get(i);
150 final var outputPoint = samples2.get(i);
151 inputPoint.normalize();
152 outputPoint.normalize();
153 x.setElementAt(pos, 0, inputPoint.getHomX());
154 x.setElementAt(pos, 1, inputPoint.getHomY());
155 x.setElementAt(pos, 2, inputPoint.getHomZ());
156 x.setElementAt(pos, 3, inputPoint.getHomW());
157 x.setElementAt(pos, 4, outputPoint.getHomX());
158 x.setElementAt(pos, 5, outputPoint.getHomY());
159 x.setElementAt(pos, 6, outputPoint.getHomZ());
160 x.setElementAt(pos, 7, outputPoint.getHomW());
161
162 y[pos] = residuals[i];
163 pos++;
164 }
165 }
166
167 final var evaluator = new LevenbergMarquardtMultiDimensionFunctionEvaluator() {
168
169 private final Point3D inputPoint = Point3D.create(CoordinatesType.HOMOGENEOUS_COORDINATES);
170
171 private final Point3D outputPoint = Point3D.create(CoordinatesType.HOMOGENEOUS_COORDINATES);
172
173 private final AffineTransformation3D transformation = new AffineTransformation3D();
174
175 private final GradientEstimator gradientEstimator = new GradientEstimator(params -> {
176
177 System.arraycopy(params, 0, transformation.getA().getBuffer(), 0,
178 AffineTransformation3D.INHOM_COORDS * AffineTransformation3D.INHOM_COORDS);
179
180 System.arraycopy(params,
181 AffineTransformation3D.INHOM_COORDS * AffineTransformation3D.INHOM_COORDS,
182 transformation.getTranslation(), 0, AffineTransformation3D.NUM_TRANSLATION_COORDS);
183
184 return residual(transformation, inputPoint, outputPoint);
185
186 });
187
188 @Override
189 public int getNumberOfDimensions() {
190 return nDims;
191 }
192
193 @Override
194 public double[] createInitialParametersArray() {
195 return initParams;
196 }
197
198 @Override
199 public double evaluate(final int i, final double[] point, final double[] params,
200 final double[] derivatives) throws EvaluationException {
201 inputPoint.setHomogeneousCoordinates(point[0], point[1], point[2], point[3]);
202 outputPoint.setHomogeneousCoordinates(point[4], point[5], point[6], point[7]);
203
204
205 System.arraycopy(params, 0, transformation.getA().getBuffer(), 0,
206 AffineTransformation3D.INHOM_COORDS * AffineTransformation3D.INHOM_COORDS);
207
208 System.arraycopy(params,
209 AffineTransformation3D.INHOM_COORDS * AffineTransformation3D.INHOM_COORDS,
210 transformation.getTranslation(), 0, AffineTransformation3D.NUM_TRANSLATION_COORDS);
211
212 final var y = residual(transformation, inputPoint, outputPoint);
213 gradientEstimator.gradient(params, derivatives);
214
215 return y;
216 }
217 };
218
219 final var fitter = new LevenbergMarquardtMultiDimensionFitter(evaluator, x, y,
220 getRefinementStandardDeviation());
221
222 fitter.fit();
223
224
225 final var params = fitter.getA();
226
227
228
229
230 System.arraycopy(params, 0, result.getA().getBuffer(), 0,
231 AffineTransformation3D.INHOM_COORDS * AffineTransformation3D.INHOM_COORDS);
232
233 System.arraycopy(params, AffineTransformation3D.INHOM_COORDS * AffineTransformation3D.INHOM_COORDS,
234 result.getTranslation(), 0, AffineTransformation3D.NUM_TRANSLATION_COORDS);
235
236 if (keepCovariance) {
237
238 covariance = fitter.getCovar();
239 }
240
241 final var finalTotalResidual = totalResidual(result);
242 final var errorDecreased = finalTotalResidual < initialTotalResidual;
243
244 if (listener != null) {
245 listener.onRefineEnd(this, initialEstimation, result, errorDecreased);
246 }
247
248 return errorDecreased;
249
250 } catch (final Exception e) {
251 throw new RefinerException(e);
252 } finally {
253 locked = false;
254 }
255 }
256
257
258
259
260
261
262
263
264
265
266 private double residual(final AffineTransformation3D transformation, final Point3D inputPoint,
267 final Point3D outputPoint) {
268 inputPoint.normalize();
269 outputPoint.normalize();
270
271 transformation.transform(inputPoint, residualTestPoint);
272 return residualTestPoint.distanceTo(outputPoint);
273 }
274
275
276
277
278
279
280
281 private double totalResidual(final AffineTransformation3D transformation) {
282 var result = 0.0;
283
284 final var nSamples = inliers.length();
285 for (var i = 0; i < nSamples; i++) {
286 if (inliers.get(i)) {
287
288 final var inputPoint = samples1.get(i);
289 final var outputPoint = samples2.get(i);
290 inputPoint.normalize();
291 outputPoint.normalize();
292 result += residual(transformation, inputPoint, outputPoint);
293 }
294 }
295
296 return result;
297 }
298 }