This MATLAB-based project implements incremental 3D reconstruction from a sequence of images to estimate:
- 3D point cloud of the scene
- Camera poses (rotation
R
and translationt
) for each image
Built as part of the ENSEIRB Matmeca curriculum, the pipeline follows the Structure from Motion (SfM) methodology, assuming pre-computed keypoint correspondences (outlier-free).
- Incremental Reconstruction: Adds images iteratively while refining poses and 3D points.
- Bundle Adjustment: Minimizes reprojection error using the Levenberg-Marquardt algorithm.
- Sparse Jacobian Optimization: Efficiently handles large-scale problems with MATLAB’s
sparse
matrices. - Visualization: Supports
pointCloud()
for interactive 3D exploration orplot3()
for basic plotting. - Error Analysis: Tracks reprojection errors before/after refinement.
The reconstruction process relies on key concepts from multi-view geometry. Initially, two images are used to estimate their relative pose by decomposing the fundamental matrix, assuming known intrinsic parameters. From this, initial 3D points are reconstructed via triangulation. A bundle adjustment algorithm is then applied to jointly refine the 3D point positions and camera poses by minimizing the reprojection error.
As new images are introduced, the process becomes incremental:
- Localization – The pose of the new camera is estimated using already triangulated 3D points.
- Triangulation – New 3D points are computed using the newly added view.
- Refinement – A global bundle adjustment is periodically applied to refine all poses and 3D points, using sparse optimization techniques for memory efficiency.
Finally, the reconstruction is visualized using colored 3D point clouds, enhancing both clarity and realism.
The optimization problem solved during bundle adjustment minimizes the reprojection error across all visible points in all images. The cost function is defined as:
Where:
-
$M$ : Number of images (in the initialization step,$M = 2$ ). -
$C_j$ : Number of 3D points observed in image$j$ . -
$R_{wj},\ t_{wj}$ : Rotation and translation matrices of image$j$ . -
$u_i^w$ : Reconstructed 3D points in world coordinates. -
$\texttt{p2DId}(c)$ : Index of the 2D point among all detected points in image$j$ . -
$\texttt{p3DId}(c)$ : Index of the corresponding 3D point. -
$\pi(\cdot)$ : Projection function that maps 3D points to 2D image coordinates. -
$K$ : Intrinsic camera calibration matrix of the form:
where
In this project, the matrix
-
$p_{j,\texttt{p2DId}(c)}$ : The 2D point in image$j$ corresponding to the projection of the 3D point$u^w_{\texttt{p3DId}(c)}$ .
To implement the Levenberg–Marquardt algorithm for bundle adjustment, we need to compute the derivative of the projection function with respect to the parameters ${R_{wj},\ t_{wj}}{j=1}^{M}$ and ${u_i^w}{i=1}^{N}$.
At each iteration, the updates are:
$R_{wj}^{(l+1)} = R_{wj}^{(l)} \cdot \exp([\delta R_{wj}]^\wedge)$ $t_{wj}^{(l+1)} = t_{wj}^{(l)} + \delta t_{wj}$ $u_i^{w,(l+1)} = u_i^{w,(l)} + \delta u_i^w$
Since rotation matrices lie in a Lie group, the increment
$\delta R_{wj}$ is a 3D vector (3 degrees of freedom) converted to a rotation matrix via the matrix exponential.
The reprojection error is linearized as:
Where:
$r_{j,i} = p_{j,i} - K \cdot \pi(R_{wj}^\top (u_i^w - t_{wj}))$ - $\delta = \begin{bmatrix} \delta R_{w1} & \delta t_{w1} & \cdots & \delta R_{wM} & \delta t_{wM} & \delta u_1^w & \cdots & \delta u_N^w \end{bmatrix}^\top$
The Jacobian
With:
- Projection Jacobian:
- Rotation Jacobian:
Where:
- Translation and Point Jacobians:
Each iteration of Levenberg–Marquardt solves:
Which leads to solving:
But when the number of cameras and 3D points becomes large, the matrix
To handle this, we use MATLAB's sparse
matrix functionality to construct
Steps:
- Create the index and value vectors
i
,j
, andv
for non-zero entries. - Fill these vectors during Jacobian construction.
- Assemble the sparse matrix using:
matlab J = sparse(i, j, v);