Comparison script
							parent
							
								
									5b0580ff5f
								
							
						
					
					
						commit
						fcf51faf0c
					
				|  | @ -0,0 +1,259 @@ | |||
| """ | ||||
|   Compare the Fundamental Matrix and Essential Matrix methods for optimizing the view-graph. | ||||
|   It measures the distance from the ground truth matrices in terms of the norm of local coordinates (geodesic distance)  | ||||
|   on the respective manifolds. It also plots the final error of the optimization. | ||||
|   Author: Frank Dellaert (with heavy assist from ChatGPT) | ||||
|   Date: October 2024 | ||||
| """ | ||||
| 
 | ||||
| import matplotlib.pyplot as plt | ||||
| import numpy as np | ||||
| from gtsam.examples import SFMdata | ||||
| 
 | ||||
| import gtsam | ||||
| import argparse | ||||
| from gtsam import ( | ||||
|     Cal3_S2, | ||||
|     EdgeKey, | ||||
|     EssentialMatrix, | ||||
|     FundamentalMatrix, | ||||
|     LevenbergMarquardtOptimizer, | ||||
|     LevenbergMarquardtParams, | ||||
|     NonlinearFactorGraph, | ||||
|     PinholeCameraCal3_S2, | ||||
|     Values, | ||||
| ) | ||||
| 
 | ||||
| # For symbol shorthand (e.g., K(0), K(1)) | ||||
| K_sym = gtsam.symbol_shorthand.K | ||||
| 
 | ||||
| # Methods to compare | ||||
| methods = ["FundamentalMatrix", "EssentialMatrix"] | ||||
| 
 | ||||
| 
 | ||||
| # Formatter function for printing keys | ||||
| def formatter(key): | ||||
|     sym = gtsam.Symbol(key) | ||||
|     if sym.chr() == ord("k"): | ||||
|         return f"k{sym.index()}" | ||||
|     else: | ||||
|         edge = EdgeKey(key) | ||||
|         return f"({edge.i()},{edge.j()})" | ||||
| 
 | ||||
| 
 | ||||
| # Function to simulate data | ||||
| def simulate_data(num_cameras): | ||||
|     # Define the camera calibration parameters | ||||
|     K = Cal3_S2(50.0, 50.0, 0.0, 50.0, 50.0) | ||||
| 
 | ||||
|     # Create the set of 8 ground-truth landmarks | ||||
|     points = SFMdata.createPoints() | ||||
| 
 | ||||
|     # Create the set of ground-truth poses | ||||
|     poses = SFMdata.posesOnCircle(num_cameras, 30) | ||||
| 
 | ||||
|     # Simulate measurements from each camera pose | ||||
|     measurements = [[None for _ in range(len(points))] for _ in range(num_cameras)] | ||||
|     for i in range(num_cameras): | ||||
|         camera = PinholeCameraCal3_S2(poses[i], K) | ||||
|         for j in range(len(points)): | ||||
|             measurements[i][j] = camera.project(points[j]) | ||||
| 
 | ||||
|     return points, poses, measurements, K | ||||
| 
 | ||||
| 
 | ||||
| # Function to compute ground truth matrices | ||||
| def compute_ground_truth_matrices(method, poses, K): | ||||
|     if method == "FundamentalMatrix": | ||||
|         F1 = FundamentalMatrix(K, poses[0].between(poses[1]), K) | ||||
|         F2 = FundamentalMatrix(K, poses[0].between(poses[2]), K) | ||||
|         return F1, F2 | ||||
|     elif method == "EssentialMatrix": | ||||
|         E1 = EssentialMatrix.FromPose3(poses[0].between(poses[1])) | ||||
|         E2 = EssentialMatrix.FromPose3(poses[0].between(poses[2])) | ||||
|         return E1, E2 | ||||
|     else: | ||||
|         raise ValueError(f"Unknown method {method}") | ||||
| 
 | ||||
| 
 | ||||
| # Function to build the factor graph | ||||
| def build_factor_graph(method, num_cameras, measurements): | ||||
|     graph = NonlinearFactorGraph() | ||||
| 
 | ||||
|     if method == "FundamentalMatrix": | ||||
|         # Use TransferFactorFundamentalMatrix | ||||
|         FactorClass = gtsam.TransferFactorFundamentalMatrix | ||||
|     elif method == "EssentialMatrix": | ||||
|         # Use EssentialTransferFactorCal3_S2 | ||||
|         FactorClass = gtsam.EssentialTransferFactorCal3_S2 | ||||
|     else: | ||||
|         raise ValueError(f"Unknown method {method}") | ||||
| 
 | ||||
