1 /*
2 * Copyright (C) 2015 Alberto Irurueta Carro (alberto@irurueta.com)
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package com.irurueta.ar.sfm;
17
18 import com.irurueta.geometry.CoordinatesType;
19 import com.irurueta.geometry.PinholeCamera;
20 import com.irurueta.geometry.Point2D;
21 import com.irurueta.geometry.Point3D;
22 import com.irurueta.geometry.estimators.LockedException;
23 import com.irurueta.geometry.estimators.NotReadyException;
24
25 import java.util.List;
26
27 /**
28 * Base class to triangulate matched 2D points into a single 3D one by using
29 * 2D points correspondences on different views along with the corresponding
30 * cameras on each of those views.
31 * Subclasses will implement different types of triangulators that can provide
32 * either LMSE or weighted solutions using either homogeneous or inhomogeneous
33 * systems of equations.
34 * Inhomogeneous methods are suitable only for cases where finite points and
35 * cameras are being used. If points or cameras are located very far or at
36 * infinity, triangulation will fail when using inhomogeneous methods.
37 * Homogeneous methods are suitable for any case, however, if points and
38 * cameras are close and well-defined, inhomogeneous methods might yield better
39 * accuracy (although the difference is minimal).
40 */
41 public abstract class SinglePoint3DTriangulator {
42
43 /**
44 * Default triangulator type.
45 */
46 public static final Point3DTriangulatorType DEFAULT_TYPE = Point3DTriangulatorType.LMSE_HOMOGENEOUS_TRIANGULATOR;
47
48 /**
49 * Minimum required number of views to triangulate 3D points.
50 */
51 public static final int MIN_REQUIRED_VIEWS = 2;
52
53 /**
54 * Matched 2D points. Each point in the list is assumed to be projected by
55 * the corresponding camera in the list.
56 */
57 protected List<Point2D> points2D;
58
59 /**
60 * List of cameras associated to the matched 2D point on the same position
61 * as the camera on the list.
62 */
63 protected List<PinholeCamera> cameras;
64
65 /**
66 * Listener to handle events generated by instances of this class.
67 */
68 protected SinglePoint3DTriangulatorListener listener;
69
70 /**
71 * Indicates whether this instance is locked doing computations.
72 */
73 protected boolean locked;
74
75 /**
76 * Constructor.
77 */
78 protected SinglePoint3DTriangulator() {
79 }
80
81 /**
82 * Constructor.
83 *
84 * @param points2D list of matched 2D points on each view. Each point in the
85 * list is assumed to be projected by the corresponding camera in the list.
86 * @param cameras camera for each view where 2D points are represented.
87 * @throws IllegalArgumentException if provided lists don't have the same
88 * length or their length is less than 2 views, which is the minimum
89 * required to compute triangulation.
90 */
91 protected SinglePoint3DTriangulator(final List<Point2D> points2D, final List<PinholeCamera> cameras) {
92 internalSetPointsAndCameras(points2D, cameras);
93 }
94
95 /**
96 * Constructor.
97 *
98 * @param listener listener to notify events generated by instances of this
99 * class.
100 */
101 protected SinglePoint3DTriangulator(final SinglePoint3DTriangulatorListener listener) {
102 this.listener = listener;
103 }
104
105 /**
106 * Constructor.
107 *
108 * @param points2D list of matched 2D points on each view. Each point in the
109 * list is assumed to be projected by the corresponding camera in the list.
110 * @param cameras cameras for each view where 2D points are represented.
111 * @param listener listener to notify events generated by instances of this
112 * class.
113 * @throws IllegalArgumentException if provided lists don't have the same
114 * length or their length is less than 2 views, which is the minimum
115 * required to compute triangulation.
116 */
117 protected SinglePoint3DTriangulator(final List<Point2D> points2D, final List<PinholeCamera> cameras,
118 final SinglePoint3DTriangulatorListener listener) {
119 this(points2D, cameras);
120 this.listener = listener;
121 }
122
123 /**
124 * Returns list of matched 2D points on each view. Each point in the list is
125 * assumed to be projected by the corresponding camera.
126 *
127 * @return list of matched 2D points on each view.
128 */
129 public List<Point2D> getPoints2D() {
130 return points2D;
131 }
132
133 /**
134 * Returns cameras for each view where 2D points are represented.
135 *
136 * @return cameras for each view where 2D points are represented.
137 */
138 public List<PinholeCamera> getCameras() {
139 return cameras;
140 }
141
142 /**
143 * Sets list of matched 2D points for each view and their corresponding
144 * cameras used to project them.
145 *
146 * @param points2D list of matched 2D points on each view. Each point in the
147 * list is assumed to be projected by the corresponding camera in the list.
148 * @param cameras cameras for each view where 2D points are represented.
149 * @throws LockedException if this instance is locked.
150 * @throws IllegalArgumentException if provided lists don't have the same
151 * length or their length is less than 2 views, which is the minimum
152 * required to compute triangulation.
153 */
154 public void setPointsAndCameras(final List<Point2D> points2D, final List<PinholeCamera> cameras)
155 throws LockedException {
156 if (isLocked()) {
157 throw new LockedException();
158 }
159 internalSetPointsAndCameras(points2D, cameras);
160 }
161
162 /**
163 * Indicates whether this instance is locked because computations are being
164 * done.
165 *
166 * @return true if instance is locked, false otherwise.
167 */
168 public boolean isLocked() {
169 return locked;
170 }
171
172 /**
173 * Indicates whether this instance is ready to start the triangulation.
174 * An instance is ready when both lists of 2D points and cameras are
175 * provided, both lists have the same length and at least data for 2 views
176 * is provided.
177 *
178 * @return true if this instance is ready, false otherwise.
179 */
180 public boolean isReady() {
181 return areValidPointsAndCameras(points2D, cameras);
182 }
183
184 /**
185 * Returns listener to be notified of events generated by instances of this
186 * class.
187 *
188 * @return listener to be notified of events generated by instances of this
189 * class.
190 */
191 public SinglePoint3DTriangulatorListener getListener() {
192 return listener;
193 }
194
195 /**
196 * Sets listener to be notified of events generated by instances of this
197 * class.
198 *
199 * @param listener listener to be notified of events generated by instances
200 * of this class.
201 * @throws LockedException if this instance is locked.
202 */
203 public void setListener(final SinglePoint3DTriangulatorListener listener) throws LockedException {
204 if (isLocked()) {
205 throw new LockedException();
206 }
207
208 this.listener = listener;
209 }
210
211 /**
212 * Triangulates provided matched 2D points being projected by each
213 * corresponding camera into a single 3D point.
214 * At least 2 matched 2D points and their corresponding 2 cameras are
215 * required to compute triangulation. If more views are provided, an
216 * averaged solution can be found.
217 *
218 * @return computed triangulated 3D point.
219 * @throws LockedException if this instance is locked.
220 * @throws NotReadyException if lists of points and cameras don't have the
221 * same length or less than 2 views are provided.
222 * @throws Point3DTriangulationException if triangulation fails for some
223 * other reason (i.e. degenerate geometry, numerical
224 * instabilities, etc.).
225 */
226 public Point3D triangulate() throws LockedException, NotReadyException, Point3DTriangulationException {
227 final var result = Point3D.create(CoordinatesType.HOMOGENEOUS_COORDINATES);
228 triangulate(result);
229 return result;
230 }
231
232 /**
233 * Triangulates provided matched 2D points being projected by each
234 * corresponding camera into a single 3D point.
235 * At least 2 matched 2D points and their corresponding 2 cameras are
236 * required to compute triangulation. If more views are provided, an
237 * averaged solution can be found.
238 *
239 * @param result instance where data for triangulated 3D point is stored.
240 * @throws LockedException if this instance is locked.
241 * @throws NotReadyException if lists of points and cameras don't have the
242 * same length or less than 2 views are provided.
243 * @throws Point3DTriangulationException if triangulation fails for some
244 * other reason (i.e. degenerate geometry, numerical
245 * instabilities, etc.).
246 */
247 public void triangulate(final Point3D result) throws LockedException, NotReadyException,
248 Point3DTriangulationException {
249 if (isLocked()) {
250 throw new LockedException();
251 }
252 if (!isReady()) {
253 throw new NotReadyException();
254 }
255
256 triangulate(points2D, cameras, result);
257 }
258
259 /**
260 * Indicates whether provided points and cameras are valid to start the
261 * triangulation.
262 * In order to triangulate points, at least two cameras and their
263 * corresponding 2 matched 2D points are required.
264 * If more views are provided, an averaged solution can be found.
265 *
266 * @param points2D list of matched points on each view.
267 * @param cameras cameras for each view where 2D points are represented.
268 * @return true if data is enough to start triangulation, false otherwise.
269 */
270 public static boolean areValidPointsAndCameras(final List<Point2D> points2D, final List<PinholeCamera> cameras) {
271 return points2D != null && cameras != null && points2D.size() == cameras.size()
272 && points2D.size() >= MIN_REQUIRED_VIEWS;
273 }
274
275 /**
276 * Creates a new 3D point triangulator instance using provided type.
277 *
278 * @param type a triangulator type.
279 * @return a 3D point triangulator instance.
280 */
281 public static SinglePoint3DTriangulator create(final Point3DTriangulatorType type) {
282 return switch (type) {
283 case WEIGHTED_INHOMOGENEOUS_TRIANGULATOR -> new WeightedInhomogeneousSinglePoint3DTriangulator();
284 case WEIGHTED_HOMOGENEOUS_TRIANGULATOR -> new WeightedHomogeneousSinglePoint3DTriangulator();
285 case LMSE_INHOMOGENEOUS_TRIANGULATOR -> new LMSEInhomogeneousSinglePoint3DTriangulator();
286 default -> new LMSEHomogeneousSinglePoint3DTriangulator();
287 };
288 }
289
290 /**
291 * Creates a new 3D point triangulator instance using provided lists of
292 * points and corresponding cameras along with provided type.
293 *
294 * @param points2D list of matched 2D points on each view. Each point in the
295 * list is assumed to be projected by the corresponding camera in the list.
296 * @param cameras camera for each view where 2D points are represented.
297 * @param type a triangulator type.
298 * @return a 3D point triangulator instance.
299 * @throws IllegalArgumentException if provided lists don't have the same
300 * length or their length is less than 2 views, which is the minimum
301 * required to compute triangulation.
302 */
303 public static SinglePoint3DTriangulator create(
304 final List<Point2D> points2D, final List<PinholeCamera> cameras, final Point3DTriangulatorType type) {
305 return switch (type) {
306 case WEIGHTED_INHOMOGENEOUS_TRIANGULATOR -> new WeightedInhomogeneousSinglePoint3DTriangulator(points2D,
307 cameras);
308 case WEIGHTED_HOMOGENEOUS_TRIANGULATOR -> new WeightedHomogeneousSinglePoint3DTriangulator(points2D,
309 cameras);
310 case LMSE_INHOMOGENEOUS_TRIANGULATOR -> new LMSEInhomogeneousSinglePoint3DTriangulator(points2D,
311 cameras);
312 default -> new LMSEHomogeneousSinglePoint3DTriangulator(points2D, cameras);
313 };
314 }
315
316 /**
317 * Creates a new 3D point triangulator instance using provided lists of
318 * points, weights and corresponding cameras along with provided type.
319 *
320 * @param points2D list of matched 2D points on each view. Each point in the
321 * list is assumed to be projected by the corresponding camera in the list.
322 * @param cameras camera for each view where 2D points are represented.
323 * @param weights weights assigned to each view.
324 * @param type a triangulator type.
325 * @return a 3D point triangulator instance.
326 * @throws IllegalArgumentException if provided lists or weights don't have
327 * the same length or their length is less than 2 views, which is the
328 * minimum required to compute triangulation.
329 */
330 public static SinglePoint3DTriangulator create(
331 final List<Point2D> points2D, final List<PinholeCamera> cameras, final double[] weights,
332 final Point3DTriangulatorType type) {
333 return switch (type) {
334 case WEIGHTED_INHOMOGENEOUS_TRIANGULATOR -> new WeightedInhomogeneousSinglePoint3DTriangulator(points2D,
335 cameras, weights);
336 case WEIGHTED_HOMOGENEOUS_TRIANGULATOR -> new WeightedHomogeneousSinglePoint3DTriangulator(points2D,
337 cameras, weights);
338 case LMSE_INHOMOGENEOUS_TRIANGULATOR -> new LMSEInhomogeneousSinglePoint3DTriangulator(points2D, cameras);
339 default -> new LMSEHomogeneousSinglePoint3DTriangulator(points2D, cameras);
340 };
341 }
342
343 /**
344 * Creates a new 3D point triangulator instance using provided listener and
345 * type.
346 *
347 * @param listener listener to notify events generated by instances of this
348 * class.
349 * @param type a triangulator type.
350 * @return a 3D point triangulator instance.
351 */
352 public static SinglePoint3DTriangulator create(
353 final SinglePoint3DTriangulatorListener listener, final Point3DTriangulatorType type) {
354 return switch (type) {
355 case WEIGHTED_INHOMOGENEOUS_TRIANGULATOR -> new WeightedInhomogeneousSinglePoint3DTriangulator(listener);
356 case WEIGHTED_HOMOGENEOUS_TRIANGULATOR -> new WeightedHomogeneousSinglePoint3DTriangulator(listener);
357 case LMSE_INHOMOGENEOUS_TRIANGULATOR -> new LMSEInhomogeneousSinglePoint3DTriangulator(listener);
358 default -> new LMSEHomogeneousSinglePoint3DTriangulator(listener);
359 };
360 }
361
362 /**
363 * Creates a new 3D point triangulator instance using provided lists of
364 * points and corresponding cameras, listener and provided type.
365 *
366 * @param points2D list of matched 2D points on each view. Each point in the
367 * list is assumed to be projected by the corresponding camera in the list.
368 * @param cameras camera for each view where 2D points are represented.
369 * @param listener listener to notify events generated by instances of this
370 * class.
371 * @param type a triangulator type.
372 * @return a 3D point triangulator instance.
373 * @throws IllegalArgumentException if provided lists don't have the same
374 * length or their length is less than 2 views, which is the minimum
375 * required to compute triangulation.
376 */
377 public static SinglePoint3DTriangulator create(
378 final List<Point2D> points2D, final List<PinholeCamera> cameras,
379 final SinglePoint3DTriangulatorListener listener, final Point3DTriangulatorType type) {
380 return switch (type) {
381 case WEIGHTED_INHOMOGENEOUS_TRIANGULATOR -> new WeightedInhomogeneousSinglePoint3DTriangulator(points2D,
382 cameras, listener);
383 case WEIGHTED_HOMOGENEOUS_TRIANGULATOR -> new WeightedHomogeneousSinglePoint3DTriangulator(points2D,
384 cameras, listener);
385 case LMSE_INHOMOGENEOUS_TRIANGULATOR -> new LMSEInhomogeneousSinglePoint3DTriangulator(points2D, cameras,
386 listener);
387 default -> new LMSEHomogeneousSinglePoint3DTriangulator(points2D, cameras, listener);
388 };
389 }
390
391 /**
392 * Creates a new 3D point triangulator instance using provided lists of
393 * points, weights, corresponding cameras, listener and provided type.
394 *
395 * @param points2D list of matched 2D points on each view. Each point in the
396 * list is assumed to be projected by the corresponding camera in the list.
397 * @param cameras camera for each view where 2D points are represented.
398 * @param weights weights assigned to each view.
399 * @param listener listener to notify events generated by instances of this
400 * class.
401 * @param type a triangulator type.
402 * @return a 3D point triangulator instance.
403 * @throws IllegalArgumentException if provided lists or weights don't have
404 * the same length or their length is less than 2 views, which is the
405 * minimum required to compute triangulation.
406 */
407 public static SinglePoint3DTriangulator create(
408 final List<Point2D> points2D, final List<PinholeCamera> cameras, final double[] weights,
409 final SinglePoint3DTriangulatorListener listener, final Point3DTriangulatorType type) {
410 return switch (type) {
411 case WEIGHTED_INHOMOGENEOUS_TRIANGULATOR -> new WeightedInhomogeneousSinglePoint3DTriangulator(points2D,
412 cameras, weights, listener);
413 case WEIGHTED_HOMOGENEOUS_TRIANGULATOR -> new WeightedHomogeneousSinglePoint3DTriangulator(points2D,
414 cameras, weights, listener);
415 case LMSE_INHOMOGENEOUS_TRIANGULATOR -> new LMSEInhomogeneousSinglePoint3DTriangulator(points2D, cameras,
416 listener);
417 default -> new LMSEHomogeneousSinglePoint3DTriangulator(points2D, cameras, listener);
418 };
419 }
420
421 /**
422 * Creates a new 3D point triangulator instance using default type.
423 *
424 * @return a 3D point triangulator instance.
425 */
426 public static SinglePoint3DTriangulator create() {
427 return create(DEFAULT_TYPE);
428 }
429
430 /**
431 * Creates a new 3D point triangulator instance using provided lists of
432 * points and corresponding cameras along with default type.
433 *
434 * @param points2D list of matched 2D points on each view. Each point in the
435 * list is assumed to be projected by the corresponding camera in the list.
436 * @param cameras camera for each view where 2D points are represented.
437 * @return a 3D point triangulator instance.
438 * @throws IllegalArgumentException if provided lists don't have the same
439 * length or their length is less than 2 views, which is the minimum
440 * required to compute triangulation.
441 */
442 public static SinglePoint3DTriangulator create(final List<Point2D> points2D, final List<PinholeCamera> cameras) {
443 return create(points2D, cameras, DEFAULT_TYPE);
444 }
445
446 /**
447 * Creates a new 3D point triangulator instance using provided lists of
448 * points, weights and corresponding cameras along with default type.
449 *
450 * @param points2D list of matched 2D points on each view. Each point in the
451 * list is assumed to be projected by the corresponding camera in the list.
452 * @param cameras camera for each view where 2D points are represented.
453 * @param weights weights assigned to each view.
454 * @return a 3D point triangulator instance.
455 * @throws IllegalArgumentException if provided lists or weights don't have
456 * the same length or their length is less than 2 views, which is the
457 * minimum required to compute triangulation.
458 */
459 public static SinglePoint3DTriangulator create(
460 final List<Point2D> points2D, final List<PinholeCamera> cameras, double[] weights) {
461 return create(points2D, cameras, weights, DEFAULT_TYPE);
462 }
463
464 /**
465 * Creates a new 3D point triangulator instance using provided listener and
466 * default type.
467 *
468 * @param listener listener to notify events generated by instances of this
469 * class.
470 * @return a 3D point triangulator instance.
471 */
472 public static SinglePoint3DTriangulator create(final SinglePoint3DTriangulatorListener listener) {
473 return create(listener, DEFAULT_TYPE);
474 }
475
476 /**
477 * Creates a new 3D point triangulator instance using provided lists of
478 * points and corresponding cameras, listener and default type.
479 *
480 * @param points2D list of matched 2D points on each view. Each point in the
481 * list is assumed to be projected by the corresponding camera in the list.
482 * @param cameras camera for each view where 2D points are represented.
483 * @param listener listener to notify events generated by instances of this
484 * class.
485 * @return a 3D point triangulator instance.
486 * @throws IllegalArgumentException if provided lists don't have the same
487 * length or their length is less than 2 views, which is the minimum
488 * required to compute triangulation.
489 */
490 public static SinglePoint3DTriangulator create(
491 final List<Point2D> points2D, final List<PinholeCamera> cameras,
492 final SinglePoint3DTriangulatorListener listener) {
493 return create(points2D, cameras, listener, DEFAULT_TYPE);
494 }
495
496 /**
497 * Creates a new 3D point triangulator instance using provided lists of
498 * points, weights, corresponding cameras, listener and default type.
499 *
500 * @param points2D list of matched 2D points on each view. Each point in the
501 * list is assumed to be projected by the corresponding camera in the list.
502 * @param cameras camera for each view where 2D points are represented.
503 * @param weights weights assigned to each view.
504 * @param listener listener to notify events generated by instances of this
505 * class.
506 * @return a 3D point triangulator instance.
507 * @throws IllegalArgumentException if provided lists or weights don't have
508 * the same length or their length is less than 2 views, which is the
509 * minimum required to compute triangulation.
510 */
511 public static SinglePoint3DTriangulator create(final List<Point2D> points2D,
512 final List<PinholeCamera> cameras,
513 final double[] weights,
514 final SinglePoint3DTriangulatorListener listener) {
515 return create(points2D, cameras, weights, listener, DEFAULT_TYPE);
516 }
517
518 /**
519 * Returns type of triangulator (a combination of homogeneous or
520 * inhomogeneous type along with an LMSE or weighted strategy).
521 *
522 * @return type of triangulator.
523 */
524 public abstract Point3DTriangulatorType getType();
525
526 /**
527 * Internal method to triangulate provided matched 2D points being projected
528 * by each corresponding camera into a single 3D point.
529 * At least 2 matched 2D points and their corresponding 2 cameras are
530 * required to compute triangulation. If more views are provided, an
531 * averaged solution is found.
532 * This method does not check whether instance is locked or ready.
533 *
534 * @param points2D matched 2D points. Each point in the list is assumed to
535 * be projected by the corresponding camera in the list.
536 * @param cameras list of cameras associated to the matched 2D point on the
537 * same position as the camera on the list.
538 * @param result instance where triangulated 3D point is stored.
539 * @throws Point3DTriangulationException if triangulation fails for some
540 * other reason (i.e. degenerate geometry, numerical
541 * instabilities, etc.).
542 */
543 protected abstract void triangulate(
544 final List<Point2D> points2D, final List<PinholeCamera> cameras, final Point3D result)
545 throws Point3DTriangulationException;
546
547 /**
548 * Internal method to sets list of matched 2D points for each view and their
549 * corresponding cameras used to project them.
550 * This method does not check whether instance is locked.
551 *
552 * @param points2D list of matched 2D points on each view. Each point in the
553 * list is assumed to be projected by the corresponding camera in the list.
554 * @param cameras cameras for each view where 2D points are represented.
555 * @throws IllegalArgumentException if provided lists don't have the same
556 * length or their length is less than 2 views, which is the minimum
557 * required to compute triangulation.
558 */
559 private void internalSetPointsAndCameras(final List<Point2D> points2D, final List<PinholeCamera> cameras) {
560 if (!areValidPointsAndCameras(points2D, cameras)) {
561 throw new IllegalArgumentException();
562 }
563
564 this.points2D = points2D;
565 this.cameras = cameras;
566 }
567 }