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.RANSACRobustEstimator;
25 import com.irurueta.numerical.robust.RANSACRobustEstimatorListener;
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 RANSAC algorithm.
36 */
37 public class RANSACRobustSinglePoint3DTriangulator extends
38 RobustSinglePoint3DTriangulator {
39
40 /**
41 * Constant defining default threshold to determine whether samples are
42 * inliers or not.
43 * By default, 1.0 is considered a good value for cases where 2D point
44 * measures are done on pixels, since typically the minimum resolution is 1
45 * pixel.
46 */
47 public static final double DEFAULT_THRESHOLD = 1.0;
48
49 /**
50 * Minimum value that can be set as threshold.
51 * Threshold must be strictly greater than 0.0.
52 */
53 public static final double MIN_THRESHOLD = 0.0;
54
55 /**
56 * Threshold to determine whether samples are inliers or not when testing
57 * possible estimation solutions.
58 * The threshold refers to the amount of projection error (i.e. distance of
59 * projected solution using each camera).
60 */
61 private double threshold;
62
63 /**
64 * Constructor.
65 */
66 public RANSACRobustSinglePoint3DTriangulator() {
67 super();
68 threshold = DEFAULT_THRESHOLD;
69 }
70
71 /**
72 * Constructor.
73 *
74 * @param listener listener to be notified of events such as when estimation
75 * starts, ends or its progress significantly changes.
76 */
77 public RANSACRobustSinglePoint3DTriangulator(final RobustSinglePoint3DTriangulatorListener listener) {
78 super(listener);
79 threshold = DEFAULT_THRESHOLD;
80 }
81
82 /**
83 * Constructor.
84 *
85 * @param points Matched 2D points. Each point in the list is assumed to be
86 * projected by the corresponding camera in the list.
87 * @param cameras List of cameras associated to the matched 2D point on the
88 * same position as the camera on the list.
89 * @throws IllegalArgumentException if provided lists don't have the same
90 * length or their length is less than 2 views, which is the minimum
91 * required to compute triangulation.
92 */
93 public RANSACRobustSinglePoint3DTriangulator(final List<Point2D> points, final List<PinholeCamera> cameras) {
94 super(points, cameras);
95 threshold = DEFAULT_THRESHOLD;
96 }
97
98 /**
99 * Constructor.
100 *
101 * @param points Matched 2D points. Each point in the list is assumed to be
102 * projected by the corresponding camera in the list.
103 * @param cameras List of cameras associated to the matched 2D point on the
104 * same position as the camera on the list.
105 * @param listener listener to be notified of events such as when estimation
106 * starts, ends or its progress significantly changes.
107 * @throws IllegalArgumentException if provided lists don't have the same
108 * length or their length is less than 2 views, which is the minimum
109 * required to compute triangulation.
110 */
111 public RANSACRobustSinglePoint3DTriangulator(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 RANSACRobustEstimator<Point3D>(new RANSACRobustEstimatorListener<>() {
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 RANSACRobustSinglePoint3DTriangulator.this.isReady();
242 }
243
244 @Override
245 public void onEstimateStart(final RobustEstimator<Point3D> estimator) {
246 if (listener != null) {
247 listener.onTriangulateStart(RANSACRobustSinglePoint3DTriangulator.this);
248 }
249 }
250
251 @Override
252 public void onEstimateEnd(final RobustEstimator<Point3D> estimator) {
253 if (listener != null) {
254 listener.onTriangulateEnd(RANSACRobustSinglePoint3DTriangulator.this);
255 }
256 }
257
258 @Override
259 public void onEstimateNextIteration(final RobustEstimator<Point3D> estimator, final int iteration) {
260 if (listener != null) {
261 listener.onTriangulateNextIteration(
262 RANSACRobustSinglePoint3DTriangulator.this, iteration);
263 }
264 }
265
266 @Override
267 public void onEstimateProgressChange(final RobustEstimator<Point3D> estimator, final float progress) {
268 if (listener != null) {
269 listener.onTriangulateProgressChange(
270 RANSACRobustSinglePoint3DTriangulator.this, progress);
271 }
272 }
273 });
274
275 try {
276 locked = true;
277 innerEstimator.setConfidence(confidence);
278 innerEstimator.setMaxIterations(maxIterations);
279 innerEstimator.setProgressDelta(progressDelta);
280 return innerEstimator.estimate();
281 } catch (final com.irurueta.numerical.LockedException e) {
282 throw new LockedException(e);
283 } catch (final com.irurueta.numerical.NotReadyException e) {
284 throw new NotReadyException(e);
285 } finally {
286 locked = false;
287 }
288 }
289
290 /**
291 * Returns method being used for robust estimation.
292 *
293 * @return method being used for robust estimation.
294 */
295 @Override
296 public RobustEstimatorMethod getMethod() {
297 return RobustEstimatorMethod.RANSAC;
298 }
299 }