--- group: img-to-3d description: "Convertir una imagen 2D en un modelo 3D: estimacion de profundidad monocular (Depth-Anything-V2) + reconstruccion de una malla de relieve texturizada exportada a glTF binario (.glb)." --- # img-to-3d — Capability Group Cluster de funciones Python (dominio `datascience`) para el flujo **imagen 2D → modelo 3D**. A partir de una sola foto se estima un mapa de profundidad monocular con un modelo de vision y se reconstruye una malla de relieve (heightmap) texturizada con la imagen original, exportada como `.glb` cargable por cualquier visor glTF (three.js `useGLTF`/`GLTFLoader`, Babylon, model-viewer). Promovido desde la app `img_to_3d_webapp` (su backend incrustaba estas funciones; ver `backend/depth.py` y `backend/bg_removal.py`). El flujo canonico encadena un pre-proceso opcional de fondo con los dos pasos de reconstruccion: ``` [remove_background (imagen -> rgb+mask)] -> estimate_image_depth (imagen -> depth+image) -> depth_to_relief_glb (depth+image[+mask] -> .glb) ``` ## Funciones | ID | Firma corta | Que hace | |---|---|---| | `remove_background_py_datascience` | `remove_background(image_path, engine?) -> dict` | **Pre-proceso (paso 0).** Elimina el fondo en cascada rembg -> GrabCut -> umbral y compone el objeto sobre gris neutro. Devuelve `image` PIL + `mask` ndarray. La `mask` se pasa a `depth_to_relief_glb` para recortar la malla al objeto. | | `estimate_image_depth_py_datascience` | `estimate_image_depth(image_path, model_name?, device?, use_cache?) -> dict` | Estima profundidad monocular con Depth-Anything-V2 (GPU/CPU). Devuelve `depth` ndarray [0,1] + `image` PIL. Paso 1. | | `depth_to_relief_glb_py_datascience` | `depth_to_relief_glb(image, depth, out_glb_path, z_scale?, max_dim?, mask?) -> dict` | Convierte `depth`+`image` en una malla de relieve texturizada y la exporta a `.glb`. Con `mask` opcional recorta las caras del fondo. Paso 2. | | `build_relief_glb_from_image_py_pipelines` | `build_relief_glb_from_image(image_path, out_glb_path, model_name?, device?, z_scale?, max_dim?) -> dict` | **Pipeline one-shot**: compone estimacion + relieve en una sola llamada (imagen -> .glb). Salida JSON-serializable, apta para `fn run`. | Las cuatro son **impuras** (cargan modelo / GPU / escriben archivo), devuelven `dict` con `status` (`ok`/`error`) y **nunca lanzan**: los fallos vuelven como `{status:'error', error:str}`. El pipeline ademas marca `stage` (`estimate`/`relief`) en el error. `remove_background` en `engine="auto"` nunca falla (cae al umbral NumPy puro sin deps externas). ## Ejemplo canonico (end-to-end imagen → glb) ```python # Requiere un venv con torch + transformers + trimesh + pillow + numpy. # Import PLANO: el paquete datascience.__init__ arrastra deps de otros dominios (bs4, duckdb...) # ausentes en el venv de vision. Ver "Fronteras / gotchas". import sys sys.path.insert(0, "python/functions/datascience") from remove_background import remove_background from estimate_image_depth import estimate_image_depth from depth_to_relief_glb import depth_to_relief_glb IMG = "apps/img_to_3d_webapp/samples/cats.jpg" OUT = "/tmp/cats_relief.glb" # Paso 0 (opcional pero recomendado): aislar el objeto del fondo. La mask recorta la malla. cut = remove_background(IMG) # engine='auto' -> rembg -> grabcut -> umbral assert cut["status"] == "ok" print(cut["engine"], cut["fg_fraction"]) # p.ej. rembg:u2net 0.42 est = estimate_image_depth(IMG) # device='auto' -> GPU si hay assert est["status"] == "ok" # est["depth"]: ndarray HxW float32 [0,1] (1=mas cerca) | est["image"]: PIL.Image RGB # Pasando la mask del paso 0, las caras del fondo se descartan: malla solo del objeto. res = depth_to_relief_glb(est["image"], est["depth"], OUT, z_scale=0.35, max_dim=220, mask=cut["mask"]) assert res["status"] == "ok" print(res["glb_path"], res["vertices"], res["faces"]) # /tmp/cats_relief.glb 36300 71832 # OUT es un glTF binario valido: trimesh.load(OUT) devuelve una Scene texturizada. ``` O en una sola llamada con el pipeline (recomendado para fn run / Launcher TUI): ```bash ./fn run build_relief_glb_from_image_py_pipelines apps/img_to_3d_webapp/samples/cats.jpg /tmp/cats_relief.glb # {"status": "ok", "glb_path": "/tmp/cats_relief.glb", "vertices": 36300, "faces": 71832, ...} ``` ## Fronteras - **Es relieve 2.5D, no reconstruccion volumetrica.** Deforma un plano segun la profundidad (heightmap); no recupera caras ocultas ni el volumen trasero del objeto. Para 3D real multivista/fotogrametria, NSP/Gaussian Splatting, esto NO aplica. - **Profundidad relativa, no metrica.** Depth-Anything devuelve disparidad normalizada a [0,1]; no comparable entre imagenes ni en unidades del mundo real. - **No cubre el render/visualizacion.** Producir el `.glb` es el limite del grupo. Cargarlo y subirlo a GPU (OpenGL) en una app C++/ImGui es el grupo **`mesh-3d`** (`gltf_load_mesh_cpp_gfx` carga justamente este tipo de `.glb`). img-to-3d **produce**; mesh-3d **consume/renderiza**. - **Deps pesadas y de dos mundos.** Requiere `torch`+`transformers` (vision), `trimesh` (mesh) y, para `remove_background`, `rembg`+`onnxruntime` (segmentacion) y `opencv-python` (GrabCut) — todas opcionales: el umbral de `remove_background` es NumPy puro. Hoy viven en el venv de `img_to_3d_webapp`, NO en el venv del registry. Ademas el `datascience.__init__` arrastra deps de scrapers (`bs4`...) que no estan en el venv de vision, por eso el import es **plano** (al modulo) y no via el paquete. `fn run` de estas funciones exige un venv que combine ambos mundos (torch + transformers + trimesh + rembg/opencv + las deps del dominio datascience). Ver gotchas en cada `.md`. ## Prerequisitos - GPU NVIDIA + CUDA recomendada (corre en CPU pero lento). Primera ejecucion descarga los pesos del modelo de profundidad a `~/.cache/huggingface/` y el de `rembg` (U2Net ~170 MB) a su cache. - Paquetes: `torch`, `transformers`, `trimesh`, `pillow`, `numpy`. Para el recorte de fondo de mayor calidad: `rembg` (+`onnxruntime`) y `opencv-python` (ambos opcionales; sin ellos `remove_background` cae al umbral NumPy).