Skip to main content

Pipeline offline de reconstrucción 3D desde video monocular: Structure-from-Motion incremental + Bundle Adjustment + densificación MVS + mallado CGAL, con front-ends de features intercambiables (SIFT, ORB, SuperPoint+LightGlue).

Project description

Pipeline de Reconstrucción 3D (SfM offline + Densificación MVS)

Pipeline offline que reconstruye una escena 3D a partir de un video monocular: estima la trayectoria de la cámara mediante Structure-from-Motion (SfM) incremental, refina con Bundle Adjustment y genera una nube de puntos —primero rala (sparse) y luego densa (dense)—.

Nota sobre el nombre: aunque el repo se llama "Template-SLAM", esto es SfM offline + densificación MVS, no SLAM en tiempo real. No hay loop-closure ni mapeo online. Ver CLAUDE.md para el detalle técnico.

Características

  • 3 front-ends de features intercambiables:
    • sift (default) — SIFT + BFMatcher (L2). Solo CPU, sin dependencias pesadas.
    • orb — ORB + BFMatcher (Hamming). Más rápido, menos preciso.
    • spglue — SuperPoint + LightGlue (deep learning, CPU/GPU). Opcional, requiere torch.
  • Bundle Adjustment local (por ventana, durante el tracking) y global.
  • Densificación MVS-lite por stereo de dos vistas (StereoSGBM) con fusión multi-vista.
  • Mallado con CGAL (cgal_mesh/) — reconstruye una malla triangulada desde la nube densa (Advancing Front o Poisson).
  • Visor POV interactivo que proyecta los puntos 3D sobre el video original.
  • Orquestador único (pipeline.py) — todo el pipeline en un solo comando.
  • Calibración desde video (calibrate.py) — estima K de tu dispositivo a partir de un video de un tablero de ajedrez y crea/actualiza camera.json.
  • Paralelización (--jobs) en extracción, matching y densificación.
  • Reconstrucción por chunks (chunked.py) — para videos largos: parte el video en fragmentos solapados, los reconstruye en paralelo y fusiona las nubes alineándolas por el solape (similaridad Sim(3)).

Requisitos

  • uv para gestionar el entorno y dependencias.
  • Python ≥ 3.10 (uv lo instala automáticamente según .python-version).
  • Solo para el mallado (mesh): dependencias nativas de CGAL (no las instala uv). En macOS: brew install cgal cmake eigen boost gmp mpfr. En Debian/Ubuntu: sudo apt install libcgal-dev cmake libeigen3-dev. El resto del pipeline funciona sin ellas.

Instalación

# Dependencias base (front-ends sift / orb):
uv sync

# Opcional: front-end spglue (SuperPoint + LightGlue, instala torch):
uv sync --extra spglue

uv crea el entorno virtual, instala el paquete reconstruct3d (editable) y resuelve todo desde pyproject.toml. No hace falta activar nada: usa uv run <comando>. Queda disponible el CLI instalado reconstruct3d (equivalente al shim python pipeline.py).

Uso como librería (API Python)

El pipeline se puede usar desde código con la clase Pipeline:

from reconstruct3d import Pipeline

pipe = Pipeline("outputs/run1", frontend="sift", camera="camera.json", jobs=0)
pipe.extract("video.mp4", k_skip=15, m_window=4)
pipe.init()
pipe.track()
pipe.bundle_adjust()
pipe.dense("video.mp4")
pipe.mesh(method="afront")
print(pipe.artifacts())   # {'extract': '.../sfm_data.pkl', 'dense': '.../dense_cloud.ply', ...}

O todo de una vez, eligiendo etapas y recibiendo progreso por callback:

from reconstruct3d import Pipeline

def on_event(ev):                      # ev = {stage, status, message, ...}
    print(ev["stage"], ev["status"])

pipe = Pipeline("outputs/run1", on_event=on_event)
pipe.run("video.mp4",
         stages=["extract", "init", "track", "dense"],
         config={"extract": {"k_skip": 15, "m_window": 4}})

Atajo: reconstruct3d.run_all(video, out_dir, stages=..., config=...).

Uso rápido (CLI)

Coloca tu video en data/ y ejecuta el pipeline completo:

uv run reconstruct3d all data/mi_video.mp4      # o: uv run python pipeline.py all ...

Esto encadena: extract → init → track → ba → dense → mesh. Los artefactos quedan en outputs/sift/ (o outputs/<frontend>/). El mallado se omite automáticamente si CGAL no está instalado (o con --no-mesh).

