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.AlgebraException;
19 import com.irurueta.algebra.Matrix;
20 import com.irurueta.geometry.AffineTransformation3D;
21 import com.irurueta.geometry.Plane;
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 PlaneCorrespondenceAffineTransformation3DRefiner extends AffineTransformation3DRefiner<Plane, Plane> {
43
44
45
46
47 private final Plane residualTestPlane = new Plane();
48
49
50
51
52 public PlaneCorrespondenceAffineTransformation3DRefiner() {
53 }
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69 public PlaneCorrespondenceAffineTransformation3DRefiner(
70 final AffineTransformation3D initialEstimation, final boolean keepCovariance,
71 final BitSet inliers, final double[] residuals, final int numInliers, final List<Plane> samples1,
72 final List<Plane> samples2, 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 PlaneCorrespondenceAffineTransformation3DRefiner(
91 final AffineTransformation3D initialEstimation, final boolean keepCovariance, final InliersData inliersData,
92 final List<Plane> samples1, final List<Plane> 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 * Plane.PLANE_NUMBER_PARAMS;
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 inputPlane = samples1.get(i);
150 final var outputPlane = samples2.get(i);
151 inputPlane.normalize();
152 outputPlane.normalize();
153 x.setElementAt(pos, 0, inputPlane.getA());
154 x.setElementAt(pos, 1, inputPlane.getB());
155 x.setElementAt(pos, 2, inputPlane.getC());
156 x.setElementAt(pos, 3, inputPlane.getD());
157 x.setElementAt(pos, 4, outputPlane.getA());
158 x.setElementAt(pos, 5, outputPlane.getB());
159 x.setElementAt(pos, 6, outputPlane.getC());
160 x.setElementAt(pos, 7, outputPlane.getD());
161
162 y[pos] = residuals[i];
163 pos++;
164 }
165 }
166
167 final var evaluator = new LevenbergMarquardtMultiDimensionFunctionEvaluator() {
168
169 private final Plane inputPlane = new Plane();
170
171 private final Plane outputPlane = new Plane();
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, inputPlane, outputPlane);
185 });
186
187 @Override
188 public int getNumberOfDimensions() {
189 return nDims;
190 }
191
192 @Override
193 public double[] createInitialParametersArray() {
194 return initParams;
195 }
196
197 @Override
198 public double evaluate(final int i, final double[] point, final double[] params,
199 final double[] derivatives) throws EvaluationException {
200 inputPlane.setParameters(point[0], point[1], point[2], point[3]);
201 outputPlane.setParameters(point[4], point[5], point[6], point[7]);
202
203
204 System.arraycopy(params, 0, transformation.getA().getBuffer(), 0,
205 AffineTransformation3D.INHOM_COORDS * AffineTransformation3D.INHOM_COORDS);
206
207 System.arraycopy(params,
208 AffineTransformation3D.INHOM_COORDS * AffineTransformation3D.INHOM_COORDS,
209 transformation.getTranslation(), 0, AffineTransformation3D.NUM_TRANSLATION_COORDS);
210
211 final var y = residual(transformation, inputPlane, outputPlane);
212 gradientEstimator.gradient(params, derivatives);
213
214 return y;
215 }
216 };
217
218 final var fitter = new LevenbergMarquardtMultiDimensionFitter(evaluator, x, y,
219 getRefinementStandardDeviation());
220
221 fitter.fit();
222
223
224 final var params = fitter.getA();
225
226
227
228
229 System.arraycopy(params, 0, result.getA().getBuffer(), 0,
230 AffineTransformation3D.INHOM_COORDS * AffineTransformation3D.INHOM_COORDS);
231
232 System.arraycopy(params, AffineTransformation3D.INHOM_COORDS * AffineTransformation3D.INHOM_COORDS,
233 result.getTranslation(), 0, AffineTransformation3D.NUM_TRANSLATION_COORDS);
234
235 if (keepCovariance) {
236
237 covariance = fitter.getCovar();
238 }
239
240 final var finalTotalResidual = totalResidual(result);
241 final var errorDecreased = finalTotalResidual < initialTotalResidual;
242
243 if (listener != null) {
244 listener.onRefineEnd(this, initialEstimation, result, errorDecreased);
245 }
246
247 return errorDecreased;
248
249 } catch (final Exception e) {
250 throw new RefinerException(e);
251 } finally {
252 locked = false;
253 }
254 }
255
256
257
258
259
260
261
262
263
264
265 private double residual(final AffineTransformation3D transformation, final Plane inputPlane,
266 final Plane outputPlane) {
267 try {
268 inputPlane.normalize();
269 outputPlane.normalize();
270
271 transformation.transform(inputPlane, residualTestPlane);
272 return 1.0 - Math.abs(outputPlane.dotProduct(residualTestPlane));
273 } catch (final AlgebraException e) {
274 return 1.0;
275 }
276 }
277
278
279
280
281
282
283
284 private double totalResidual(final AffineTransformation3D transformation) {
285 var result = 0.0;
286
287 final var nSamples = inliers.length();
288 for (var i = 0; i < nSamples; i++) {
289 if (inliers.get(i)) {
290
291 final var inputPlane = samples1.get(i);
292 final var outputPlane = samples2.get(i);
293 inputPlane.normalize();
294 outputPlane.normalize();
295 result += residual(transformation, inputPlane, outputPlane);
296 }
297 }
298
299 return result;
300 }
301 }