|     for a in range(num_cameras): | ||||
|         b = (a + 1) % num_cameras  # Next camera | ||||
|         c = (a + 2) % num_cameras  # Camera after next | ||||
| 
 | ||||
|         # Vectors to collect tuples for each factor | ||||
|         tuples1 = [] | ||||
|         tuples2 = [] | ||||
|         tuples3 = [] | ||||
| 
 | ||||
|         # Collect data for the three factors | ||||
|         for j in range(len(measurements[0])): | ||||
|             tuples1.append((measurements[a][j], measurements[b][j], measurements[c][j])) | ||||
|             tuples2.append((measurements[a][j], measurements[c][j], measurements[b][j])) | ||||
|             tuples3.append((measurements[c][j], measurements[b][j], measurements[a][j])) | ||||
| 
 | ||||
|         # Add transfer factors between views a, b, and c. | ||||
|         graph.add(FactorClass(EdgeKey(a, c), EdgeKey(b, c), tuples1)) | ||||
|         graph.add(FactorClass(EdgeKey(a, b), EdgeKey(b, c), tuples2)) | ||||
|         graph.add(FactorClass(EdgeKey(a, c), EdgeKey(a, b), tuples3)) | ||||
| 
 | ||||
|     return graph | ||||
| 
 | ||||
| 
 | ||||
| # Function to get initial estimates | ||||
| def get_initial_estimate(method, num_cameras, ground_truth_matrices, K_initial): | ||||
|     initialEstimate = Values() | ||||
| 
 | ||||
|     if method == "FundamentalMatrix": | ||||
|         F1, F2 = ground_truth_matrices | ||||
|         for a in range(num_cameras): | ||||
|             b = (a + 1) % num_cameras  # Next camera | ||||
|             c = (a + 2) % num_cameras  # Camera after next | ||||
|             initialEstimate.insert(EdgeKey(a, b).key(), F1) | ||||
|             initialEstimate.insert(EdgeKey(a, c).key(), F2) | ||||
|     elif method == "EssentialMatrix": | ||||
|         E1, E2 = ground_truth_matrices | ||||
|         for a in range(num_cameras): | ||||
|             b = (a + 1) % num_cameras  # Next camera | ||||
|             c = (a + 2) % num_cameras  # Camera after next | ||||
|             initialEstimate.insert(EdgeKey(a, b).key(), E1) | ||||
|             initialEstimate.insert(EdgeKey(a, c).key(), E2) | ||||
|         # Insert initial calibrations | ||||
|         for i in range(num_cameras): | ||||
|             initialEstimate.insert(K_sym(i), K_initial) | ||||
|     else: | ||||
|         raise ValueError(f"Unknown method {method}") | ||||
| 
 | ||||
|     return initialEstimate | ||||
| 
 | ||||
| 
 | ||||
| # Function to optimize the graph | ||||
| def optimize(graph, initialEstimate): | ||||
|     params = LevenbergMarquardtParams() | ||||
|     params.setlambdaInitial(1000.0)  # Initialize lambda to a high value | ||||
|     params.setVerbosityLM("SUMMARY") | ||||
|     optimizer = LevenbergMarquardtOptimizer(graph, initialEstimate, params) | ||||
|     result = optimizer.optimize() | ||||
|     return result | ||||
| 
 | ||||
| 
 | ||||