Con otro front-end, o saltando etapas:

uv run python pipeline.py all data/mi_video.mp4 --frontend spglue
uv run python pipeline.py all data/mi_video.mp4 --no-ba --no-dense   # solo sparse
uv run python pipeline.py all data/mi_video.mp4 --view               # abre el visor al final

Cámara / calibración (por dispositivo)

La matriz intrínseca K depende del dispositivo con el que grabaste, así que es configurable. Copia camera.example.json, ajústalo a tu cámara y pásalo en extract (o en all):

uv run python pipeline.py all data/mi_video.mp4 --camera camera.json

La config se persiste en sfm_data.pkl y map_state.npy y se propaga automáticamente a todas las etapas — solo la indicas una vez. Sin --camera, se usan los valores por defecto (iPhone @ 540×960).

Formato del JSON (atajo fx/fy/cx/cy, o matriz K completa):

{
  "fx": 801.25, "fy": 801.25, "cx": 188.67, "cy": 390.22,
  "width": 540, "height": 960,
  "dist_coeffs": [0, 0, 0, 0, 0]
}

width/height es la resolución a la que K está calibrada: el video se redimensiona a ese tamaño antes de extraer features. Si das fx/fy/cx/cy, deben corresponder a esa resolución.

Calibrar K automáticamente desde un video

Si no conoces K, grábala: graba un video moviendo un tablero de ajedrez frente a la cámara (cubriendo zonas y ángulos) y calibra:

# Genera/actualiza camera.json (tablero de 9x6 esquinas internas):
uv run python pipeline.py calibrate data/calib.mp4 --board 9x6 --square 0.025
# Revisión manual de cada vista (ventana OpenCV):
uv run python pipeline.py calibrate data/calib.mp4 --interactive

También puedes calibrar en el mismo comando de reconstrucción, o dejar que el pipeline lo haga solo:

# Calibra y luego reconstruye, en un paso:
uv run python pipeline.py all data/video.mp4 --calibrate data/calib.mp4

# Auto-detección: si no pasas --camera, el pipeline usa ./camera.json si existe,
# o busca un video con 'calib' en el nombre (en data/calib/, data/ o .) y calibra.
uv run python pipeline.py all data/video.mp4

Calibrar fija proc_size a la resolución nativa del video de calibración, así que el punto principal queda centrado y K siempre es coherente con la resolución.

Paralelización (--jobs)

La extracción de features, el matching por pares y la densificación MVS están paralelizados con hilos. Controla los hilos con --jobs (0=auto=nº de CPUs, 1=secuencial):

uv run python pipeline.py extract data/video.mp4 --jobs 8
uv run python pipeline.py dense data/video.mp4 --jobs 8
uv run python pipeline.py all data/video.mp4 --jobs 8

Notas: los detectores de OpenCV no son thread-safe compartidos, así que cada hilo usa su propia instancia (resultados idénticos al modo secuencial). Con --frontend spglue el paralelismo se fuerza a 1 hilo (torch ya paraleliza internamente y su modelo no es thread-safe).

Videos largos: reconstrucción por chunks

Para videos largos, reconstruir todo de una vez es lento y acumula deriva. El modo chunked parte el video en fragmentos solapados, reconstruye cada uno de forma independiente y en paralelo (procesos), y fusiona las sub-nubes alineándolas por los frames del solape (similaridad Sim(3) vía Umeyama sobre los centros de cámara compartidos):

uv run python pipeline.py chunked data/video.mp4 \
    --chunk 80 --overlap 20 --chunk-jobs 4
  • --chunk / --overlap: tamaño y solape del fragmento, en frames muestreados. El solape debe ser suficiente para alinear (≥4 frames; por defecto 20).
  • --chunk-jobs: cuántos chunks se reconstruyen en paralelo (procesos).
  • --inner-jobs: hilos de extracción dentro de cada chunk (default 1, para no saturar al correr varios chunks a la vez).

Salida en outputs/<frontend>/: merged_cloud.ply (nube global) y merged_state.npy (poses + puntos en el marco global, copiado también como map_state.npy para poder lanzar dense/view sobre el resultado fusionado).

Mallado de la nube (CGAL)

Convierte la nube densa en una malla triangulada con CGAL. Requiere las dependencias nativas (ver Requisitos); el binario C++ se compila solo la primera vez que se ejecuta.

uv run python pipeline.py mesh                                 # usa outputs/sift/dense_cloud.ply
uv run python pipeline.py mesh outputs/sift/dense_cloud.ply    # ruta explícita a la nube
uv run python pipeline.py mesh ruta/a/nube.ply --method poisson --smooth 24

Puedes indicar la nube de entrada como argumento posicional (o con --input); si no pasas --out, el mesh.ply se escribe en la carpeta de esa nube.

Dos métodos:

  • afront (Advancing Front, default) — interpola los puntos de entrada, conserva el color y respeta bordes abiertos. Ideal para superficies vistas de un lado (fachadas). Fiel a la nube.
  • poisson (Poisson screened) — superficie suave y cerrada, tolera mejor el ruido pero puede "inflar" zonas abiertas. Estima normales y transfiere el color por vecino más cercano.

Mejorar la calidad (la nube tiene ruido)

Advancing Front interpola los puntos, así que el ruido se vuelve picos ("grumoso"). Para una malla más limpia hay tres palancas, aplicadas por defecto y ajustables:

  • Limpiar la nube antes de mallar: --outlier-pct P (elimina % de outliers), --simplify CELL (rejilla; 0=auto~2×spacing, coarsea→suaviza y acelera; <0=sin simplificar para máximo detalle), --smooth N (suavizado jet de los puntos con N vecinos).
  • Post-procesar la malla (activo por defecto): --mesh-smooth ITERS (suavizado tangencial que respeta bordes, default 2) y --min-component FRAC (elimina componentes con menos de FRAC×caras, p.ej. islas de ruido flotantes, default 0.002).
  • Cambiar de método: --method poisson produce una superficie suave y cerrada que tolera mucho mejor el ruido (a costa de "inflar" bordes abiertos).
# Más limpio (afront + suavizado fuerte + quitar islas):
uv run python pipeline.py mesh --mesh-smooth 5 --min-component 0.01 --smooth 12
# Superficie suave (Poisson):
uv run python pipeline.py mesh --method poisson --smooth 12
# Máximo detalle (sin simplificar ni suavizar):
uv run python pipeline.py mesh --simplify -1 --mesh-smooth 0 --min-component 0

La salida es mesh.ply, abrible en MeshLab/CloudCompare o cualquier visor de PLY.

Uso por etapas

Las etapas comparten el mismo directorio de salida. Útil para iterar sobre una etapa sin recalcular las anteriores:

Etapa Comando Produce
1. Extracción uv run python pipeline.py extract data/v.mp4 sfm_data.pkl
2. Inicialización uv run python pipeline.py init map_state.npy, init_cloud.ply
3. Tracking (sparse) uv run python pipeline.py track tracked_cloud.ply
4. Bundle Adjustment uv run python pipeline.py ba map_state.npy (refinado)
5. Densificación uv run python pipeline.py dense data/v.mp4 dense_cloud.ply
6. Mallado (CGAL) uv run python pipeline.py mesh mesh.ply
7. Visor uv run python pipeline.py view data/v.mp4 (interactivo)

Cada subcomando expone sus parámetros; consúltalos con --help:

uv run python pipeline.py extract --help
uv run python pipeline.py track --help

Parámetros frecuentes:

  • extract --k-skip N — procesa 1 de cada N frames (default 5).
  • extract --start-seconds S — salta un arranque malo (rotación casi pura).
  • track --min-angle G — ángulo mínimo de triangulación (descarta ruido de escala).
  • dense --min-views N — conserva solo puntos confirmados por ≥N pares.

Resultados

Las nubes .ply se abren en MeshLab o CloudCompare. El visor interactivo proyecta los puntos sobre el video:

  • Slider Frame — navega en el tiempo.
  • Slider Fondo % — opacidad del video (0 = fondo negro, 100 = video normal).
  • q o ESC — cerrar.

Demo web (API + frontend)

Una demo de la librería: backend FastAPI que expone el pipeline por HTTP + frontend Vite/React con visor 3D (Three.js) para subir un video, elegir las etapas, ver el progreso y explorar la nube/malla resultante en el navegador.

# 1) Backend (instala FastAPI con el extra `api`):
uv sync --extra api
uv run uvicorn backend.app:app --reload --port 8000

# 2) Frontend (en otra terminal):
cd frontend
npm install
npm run dev          # http://localhost:5173 (proxy /api -> :8000)

Abre http://localhost:5173, sube un video, marca las etapas y pulsa Reconstruir; al terminar, haz clic en un artefacto para verlo en 3D.

