Add prior, use calibrations
parent
60ce938e31
commit
bbba497ca1
|
@ -71,7 +71,7 @@ def compute_ground_truth(method, poses, cal):
|
||||||
E1 = EssentialMatrix.FromPose3(poses[0].between(poses[1]))
|
E1 = EssentialMatrix.FromPose3(poses[0].between(poses[1]))
|
||||||
E2 = EssentialMatrix.FromPose3(poses[0].between(poses[2]))
|
E2 = EssentialMatrix.FromPose3(poses[0].between(poses[2]))
|
||||||
|
|
||||||
# Assert that E1.matrix and F1 are the same
|
# Assert that E1.matrix and F1 are the same, with known calibration
|
||||||
invK = np.linalg.inv(cal.K())
|
invK = np.linalg.inv(cal.K())
|
||||||
G1 = invK.transpose() @ E1.matrix() @ invK
|
G1 = invK.transpose() @ E1.matrix() @ invK
|
||||||
G2 = invK.transpose() @ E2.matrix() @ invK
|
G2 = invK.transpose() @ E2.matrix() @ invK
|
||||||
|
@ -94,6 +94,10 @@ def build_factor_graph(method, num_cameras, measurements):
|
||||||
FactorClass = gtsam.TransferFactorFundamentalMatrix
|
FactorClass = gtsam.TransferFactorFundamentalMatrix
|
||||||
elif method == "EssentialMatrix":
|
elif method == "EssentialMatrix":
|
||||||
FactorClass = gtsam.EssentialTransferFactorCal3f
|
FactorClass = gtsam.EssentialTransferFactorCal3f
|
||||||
|
# add priors on all calibrations:
|
||||||
|
for i in range(num_cameras):
|
||||||
|
model = gtsam.noiseModel.Isotropic.Sigma(1, 10.0)
|
||||||
|
graph.addPriorCal3f(K(i), Cal3f(50.0, 50.0, 50.0), model)
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Unknown method {method}")
|
raise ValueError(f"Unknown method {method}")
|
||||||
|
|
||||||
|
@ -120,7 +124,7 @@ def build_factor_graph(method, num_cameras, measurements):
|
||||||
return graph
|
return graph
|
||||||
|
|
||||||
|
|
||||||
def get_initial_estimate(method, num_cameras, ground_truth, cal, std=1e-6):
|
def get_initial_estimate(method, num_cameras, ground_truth, cal):
|
||||||
"""get initial estimate for method"""
|
"""get initial estimate for method"""
|
||||||
initialEstimate = Values()
|
initialEstimate = Values()
|
||||||
total_dimension = 0
|
total_dimension = 0
|
||||||
|
@ -130,22 +134,16 @@ def get_initial_estimate(method, num_cameras, ground_truth, cal, std=1e-6):
|
||||||
for a in range(num_cameras):
|
for a in range(num_cameras):
|
||||||
b = (a + 1) % num_cameras # Next camera
|
b = (a + 1) % num_cameras # Next camera
|
||||||
c = (a + 2) % num_cameras # Camera after next
|
c = (a + 2) % num_cameras # Camera after next
|
||||||
# Retract with delta drawn from zero-mean normal
|
initialEstimate.insert(EdgeKey(a, b).key(), F1)
|
||||||
F1_perturbed = F1.retract(np.random.normal(0, std, F1.dim()))
|
initialEstimate.insert(EdgeKey(a, c).key(), F2)
|
||||||
F2_perturbed = F2.retract(np.random.normal(0, 1e-4, F2.dim()))
|
|
||||||
initialEstimate.insert(EdgeKey(a, b).key(), F1_perturbed)
|
|
||||||
initialEstimate.insert(EdgeKey(a, c).key(), F2_perturbed)
|
|
||||||
total_dimension += F1.dim() + F2.dim()
|
total_dimension += F1.dim() + F2.dim()
|
||||||
elif method == "EssentialMatrix":
|
elif method == "EssentialMatrix":
|
||||||
E1, E2 = ground_truth
|
E1, E2 = ground_truth
|
||||||
for a in range(num_cameras):
|
for a in range(num_cameras):
|
||||||
b = (a + 1) % num_cameras # Next camera
|
b = (a + 1) % num_cameras # Next camera
|
||||||
c = (a + 2) % num_cameras # Camera after next
|
c = (a + 2) % num_cameras # Camera after next
|
||||||
# Retract with delta drawn from zero-mean normal
|
initialEstimate.insert(EdgeKey(a, b).key(), E1)
|
||||||
E1_perturbed = E1.retract(np.random.normal(0, std, E1.dim()))
|
initialEstimate.insert(EdgeKey(a, c).key(), E2)
|
||||||
E2_perturbed = E2.retract(np.random.normal(0, 1e-4, E2.dim()))
|
|
||||||
initialEstimate.insert(EdgeKey(a, b).key(), E1_perturbed)
|
|
||||||
initialEstimate.insert(EdgeKey(a, c).key(), E2_perturbed)
|
|
||||||
total_dimension += E1.dim() + E2.dim()
|
total_dimension += E1.dim() + E2.dim()
|
||||||
# Insert initial calibrations
|
# Insert initial calibrations
|
||||||
for i in range(num_cameras):
|
for i in range(num_cameras):
|
||||||
|
@ -158,14 +156,17 @@ def get_initial_estimate(method, num_cameras, ground_truth, cal, std=1e-6):
|
||||||
return initialEstimate
|
return initialEstimate
|
||||||
|
|
||||||
|
|
||||||
def optimize(graph, initialEstimate):
|
def optimize(graph, initialEstimate, method):
|
||||||
"""optimize the graph"""
|
"""optimize the graph"""
|
||||||
params = LevenbergMarquardtParams()
|
params = LevenbergMarquardtParams()
|
||||||
params.setlambdaInitial(1000.0) # Initialize lambda to a high value
|
params.setlambdaInitial(1e10) # Initialize lambda to a high value
|
||||||
params.setAbsoluteErrorTol(1.0)
|
params.setlambdaUpperBound(1e10)
|
||||||
|
# params.setAbsoluteErrorTol(0.1)
|
||||||
params.setVerbosityLM("SUMMARY")
|
params.setVerbosityLM("SUMMARY")
|
||||||
optimizer = LevenbergMarquardtOptimizer(graph, initialEstimate, params)
|
optimizer = LevenbergMarquardtOptimizer(graph, initialEstimate, params)
|
||||||
result = optimizer.optimize()
|
result = optimizer.optimize()
|
||||||
|
# if method == "EssentialMatrix":
|
||||||
|
# result.print("Final results:\n", formatter)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@ -195,9 +196,15 @@ def compute_distances(method, result, ground_truth, num_cameras, cal):
|
||||||
elif method == "EssentialMatrix":
|
elif method == "EssentialMatrix":
|
||||||
E_est_ab = result.atEssentialMatrix(key_ab)
|
E_est_ab = result.atEssentialMatrix(key_ab)
|
||||||
E_est_ac = result.atEssentialMatrix(key_ac)
|
E_est_ac = result.atEssentialMatrix(key_ac)
|
||||||
|
|
||||||
|
# Retrieve correct calibrations from result:
|
||||||
|
cal_a = result.atCal3f(K(a))
|
||||||
|
cal_b = result.atCal3f(K(b))
|
||||||
|
cal_c = result.atCal3f(K(c))
|
||||||
|
|
||||||
# Convert estimated EssentialMatrices to FundamentalMatrices
|
# Convert estimated EssentialMatrices to FundamentalMatrices
|
||||||
F_est_ab = gtsam.FundamentalMatrix(cal.K(), E_est_ab, cal.K())
|
F_est_ab = gtsam.FundamentalMatrix(cal_a.K(), E_est_ab, cal_b.K())
|
||||||
F_est_ac = gtsam.FundamentalMatrix(cal.K(), E_est_ac, cal.K())
|
F_est_ac = gtsam.FundamentalMatrix(cal_a.K(), E_est_ac, cal_c.K())
|
||||||
|
|
||||||
# Compute local coordinates (geodesic distance on the F-manifold)
|
# Compute local coordinates (geodesic distance on the F-manifold)
|
||||||
dist_ab = np.linalg.norm(F1.localCoordinates(F_est_ab))
|
dist_ab = np.linalg.norm(F1.localCoordinates(F_est_ab))
|
||||||
|
@ -240,7 +247,7 @@ def main():
|
||||||
parser.add_argument("--num_cameras", type=int, default=4, help="Number of cameras (default: 4)")
|
parser.add_argument("--num_cameras", type=int, default=4, help="Number of cameras (default: 4)")
|
||||||
parser.add_argument("--nr_trials", type=int, default=5, help="Number of trials (default: 5)")
|
parser.add_argument("--nr_trials", type=int, default=5, help="Number of trials (default: 5)")
|
||||||
parser.add_argument("--seed", type=int, default=42, help="Random seed (default: 42)")
|
parser.add_argument("--seed", type=int, default=42, help="Random seed (default: 42)")
|
||||||
parser.add_argument("--noise_std", type=float, default=1.0, help="Standard deviation of noise (default: 1.0)")
|
parser.add_argument("--noise_std", type=float, default=0.5, help="Standard deviation of noise (default: 0.5)")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
# Initialize the random number generator
|
# Initialize the random number generator
|
||||||
|
@ -273,17 +280,14 @@ def main():
|
||||||
graph = build_factor_graph(method, args.num_cameras, measurements)
|
graph = build_factor_graph(method, args.num_cameras, measurements)
|
||||||
|
|
||||||
# Assert that the initial error is the same for both methods:
|
# Assert that the initial error is the same for both methods:
|
||||||
if trial == 0 and method == methods[0]:
|
if method == methods[0]:
|
||||||
initial_error = graph.error(initial_estimate[method])
|
error0 = graph.error(initial_estimate[method])
|
||||||
else:
|
else:
|
||||||
current_error = graph.error(initial_estimate[method])
|
current_error = graph.error(initial_estimate[method])
|
||||||
if not np.allclose(initial_error, current_error):
|
assert np.allclose(error0, current_error)
|
||||||
print(f"Initial error for {method} ({current_error}) does not match ")
|
|
||||||
print(f"initial error for {methods[0]} ({initial_error})")
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
# Optimize the graph
|
# Optimize the graph
|
||||||
result = optimize(graph, initial_estimate[method])
|
result = optimize(graph, initial_estimate[method], method)
|
||||||
|
|
||||||
# Compute distances from ground truth
|
# Compute distances from ground truth
|
||||||
distances = compute_distances(method, result, ground_truth[method], args.num_cameras, cal)
|
distances = compute_distances(method, result, ground_truth[method], args.num_cameras, cal)
|
||||||
|
|
Loading…
Reference in New Issue