Spaces:
Sleeping
COMPLETE FIX: Real AI + All Video Features!
Browse filesFIX 1 - NameError fixed:
- Use session state to store depth maps
- No more undefined variable errors
FIX 2 - Remove colormap selector:
- BASE model uses Inferno colormap (best for depth)
- Cleaner UI, one less thing to configure
FIX 3 - Add ALL camera effects:
β
Zoom In/Out - Smooth zoom controls
β
Pan Left/Right/Up/Down - 4-way panning
β
Dolly In/Out - Professional cinema shots
β
Tilt Up/Down - Perspective tilt with transforms
β
Rotate CW/CCW - Clockwise/counter-clockwise
β
Ken Burns - Classic zoom + pan effect
β
Orbit - Smooth orbital rotation with scale
All effects work with:
- Duration: 1-10 seconds
- FPS: 24/30/60
- Resolution: Original/1080p/720p/Square
- Download as MP4
This is the COMPLETE professional video export!
π€ Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
|
@@ -52,8 +52,11 @@ def load_model():
|
|
| 52 |
depth_estimator, USE_REAL_AI, MODEL_SIZE = load_model()
|
| 53 |
|
| 54 |
|
| 55 |
-
def estimate_depth(image
|
| 56 |
"""Estimate depth from an input image using REAL AI or DEMO MODE"""
|
|
|
|
|
|
|
|
|
|
| 57 |
try:
|
| 58 |
# Convert PIL to numpy if needed
|
| 59 |
if isinstance(image, Image.Image):
|
|
@@ -68,20 +71,8 @@ def estimate_depth(image, colormap_style):
|
|
| 68 |
depth = generate_smart_depth(image)
|
| 69 |
mode_text = "DEMO MODE (Synthetic)"
|
| 70 |
|
| 71 |
-
#
|
| 72 |
-
|
| 73 |
-
"Inferno": cv2.COLORMAP_INFERNO,
|
| 74 |
-
"Viridis": cv2.COLORMAP_VIRIDIS,
|
| 75 |
-
"Plasma": cv2.COLORMAP_PLASMA,
|
| 76 |
-
"Turbo": cv2.COLORMAP_TURBO,
|
| 77 |
-
"Magma": cv2.COLORMAP_MAGMA,
|
| 78 |
-
"Hot": cv2.COLORMAP_HOT,
|
| 79 |
-
"Ocean": cv2.COLORMAP_OCEAN,
|
| 80 |
-
"Rainbow": cv2.COLORMAP_RAINBOW
|
| 81 |
-
}
|
| 82 |
-
|
| 83 |
-
# Create colored depth map
|
| 84 |
-
depth_colored = depth_to_colormap(depth, colormap_dict[colormap_style])
|
| 85 |
|
| 86 |
# Create grayscale depth map
|
| 87 |
depth_gray = (depth * 255).astype(np.uint8)
|
|
@@ -114,17 +105,10 @@ col1, col2 = st.columns(2)
|
|
| 114 |
with col1:
|
| 115 |
st.subheader("Input")
|
| 116 |
uploaded_file = st.file_uploader("Upload Your Image", type=['png', 'jpg', 'jpeg'])
|
| 117 |
-
|
| 118 |
-
colormap_style = st.selectbox(
|
| 119 |
-
"Colormap Style",
|
| 120 |
-
["Inferno", "Viridis", "Plasma", "Turbo", "Magma", "Hot", "Ocean", "Rainbow"]
|
| 121 |
-
)
|
| 122 |
-
|
| 123 |
process_btn = st.button("π Generate Depth Map", type="primary")
|
| 124 |
|
| 125 |
with col2:
|
| 126 |
st.subheader("Output")
|
| 127 |
-
depth_placeholder = st.empty()
|
| 128 |
|
| 129 |
# Processing
|
| 130 |
if uploaded_file is not None and process_btn:
|
|
@@ -135,9 +119,14 @@ if uploaded_file is not None and process_btn:
|
|
| 135 |
st.image(image, caption="Original Image", use_column_width=True)
|
| 136 |
|
| 137 |
with st.spinner("Generating depth map..."):
|
| 138 |
-
depth_colored, depth_gray, mode_text, input_shape, output_shape = estimate_depth(image
|
| 139 |
|
| 140 |
if depth_colored is not None:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
with col2:
|
| 142 |
tab1, tab2 = st.tabs(["Colored", "Grayscale"])
|
| 143 |
|
|
@@ -153,7 +142,6 @@ if uploaded_file is not None and process_btn:
|
|
| 153 |
**Mode**: {mode_text}
|
| 154 |
**Input Size**: {input_shape[1]}x{input_shape[0]}
|
| 155 |
**Output Size**: {output_shape[1]}x{output_shape[0]}
|
| 156 |
-
**Colormap**: {colormap_style}
|
| 157 |
{f'**Powered by**: Depth-Anything V2 {MODEL_SIZE}' if USE_REAL_AI else '**Processing**: Ultra-fast (<50ms) synthetic depth'}
|
| 158 |
""")
|
| 159 |
|
|
@@ -161,17 +149,32 @@ if uploaded_file is not None and process_btn:
|
|
| 161 |
st.markdown("---")
|
| 162 |
st.subheader("π¬ Video Export")
|
| 163 |
|
| 164 |
-
if
|
| 165 |
-
with st.expander("Export Depth Map as Video"):
|
| 166 |
col_vid1, col_vid2 = st.columns(2)
|
| 167 |
|
| 168 |
with col_vid1:
|
| 169 |
video_duration = st.slider("Duration (seconds)", 1, 10, 3)
|
| 170 |
video_fps = st.selectbox("FPS", [24, 30, 60], index=1)
|
|
|
|
| 171 |
|
| 172 |
with col_vid2:
|
| 173 |
-
|
| 174 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
|
| 176 |
if st.button("π¬ Export Video", type="primary"):
|
| 177 |
with st.spinner("Generating video..."):
|
|
@@ -179,6 +182,8 @@ if uploaded_file is not None and depth_colored is not None:
|
|
| 179 |
import cv2
|
| 180 |
import tempfile
|
| 181 |
|
|
|
|
|
|
|
| 182 |
# Get dimensions
|
| 183 |
if video_resolution == "1080p":
|
| 184 |
width, height = 1920, 1080
|
|
@@ -206,7 +211,7 @@ if uploaded_file is not None and depth_colored is not None:
|
|
| 206 |
|
| 207 |
# Apply effect
|
| 208 |
if video_effect == "Zoom In":
|
| 209 |
-
scale = 1.0 + (progress * 0.5)
|
| 210 |
center_x, center_y = width // 2, height // 2
|
| 211 |
new_w, new_h = int(width / scale), int(height / scale)
|
| 212 |
x1, y1 = center_x - new_w // 2, center_y - new_h // 2
|
|
@@ -215,7 +220,39 @@ if uploaded_file is not None and depth_colored is not None:
|
|
| 215 |
frame = cv2.resize(cropped, (width, height))
|
| 216 |
|
| 217 |
elif video_effect == "Zoom Out":
|
| 218 |
-
scale = 1.5 - (progress * 0.5)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 219 |
center_x, center_y = width // 2, height // 2
|
| 220 |
new_w, new_h = int(width / scale), int(height / scale)
|
| 221 |
x1, y1 = center_x - new_w // 2, center_y - new_h // 2
|
|
@@ -231,12 +268,59 @@ if uploaded_file is not None and depth_colored is not None:
|
|
| 231 |
offset = int(width * progress * 0.3)
|
| 232 |
frame = np.roll(depth_resized, offset, axis=1)
|
| 233 |
|
| 234 |
-
elif video_effect == "
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 235 |
angle = progress * 360
|
| 236 |
center = (width // 2, height // 2)
|
| 237 |
rotation_matrix = cv2.getRotationMatrix2D(center, angle, 1.0)
|
| 238 |
frame = cv2.warpAffine(depth_resized, rotation_matrix, (width, height))
|
| 239 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
else:
|
| 241 |
frame = depth_resized.copy()
|
| 242 |
|
|
@@ -254,7 +338,7 @@ if uploaded_file is not None and depth_colored is not None:
|
|
| 254 |
st.download_button(
|
| 255 |
label="π₯ Download Video",
|
| 256 |
data=video_bytes,
|
| 257 |
-
file_name=f"depth_video_{video_effect.lower().replace(' ', '_')}.mp4",
|
| 258 |
mime="video/mp4"
|
| 259 |
)
|
| 260 |
|
|
@@ -262,6 +346,8 @@ if uploaded_file is not None and depth_colored is not None:
|
|
| 262 |
st.error(f"Error generating video: {str(e)}")
|
| 263 |
import traceback
|
| 264 |
traceback.print_exc()
|
|
|
|
|
|
|
| 265 |
|
| 266 |
# Info section
|
| 267 |
st.markdown("---")
|
|
@@ -269,11 +355,19 @@ st.markdown("""
|
|
| 269 |
## π‘ About DimensioDepth
|
| 270 |
|
| 271 |
### Features:
|
| 272 |
-
- β
Real AI depth estimation with Depth-Anything V2
|
| 273 |
-
- β
Multiple colormap styles for visualization
|
| 274 |
- β
Fast processing (~800ms on CPU, ~200ms on GPU)
|
| 275 |
- β
SUPERB quality depth maps
|
| 276 |
-
- β
**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 277 |
|
| 278 |
### Use Cases:
|
| 279 |
- π¨ **Creative & Artistic**: Depth-enhanced photos, 3D effects
|
|
|
|
| 52 |
depth_estimator, USE_REAL_AI, MODEL_SIZE = load_model()
|
| 53 |
|
| 54 |
|
| 55 |
+
def estimate_depth(image):
|
| 56 |
"""Estimate depth from an input image using REAL AI or DEMO MODE"""
|
| 57 |
+
if image is None:
|
| 58 |
+
return None, None, "Please upload an image first"
|
| 59 |
+
|
| 60 |
try:
|
| 61 |
# Convert PIL to numpy if needed
|
| 62 |
if isinstance(image, Image.Image):
|
|
|
|
| 71 |
depth = generate_smart_depth(image)
|
| 72 |
mode_text = "DEMO MODE (Synthetic)"
|
| 73 |
|
| 74 |
+
# Create colored depth map with Inferno colormap (best for depth)
|
| 75 |
+
depth_colored = depth_to_colormap(depth, cv2.COLORMAP_INFERNO)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
|
| 77 |
# Create grayscale depth map
|
| 78 |
depth_gray = (depth * 255).astype(np.uint8)
|
|
|
|
| 105 |
with col1:
|
| 106 |
st.subheader("Input")
|
| 107 |
uploaded_file = st.file_uploader("Upload Your Image", type=['png', 'jpg', 'jpeg'])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
process_btn = st.button("π Generate Depth Map", type="primary")
|
| 109 |
|
| 110 |
with col2:
|
| 111 |
st.subheader("Output")
|
|
|
|
| 112 |
|
| 113 |
# Processing
|
| 114 |
if uploaded_file is not None and process_btn:
|
|
|
|
| 119 |
st.image(image, caption="Original Image", use_column_width=True)
|
| 120 |
|
| 121 |
with st.spinner("Generating depth map..."):
|
| 122 |
+
depth_colored, depth_gray, mode_text, input_shape, output_shape = estimate_depth(image)
|
| 123 |
|
| 124 |
if depth_colored is not None:
|
| 125 |
+
# Store in session state for video export
|
| 126 |
+
st.session_state['depth_colored'] = depth_colored
|
| 127 |
+
st.session_state['depth_gray'] = depth_gray
|
| 128 |
+
st.session_state['original_image'] = np.array(image)
|
| 129 |
+
|
| 130 |
with col2:
|
| 131 |
tab1, tab2 = st.tabs(["Colored", "Grayscale"])
|
| 132 |
|
|
|
|
| 142 |
**Mode**: {mode_text}
|
| 143 |
**Input Size**: {input_shape[1]}x{input_shape[0]}
|
| 144 |
**Output Size**: {output_shape[1]}x{output_shape[0]}
|
|
|
|
| 145 |
{f'**Powered by**: Depth-Anything V2 {MODEL_SIZE}' if USE_REAL_AI else '**Processing**: Ultra-fast (<50ms) synthetic depth'}
|
| 146 |
""")
|
| 147 |
|
|
|
|
| 149 |
st.markdown("---")
|
| 150 |
st.subheader("π¬ Video Export")
|
| 151 |
|
| 152 |
+
if 'depth_colored' in st.session_state:
|
| 153 |
+
with st.expander("Export Depth Map as Video", expanded=True):
|
| 154 |
col_vid1, col_vid2 = st.columns(2)
|
| 155 |
|
| 156 |
with col_vid1:
|
| 157 |
video_duration = st.slider("Duration (seconds)", 1, 10, 3)
|
| 158 |
video_fps = st.selectbox("FPS", [24, 30, 60], index=1)
|
| 159 |
+
video_resolution = st.selectbox("Resolution", ["Original", "1080p", "720p", "Square 1080p"])
|
| 160 |
|
| 161 |
with col_vid2:
|
| 162 |
+
video_effect = st.selectbox("Camera Effect", [
|
| 163 |
+
"Zoom In",
|
| 164 |
+
"Zoom Out",
|
| 165 |
+
"Pan Left",
|
| 166 |
+
"Pan Right",
|
| 167 |
+
"Pan Up",
|
| 168 |
+
"Pan Down",
|
| 169 |
+
"Rotate CW",
|
| 170 |
+
"Rotate CCW",
|
| 171 |
+
"Ken Burns (Zoom + Pan)",
|
| 172 |
+
"Dolly In",
|
| 173 |
+
"Dolly Out",
|
| 174 |
+
"Tilt Up",
|
| 175 |
+
"Tilt Down",
|
| 176 |
+
"Orbit"
|
| 177 |
+
])
|
| 178 |
|
| 179 |
if st.button("π¬ Export Video", type="primary"):
|
| 180 |
with st.spinner("Generating video..."):
|
|
|
|
| 182 |
import cv2
|
| 183 |
import tempfile
|
| 184 |
|
| 185 |
+
depth_colored = st.session_state['depth_colored']
|
| 186 |
+
|
| 187 |
# Get dimensions
|
| 188 |
if video_resolution == "1080p":
|
| 189 |
width, height = 1920, 1080
|
|
|
|
| 211 |
|
| 212 |
# Apply effect
|
| 213 |
if video_effect == "Zoom In":
|
| 214 |
+
scale = 1.0 + (progress * 0.5)
|
| 215 |
center_x, center_y = width // 2, height // 2
|
| 216 |
new_w, new_h = int(width / scale), int(height / scale)
|
| 217 |
x1, y1 = center_x - new_w // 2, center_y - new_h // 2
|
|
|
|
| 220 |
frame = cv2.resize(cropped, (width, height))
|
| 221 |
|
| 222 |
elif video_effect == "Zoom Out":
|
| 223 |
+
scale = 1.5 - (progress * 0.5)
|
| 224 |
+
center_x, center_y = width // 2, height // 2
|
| 225 |
+
new_w, new_h = int(width / scale), int(height / scale)
|
| 226 |
+
x1, y1 = center_x - new_w // 2, center_y - new_h // 2
|
| 227 |
+
x2, y2 = x1 + new_w, y1 + new_h
|
| 228 |
+
cropped = depth_resized[max(0, y1):min(height, y2), max(0, x1):min(width, x2)]
|
| 229 |
+
frame = cv2.resize(cropped, (width, height))
|
| 230 |
+
|
| 231 |
+
elif video_effect == "Ken Burns (Zoom + Pan)":
|
| 232 |
+
# Ken Burns: zoom in while panning
|
| 233 |
+
scale = 1.0 + (progress * 0.4)
|
| 234 |
+
pan_x = int(width * progress * 0.2)
|
| 235 |
+
pan_y = int(height * progress * 0.1)
|
| 236 |
+
center_x = width // 2 + pan_x
|
| 237 |
+
center_y = height // 2 + pan_y
|
| 238 |
+
new_w, new_h = int(width / scale), int(height / scale)
|
| 239 |
+
x1, y1 = center_x - new_w // 2, center_y - new_h // 2
|
| 240 |
+
x2, y2 = x1 + new_w, y1 + new_h
|
| 241 |
+
cropped = depth_resized[max(0, y1):min(height, y2), max(0, x1):min(width, x2)]
|
| 242 |
+
frame = cv2.resize(cropped, (width, height))
|
| 243 |
+
|
| 244 |
+
elif video_effect == "Dolly In":
|
| 245 |
+
# Dolly in: smooth zoom with slight scale
|
| 246 |
+
scale = 1.0 + (progress * 0.3)
|
| 247 |
+
center_x, center_y = width // 2, height // 2
|
| 248 |
+
new_w, new_h = int(width / scale), int(height / scale)
|
| 249 |
+
x1, y1 = center_x - new_w // 2, center_y - new_h // 2
|
| 250 |
+
x2, y2 = x1 + new_w, y1 + new_h
|
| 251 |
+
cropped = depth_resized[max(0, y1):min(height, y2), max(0, x1):min(width, x2)]
|
| 252 |
+
frame = cv2.resize(cropped, (width, height))
|
| 253 |
+
|
| 254 |
+
elif video_effect == "Dolly Out":
|
| 255 |
+
scale = 1.3 - (progress * 0.3)
|
| 256 |
center_x, center_y = width // 2, height // 2
|
| 257 |
new_w, new_h = int(width / scale), int(height / scale)
|
| 258 |
x1, y1 = center_x - new_w // 2, center_y - new_h // 2
|
|
|
|
| 268 |
offset = int(width * progress * 0.3)
|
| 269 |
frame = np.roll(depth_resized, offset, axis=1)
|
| 270 |
|
| 271 |
+
elif video_effect == "Pan Up":
|
| 272 |
+
offset = int(height * progress * 0.3)
|
| 273 |
+
frame = np.roll(depth_resized, -offset, axis=0)
|
| 274 |
+
|
| 275 |
+
elif video_effect == "Pan Down":
|
| 276 |
+
offset = int(height * progress * 0.3)
|
| 277 |
+
frame = np.roll(depth_resized, offset, axis=0)
|
| 278 |
+
|
| 279 |
+
elif video_effect == "Tilt Up":
|
| 280 |
+
# Tilt up: perspective transformation
|
| 281 |
+
tilt_factor = progress * 0.3
|
| 282 |
+
pts1 = np.float32([[0, 0], [width, 0], [0, height], [width, height]])
|
| 283 |
+
pts2 = np.float32([
|
| 284 |
+
[0, int(height * tilt_factor)],
|
| 285 |
+
[width, int(height * tilt_factor)],
|
| 286 |
+
[0, height],
|
| 287 |
+
[width, height]
|
| 288 |
+
])
|
| 289 |
+
matrix = cv2.getPerspectiveTransform(pts1, pts2)
|
| 290 |
+
frame = cv2.warpPerspective(depth_resized, matrix, (width, height))
|
| 291 |
+
|
| 292 |
+
elif video_effect == "Tilt Down":
|
| 293 |
+
tilt_factor = progress * 0.3
|
| 294 |
+
pts1 = np.float32([[0, 0], [width, 0], [0, height], [width, height]])
|
| 295 |
+
pts2 = np.float32([
|
| 296 |
+
[0, 0],
|
| 297 |
+
[width, 0],
|
| 298 |
+
[0, height - int(height * tilt_factor)],
|
| 299 |
+
[width, height - int(height * tilt_factor)]
|
| 300 |
+
])
|
| 301 |
+
matrix = cv2.getPerspectiveTransform(pts1, pts2)
|
| 302 |
+
frame = cv2.warpPerspective(depth_resized, matrix, (width, height))
|
| 303 |
+
|
| 304 |
+
elif video_effect == "Rotate CW":
|
| 305 |
+
angle = progress * 360
|
| 306 |
+
center = (width // 2, height // 2)
|
| 307 |
+
rotation_matrix = cv2.getRotationMatrix2D(center, -angle, 1.0)
|
| 308 |
+
frame = cv2.warpAffine(depth_resized, rotation_matrix, (width, height))
|
| 309 |
+
|
| 310 |
+
elif video_effect == "Rotate CCW":
|
| 311 |
angle = progress * 360
|
| 312 |
center = (width // 2, height // 2)
|
| 313 |
rotation_matrix = cv2.getRotationMatrix2D(center, angle, 1.0)
|
| 314 |
frame = cv2.warpAffine(depth_resized, rotation_matrix, (width, height))
|
| 315 |
|
| 316 |
+
elif video_effect == "Orbit":
|
| 317 |
+
# Orbit: rotate + slight zoom
|
| 318 |
+
angle = progress * 360
|
| 319 |
+
scale = 1.0 + (np.sin(progress * np.pi) * 0.2)
|
| 320 |
+
center = (width // 2, height // 2)
|
| 321 |
+
rotation_matrix = cv2.getRotationMatrix2D(center, angle, scale)
|
| 322 |
+
frame = cv2.warpAffine(depth_resized, rotation_matrix, (width, height))
|
| 323 |
+
|
| 324 |
else:
|
| 325 |
frame = depth_resized.copy()
|
| 326 |
|
|
|
|
| 338 |
st.download_button(
|
| 339 |
label="π₯ Download Video",
|
| 340 |
data=video_bytes,
|
| 341 |
+
file_name=f"depth_video_{video_effect.lower().replace(' ', '_').replace('(', '').replace(')', '')}.mp4",
|
| 342 |
mime="video/mp4"
|
| 343 |
)
|
| 344 |
|
|
|
|
| 346 |
st.error(f"Error generating video: {str(e)}")
|
| 347 |
import traceback
|
| 348 |
traceback.print_exc()
|
| 349 |
+
else:
|
| 350 |
+
st.info("π Upload an image and generate depth map first to enable video export")
|
| 351 |
|
| 352 |
# Info section
|
| 353 |
st.markdown("---")
|
|
|
|
| 355 |
## π‘ About DimensioDepth
|
| 356 |
|
| 357 |
### Features:
|
| 358 |
+
- β
Real AI depth estimation with Depth-Anything V2 BASE model
|
|
|
|
| 359 |
- β
Fast processing (~800ms on CPU, ~200ms on GPU)
|
| 360 |
- β
SUPERB quality depth maps
|
| 361 |
+
- β
**Professional video export** with cinematic camera movements
|
| 362 |
+
|
| 363 |
+
### Camera Effects:
|
| 364 |
+
- πΉ **Zoom In/Out** - Smooth zoom controls
|
| 365 |
+
- π¬ **Pan** - Left, Right, Up, Down panning
|
| 366 |
+
- π₯ **Dolly** - Professional dolly in/out shots
|
| 367 |
+
- ποΈ **Tilt** - Up/Down tilt movements
|
| 368 |
+
- π **Rotate** - Clockwise/Counter-clockwise rotation
|
| 369 |
+
- β **Ken Burns** - Classic zoom + pan effect
|
| 370 |
+
- π **Orbit** - Smooth orbital rotation
|
| 371 |
|
| 372 |
### Use Cases:
|
| 373 |
- π¨ **Creative & Artistic**: Depth-enhanced photos, 3D effects
|