Endpoints principales del backend: POST /api/jobs (video + config), GET /api/jobs/{id} (estado/progreso/eventos), GET /api/jobs/{id}/artifacts/{name}.

Estructura del proyecto

reconstruct3d/         Paquete de la librería (instalable)
  api.py               API de alto nivel (clase Pipeline)
  cli.py               Orquestador CLI (subcomandos)
  core.py              Cámara, features, front-ends, base de datos SfM
  init_sfm.py          Par semilla + triangulación inicial
  track_sfm.py         Registro incremental PnP + BA local
  bundle_adjust.py     BA local (ventana) y global + fusión de puntos
  dense_mvs.py         Densificación stereo multi-vista
  mesh.py              Wrapper del mallado CGAL (compila y llama al binario)
  calibrate.py         Calibración de K desde un video de tablero
  chunked.py           Reconstrucción por chunks con solape + fusión
  viewer.py            Visor POV interactivo (OpenCV)
  cgal_mesh/           Programa C++ CGAL (mesh_reconstruct.cpp + CMakeLists)
pipeline.py            Shim de compatibilidad -> reconstruct3d.cli
backend/               Demo: API FastAPI (app.py)
frontend/              Demo: Vite + React + Three.js (visor 3D)
camera.example.json    Plantilla de intrínsecos por dispositivo
pyproject.toml         Paquete + dependencias (fuente de verdad)
data/                  Videos de entrada (no versionados)
outputs/               Artefactos regenerables (no versionados)

Publicar en PyPI

El paquete está listo para publicarse (twine check PASSED; nombre reconstruct3d libre). Pasos:

uv build                      # genera dist/*.whl y dist/*.tar.gz
uv run --with twine twine check dist/*

# Ensayo en TestPyPI (recomendado):
uv publish --publish-url https://test.pypi.org/legacy/ --token <TEST_PYPI_TOKEN>

# Publicación real:
uv publish --token <PYPI_TOKEN>

Notas para quien instale desde PyPI:

  • pip install reconstruct3d trae los front-ends SIFT/ORB (sin torch).
  • El front-end spglue (SuperPoint+LightGlue) requiere instalar LightGlue a mano (no está en PyPI): pip install "reconstruct3d[spglue]" y luego pip install "lightglue @ git+https://github.com/cvg/LightGlue.git".
  • El paso mesh necesita CGAL nativo (CGAL + cmake + compilador); es opcional y se compila bajo demanda. El resto del pipeline funciona sin él.

Notas técnicas

  • Reset: si el tracking se estanca, vuelve a correr init antes de reintentar.
  • Calibración: los intrínsecos por defecto son de un iPhone @ 540×960. Para otro dispositivo, pasa --camera tu_camara.json (ver sección Cámara). El video se redimensiona a la resolución de calibración antes de extraer.
  • req.txt queda como referencia histórica; la gestión real de dependencias es pyproject.toml + uv.

Para contexto de arquitectura, convenciones y gotchas, ver CLAUDE.md.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

reconstruct3d-0.1.2.tar.gz (270.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

reconstruct3d-0.1.2-py3-none-any.whl (64.0 kB view details)

Uploaded Python 3

File details

Details for the file reconstruct3d-0.1.2.tar.gz.

File metadata

  • Download URL: reconstruct3d-0.1.2.tar.gz
  • Upload date:
  • Size: 270.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for reconstruct3d-0.1.2.tar.gz
Algorithm Hash digest
SHA256 77eb17e70b01190f00774a28b7821896eff2707947e6d78bf2ecf2db79bf335b
MD5 5b795f9995ec0cc822630f284475c118
BLAKE2b-256 4fdc5af95030efdfd2dddca2504d35160d6887ead2106d2173d293f43a662591

See more details on using hashes here.

Provenance

The following attestation bundles were made for reconstruct3d-0.1.2.tar.gz:

Publisher: publish.yml on FernandoUs/Template-SLAM

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file reconstruct3d-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: reconstruct3d-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 64.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for reconstruct3d-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 a02fbcb2410c8ebef0b1e3120c49a0a418ad20bdfa09f3911db8d76b74f57026
MD5 329b7328a95de2c31d59b1fa61755ae1
BLAKE2b-256 02ae14ae8acda84617564b184dc91ed83e81750d918cf0602b95533665ba96dd

See more details on using hashes here.

Provenance

The following attestation bundles were made for reconstruct3d-0.1.2-py3-none-any.whl:

Publisher: publish.yml on FernandoUs/Template-SLAM

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page