Feature-based monocular visual odometry in Python with OpenCV, SciPy, NumPy, and Matplotlib. The pipeline bootstraps an initial 3D landmark map, tracks features with KLT, estimates camera pose with PnP RANSAC, triangulates new landmarks, and can refine recent poses and landmarks with sliding-window bundle adjustment.
KITTI_short_clip.mp4
- Shi-Tomasi feature detection on configurable image-grid regions
- KLT feature tracking across bootstrap and continuous-operation frames
- Fundamental-matrix bootstrapping with RANSAC and triangulation
- P3P pose estimation with RANSAC
- Optional ground-plane scale initialization
- Optional sliding-window bundle adjustment
- Live trajectory, keypoint, optical-flow, and local-map visualization
.
|-- main.py # Pipeline configuration, VO state, CLI entrypoint
|-- src/
| |-- BA_helper.py # Bundle-adjustment packing, projection, residuals
| |-- GD_helper.py # Ground-plane and scale-estimation helpers
| |-- visualization.py # Matplotlib/OpenCV visualization utilities
| `-- dataset_loading.py # Dataset discovery/calibration helpers
|-- env.yml # Conda environment
|-- assets/ # Demo media
|-- kitti/ # Local KITTI data placeholder
|-- malaga/ # Local Malaga data placeholder
|-- parking/ # Local parking data placeholder
`-- VAMR_Rome_dataset/ # Local custom dataset placeholder
Dataset payloads, caches, logs, and generated outputs are ignored by Git. The placeholder .gitignore files keep the expected dataset folders visible on GitHub.
Create the Conda environment from the repository root:
conda env create -f env.yml
conda activate vo_envIf the environment already exists:
conda env update -f env.yml --prune
conda activate vo_envQuick dependency check:
python -c "import cv2; print(cv2.__version__)"The pipeline supports four layouts:
D.KITTI: KITTI sequence 05 underkitti/kitti05/kittiD.MALAGA: Malaga urban extract 07 undermalaga/malaga-urban-dataset-extract-07D.PARKING: parking dataset underparking/parkingD.CUSTOM: custom Rome dataset underVAMR_Rome_dataset/VAMR_Rome_dataset
Select the dataset by changing DATASET near the top of main.py. Keep large image folders local; they should not be committed.
Default run, with sliding-window BA and scale enabled:
python main.pyUseful options:
python main.py --no-ba
python main.py --no-scale
python main.py --max-frames 100
python main.py --separate-windows
python main.py --show-ground-debugIf the selected dataset folder is empty, main.py exits with a clear message explaining how many frames were found and where it expected them.
- The script is now import-safe: importing
main.pyno longer starts the pipeline. - Generated bytecode and local dataset payloads are excluded through
.gitignore. - Keep
env.ymlupdated when dependencies change. - Use
src/dataset_loading.pyfor lightweight dataset discovery in notebooks or experiments.