NOTE / 5/4/2019
[V-SLAM] Implementing Bundle Adjustment
SLAM back ends are commonly filter-based or optimization-based. Optimization can linearize repeatedly and is generally more accurate; ORB-SLAM and DSO are representative systems. Bundle Adjustment (BA) is the core optimization problem. This article introduces purely visual BA, a small implementation, and simulation tests.
1. Fundamentals
Feature-based visual SLAM minimizes reprojection error to optimize camera poses and map-point positions.
1.1 Map-point parameterization
A point can use Cartesian coordinates or inverse depth. This implementation uses the conventional Cartesian form.
1.2 Camera model
With a pinhole camera, a 3D point projects to pixel :
is the intrinsic matrix, the pose, and the homogeneous map point. With known intrinsics,
1.3 Error and least squares
The variables are camera poses and map points , contains all 3D-to-2D observations, is residual, and is the robust kernel. Use Huber:
As in g2o, express robustification as a weight:
The objective becomes
1.4 Jacobians
For , use a Lie-algebra pose perturbation. Define
The pose Jacobian is
and the map-point Jacobian is
1.5 Solving increments
Gauss–Newton and Levenberg–Marquardt solve
BA’s Hessian is sparse.

Its camera-only block and map-point-only block are block diagonal:
Schur-complement elimination first solves camera increments:
then map-point increments:
is block diagonal, so this is efficient.
1.6 Gauge freedom
BA must fix states or include priors; otherwise infinitely many globally transformed solutions have identical reprojection error. Conventionally fix the first pose. This solver directly fixes states.
2. Implementation
This small implementation solves only the formulation above. It uses dense storage, does not exploit sparse-matrix acceleration, implements LM only, supports motion-only, structure-only, and full BA without priors, and has limited invalid-input robustness. Complete source: ydsf16/vslam.
2.1 MapPoint
Stores an optimizable map point and position increment. When its fixed flag is set, it is excluded from the state vector.
class MapPoint{
public:
MapPoint(const Eigen::Vector3d& position, int id, bool fixed = false);
const Eigen::Vector3d& getPosition();
void setPosition(const Eigen::Vector3d& position);
int getId();
void setId(int id);
void setFixed();
bool isFixed();
void addDeltaPosition(const Eigen::Vector3d& delta_position);
int state_index_;
private:
Eigen::Vector3d position_;
int id_;
bool fixed_;
}; //class MapPoint
2.2 Camera
Stores an optimizable camera and its left-multiplicative pose increment. A fixed camera is excluded from the state vector.
class Camera{
public:
Camera(const Sophus::SE3& pose, int id, bool fixed = false);
const Sophus::SE3& getPose();
void setPose(const Sophus::SE3& pose);
int getId();
void setId(int id);
void setFixed();
bool isFixed();
void addDeltaPose(const Eigen::Matrix<double, 6, 1>& delta_pose);
void addDeltaPose(const Sophus::SE3& delta_pose);
int state_index_;
private:
Sophus::SE3 pose_;
int id_;
bool fixed_;
}; // class CameraPose
2.3 CostFunction
Stores an observation, evaluates cost and Jacobians, and computes Huber weighting.
class CostFunction{
public:
CostFunction(MapPoint* map_point, Camera* camera,
double fx, double fy, double cx, double cy, const Eigen::Vector2d& ob_z);
void setHuberParameter(double b = 1.0);
void setCovariance(const Eigen::Matrix2d& cov);
void computeInterVars(Eigen::Vector2d& e, Eigen::Matrix2d& weighted_info, double& weighted_e2);
void computeJT(Eigen::Matrix<double, 2, 6>& JT);
void computeJX(Eigen::Matrix<double, 2, 3>& JX);
MapPoint* map_point_;
Camera* camera_;
private:
double fx_, fy_, cx_, cy_;
Eigen::Vector2d ob_z_;
Eigen::Matrix2d info_matrix_;
double huber_b_;
}; // class CostFunction
2.4 BundleAdjustment
Stores the full BA problem. Maps provide ID lookup, the cost-function set stores residual terms, and the implementation aggregates Jacobians, information, Hessian, and residuals for LM.
class BundleAdjustment{
public:
BundleAdjustment();
~BundleAdjustment(); // free memory.
void addMapPoint(MapPoint* mpt);
void addCamera(Camera* cam);
void addCostFunction(CostFunction* cost_func);
MapPoint* getMapPoint(int id);
Camera* getCamera(int id);
void setConvergenceCondition(int max_iters, double min_delta, double min_error);
void setVerbose(bool flag);
void optimize();
private:
void optimizationInit(); // init for the optimization.
void computeStateIndexes(); // compute index for every state.
void computeHAndbAndError();
void solveNormalEquation();
void inverseM(const Eigen::MatrixXd& M, Eigen::MatrixXd& M_inv);
void updateStates();
void recoverStates();
std::map<int, MapPoint*> mappoints_; // all mappoints
std::map<int, Camera*> cameras_; // all cameras.
std::set<CostFunction*> cost_functions_; // all cost functions.
Eigen::MatrixXd J_; // Jocabian matrix.
Eigen::MatrixXd JTinfo_;
Eigen::MatrixXd H_; // Hassian matrix.
Eigen::MatrixXd r_; // residual vector.
Eigen::MatrixXd b_;
Eigen::MatrixXd info_matrix_; // information matrix.
Eigen::MatrixXd Delta_X_; // Delta_X_
Eigen::MatrixXd I_;
int n_cam_state_; // number of cameras in the state vector.
int n_mpt_state_; // number of map points in the state vector.
/* Convergence condition */
int max_iters_;
double min_delta_;
double min_error_;
double sum_error2_;
double last_sum_error2_;
bool verbose_;
}; // BundleAdjustment
2.5 Building a BA problem
The workflow matches g2o:
- Create map points and cameras and add them to the BA object.
- Create observation cost functions and add them.
- Iterate optimization.
- Retrieve results by ID.
BundleAdjustment ba_mba;
ba_mba.setConvergenceCondition(20, 1e-5, 1e-10);
ba_mba.setVerbose(true);
// add mappoints
for(size_t i = 0; i < noise_mappoints.size(); i ++)
{
const Eigen::Vector3d& npt = noise_mappoints.at(i);
MapPoint* mpt = new MapPoint(npt, i);
mpt->setFixed(); // fixed all mappoints.
ba_mba.addMapPoint(mpt);
} // add mappoints
// add cameras.
for(size_t i = 0; i < noise_cameras.size(); i ++)
{
const Sophus::SE3& ncam = noise_cameras.at(i);
Camera* cam = new Camera(ncam, i);
ba_mba.addCamera(cam);
} // add cameras.
// add observations
for(size_t i = 0; i < noise_observations.size(); i ++)
{
const Observation& ob = noise_observations.at(i);
MapPoint* mpt = ba_mba.getMapPoint(ob.mpt_id_);
Camera* cam = ba_mba.getCamera(ob.cam_id_);
CostFunction* cost_func = new CostFunction(mpt, cam, fx, fy, cx, cy, ob.ob_);
ba_mba.addCostFunction(cost_func);
} // add observations.
// Optimize
ba_mba.optimize();
// Compute pose Error
double sum_rot_error = 0.0;
double sum_trans_error = 0.0;
for(size_t i = 0; i < cameras.size(); i ++)
{
Camera* cam = ba_mba.getCamera(i);
const Sophus::SE3& opt_pose = cam->getPose();
const Sophus::SE3& org_pose = cameras.at(i);
Sophus::SE3 pose_err = opt_pose * org_pose.inverse();
sum_rot_error += pose_err.so3().log().norm();
sum_trans_error += pose_err.translation().norm();
}
std::cout << "Mean rot error: " << sum_rot_error / (double)(cameras.size())
<< "\tMean trans error: " << sum_trans_error / (double)(cameras.size()) << std::endl;
3. Experiments
Simulation supplies known ground truth: generate cameras, map points, and observations, add noise, then evaluate recovery. The example uses 6 cameras, 275 map points, and 1,650 observations.
3.1 Motion-only BA
Fix map points and optimize poses. With no noise, optimized output equals ground truth. Test noise is map-point , translation , rotation , and observation .

3.2 Structure-only BA
Fix cameras and optimize map points with map-point noise , zero camera noise, and one-pixel observation noise.

3.3 Full BA
Fix only the first camera; optimize all other cameras and points with map-point noise , translation noise , rotation noise , and one-pixel observation noise.

The solver converges well. Camera-pose accuracy is higher than map-point accuracy because a camera observes many points, while a point is observed by only a few cameras.
Runtime is slow due to frequent memory copying, slow normal-equation solves, no use of sparse structure, and limited Eigen tuning. This is a foundational version.
References
- Triggs, B. Bundle Adjustment — A Modern Synthesis. Vision Algorithms: Theory and Practice, 1999.
- 14 Lectures on Visual SLAM
- Methods for Non-Linear Least Squares Problems