| # Function to compute distances from ground truth | ||||
| def compute_distances(method, result, ground_truth_matrices, num_cameras): | ||||
|     distances = [] | ||||
|     if method == "FundamentalMatrix": | ||||
|         F1, F2 = ground_truth_matrices | ||||
|         for a in range(num_cameras): | ||||
|             b = (a + 1) % num_cameras | ||||
|             c = (a + 2) % num_cameras | ||||
|             key_ab = EdgeKey(a, b).key() | ||||
|             key_ac = EdgeKey(a, c).key() | ||||
|             F_est_ab = result.atFundamentalMatrix(key_ab) | ||||
|             F_est_ac = result.atFundamentalMatrix(key_ac) | ||||
|             # Compute local coordinates | ||||
|             dist_ab = np.linalg.norm(F1.localCoordinates(F_est_ab)) | ||||
|             dist_ac = np.linalg.norm(F2.localCoordinates(F_est_ac)) | ||||
|             distances.append(dist_ab) | ||||
|             distances.append(dist_ac) | ||||
|     elif method == "EssentialMatrix": | ||||
|         E1, E2 = ground_truth_matrices | ||||
|         for a in range(num_cameras): | ||||
|             b = (a + 1) % num_cameras | ||||
|             c = (a + 2) % num_cameras | ||||
|             key_ab = EdgeKey(a, b).key() | ||||
|             key_ac = EdgeKey(a, c).key() | ||||
|             E_est_ab = result.atEssentialMatrix(key_ab) | ||||
|             E_est_ac = result.atEssentialMatrix(key_ac) | ||||
|             # Compute local coordinates | ||||
|             dist_ab = np.linalg.norm(E1.localCoordinates(E_est_ab)) | ||||
|             dist_ac = np.linalg.norm(E2.localCoordinates(E_est_ac)) | ||||
|             distances.append(dist_ab) | ||||
|             distances.append(dist_ac) | ||||
|     else: | ||||
|         raise ValueError(f"Unknown method {method}") | ||||
|     return distances | ||||
| 
 | ||||
| 
 | ||||
| # Function to plot results | ||||
| def plot_results(results): | ||||
|     methods = list(results.keys()) | ||||
|     final_errors = [results[method]["final_error"] for method in methods] | ||||
|     distances = [np.mean(results[method]["distances"]) for method in methods] | ||||
| 
 | ||||
|     fig, ax1 = plt.subplots() | ||||
| 
 | ||||
|     color = "tab:red" | ||||
|     ax1.set_xlabel("Method") | ||||
|     ax1.set_ylabel("Final Error", color=color) | ||||
|     ax1.bar(methods, final_errors, color=color, alpha=0.6) | ||||
|     ax1.tick_params(axis="y", labelcolor=color) | ||||
| 
 | ||||
|     ax2 = ax1.twinx() | ||||
|     color = "tab:blue" | ||||
|     ax2.set_ylabel("Mean Geodesic Distance", color=color) | ||||
|     ax2.plot(methods, distances, color=color, marker="o") | ||||
|     ax2.tick_params(axis="y", labelcolor=color) | ||||
| 
 | ||||
|     plt.title("Comparison of Methods") | ||||
|     fig.tight_layout() | ||||
|     plt.show() | ||||
| 
 | ||||
| 
 | ||||
| # Main function | ||||
| def main(): | ||||
|     # Parse command line arguments | ||||
|     parser = argparse.ArgumentParser(description="Compare Fundamental and Essential Matrix Methods") | ||||
|     parser.add_argument("--num_cameras", type=int, default=4, help="Number of cameras (default: 4)") | ||||
|     args = parser.parse_args() | ||||
| 
 | ||||
|     # Initialize results dictionary | ||||
|     results = {} | ||||
| 
 | ||||
|     for method in methods: | ||||
|         print(f"Running method: {method}") | ||||
| 
 | ||||
|         # Simulate data | ||||
|         points, poses, measurements, K_initial = simulate_data(args.num_cameras) | ||||
| 
 | ||||
|         # Compute ground truth matrices | ||||
|         ground_truth_matrices = compute_ground_truth_matrices(method, poses, K_initial) | ||||
| 
 | ||||
|         # Build the factor graph | ||||
|         graph = build_factor_graph(method, args.num_cameras, measurements) | ||||
| 
 | ||||
|         # Get initial estimates | ||||
|         initialEstimate = get_initial_estimate(method, args.num_cameras, ground_truth_matrices, K_initial) | ||||
| 
 | ||||
|         # Optimize the graph | ||||
|         result = optimize(graph, initialEstimate) | ||||
| 
 | ||||
|         # Compute distances from ground truth | ||||
|         distances = compute_distances(method, result, ground_truth_matrices, args.num_cameras) | ||||
| 
 | ||||
|         # Compute final error | ||||
|         final_error = graph.error(result) | ||||
| 
 | ||||
|         # Store results | ||||
|         results[method] = {"distances": distances, "final_error": final_error} | ||||
| 
 | ||||
|         print(f"Method: {method}") | ||||
|         print(f"Final Error: {final_error}") | ||||
|         print(f"Mean Geodesic Distance: {np.mean(distances)}\n") | ||||
| 
 | ||||
|     # Plot results | ||||
|     plot_results(results) | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|     main() | ||||
		Loading…
	
		Reference in New Issue