1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package com.irurueta.ar.sfm;
17
18 import com.irurueta.algebra.AlgebraException;
19 import com.irurueta.ar.slam.BaseCalibrationData;
20 import com.irurueta.ar.slam.BaseSlamEstimator;
21 import com.irurueta.geometry.GeometryException;
22 import com.irurueta.geometry.InhomogeneousPoint3D;
23 import com.irurueta.geometry.MetricTransformation3D;
24 import com.irurueta.geometry.PinholeCamera;
25 import com.irurueta.geometry.PinholeCameraIntrinsicParameters;
26 import com.irurueta.geometry.Quaternion;
27 import com.irurueta.geometry.Rotation3D;
28
29 import java.util.ArrayList;
30
31 @SuppressWarnings("DuplicatedCode")
32 public abstract class BaseSlamPairedViewsSparseReconstructor<
33 D extends BaseCalibrationData,
34 C extends BaseSlamPairedViewsSparseReconstructorConfiguration<D, C>,
35 R extends BaseSlamPairedViewsSparseReconstructor<D, C, R, L, S>,
36 L extends BaseSlamPairedViewsSparseReconstructorListener<R>,
37 S extends BaseSlamEstimator<D>> extends BasePairedViewsSparseReconstructor<C, R, L> {
38
39
40
41
42
43 protected S slamEstimator;
44
45
46
47
48 private final InhomogeneousPoint3D slamPosition = new InhomogeneousPoint3D();
49
50
51
52
53 private Rotation3D invEuclideanCameraRotation;
54
55
56
57
58 private final PinholeCamera slamCamera = new PinholeCamera();
59
60
61
62
63 private final Quaternion slamRotation = new Quaternion();
64
65
66
67
68 private long lastTimestamp = -1;
69
70
71
72
73 private long lastViewPairTimestamp = -1;
74
75
76
77
78
79
80
81
82
83 protected BaseSlamPairedViewsSparseReconstructor(final C configuration, final L listener) {
84 super(configuration, listener);
85 }
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102 public void updateAccelerometerSample(final long timestamp, final float accelerationX,
103 final float accelerationY, final float accelerationZ) {
104 if (lastViewPairTimestamp < 0) {
105 lastViewPairTimestamp = timestamp;
106 }
107
108 if (slamEstimator != null) {
109 slamEstimator.updateAccelerometerSample(timestamp - lastViewPairTimestamp,
110 accelerationX, accelerationY, accelerationZ);
111 }
112 lastTimestamp = timestamp;
113 }
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128 public void updateAccelerometerSample(final long timestamp, final float[] data) {
129 if (lastViewPairTimestamp < 0) {
130 lastViewPairTimestamp = timestamp;
131 }
132
133 if (slamEstimator != null) {
134 slamEstimator.updateAccelerometerSample(timestamp - lastViewPairTimestamp, data);
135 }
136 lastTimestamp = timestamp;
137 }
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152 public void updateGyroscopeSample(final long timestamp, final float angularSpeedX,
153 final float angularSpeedY, final float angularSpeedZ) {
154 if (lastViewPairTimestamp < 0) {
155 lastViewPairTimestamp = timestamp;
156 }
157
158 if (slamEstimator != null) {
159 slamEstimator.updateGyroscopeSample(timestamp - lastViewPairTimestamp,
160 angularSpeedX, angularSpeedY, angularSpeedZ);
161 }
162 lastTimestamp = timestamp;
163 }
164
165
166
167
168
169
170
171
172
173
174
175
176 public void updateGyroscopeSample(final long timestamp, final float[] data) {
177 if (lastViewPairTimestamp < 0) {
178 lastViewPairTimestamp = timestamp;
179 }
180
181 if (slamEstimator != null) {
182 slamEstimator.updateGyroscopeSample(timestamp - lastViewPairTimestamp, data);
183 }
184 lastTimestamp = timestamp;
185 }
186
187
188
189
190
191 @Override
192 public void reset() {
193 super.reset();
194 lastTimestamp = lastViewPairTimestamp = -1;
195 }
196
197
198
199
200
201
202
203 @Override
204 protected boolean hasAbsoluteOrientation() {
205 return false;
206 }
207
208
209
210
211
212 protected void setUpCalibrationData() {
213 final var calibrationData = configuration.getCalibrationData();
214 if (calibrationData != null) {
215 slamEstimator.setCalibrationData(calibrationData);
216 }
217 }
218
219
220
221
222 protected void setUpSlamEstimatorListener() {
223 slamEstimator.setListener(new BaseSlamEstimator.BaseSlamEstimatorListener<>() {
224 @Override
225 public void onFullSampleReceived(final BaseSlamEstimator<D> estimator) {
226
227 }
228
229 @Override
230 public void onFullSampleProcessed(final BaseSlamEstimator<D> estimator) {
231 notifySlamStateAndCamera();
232 }
233
234 @Override
235 public void onCorrectWithPositionMeasure(final BaseSlamEstimator<D> estimator) {
236
237 }
238
239 @Override
240 public void onCorrectedWithPositionMeasure(final BaseSlamEstimator<D> estimator) {
241 notifySlamStateAndCamera();
242 }
243
244 private void notifySlamStateAndCamera() {
245 notifySlamStateIfNeeded();
246 notifySlamCameraIfNeeded();
247 }
248 });
249 }
250
251
252
253
254
255
256
257
258
259
260 @Override
261 protected boolean transformPairOfCamerasAndPoints(final boolean isInitialPairOfViews,
262 final boolean hasAbsoluteOrientation) {
263 final var previousMetricCamera = previousMetricEstimatedCamera.getCamera();
264 final var currentMetricCamera = currentMetricEstimatedCamera.getCamera();
265 if (previousMetricCamera == null || currentMetricCamera == null) {
266 return false;
267 }
268
269 currentScale = estimateCurrentScale();
270 final var sqrScale = currentScale * currentScale;
271
272 referenceEuclideanTransformation = new MetricTransformation3D(currentScale);
273 if (hasAbsoluteOrientation) {
274 invEuclideanCameraRotation = lastEuclideanCameraRotation.inverseRotationAndReturnNew();
275 if (isInitialPairOfViews) {
276 referenceEuclideanTransformation.setRotation(invEuclideanCameraRotation);
277 }
278 }
279
280 if (!isInitialPairOfViews) {
281
282 if (invEuclideanCameraRotation == null) {
283 invEuclideanCameraRotation = lastEuclideanCameraRotation.inverseRotationAndReturnNew();
284 } else {
285 lastEuclideanCameraRotation.inverseRotation(invEuclideanCameraRotation);
286 }
287 referenceEuclideanTransformation.setRotation(invEuclideanCameraRotation);
288 referenceEuclideanTransformation.setTranslation(lastEuclideanCameraCenter);
289 }
290
291 try {
292
293 final var previousEuclideanCamera = referenceEuclideanTransformation.transformAndReturnNew(
294 previousMetricCamera);
295 final var currentEuclideanCamera = referenceEuclideanTransformation.transformAndReturnNew(
296 currentMetricCamera);
297
298 previousEuclideanEstimatedCamera = new EstimatedCamera();
299 previousEuclideanEstimatedCamera.setCamera(previousEuclideanCamera);
300 previousEuclideanEstimatedCamera.setViewId(previousMetricEstimatedCamera.getViewId());
301 previousEuclideanEstimatedCamera.setQualityScore(previousMetricEstimatedCamera.getQualityScore());
302 if (previousMetricEstimatedCamera.getCovariance() != null) {
303 previousEuclideanEstimatedCamera.setCovariance(previousMetricEstimatedCamera.getCovariance()
304 .multiplyByScalarAndReturnNew(sqrScale));
305 }
306
307 currentEuclideanEstimatedCamera = new EstimatedCamera();
308 currentEuclideanEstimatedCamera.setCamera(currentEuclideanCamera);
309 currentEuclideanEstimatedCamera.setViewId(currentMetricEstimatedCamera.getViewId());
310 currentEuclideanEstimatedCamera.setQualityScore(currentMetricEstimatedCamera.getQualityScore());
311 if (currentMetricEstimatedCamera.getCovariance() != null) {
312 currentEuclideanEstimatedCamera.setCovariance(currentMetricEstimatedCamera.getCovariance()
313 .multiplyByScalarAndReturnNew(sqrScale));
314 }
315
316
317 euclideanReconstructedPoints = new ArrayList<>();
318 ReconstructedPoint3D euclideanReconstructedPoint;
319 for (final ReconstructedPoint3D metricReconstructedPoint : metricReconstructedPoints) {
320 final var metricPoint = metricReconstructedPoint.getPoint();
321 final var euclideanPoint = referenceEuclideanTransformation.transformAndReturnNew(metricPoint);
322 euclideanReconstructedPoint = new ReconstructedPoint3D();
323 euclideanReconstructedPoint.setPoint(euclideanPoint);
324 euclideanReconstructedPoint.setInlier(metricReconstructedPoint.isInlier());
325 euclideanReconstructedPoint.setId(metricReconstructedPoint.getId());
326 euclideanReconstructedPoint.setColorData(metricReconstructedPoint.getColorData());
327 if (metricReconstructedPoint.getCovariance() != null) {
328 euclideanReconstructedPoint.setCovariance(metricReconstructedPoint.getCovariance()
329 .multiplyByScalarAndReturnNew(sqrScale));
330 }
331 euclideanReconstructedPoint.setQualityScore(metricReconstructedPoint.getQualityScore());
332 euclideanReconstructedPoints.add(euclideanReconstructedPoint);
333 }
334
335 } catch (final AlgebraException e) {
336 return false;
337 }
338
339 return super.transformPairOfCamerasAndPoints(isInitialPairOfViews, hasAbsoluteOrientation);
340 }
341
342
343
344
345
346
347 private double estimateCurrentScale() {
348 try {
349 final var metricCamera1 = previousMetricEstimatedCamera.getCamera();
350 final var metricCamera2 = currentMetricEstimatedCamera.getCamera();
351
352 if (!metricCamera1.isCameraCenterAvailable()) {
353 metricCamera1.decompose(false, true);
354 }
355 if (!metricCamera2.isCameraCenterAvailable()) {
356 metricCamera2.decompose(false, true);
357 }
358
359 final var metricCenter1 = metricCamera1.getCameraCenter();
360 final var metricCenter2 = metricCamera2.getCameraCenter();
361
362
363 final var slamPosX = slamEstimator.getStatePositionX();
364 final var slamPosY = slamEstimator.getStatePositionY();
365 final var slamPosZ = slamEstimator.getStatePositionZ();
366
367 slamPosition.setInhomogeneousCoordinates(slamPosX, slamPosY, slamPosZ);
368
369
370
371
372 final var euclideanBaseline = Math.sqrt(slamPosX * slamPosX + slamPosY * slamPosY + slamPosZ * slamPosZ);
373 final var metricBaseline = metricCenter1.distanceTo(metricCenter2);
374
375
376
377 lastViewPairTimestamp = lastTimestamp;
378
379
380 slamEstimator.resetPositionAndVelocity();
381
382 return euclideanBaseline / metricBaseline;
383 } catch (final Exception e) {
384 failed = true;
385
386 listener.onFail((R) this);
387 return DEFAULT_SCALE;
388 }
389 }
390
391
392
393
394 private void notifySlamStateIfNeeded() {
395 if (!configuration.isNotifyAvailableSlamDataEnabled()) {
396 return;
397 }
398
399 final var lastPosX = lastEuclideanCameraCenter != null ? lastEuclideanCameraCenter.getInhomX() : 0.0;
400 final var lastPosY = lastEuclideanCameraCenter != null ? lastEuclideanCameraCenter.getInhomY() : 0.0;
401 final var lastPosZ = lastEuclideanCameraCenter != null ? lastEuclideanCameraCenter.getInhomZ() : 0.0;
402
403 final var positionX = lastPosX + slamEstimator.getStatePositionX();
404 final var positionY = lastPosY + slamEstimator.getStatePositionY();
405 final var positionZ = lastPosZ + slamEstimator.getStatePositionZ();
406
407 final var velocityX = slamEstimator.getStateVelocityX();
408 final var velocityY = slamEstimator.getStateVelocityY();
409 final var velocityZ = slamEstimator.getStateVelocityZ();
410
411 final var accelerationX = slamEstimator.getStateAccelerationX();
412 final var accelerationY = slamEstimator.getStateAccelerationY();
413 final var accelerationZ = slamEstimator.getStateAccelerationZ();
414
415 final var quaternionA = slamEstimator.getStateQuaternionA();
416 final var quaternionB = slamEstimator.getStateQuaternionB();
417 final var quaternionC = slamEstimator.getStateQuaternionC();
418 final var quaternionD = slamEstimator.getStateQuaternionD();
419
420 final var angularSpeedX = slamEstimator.getStateAngularSpeedX();
421 final var angularSpeedY = slamEstimator.getStateAngularSpeedY();
422 final var angularSpeedZ = slamEstimator.getStateAngularSpeedZ();
423
424
425 listener.onSlamDataAvailable((R) this, positionX, positionY, positionZ, velocityX, velocityY, velocityZ,
426 accelerationX, accelerationY, accelerationZ, quaternionA, quaternionB, quaternionC, quaternionD,
427 angularSpeedX, angularSpeedY, angularSpeedZ, slamEstimator.getStateCovariance());
428 }
429
430
431
432
433
434 private void notifySlamCameraIfNeeded() {
435 if (!configuration.isNotifyEstimatedSlamCameraEnabled()) {
436 return;
437 }
438
439
440 var camera = currentEuclideanEstimatedCamera != null ? currentEuclideanEstimatedCamera.getCamera() : null;
441 if (camera == null) {
442
443 camera = previousEuclideanEstimatedCamera != null ? previousEuclideanEstimatedCamera.getCamera() : null;
444 }
445
446 try {
447 PinholeCameraIntrinsicParameters intrinsicParameters = null;
448 if (camera != null) {
449 if (!camera.areIntrinsicParametersAvailable()) {
450
451 camera.decompose();
452 }
453
454 intrinsicParameters = camera.getIntrinsicParameters();
455 } else if (configuration.areIntrinsicParametersKnown()) {
456
457 intrinsicParameters = listener.onIntrinsicParametersRequested((R) this, currentViewId);
458 }
459
460 if (intrinsicParameters == null) {
461 return;
462 }
463
464 final var lastPosX = lastEuclideanCameraCenter != null ? lastEuclideanCameraCenter.getInhomX() : 0.0;
465 final var lastPosY = lastEuclideanCameraCenter != null ? lastEuclideanCameraCenter.getInhomY() : 0.0;
466 final var lastPosZ = lastEuclideanCameraCenter != null ? lastEuclideanCameraCenter.getInhomZ() : 0.0;
467
468 final var positionX = lastPosX + slamEstimator.getStatePositionX();
469 final var positionY = lastPosY + slamEstimator.getStatePositionY();
470 final var positionZ = lastPosZ + slamEstimator.getStatePositionZ();
471 slamPosition.setInhomogeneousCoordinates(positionX, positionY, positionZ);
472
473 final var quaternionA = slamEstimator.getStateQuaternionA();
474 final var quaternionB = slamEstimator.getStateQuaternionB();
475 final var quaternionC = slamEstimator.getStateQuaternionC();
476 final var quaternionD = slamEstimator.getStateQuaternionD();
477 slamRotation.setA(quaternionA);
478 slamRotation.setB(quaternionB);
479 slamRotation.setC(quaternionC);
480 slamRotation.setD(quaternionD);
481
482 slamCamera.setIntrinsicAndExtrinsicParameters(intrinsicParameters, slamRotation, slamPosition);
483
484
485 listener.onSlamCameraEstimated((R) this, slamCamera);
486
487 } catch (final GeometryException ignore) {
488
489 }
490 }
491 }