I realise the above is probably a bit complicated to answer. I've simplified my scenario to just two camera measurement points, each linked with a between factor, a prior on the first measurement point and a prior on the landmark. The prior on the landmark is slightly incorrect, everything else is exact. I expect to see the landmark location updated to roughly the correct location (maybe with some adjustment of the camera measurement locations), but it seems the GenericProjectionFactorCal3_S2 are not having any effect.
import gtsam
import numpy as np
from gtsam.symbol_shorthand import T, X
def testsimple():
#Noise Values.
prior_xy_sigma = 1.0
prior_theta_sigma=5* np.pi/180
odom_xy_sigma=1.0
odom_theta_sigma=5*np.pi/180.
PRIOR_NOISE = gtsam.noiseModel.Diagonal.Sigmas(np.array([
0.001,
0.001,
prior_theta_sigma,
prior_xy_sigma,
prior_xy_sigma,
0.001
]))
TGT_PRIOR_NOISE = gtsam.noiseModel.Diagonal.Sigmas(np.array([
10.3,
10.3,
10.1
]))
ODOM_NOISE = gtsam.noiseModel.Diagonal.Sigmas(np.array([
0.001,
0.001,
odom_theta_sigma,
odom_xy_sigma,
odom_xy_sigma,
0.001,
]))
#Camera Measurement Noise
MEAS_NOISE = gtsam.noiseModel.Isotropic.Sigma(2, 1.) #one pixel in u and v
#Landmark Point
landmark3 = gtsam.Point3(10., 0., 0.)
#Two Measurement Poses
poseOne = gtsam.Pose2(0., 0., 0.)
poseOne3 = gtsam.Pose3(poseOne)
poseTwo = gtsam.Pose2(1., 0., 0.)
poseTwo3 = gtsam.Pose3(poseTwo)
#Camera Calibration - 20 x 1 line camera with fov of 10 degrees
K = gtsam.Cal3_S2(10., 20, 1)
#Rotate camera pose so it is looking down x direction
#Measurement from Pose one
cameraPoseOne3 = gtsam.Pose3(gtsam.Rot3.Ry(np.pi/2)*poseOne3.rotation(), poseOne3.translation())
cameraOne = gtsam.PinholeCameraCal3_S2(cameraPoseOne3, K)
measurementOne = cameraOne.project(landmark3)
#Measurement From Pose two
cameraPoseTwo3 = gtsam.Pose3(gtsam.Rot3.Ry(np.pi/2)*poseTwo3.rotation(), poseTwo3.translation())
cameraTwo = gtsam.PinholeCameraCal3_S2(cameraPoseTwo3, K)
measurementTwo = cameraTwo.project(landmark3)
#initialise values and graph
initestimate = gtsam.Values()
graph = gtsam.NonlinearFactorGraph()
#Add Factors.
#Add Priors:
#add landmark prior
landmark_estimate_error = gtsam.Point3(1., 1., 0.3)
initestimate.insert(T(0), landmark3)
graph.push_back(gtsam.PriorFactorPoint3(
T(0),
landmark3 + landmark_estimate_error,
TGT_PRIOR_NOISE))
#add first pose prior
graph.push_back(gtsam.PriorFactorPose3(
X(0),
poseOne3,
PRIOR_NOISE))
initestimate.insert(X(0), poseOne3)
#Add between factor for poses.
btfactor = poseTwo3 * poseOne3.inverse()
graph.push_back(gtsam.BetweenFactorPose3(
X(0),
X(1),
btfactor,
ODOM_NOISE
))
initestimate.insert(X(1), poseTwo3)
#Add camera measurements
factor1 = gtsam.GenericProjectionFactorCal3_S2(
measurementOne,
MEAS_NOISE,
X(0),
T(0),
K
)
graph.push_back(factor1)
factor2 = gtsam.GenericProjectionFactorCal3_S2(
measurementTwo,
MEAS_NOISE,
X(1),
T(0),
K
)
graph.push_back(factor2)
#Optimise:
params = gtsam.ISAM2Params()
params.setRelinearizeThreshold(0.1)
params.relinearizeSkip = 1
isam = gtsam.ISAM2(params)
print(f"Init estimate \n {initestimate}")
isam.update(graph, initestimate )
current_estimate = isam.calculateEstimate()
print(f"Current estimate \n {current_estimate}")
if __name__ == "__main__":
testsimple()
The std output of values from the two print statements at the end is:
init estimate
Values with 3 values:
Value t0: (Eigen::Matrix<double, -1, 1, 0, -1, 1>)
[
10;
0;
0
]
Value x0: (gtsam::Pose3)
R: [
1, 0, 0;
0, 1, 0;
0, 0, 1
]
t: 0 0 0
Value x1: (gtsam::Pose3)
R: [
1, 0, 0;
0, 1, 0;
0, 0, 1
]
t: 1 0 0
Current estimate
Values with 3 values:
Value t0: (Eigen::Matrix<double, -1, 1, 0, -1, 1>)
[
11;
1;
0.3
]
Value x0: (gtsam::Pose3)
R: [
1, 0, 0;
0, 1, 0;
0, 0, 1
]
t: 0 0 0
Value x1: (gtsam::Pose3)
R: [
1, 0, 0;
0, 1, 0;
0, 0, 1
]
t: 1 0 0
As you can see neither X1 or X0 are changed, and T0 is only updated to conform to the prior on the landmark. I am expecting to see a general adjustment across all variables.
cheers
Peter