Spaces:
Sleeping
Sleeping
Commit ·
ed72314
1
Parent(s): e3d6629
Add core functionality and project structure for MV3DR application
Browse files- Implement main application logic in main.py
- Set up configuration management in config.py
- Create preprocessing, inference, postprocessing, and visualization modules
- Add Gradio UI for user interaction
- Include license information and README documentation
- Add certificate and update requirements for dependencies
- .gradio/certificate.pem +31 -0
- LICENSE +23 -0
- README.md +78 -0
- assets/fullscreen.js +14 -7
- assets/style.css +63 -11
- config.py +21 -0
- core/__init__.py +12 -0
- core/inference.py +26 -0
- core/postprocessing.py +48 -0
- core/preprocessing.py +25 -0
- core/visualization.py +24 -0
- dust3r +1 -0
- gradio_ui.py +35 -37
- main.py +32 -0
- model.py +13 -11
- pipeline.py +53 -47
- requirement.txt → requirements.txt +1 -0
- setup.sh +4 -2
.gradio/certificate.pem
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
-----BEGIN CERTIFICATE-----
|
| 2 |
+
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
|
| 31 |
+
-----END CERTIFICATE-----
|
LICENSE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
|
| 2 |
+
|
| 3 |
+
This work is licensed under CC BY-NC-SA 4.0
|
| 4 |
+
(Attribution-NonCommercial-ShareAlike 4.0 International)
|
| 5 |
+
|
| 6 |
+
You are free to:
|
| 7 |
+
- Share: copy and redistribute the material in any medium or format
|
| 8 |
+
- Adapt: remix, transform, and build upon the material
|
| 9 |
+
|
| 10 |
+
Under the following terms:
|
| 11 |
+
- Attribution: You must give appropriate credit, provide a link to the license,
|
| 12 |
+
and indicate if changes were made.
|
| 13 |
+
- NonCommercial: You may not use the material for commercial purposes.
|
| 14 |
+
- ShareAlike: If you remix, transform, or build upon the material, you must
|
| 15 |
+
distribute your contributions under the same license as the original.
|
| 16 |
+
|
| 17 |
+
Full license: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
| 18 |
+
|
| 19 |
+
---
|
| 20 |
+
|
| 21 |
+
This project is built upon DUSt3R by Naver Corporation.
|
| 22 |
+
DUSt3R is licensed under CC BY-NC-SA 4.0.
|
| 23 |
+
Copyright (C) 2024-present Naver Corporation.
|
README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Multi-View 3D Reconstruction (MV3DR)
|
| 2 |
+
|
| 3 |
+
A web application for multi-view 3D object reconstruction using DUSt3R (Dense and Unconstrained Stereo 3D Reconstruction).
|
| 4 |
+
|
| 5 |
+
## Overview
|
| 6 |
+
|
| 7 |
+
MV3DR is a production-ready system that generates 3D models from multiple 2D images through dense stereo reconstruction. The application features a dark-themed web interface built with Gradio, providing real-time visualization of depth maps, confidence heatmaps, and interactive 3D outputs.
|
| 8 |
+
|
| 9 |
+
## Features
|
| 10 |
+
|
| 11 |
+
- Multi-view stereo reconstruction from 2+ images
|
| 12 |
+
- Dual export modes: point cloud or textured mesh
|
| 13 |
+
- Real-time depth map and confidence visualization
|
| 14 |
+
- Advanced post-processing with background filtering
|
| 15 |
+
- Point cloud cleaning and alignment optimization
|
| 16 |
+
- Interactive 3D viewer with fullscreen support
|
| 17 |
+
- Modular architecture with separated core modules
|
| 18 |
+
|
| 19 |
+
## Technical Stack
|
| 20 |
+
|
| 21 |
+
- **Model**: DUSt3R (Dense and Unconstrained Stereo 3D Reconstruction)
|
| 22 |
+
- **Framework**: PyTorch with mixed-precision inference
|
| 23 |
+
- **Interface**: Gradio with custom dark theme
|
| 24 |
+
- **3D Processing**: Trimesh for mesh/point cloud generation
|
| 25 |
+
- **Visualization**: Matplotlib for heatmaps
|
| 26 |
+
|
| 27 |
+
## Architecture
|
| 28 |
+
|
| 29 |
+
```
|
| 30 |
+
MV3DR/
|
| 31 |
+
├── main.py # Application entry point
|
| 32 |
+
├── config.py # Centralized configuration
|
| 33 |
+
├── model.py # DUSt3R model initialization
|
| 34 |
+
├── pipeline.py # Main reconstruction pipeline
|
| 35 |
+
├── gradio_ui.py # Web interface
|
| 36 |
+
├── core/
|
| 37 |
+
│ ├── inference.py # Model inference with AMP
|
| 38 |
+
│ ├── preprocessing.py # Image enhancement
|
| 39 |
+
│ ├── postprocessing.py# 3D output generation
|
| 40 |
+
│ └── visualization.py # Artifact generation
|
| 41 |
+
└── assets/
|
| 42 |
+
├── style.css # Dark theme styling
|
| 43 |
+
└── fullscreen.js # 3D viewer controls
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
## Pipeline
|
| 47 |
+
|
| 48 |
+
1. **Preprocessing**: Contrast and sharpness enhancement
|
| 49 |
+
2. **Pair Generation**: Create symmetrized image pairs
|
| 50 |
+
3. **Inference**: DUSt3R stereo reconstruction with mixed precision
|
| 51 |
+
4. **Alignment**: Multi-view point cloud optimization
|
| 52 |
+
5. **Post-processing**: Depth cleaning and background filtering
|
| 53 |
+
6. **Export**: GLB file generation (mesh or point cloud)
|
| 54 |
+
7. **Visualization**: Depth maps and confidence heatmaps
|
| 55 |
+
|
| 56 |
+
## Performance
|
| 57 |
+
|
| 58 |
+
- RTX 4090: ~15-20 seconds (3 images)
|
| 59 |
+
- RTX 3080: ~25-30 seconds (3 images)
|
| 60 |
+
- T4 GPU: ~40-50 seconds (3 images)
|
| 61 |
+
- CPU: ~5-10 minutes (3 images)
|
| 62 |
+
|
| 63 |
+
## Requirements
|
| 64 |
+
|
| 65 |
+
- Python 3.10+
|
| 66 |
+
- CUDA-capable GPU (16GB+ VRAM recommended)
|
| 67 |
+
- 16GB+ RAM
|
| 68 |
+
|
| 69 |
+
## License
|
| 70 |
+
|
| 71 |
+
Licensed under CC BY-NC-SA 4.0 (non-commercial use only).
|
| 72 |
+
Built on DUSt3R by Naver Corporation.
|
| 73 |
+
|
| 74 |
+
## Credits
|
| 75 |
+
|
| 76 |
+
- DUSt3R: Naver Corporation
|
| 77 |
+
- Gradio: Hugging Face
|
| 78 |
+
- Trimesh: Michael Dawson-Haggerty
|
assets/fullscreen.js
CHANGED
|
@@ -1,12 +1,19 @@
|
|
| 1 |
() => {
|
| 2 |
const el = document.getElementById("model-container");
|
| 3 |
-
if (!el)
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
-
if (
|
| 6 |
-
|
| 7 |
-
} else
|
| 8 |
-
el.
|
| 9 |
-
|
| 10 |
-
el.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
}
|
| 12 |
}
|
|
|
|
| 1 |
() => {
|
| 2 |
const el = document.getElementById("model-container");
|
| 3 |
+
if (!el) {
|
| 4 |
+
console.error("Model container not found");
|
| 5 |
+
return;
|
| 6 |
+
}
|
| 7 |
|
| 8 |
+
if (document.fullscreenElement) {
|
| 9 |
+
document.exitFullscreen();
|
| 10 |
+
} else {
|
| 11 |
+
if (el.requestFullscreen) {
|
| 12 |
+
el.requestFullscreen();
|
| 13 |
+
} else if (el.webkitRequestFullscreen) {
|
| 14 |
+
el.webkitRequestFullscreen();
|
| 15 |
+
} else if (el.msRequestFullscreen) {
|
| 16 |
+
el.msRequestFullscreen();
|
| 17 |
+
}
|
| 18 |
}
|
| 19 |
}
|
assets/style.css
CHANGED
|
@@ -3,19 +3,48 @@ footer {display: none !important;}
|
|
| 3 |
|
| 4 |
:root {
|
| 5 |
--primary-500: #FFFFFF !important;
|
| 6 |
-
--body-background-fill: #
|
| 7 |
-
--block-background-fill: #
|
| 8 |
-
--input-background-fill: #
|
| 9 |
-
--border-color-primary: #
|
| 10 |
-
--background-fill-secondary: #
|
|
|
|
| 11 |
}
|
| 12 |
|
| 13 |
.gradio-container {
|
| 14 |
-
background-color: #
|
| 15 |
color: #FFFFFF !important;
|
| 16 |
font-family: 'Inter', system-ui, sans-serif !important;
|
| 17 |
}
|
| 18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
button.primary {
|
| 20 |
background-color: #FFFFFF !important;
|
| 21 |
color: #000000 !important;
|
|
@@ -23,25 +52,44 @@ button.primary {
|
|
| 23 |
font-weight: 600 !important;
|
| 24 |
text-transform: uppercase;
|
| 25 |
letter-spacing: 1px;
|
|
|
|
| 26 |
}
|
| 27 |
|
| 28 |
button.primary:hover {
|
| 29 |
background-color: #B2B2B2 !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
}
|
| 31 |
|
| 32 |
-
|
| 33 |
-
color: #
|
| 34 |
-
|
| 35 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
}
|
| 37 |
|
| 38 |
#model-container:fullscreen {
|
| 39 |
-
background-color:
|
| 40 |
width: 100vw;
|
| 41 |
height: 100vh;
|
| 42 |
}
|
| 43 |
|
| 44 |
.generating::after {
|
|
|
|
| 45 |
color: #808080;
|
| 46 |
font-size: 10px;
|
| 47 |
letter-spacing: 2px;
|
|
@@ -51,3 +99,7 @@ button.primary:hover {
|
|
| 51 |
@keyframes blink {
|
| 52 |
50% { opacity: 0; }
|
| 53 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
|
| 4 |
:root {
|
| 5 |
--primary-500: #FFFFFF !important;
|
| 6 |
+
--body-background-fill: #0a0a0a !important;
|
| 7 |
+
--block-background-fill: #0a0a0a !important;
|
| 8 |
+
--input-background-fill: #0a0a0a !important;
|
| 9 |
+
--border-color-primary: #1a1a1a !important;
|
| 10 |
+
--background-fill-secondary: #0a0a0a !important;
|
| 11 |
+
--block-border-width: 1px !important;
|
| 12 |
}
|
| 13 |
|
| 14 |
.gradio-container {
|
| 15 |
+
background-color: #0a0a0a !important;
|
| 16 |
color: #FFFFFF !important;
|
| 17 |
font-family: 'Inter', system-ui, sans-serif !important;
|
| 18 |
}
|
| 19 |
|
| 20 |
+
h1 {
|
| 21 |
+
text-align: center !important;
|
| 22 |
+
font-weight: 700 !important;
|
| 23 |
+
margin: 2rem 0 !important;
|
| 24 |
+
font-size: 2.5rem !important;
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
.block {
|
| 28 |
+
background-color: #0a0a0a !important;
|
| 29 |
+
border: 1px solid #1a1a1a !important;
|
| 30 |
+
}
|
| 31 |
+
CCCCCC !important;
|
| 32 |
+
transform: translateY(-2px);
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
button:not(.primary) {
|
| 36 |
+
background-color: #1a1a1a !important;
|
| 37 |
+
color: #FFFFFF !important;
|
| 38 |
+
border: 1px solid #333333 !important;
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
button:not(.primary):hover {
|
| 42 |
+
background-color: #2a2a2a !importantput {
|
| 43 |
+
background-color: #141414 !important;
|
| 44 |
+
border: 1px solid #1a1a1a !important;
|
| 45 |
+
color: #FFFFFF !important;
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
button.primary {
|
| 49 |
background-color: #FFFFFF !important;
|
| 50 |
color: #000000 !important;
|
|
|
|
| 52 |
font-weight: 600 !important;
|
| 53 |
text-transform: uppercase;
|
| 54 |
letter-spacing: 1px;
|
| 55 |
+
transition: all 0.3s ease;
|
| 56 |
}
|
| 57 |
|
| 58 |
button.primary:hover {
|
| 59 |
background-color: #B2B2B2 !important;
|
| 60 |
+
transform: translateY(-2px);
|
| 61 |
+
}1a1a1a;
|
| 62 |
+
border-radius: 4px;
|
| 63 |
+
background-color: #0a0a0a !important;
|
| 64 |
}
|
| 65 |
|
| 66 |
+
#model-container:fullscreen {
|
| 67 |
+
background-color: #0a0a0a;
|
| 68 |
+
width: 100vw;
|
| 69 |
+
height: 100vh;
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
.gallery {
|
| 73 |
+
background-color: #0a0a0a !important;
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
.gallery-item {
|
| 77 |
+
background-color: #141414 !important;
|
| 78 |
+
border: 1px solid #1a1a1a !important
|
| 79 |
+
|
| 80 |
+
#model-container {
|
| 81 |
+
border: 1px solid #333333;
|
| 82 |
+
border-radius: 4px;
|
| 83 |
}
|
| 84 |
|
| 85 |
#model-container:fullscreen {
|
| 86 |
+
background-color: #000000;
|
| 87 |
width: 100vw;
|
| 88 |
height: 100vh;
|
| 89 |
}
|
| 90 |
|
| 91 |
.generating::after {
|
| 92 |
+
content: "PROCESSING...";
|
| 93 |
color: #808080;
|
| 94 |
font-size: 10px;
|
| 95 |
letter-spacing: 2px;
|
|
|
|
| 99 |
@keyframes blink {
|
| 100 |
50% { opacity: 0; }
|
| 101 |
}
|
| 102 |
+
|
| 103 |
+
@keyframes blink {
|
| 104 |
+
50% { opacity: 0; }
|
| 105 |
+
}
|
config.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 4 |
+
DUST3R_DIR = os.path.join(ROOT_DIR, "dust3r")
|
| 5 |
+
ASSETS_DIR = os.path.join(ROOT_DIR, "assets")
|
| 6 |
+
OUTPUT_DIR = os.path.join(ROOT_DIR, "results")
|
| 7 |
+
WEIGHTS_PATH = os.path.join(DUST3R_DIR, "checkpoints/DUSt3R_ViTLarge_BaseDecoder_512_dpt.pth")
|
| 8 |
+
|
| 9 |
+
BATCH_SIZE = 1
|
| 10 |
+
IMAGE_SIZE = 512
|
| 11 |
+
|
| 12 |
+
DEFAULT_ITERATIONS = 300
|
| 13 |
+
DEFAULT_CONF_THRESHOLD = 0.001
|
| 14 |
+
|
| 15 |
+
CONTRAST_ENHANCEMENT = 1.2
|
| 16 |
+
SHARPNESS_ENHANCEMENT = 1.5
|
| 17 |
+
|
| 18 |
+
SERVER_NAME = "0.0.0.0"
|
| 19 |
+
SERVER_PORT = 7860
|
| 20 |
+
SHARE = True
|
| 21 |
+
SHOW_ERROR = True
|
core/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .inference import run_inference
|
| 2 |
+
from .preprocessing import preprocess_images
|
| 3 |
+
from .postprocessing import filter_background_points, create_3d_output
|
| 4 |
+
from .visualization import generate_artifacts
|
| 5 |
+
|
| 6 |
+
__all__ = [
|
| 7 |
+
'run_inference',
|
| 8 |
+
'preprocess_images',
|
| 9 |
+
'filter_background_points',
|
| 10 |
+
'create_3d_output',
|
| 11 |
+
'generate_artifacts'
|
| 12 |
+
]
|
core/inference.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from typing import List, Dict, Any
|
| 3 |
+
|
| 4 |
+
from dust3r.utils.device import to_cpu, collate_with_cat as collate
|
| 5 |
+
from config import BATCH_SIZE
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def run_inference(pairs: List, model: torch.nn.Module, device: str, batch_size: int = BATCH_SIZE) -> Dict[str, Any]:
|
| 9 |
+
result = []
|
| 10 |
+
|
| 11 |
+
for i in range(0, len(pairs), batch_size):
|
| 12 |
+
batch = collate(pairs[i:i+batch_size])
|
| 13 |
+
|
| 14 |
+
for view in batch:
|
| 15 |
+
for k in ["img", "pts3d", "valid_mask", "camera_pose", "camera_intrinsics"]:
|
| 16 |
+
if k in view:
|
| 17 |
+
view[k] = view[k].to(device)
|
| 18 |
+
|
| 19 |
+
v1, v2 = batch
|
| 20 |
+
|
| 21 |
+
with torch.cuda.amp.autocast():
|
| 22 |
+
p1, p2 = model(v1, v2)
|
| 23 |
+
|
| 24 |
+
result.append(to_cpu(dict(view1=v1, view2=v2, pred1=p1, pred2=p2)))
|
| 25 |
+
|
| 26 |
+
return collate(result, lists=True)
|
core/postprocessing.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import numpy as np
|
| 3 |
+
import trimesh
|
| 4 |
+
from typing import List, Tuple
|
| 5 |
+
|
| 6 |
+
from dust3r.utils.device import to_numpy
|
| 7 |
+
from dust3r.viz import pts3d_to_trimesh, cat_meshes
|
| 8 |
+
from config import DEFAULT_CONF_THRESHOLD
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def filter_background_points(scene, conf_threshold: float = DEFAULT_CONF_THRESHOLD):
|
| 12 |
+
masks = scene.get_masks()
|
| 13 |
+
confs = [c for c in scene.im_conf]
|
| 14 |
+
|
| 15 |
+
for i, (mask, conf) in enumerate(zip(masks, confs)):
|
| 16 |
+
conf_mask = conf > conf_threshold
|
| 17 |
+
masks[i] = mask & conf_mask
|
| 18 |
+
|
| 19 |
+
return scene
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def create_3d_output(outdir: str, imgs: List, pts3d: List, mask: List,
|
| 23 |
+
focals: List, cams2world: List, as_pointcloud: bool = False) -> str:
|
| 24 |
+
|
| 25 |
+
pts3d, imgs, focals, cams2world = map(to_numpy, [pts3d, imgs, focals, cams2world])
|
| 26 |
+
scene = trimesh.Scene()
|
| 27 |
+
|
| 28 |
+
if as_pointcloud:
|
| 29 |
+
pts = np.concatenate([p[m] for p, m in zip(pts3d, mask)])
|
| 30 |
+
col = np.concatenate([imgs[i][mask[i]] for i in range(len(imgs))])
|
| 31 |
+
geometry = trimesh.PointCloud(pts.reshape(-1, 3), colors=col.reshape(-1, 3))
|
| 32 |
+
else:
|
| 33 |
+
meshes = [pts3d_to_trimesh(imgs[i], pts3d[i], mask[i]) for i in range(len(imgs))]
|
| 34 |
+
geometry = trimesh.Trimesh(**cat_meshes(meshes))
|
| 35 |
+
|
| 36 |
+
centroid = geometry.centroid
|
| 37 |
+
geometry.apply_translation(-centroid)
|
| 38 |
+
scene.add_geometry(geometry)
|
| 39 |
+
|
| 40 |
+
flip_correction = np.eye(4)
|
| 41 |
+
flip_correction[1, 1] = -1
|
| 42 |
+
flip_correction[2, 2] = -1
|
| 43 |
+
scene.apply_transform(flip_correction)
|
| 44 |
+
|
| 45 |
+
outfile = os.path.join(outdir, 'object.glb')
|
| 46 |
+
scene.export(outfile)
|
| 47 |
+
|
| 48 |
+
return outfile
|
core/preprocessing.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import tempfile
|
| 3 |
+
from PIL import Image, ImageEnhance
|
| 4 |
+
from typing import List
|
| 5 |
+
|
| 6 |
+
from config import CONTRAST_ENHANCEMENT, SHARPNESS_ENHANCEMENT
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def preprocess_images(image_paths: List[str]) -> List[str]:
|
| 10 |
+
cleaned_paths = []
|
| 11 |
+
|
| 12 |
+
for i, path in enumerate(image_paths):
|
| 13 |
+
img = Image.open(path).convert("RGB")
|
| 14 |
+
|
| 15 |
+
enhancer = ImageEnhance.Contrast(img)
|
| 16 |
+
img = enhancer.enhance(CONTRAST_ENHANCEMENT)
|
| 17 |
+
|
| 18 |
+
enhancer = ImageEnhance.Sharpness(img)
|
| 19 |
+
img = enhancer.enhance(SHARPNESS_ENHANCEMENT)
|
| 20 |
+
|
| 21 |
+
save_path = os.path.join(tempfile.gettempdir(), f"input_refined_{i}.png")
|
| 22 |
+
img.save(save_path)
|
| 23 |
+
cleaned_paths.append(save_path)
|
| 24 |
+
|
| 25 |
+
return cleaned_paths
|
core/visualization.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import matplotlib.pyplot as plt
|
| 2 |
+
from typing import List, Tuple
|
| 3 |
+
|
| 4 |
+
from dust3r.utils.image import rgb
|
| 5 |
+
from dust3r.utils.device import to_numpy
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def generate_artifacts(scene) -> List[Tuple]:
|
| 9 |
+
artifacts = []
|
| 10 |
+
cmap = plt.get_cmap('jet')
|
| 11 |
+
|
| 12 |
+
depths = to_numpy(scene.get_depthmaps())
|
| 13 |
+
confs = to_numpy([c for c in scene.im_conf])
|
| 14 |
+
|
| 15 |
+
for i in range(len(scene.imgs)):
|
| 16 |
+
artifacts.append((scene.imgs[i], f"View {i+1}: RGB"))
|
| 17 |
+
|
| 18 |
+
d_norm = depths[i] / (depths[i].max() + 1e-8)
|
| 19 |
+
artifacts.append((rgb(d_norm), f"View {i+1}: Depth Map"))
|
| 20 |
+
|
| 21 |
+
c_norm = cmap(confs[i] / (confs[i].max() + 1e-8))
|
| 22 |
+
artifacts.append((rgb(c_norm), f"View {i+1}: Confidence Heatmap"))
|
| 23 |
+
|
| 24 |
+
return artifacts
|
dust3r
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
Subproject commit 78e55fd11ef6d838fc3d8c5c5a52b32eac426e09
|
gradio_ui.py
CHANGED
|
@@ -1,57 +1,55 @@
|
|
| 1 |
import gradio as gr
|
| 2 |
import functools
|
| 3 |
-
|
| 4 |
-
from pipeline import run_pipeline
|
| 5 |
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
FULLSCREEN_JS = (ASSETS / "fullscreen.js").read_text()
|
| 9 |
|
| 10 |
|
| 11 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
-
pipeline = functools.partial(run_pipeline, outdir, model, device, 512)
|
| 14 |
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
theme=gr.themes.Base(),
|
| 19 |
-
fill_width=True,
|
| 20 |
-
) as app:
|
| 21 |
|
| 22 |
-
|
|
|
|
| 23 |
|
| 24 |
with gr.Row():
|
| 25 |
-
|
| 26 |
with gr.Column(scale=1):
|
| 27 |
-
|
| 28 |
-
run_btn = gr.Button("Run Inference"
|
| 29 |
-
|
| 30 |
with gr.Accordion("Settings", open=False):
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
|
| 36 |
with gr.Column(scale=2):
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
height=600,
|
| 40 |
-
elem_id="model-container",
|
| 41 |
-
)
|
| 42 |
-
fs_btn = gr.Button("Toggle Full Screen ⛶", size="sm")
|
| 43 |
|
| 44 |
gr.Markdown("---")
|
| 45 |
-
gallery = gr.Gallery(columns=3, label="RGB | DEPTH | CONFIDENCE")
|
| 46 |
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
-
|
| 50 |
-
run_btn.click(
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
show_progress="minimal",
|
| 55 |
-
)
|
| 56 |
|
| 57 |
return app
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
import functools
|
| 3 |
+
import os
|
|
|
|
| 4 |
|
| 5 |
+
from pipeline import run_pipeline
|
| 6 |
+
from config import ASSETS_DIR, IMAGE_SIZE
|
|
|
|
| 7 |
|
| 8 |
|
| 9 |
+
def load_assets():
|
| 10 |
+
with open(os.path.join(ASSETS_DIR, 'style.css'), 'r') as f:
|
| 11 |
+
css = f.read()
|
| 12 |
+
|
| 13 |
+
with open(os.path.join(ASSETS_DIR, 'fullscreen.js'), 'r') as f:
|
| 14 |
+
fullscreen_js = f.read()
|
| 15 |
+
|
| 16 |
+
return css, fullscreen_js
|
| 17 |
|
|
|
|
| 18 |
|
| 19 |
+
def build_ui(out_dir: str, model, device: str) -> gr.Blocks:
|
| 20 |
+
css, fullscreen_js = load_assets()
|
| 21 |
+
pipeline = functools.partial(run_pipeline, out_dir, model, device, IMAGE_SIZE)
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
+
with gr.Blocks(title="Multi-View 3D Reconstruction (MV3DR)", css=css, theme=gr.themes.Base(), fill_width=True) as app:
|
| 24 |
+
gr.Markdown("# Multi-View 3D Reconstruction (MV3DR)")
|
| 25 |
|
| 26 |
with gr.Row():
|
|
|
|
| 27 |
with gr.Column(scale=1):
|
| 28 |
+
input_files = gr.File(file_count="multiple", label="Images")
|
| 29 |
+
run_btn = gr.Button("Run Inference")
|
|
|
|
| 30 |
with gr.Accordion("Settings", open=False):
|
| 31 |
+
n_iterations = gr.Slider(100, 1000, 300, label="Alignment Iterations")
|
| 32 |
+
render_mode = gr.Checkbox(True, label="Render as Point Cloud")
|
| 33 |
+
post_proc = gr.Checkbox(True, label="Filter Background Points")
|
| 34 |
+
clean_depth = gr.Checkbox(True, label="Clean Point Cloud")
|
| 35 |
|
| 36 |
with gr.Column(scale=2):
|
| 37 |
+
output_model = gr.Model3D(label="3D Output", height=600, elem_id="model-container")
|
| 38 |
+
full_screen_btn = gr.Button("Toggle Full Screen ⛶", size="sm")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
gr.Markdown("---")
|
|
|
|
| 41 |
|
| 42 |
+
with gr.Row():
|
| 43 |
+
with gr.Column():
|
| 44 |
+
gr.Markdown("## RGB | DEPTH | CONFIDENCE")
|
| 45 |
+
artifact_gallery = gr.Gallery(columns=3, height="auto", label="Logs")
|
| 46 |
+
|
| 47 |
+
full_screen_btn.click(None, None, None, js=fullscreen_js)
|
| 48 |
|
| 49 |
+
saved_state = gr.State()
|
| 50 |
+
run_btn.click(fn=pipeline,
|
| 51 |
+
inputs=[input_files, n_iterations, render_mode, post_proc, clean_depth],
|
| 52 |
+
outputs=[saved_state, output_model, artifact_gallery],
|
| 53 |
+
show_progress="minimal")
|
|
|
|
|
|
|
| 54 |
|
| 55 |
return app
|
main.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
import torch
|
| 4 |
+
|
| 5 |
+
sys.path.append(os.path.join(os.path.dirname(__file__), 'dust3r'))
|
| 6 |
+
|
| 7 |
+
from model import initialize
|
| 8 |
+
from gradio_ui import build_ui
|
| 9 |
+
from config import WEIGHTS_PATH, OUTPUT_DIR, SERVER_NAME, SERVER_PORT, SHARE, SHOW_ERROR
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def main():
|
| 13 |
+
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
| 14 |
+
|
| 15 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 16 |
+
print(f"Using device: {device}")
|
| 17 |
+
|
| 18 |
+
print("Loading DUSt3R model...")
|
| 19 |
+
model = initialize(WEIGHTS_PATH, device)
|
| 20 |
+
|
| 21 |
+
print("Starting Multi-View 3D Reconstruction (MV3DR)...")
|
| 22 |
+
app = build_ui(OUTPUT_DIR, model, device)
|
| 23 |
+
app.launch(
|
| 24 |
+
server_name=SERVER_NAME,
|
| 25 |
+
server_port=SERVER_PORT,
|
| 26 |
+
share=SHARE,
|
| 27 |
+
show_error=SHOW_ERROR
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
if __name__ == "__main__":
|
| 32 |
+
main()
|
model.py
CHANGED
|
@@ -1,18 +1,20 @@
|
|
| 1 |
import torch
|
|
|
|
| 2 |
|
| 3 |
-
def initialize(model_path, device):
|
| 4 |
-
ckpt = torch.load(model_path, map_location="cpu", weights_only=False)
|
| 5 |
-
args = ckpt["args"].model.replace(
|
| 6 |
-
"ManyAR_PatchEmbed", "PatchEmbedDust3R"
|
| 7 |
-
)
|
| 8 |
|
| 9 |
-
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
else:
|
| 12 |
-
args = args.replace(" ", "").replace(
|
| 13 |
-
"landscape_only=True", "landscape_only=False"
|
| 14 |
-
)
|
| 15 |
|
| 16 |
net = eval(args)
|
| 17 |
-
net.load_state_dict(ckpt[
|
|
|
|
|
|
|
| 18 |
return net.to(device)
|
|
|
|
| 1 |
import torch
|
| 2 |
+
from dust3r.model import AsymmetricCroCo3DStereo, inf
|
| 3 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
+
def initialize(model_path: str, device: str) -> torch.nn.Module:
|
| 6 |
+
print(f"Loading model from: {model_path}")
|
| 7 |
+
ckpt = torch.load(model_path, map_location='cpu', weights_only=False)
|
| 8 |
+
|
| 9 |
+
args = ckpt['args'].model.replace("ManyAR_PatchEmbed", "PatchEmbedDust3R")
|
| 10 |
+
|
| 11 |
+
if 'landscape_only' not in args:
|
| 12 |
+
args = args[:-1] + ', landscape_only=False)'
|
| 13 |
else:
|
| 14 |
+
args = args.replace(" ", "").replace('landscape_only=True', 'landscape_only=False')
|
|
|
|
|
|
|
| 15 |
|
| 16 |
net = eval(args)
|
| 17 |
+
net.load_state_dict(ckpt['model'], strict=False)
|
| 18 |
+
|
| 19 |
+
print(f"Model loaded successfully on {device}")
|
| 20 |
return net.to(device)
|
pipeline.py
CHANGED
|
@@ -1,49 +1,55 @@
|
|
| 1 |
-
import torch
|
|
|
|
|
|
|
|
|
|
| 2 |
from dust3r.image_pairs import make_pairs
|
| 3 |
-
from dust3r.utils.image import load_images
|
| 4 |
-
from dust3r.utils.device import to_numpy
|
| 5 |
-
from dust3r.viz import pts3d_to_trimesh, cat_meshes
|
| 6 |
from dust3r.cloud_opt import global_aligner, GlobalAlignerMode
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import copy
|
| 3 |
+
from typing import List, Tuple, Optional
|
| 4 |
+
|
| 5 |
from dust3r.image_pairs import make_pairs
|
| 6 |
+
from dust3r.utils.image import load_images
|
| 7 |
+
from dust3r.utils.device import to_numpy
|
|
|
|
| 8 |
from dust3r.cloud_opt import global_aligner, GlobalAlignerMode
|
| 9 |
+
|
| 10 |
+
from core.preprocessing import preprocess_images
|
| 11 |
+
from core.inference import run_inference
|
| 12 |
+
from core.postprocessing import filter_background_points, create_3d_output
|
| 13 |
+
from core.visualization import generate_artifacts
|
| 14 |
+
from config import IMAGE_SIZE, DEFAULT_ITERATIONS
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def run_pipeline(outdir: str, model: torch.nn.Module, device: str, img_size: int,
|
| 18 |
+
filelist: List[str], niter: int, as_pc: bool, refinement: bool,
|
| 19 |
+
clean_depth: bool = True) -> Tuple[Optional[object], Optional[str], List]:
|
| 20 |
+
|
| 21 |
+
if not filelist or len(filelist) == 0:
|
| 22 |
+
return None, None, []
|
| 23 |
+
|
| 24 |
+
processed_list = preprocess_images(filelist)
|
| 25 |
+
imgs = load_images(processed_list, size=img_size)
|
| 26 |
+
|
| 27 |
+
if len(imgs) == 1:
|
| 28 |
+
imgs = [imgs[0], copy.deepcopy(imgs[0])]
|
| 29 |
+
imgs[1]['idx'] = 1
|
| 30 |
+
|
| 31 |
+
pairs = make_pairs(imgs, scene_graph="complete", prefilter=None, symmetrize=True)
|
| 32 |
+
|
| 33 |
+
output = run_inference(pairs, model, device)
|
| 34 |
+
|
| 35 |
+
mode = GlobalAlignerMode.PointCloudOptimizer if len(imgs) > 2 else GlobalAlignerMode.PairViewer
|
| 36 |
+
scene = global_aligner(output, device=device, mode=mode)
|
| 37 |
+
|
| 38 |
+
if mode == GlobalAlignerMode.PointCloudOptimizer:
|
| 39 |
+
scene.compute_global_alignment(init='mst', niter=niter, schedule='linear', lr=0.01)
|
| 40 |
+
|
| 41 |
+
if clean_depth:
|
| 42 |
+
scene = scene.clean_pointcloud()
|
| 43 |
+
|
| 44 |
+
if refinement:
|
| 45 |
+
scene = filter_background_points(scene)
|
| 46 |
+
|
| 47 |
+
glb_path = create_3d_output(
|
| 48 |
+
outdir, scene.imgs, scene.get_pts3d(),
|
| 49 |
+
to_numpy(scene.get_masks()), scene.get_focals().cpu(),
|
| 50 |
+
scene.get_im_poses().cpu(), as_pointcloud=as_pc
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
artifacts = generate_artifacts(scene)
|
| 54 |
+
|
| 55 |
+
return scene, glb_path, artifacts
|
requirement.txt → requirements.txt
RENAMED
|
@@ -9,3 +9,4 @@ matplotlib
|
|
| 9 |
pillow
|
| 10 |
tqdm
|
| 11 |
numpy
|
|
|
|
|
|
| 9 |
pillow
|
| 10 |
tqdm
|
| 11 |
numpy
|
| 12 |
+
opencv-python
|
setup.sh
CHANGED
|
@@ -8,9 +8,11 @@ if [ ! -d "dust3r" ]; then
|
|
| 8 |
git clone -b dev --recursive https://github.com/camenduru/dust3r
|
| 9 |
fi
|
| 10 |
|
| 11 |
-
pip install -r requirements.txt
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
| 14 |
|
| 15 |
mkdir -p dust3r/checkpoints
|
| 16 |
|
|
|
|
| 8 |
git clone -b dev --recursive https://github.com/camenduru/dust3r
|
| 9 |
fi
|
| 10 |
|
| 11 |
+
python -m pip install -r requirements.txt
|
| 12 |
|
| 13 |
+
if [[ "$(uname)" == "Linux" ]]; then
|
| 14 |
+
python -m pip install https://github.com/camenduru/wheels/releases/download/colab/curope-0.0.0-cp310-cp310-linux_x86_64.whl
|
| 15 |
+
fi
|
| 16 |
|
| 17 |
mkdir -p dust3r/checkpoints
|
| 18 |
|