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, requieretorch.
- 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
Kde tu dispositivo a partir de un video de un tablero de ajedrez y crea/actualizacamera.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 deFRAC×caras, p.ej. islas de ruido flotantes, default 0.002). - Cambiar de método:
--method poissonproduce 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).
qoESC— 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 reconstruct3dtrae 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 luegopip install "lightglue @ git+https://github.com/cvg/LightGlue.git". - El paso
meshnecesita 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
initantes 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.txtqueda como referencia histórica; la gestión real de dependencias espyproject.toml+uv.
Para contexto de arquitectura, convenciones y gotchas, ver CLAUDE.md.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file reconstruct3d-0.1.0.tar.gz.
File metadata
- Download URL: reconstruct3d-0.1.0.tar.gz
- Upload date:
- Size: 244.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7125526c7796ed1b89dff49d70bb35fa857f58b29de1e2a141a3e02a97aacb8d
|
|
| MD5 |
8986d1fa80395cbbacd7337b6d64b1b5
|
|
| BLAKE2b-256 |
430986740b3b0a4fa1de2d166c768e2cfc4b4ae49994c2d16458d4519cc33805
|
Provenance
The following attestation bundles were made for reconstruct3d-0.1.0.tar.gz:
Publisher:
publish.yml on FernandoUs/Template-SLAM
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
reconstruct3d-0.1.0.tar.gz -
Subject digest:
7125526c7796ed1b89dff49d70bb35fa857f58b29de1e2a141a3e02a97aacb8d - Sigstore transparency entry: 1886590813
- Sigstore integration time:
-
Permalink:
FernandoUs/Template-SLAM@f642b1ddfec4545e4ef45678d0842d7fa3cc6fb1 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/FernandoUs
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f642b1ddfec4545e4ef45678d0842d7fa3cc6fb1 -
Trigger Event:
push
-
Statement type:
File details
Details for the file reconstruct3d-0.1.0-py3-none-any.whl.
File metadata
- Download URL: reconstruct3d-0.1.0-py3-none-any.whl
- Upload date:
- Size: 61.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e05831e3d184892cef5538ab7877d220475d4dc5250e81b075ed7e4649299512
|
|
| MD5 |
267bead3bc094a73f7084405ac223597
|
|
| BLAKE2b-256 |
df52bd9ee269e4a0cba8e9f858af973e9ce05c97e00979231cb4b3ff534dd9cd
|
Provenance
The following attestation bundles were made for reconstruct3d-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on FernandoUs/Template-SLAM
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
reconstruct3d-0.1.0-py3-none-any.whl -
Subject digest:
e05831e3d184892cef5538ab7877d220475d4dc5250e81b075ed7e4649299512 - Sigstore transparency entry: 1886590873
- Sigstore integration time:
-
Permalink:
FernandoUs/Template-SLAM@f642b1ddfec4545e4ef45678d0842d7fa3cc6fb1 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/FernandoUs
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f642b1ddfec4545e4ef45678d0842d7fa3cc6fb1 -
Trigger Event:
push
-
Statement type: