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.AffineTransformation2D;
20 import com.irurueta.geometry.CoordinatesType;
21 import com.irurueta.geometry.Point2D;
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 PointCorrespondenceAffineTransformation2DRefiner extends AffineTransformation2DRefiner<Point2D, Point2D> {
43
44
45
46
47 private final Point2D residualTestPoint = Point2D.create(CoordinatesType.HOMOGENEOUS_COORDINATES);
48
49
50
51
52 public PointCorrespondenceAffineTransformation2DRefiner() {
53 }
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69 public PointCorrespondenceAffineTransformation2DRefiner(
70 final AffineTransformation2D initialEstimation, final boolean keepCovariance, final BitSet inliers,
71 final double[] residuals, final int numInliers, final List<Point2D> samples1, final List<Point2D> 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 PointCorrespondenceAffineTransformation2DRefiner(
91 final AffineTransformation2D initialEstimation, final boolean keepCovariance, final InliersData inliersData,
92 final List<Point2D> samples1, final List<Point2D> 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 AffineTransformation2D 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[AffineTransformation2D.INHOM_COORDS * AffineTransformation2D.INHOM_COORDS
130 + AffineTransformation2D.NUM_TRANSLATION_COORDS];
131
132 System.arraycopy(initialEstimation.getA().getBuffer(), 0, initParams, 0,
133 AffineTransformation2D.INHOM_COORDS * AffineTransformation2D.INHOM_COORDS);
134
135 System.arraycopy(initialEstimation.getTranslation(), 0, initParams,
136 AffineTransformation2D.INHOM_COORDS * AffineTransformation2D.INHOM_COORDS,
137 AffineTransformation2D.NUM_TRANSLATION_COORDS);
138
139
140 final var y = new double[numInliers];
141
142 final var nDims = 2 * Point2D.POINT2D_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.getHomW());
156 x.setElementAt(pos, 3, outputPoint.getHomX());
157 x.setElementAt(pos, 4, outputPoint.getHomY());
158 x.setElementAt(pos, 5, outputPoint.getHomW());
159
160 y[pos] = residuals[i];
161 pos++;
162 }
163 }
164
165 final var evaluator = new LevenbergMarquardtMultiDimensionFunctionEvaluator() {
166
167 private final Point2D inputPoint = Point2D.create(CoordinatesType.HOMOGENEOUS_COORDINATES);
168
169 private final Point2D outputPoint = Point2D.create(CoordinatesType.HOMOGENEOUS_COORDINATES);
170
171 private final AffineTransformation2D transformation = new AffineTransformation2D();
172
173 private final GradientEstimator gradientEstimator = new GradientEstimator(params -> {
174
175 System.arraycopy(params, 0, transformation.getA().getBuffer(), 0,
176 AffineTransformation2D.INHOM_COORDS * AffineTransformation2D.INHOM_COORDS);
177
178 System.arraycopy(params,
179 AffineTransformation2D.INHOM_COORDS * AffineTransformation2D.INHOM_COORDS,
180 transformation.getTranslation(), 0, AffineTransformation2D.NUM_TRANSLATION_COORDS);
181
182 return residual(transformation, inputPoint, outputPoint);
183 });
184
185 @Override
186 public int getNumberOfDimensions() {
187 return nDims;
188 }
189
190 @Override
191 public double[] createInitialParametersArray() {
192 return initParams;
193 }
194
195 @Override
196 public double evaluate(final int i, final double[] point, final double[] params,
197 final double[] derivatives) throws EvaluationException {
198 inputPoint.setHomogeneousCoordinates(point[0], point[1], point[2]);
199 outputPoint.setHomogeneousCoordinates(point[3], point[4], point[5]);
200
201
202 System.arraycopy(params, 0, transformation.getA().getBuffer(), 0,
203 AffineTransformation2D.INHOM_COORDS * AffineTransformation2D.INHOM_COORDS);
204
205 System.arraycopy(params,
206 AffineTransformation2D.INHOM_COORDS * AffineTransformation2D.INHOM_COORDS,
207 transformation.getTranslation(), 0, AffineTransformation2D.NUM_TRANSLATION_COORDS);
208
209 final var y = residual(transformation, inputPoint, outputPoint);
210 gradientEstimator.gradient(params, derivatives);
211
212 return y;
213 }
214 };
215
216 final var fitter = new LevenbergMarquardtMultiDimensionFitter(evaluator, x, y,
217 getRefinementStandardDeviation());
218
219 fitter.fit();
220
221
222 final var params = fitter.getA();
223
224
225
226
227 System.arraycopy(params, 0, result.getA().getBuffer(), 0,
228 AffineTransformation2D.INHOM_COORDS * AffineTransformation2D.INHOM_COORDS);
229
230 System.arraycopy(params, AffineTransformation2D.INHOM_COORDS * AffineTransformation2D.INHOM_COORDS,
231 result.getTranslation(), 0, AffineTransformation2D.NUM_TRANSLATION_COORDS);
232
233 if (keepCovariance) {
234
235 covariance = fitter.getCovar();
236 }
237
238 final var finalTotalResidual = totalResidual(result);
239 final var errorDecreased = finalTotalResidual < initialTotalResidual;
240
241 if (listener != null) {
242 listener.onRefineEnd(this, initialEstimation, result, errorDecreased);
243 }
244
245 return errorDecreased;
246
247 } catch (final Exception e) {
248 throw new RefinerException(e);
249 } finally {
250 locked = false;
251 }
252 }
253
254
255
256
257
258
259
260
261
262
263 private double residual(final AffineTransformation2D transformation, final Point2D inputPoint,
264 final Point2D outputPoint) {
265 inputPoint.normalize();
266 outputPoint.normalize();
267
268 transformation.transform(inputPoint, residualTestPoint);
269 return residualTestPoint.distanceTo(outputPoint);
270 }
271
272
273
274
275
276
277
278 private double totalResidual(final AffineTransformation2D transformation) {
279 var result = 0.0;
280
281 final var nSamples = inliers.length();
282 for (var i = 0; i < nSamples; i++) {
283 if (inliers.get(i)) {
284
285 final var inputPoint = samples1.get(i);
286 final var outputPoint = samples2.get(i);
287 inputPoint.normalize();
288 outputPoint.normalize();
289 result += residual(transformation, inputPoint, outputPoint);
290 }
291 }
292
293 return result;
294 }
295 }