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 import com.irurueta.numerical.robust.MSACRobustEstimator;
25 import com.irurueta.numerical.robust.MSACRobustEstimatorListener;
26 import com.irurueta.numerical.robust.RobustEstimator;
27 import com.irurueta.numerical.robust.RobustEstimatorException;
28 import com.irurueta.numerical.robust.RobustEstimatorMethod;
29
30 import java.util.ArrayList;
31 import java.util.List;
32
33 /**
34 * Robustly triangulates 3D points from matched 2D points and their
35 * corresponding cameras on several views using MSAC algorithm.
36 */
37 public class MSACRobustSinglePoint3DTriangulator extends RobustSinglePoint3DTriangulator {
38
39 /**
40 * Constant defining default threshold to determine whether samples are
41 * inliers or not.
42 * By default, 1.0 is considered a good value for cases where 2D point
43 * measures are done on pixels, since typically the minimum resolution is 1
44 * pixel.
45 */
46 public static final double DEFAULT_THRESHOLD = 1.0;
47
48 /**
49 * Minimum value that can be set as threshold.
50 * Threshold must be strictly greater than 0.0.
51 */
52 public static final double MIN_THRESHOLD = 0.0;
53
54 /**
55 * Threshold to determine whether samples are inliers or not when testing
56 * possible estimation solutions.
57 * The threshold refers to the amount of projection error (i.e. distance of
58 * projected solution using each camera).
59 */
60 private double threshold;
61
62 /**
63 * Constructor.
64 */
65 public MSACRobustSinglePoint3DTriangulator() {
66 super();
67 threshold = DEFAULT_THRESHOLD;
68 }
69
70 /**
71 * Constructor.
72 *
73 * @param listener listener to be notified of events such as when estimation
74 * starts, ends or its progress significantly changes.
75 */
76 public MSACRobustSinglePoint3DTriangulator(final RobustSinglePoint3DTriangulatorListener listener) {
77 super(listener);
78 threshold = DEFAULT_THRESHOLD;
79 }
80
81 /**
82 * Constructor.
83 *
84 * @param points Matched 2D points. Each point in the list is assumed to be
85 * projected by the corresponding camera in the list.
86 * @param cameras List of cameras associated to the matched 2D point on the
87 * same position as the camera on the list.
88 * @throws IllegalArgumentException if provided lists don't have the same
89 * length or their length is less than 2 views, which is the minimum
90 * required to compute triangulation.
91 */
92 public MSACRobustSinglePoint3DTriangulator(final List<Point2D> points, final List<PinholeCamera> cameras) {
93 super(points, cameras);
94 threshold = DEFAULT_THRESHOLD;
95 }
96
97 /**
98 * Constructor.
99 *
100 * @param points Matched 2D points. Each point in the list is assumed to be
101 * projected by the corresponding camera in the list.
102 * @param cameras List of cameras associated to the matched 2D point on the
103 * same position as the camera on the list.
104 * @param listener listener to be notified of events such as when estimation
105 * starts, ends or its progress significantly changes.
106 * @throws IllegalArgumentException if provided lists don't have the same
107 * length or their length is less than 2 views, which is the minimum
108 * required to compute triangulation.
109 */
110 public MSACRobustSinglePoint3DTriangulator(
111 final List<Point2D> points, final List<PinholeCamera> cameras,
112 final RobustSinglePoint3DTriangulatorListener listener) {
113 super(points, cameras, listener);
114 threshold = DEFAULT_THRESHOLD;
115 }
116
117 /**
118 * Returns threshold to determine whether points are inliers or not when
119 * testing possible estimation solutions.
120 * The threshold refers to the amount of error (i.e. Euclidean distance) a
121 * possible solution has on projected 2D points.
122 *
123 * @return threshold to determine whether points are inliers or not when
124 * testing possible estimation solutions.
125 */
126 public double getThreshold() {
127 return threshold;
128 }
129
130 /**
131 * Sets threshold to determine whether points are inliers or not when
132 * testing possible estimation solutions.
133 * The threshold refers to the amount of error (i.e. Euclidean distance) a
134 * possible solution has on projected 2D points.
135 *
136 * @param threshold threshold to be set.
137 * @throws IllegalArgumentException if provided value is equal or less than
138 * zero.
139 * @throws LockedException if robust estimator is locked because an
140 * estimation is already in progress.
141 */
142 public void setThreshold(final double threshold) throws LockedException {
143 if (isLocked()) {
144 throw new LockedException();
145 }
146 if (threshold <= MIN_THRESHOLD) {
147 throw new IllegalArgumentException();
148 }
149 this.threshold = threshold;
150 }
151
152 /**
153 * Triangulates provided matched 2D points being projected by each
154 * corresponding camera into a single 3D point.
155 * At least 2 matched 2D points and their corresponding 2 cameras are
156 * required to compute triangulation. If more views are provided, an
157 * averaged solution can be found.
158 *
159 * @return computed triangulated 3D point.
160 * @throws LockedException if this instance is locked.
161 * @throws NotReadyException if lists of points and cameras don't have the
162 * same length or less than 2 views are provided.
163 * @throws RobustEstimatorException if estimation fails for any reason
164 * (i.e. numerical instability, no solution available, etc).
165 */
166 @SuppressWarnings("DuplicatedCode")
167 @Override
168 public Point3D triangulate() throws LockedException, NotReadyException, RobustEstimatorException {
169 if (isLocked()) {
170 throw new LockedException();
171 }
172 if (!isReady()) {
173 throw new NotReadyException();
174 }
175
176 final var innerEstimator = new MSACRobustEstimator<Point3D>(new MSACRobustEstimatorListener<>() {
177
178 // point to be reused when computing residuals
179 private final Point2D testPoint = Point2D.create(CoordinatesType.HOMOGENEOUS_COORDINATES);
180
181 // non-robust 3D point triangulator
182 private final SinglePoint3DTriangulator triangulator = SinglePoint3DTriangulator.create(
183 useHomogeneousSolution ? Point3DTriangulatorType.LMSE_HOMOGENEOUS_TRIANGULATOR
184 : Point3DTriangulatorType.LMSE_INHOMOGENEOUS_TRIANGULATOR);
185
186 // subset of 2D points
187 private final List<Point2D> subsetPoints = new ArrayList<>();
188
189 // subst of cameras
190 private final List<PinholeCamera> subsetCameras = new ArrayList<>();
191
192 @Override
193 public double getThreshold() {
194 return threshold;
195 }
196
197 @Override
198 public int getTotalSamples() {
199 return points2D.size();
200 }
201
202 @Override
203 public int getSubsetSize() {
204 return MIN_REQUIRED_VIEWS;
205 }
206
207 @Override
208 public void estimatePreliminarSolutions(final int[] samplesIndices, final List<Point3D> solutions) {
209 subsetPoints.clear();
210 subsetPoints.add(points2D.get(samplesIndices[0]));
211 subsetPoints.add(points2D.get(samplesIndices[1]));
212
213 subsetCameras.clear();
214 subsetCameras.add(cameras.get(samplesIndices[0]));
215 subsetCameras.add(cameras.get(samplesIndices[1]));
216
217 try {
218 triangulator.setPointsAndCameras(subsetPoints, subsetCameras);
219 final var triangulated = triangulator.triangulate();
220 solutions.add(triangulated);
221 } catch (final Exception e) {
222 // if anything fails, no solution is added
223 }
224 }
225
226 @Override
227 public double computeResidual(final Point3D currentEstimation, final int i) {
228 final var point2D = points2D.get(i);
229 final var camera = cameras.get(i);
230
231 // project estimated point with camera
232 camera.project(currentEstimation, testPoint);
233
234 // return distance of projected point respect to the original one
235 // as a residual
236 return testPoint.distanceTo(point2D);
237 }
238
239 @Override
240 public boolean isReady() {
241 return MSACRobustSinglePoint3DTriangulator.this.isReady();
242 }
243
244 @Override
245 public void onEstimateStart(final RobustEstimator<Point3D> estimator) {
246 if (listener != null) {
247 listener.onTriangulateStart(MSACRobustSinglePoint3DTriangulator.this);
248 }
249 }
250
251 @Override
252 public void onEstimateEnd(final RobustEstimator<Point3D> estimator) {
253 if (listener != null) {
254 listener.onTriangulateEnd(MSACRobustSinglePoint3DTriangulator.this);
255 }
256 }
257
258 @Override
259 public void onEstimateNextIteration(final RobustEstimator<Point3D> estimator, final int iteration) {
260 if (listener != null) {
261 listener.onTriangulateNextIteration(MSACRobustSinglePoint3DTriangulator.this, iteration);
262 }
263 }
264
265 @Override
266 public void onEstimateProgressChange(final RobustEstimator<Point3D> estimator, final float progress) {
267 if (listener != null) {
268 listener.onTriangulateProgressChange(MSACRobustSinglePoint3DTriangulator.this, progress);
269 }
270 }
271 });
272
273 try {
274 locked = true;
275 innerEstimator.setConfidence(confidence);
276 innerEstimator.setMaxIterations(maxIterations);
277 innerEstimator.setProgressDelta(progressDelta);
278 return innerEstimator.estimate();
279 } catch (final com.irurueta.numerical.LockedException e) {
280 throw new LockedException(e);
281 } catch (final com.irurueta.numerical.NotReadyException e) {
282 throw new NotReadyException(e);
283 } finally {
284 locked = false;
285 }
286 }
287
288 /**
289 * Returns method being used for robust estimation.
290 *
291 * @return method being used for robust estimation.
292 */
293 @Override
294 public RobustEstimatorMethod getMethod() {
295 return RobustEstimatorMethod.MSAC;
296 }
297 }