OpenMVS is a comprehensive photogrammetry library implementing a complete pipeline from image sequences to textured 3D models. It includes Structure-from-Motion (SFM) for camera pose estimation and sparse reconstruction, plus Multi-View Stereo (MVS) for dense reconstruction and mesh generation. The codebase is mature C++ with custom framework patterns.
- SFM namespace: Structure-from-Motion reconstruction algorithms (
libs/SFM/) - MVS namespace: Multi-view Stereo reconstruction algorithms (
libs/MVS/) - VIEWER namespace: 3D visualization application (
apps/Viewer/) - SEACAVE namespace: Low-level utilities (
libs/Common/)
Common/: Custom framework (types, logging, containers, math utilities)IO/: File format support (PLY, OBJ, MVS formats)Math/: Mathematical primitives and operationsSFM/: Core SFM algorithms (Scene, Image, Camera, FeaturesExtractor, PairsMatcher, BundleAdjustment, etc.)MVS/: Core MVS algorithms (Scene, Image, Camera, Mesh, PointCloud, etc.)
Each app is a standalone executable for specific pipeline stages:
SFM Stage:
CreateStructure: Initialize SFM scene from image metadata and camera parameters
MVS Stage:
DensifyPointCloud: Dense reconstruction from sparse SFM pointsReconstructMesh: Surface reconstruction from dense point cloudsRefineMesh: Mesh quality improvement and optimizationTextureMesh: Texture mapping onto reconstructed meshes
Utilities:
ExtractKeyframes: Extract representative frames from video sequencesTransformScene: Apply transformations to scene geometryTests: Test suite for SFM and MVS algorithmsViewer: Interactive 3D visualization with OpenGL
Interface/Import-Export:
InterfaceCOLMAP: COLMAP format import/exportInterfaceOpenMVG: OpenMVG format import/exportInterfaceMetashape: Metashape format import/exportInterfaceMVSNet: MVSNet format import/exportInterfacePolycam: Polycam format import/export
# Standard build (uses vcpkg for dependencies)
mkdir make && cd make
cmake ..
cmake --build . -j4 # or ninja (generates ninja files)- vcpkg: Automatic dependency management (see
vcpkg.json) - CMake: Primary build system with custom utilities in
build/Utils.cmake - ninja: Preferred generator (faster than make)
- Debug builds in
make/bin/Debug/ - Built executables:
./bin/Debug/Viewer,./bin/Debug/DensifyPointCloud, etc. - Use
cmake --build . -j4frommake/directory for incremental builds
- Reference counting with automatic cleanup
- RAII patterns throughout
DEBUG("Message"); // Level 0 (always shown in debug)
DEBUG_EXTRA("Details"); // Level 1 (verbose)
VERBOSE("Info: %s", str); // General loggingASSERT(condition)for debug checks- Return false/NULL for failures
typedef SEACAVE::TPoint3<float> Point3f; // 3D points compatible with both OpenCV and Eigen
typedef SEACAVE::TMatrix<float,3,3> Matrix3x3f; // 3x3 floats matrix compatible with both OpenCV and Eigen
typedef SEACAVE::String String; // String type
#define NO_ID ((uint32_t)-1) // Invalid indexMost of OpenMVS code uses custom point and matrix types derived from OpenCV types, e.g., SEACAVE::Point3f, SEACAVE::Matrix4f. Hoewever, some components use Eigen3 types, e.g. SEACAVE::AABB3d and SEACAVE::Ray3d classes. There custom types support convertion operation to and from Eigen3 types.
SEACAVE::cList template class is a custom vector implementation used throughout the codebase, fully compatible with std::vector. It provides additional functionality such as using or not the constructor for the elements, custom size type, and additional operations like GetMean, GetMedian, Sort, etc. By default it uses memcpy to manage elements, but it can be configured (useConstruct) to use constructors and destructors when needed. It also provides a custom size type (IDX_TYPE) which is typically defined as size_t, but can be changed if needed.
Often used is also FOREACH macro for iterating over any vector-like container:
FOREACH(index, container) {
auto& element = container[index];
// Do something with element
}Similarly, RFOREACH macro iterates in reverse order.
SEACAVE::PairIdx (in libs/Common/Types.h) packs two uint32_t indices into a single uint64_t via a union. Use it — never hand-rolled (uint64_t(a) << 32) | b bit-packing — whenever you need a composite key from two 32-bit indices. Common cases: (imageID, featureID) for per-observation lookups, (platformID, cameraID), or image-pair buckets. Two forms:
PairIdx(a, b)— raw constructor; stores the two fields in order, no reordering. Use this whenaandbhave different meaning (e.g. image vs feature). Works as anunordered_mapkey out-of-the-box sincestd::hash<PairIdx>is already specialized inTypes.inl.MakePairIdx(a, b)— assertsa != band swaps so the smaller index comes first. Use this only for symmetric pairs where(a,b)and(b,a)should hash to the same bucket, e.g. image-pair buckets in match graphs.
TD_TIMER_START() and TD_TIMER_GET_FMT() macros are used for performance measurements. TD_TIMER_START() (similarly TD_TIMER_STARTD() paird with DEBUG() prints) starts a timer, and TD_TIMER_GET_FMT() returns a formatted string with the elapsed time since the timer was started.
VERBOSE() macro is used for general logging messages. It works similarly to DEBUG() macro, but is intended for critical information, as it always prints regardless of debug level. DEBUG_EXTRA() and DEBUG_ULTIMATE() macros are used for more verbose logging, with DEBUG_ULTIMATE() being the most verbose.
TPixel<TYPE> is a 3-channel color type with BGR memory layout (matching OpenCV). Access channels by name (p.r, p.g, p.b) or by index (p.c[0]=b, c[1]=g, c[2]=r). Common typedefs: Pixel8U (TPixel<uint8_t>), Pixel32F (TPixel<float>).
TImage<TYPE> wraps cv::Mat_<TYPE>. Common typedefs: Image8U3 (TImage<Pixel8U>), Image32F3 (TImage<Pixel32F>), Image8U, Image32F, Image16U.
Critical: Point3f (TPoint3<float>) has {x, y, z} fields mapping to memory positions [0, 1, 2], while Pixel32F stores {b, g, r} at positions [0, 1, 2]. When Image32F3 stores Pixel32F but is accessed via Point3f&, field .x reads b, not r. Always use Pixel32F with named fields (.r, .g, .b) for pixel operations, not Point3f.
Pixel conversions:
- Use
Pixel32F::cast<uint8_t>()for float→uint8 conversion (proper channel mapping with clamping) - Use
Pixel32F(Pixel8U::RED)to construct from named constants (channel-correct) - Named constants:
Pixel8U::RED,BLACK,WHITE,GREEN,BLUE,CYAN,GRAY(uint8 range 0-255);Pixel32F::REDetc. use float range [0,1]
Image sampling — use TImage built-in samplers instead of manual bilinear interpolation:
typedef Sampler::Linear<float> LinearSampler;
static const LinearSampler linearSampler;
// bilinear sample returning Pixel32F from an Image8U3
Pixel32F color = img.sample<LinearSampler, Pixel32F>(linearSampler, pt);Bounds checking — use TImage::isInsideWithBorder instead of manual coordinate checks:
// check that bilinear sampling (border=1) won't read out of bounds
if (img.isInsideWithBorder<float, 1>(pt))
color = img.sample<LinearSampler, Pixel32F>(linearSampler, pt);Image I/O — use TImage::Load/TImage::Save instead of cv::imread/cv::imwrite:
Image8U4 image;
image.Load(fileName); // loads with correct channel/depth conversion
image.Save(fileName); // saves via OpenCV with correct format- Build-time config in
ConfigLocal.h(generated) included in every code file - Runtime options via boost::program_options pattern
- Feature flags like
OpenMVS_USE_CUDA,OpenMVS_USE_CERES - Each library uses a precompiled header (
Common.h) for common includes, like Eigen, OpenCV, etc.
Scene: Main data container (MVS::Scene + rendering state)Window: GLFW window management + input handlingRenderer: OpenGL rendering (points, meshes, cameras)Camera: View/projection matrices + navigationUI: ImGui interface components
window.Run(scene) →
Render(scene) →
renderer->RenderPointCloud/RenderMesh →
OpenGL draw calls- GLFW events → Window callbacks → Control system updates
- Render-only-on-change optimization uses
glfwWaitEventsTimeout() Window::RequestRedraw()triggers frame updates
.mvs: Native binary format (boost serialization).ply: Point clouds and meshes (ASCII/binary).obj: Mesh export with MTL materials- Interface apps handle external formats (COLMAP, etc.)
- Eigen3: Linear algebra (matrices, vectors)
- OpenCV: Image processing and I/O
- CGAL: Computational geometry
- Boost: Serialization, program options, containers
- CUDA: GPU acceleration (optional)
- GLFW/OpenGL: Viewer rendering
SFM::Sceneis used for sparse reconstruction and camera pose managementMVS::Sceneis the central data exchange format for dense reconstruction- Applications typically: load scene → process → save scene
- Viewer loads and visualizes any stage of the pipeline
The typical photogrammetry workflow:
- SFM Stage: Feature extraction → Pair matching → Bundle adjustment → Global alignment/scale averaging
- MVS Stage: Dense point cloud generation → Mesh reconstruction → Mesh refinement → Texturing → Viewer visualization
# From make/ directory
ctest # Run all tests
./bin/Debug/Tests # Direct test executable- Use
DEBUG()macros liberally - Check
ASSERT()failures for logic errors - Use
TD_TIMER_START()for performance timing. - Viewer: Use F1 for help dialog, check console output
- Memory issues: Build with
_DEBUGfor additional checks
- Naming: Functions
CamelCase(), variableslowerCamelCase, type prefixes:n(numeric),f(float),b(bool),p(pointer),_(private members). Constants/enums UPPERCASE. - Formatting: K&R brackets, tabs for indentation.
- Patterns: Early returns, range-based loops preferred, STL/cList containers, const correctness.
- Multi-threading via OpenMP (
#pragma omp parallel) for simple parallelism andBS::light_thread_poolfor task-based parallelism. - CUDA kernels for GPU acceleration (when enabled)
- Memory-mapped files for large datasets
- Spatial data structures (octrees) for efficient queries
- Viewer optimizations: frustum culling, render-only-on-change mode
The task files in .github/instructions/ define step-by-step
workflows. Read and follow the relevant one when the context matches a defined workflow.
claude --agent analyze-codebase
claude --agent catalog-features claude --agent suggest-improvements