236 lines
5.9 KiB
Markdown
236 lines
5.9 KiB
Markdown
# Collecting Render from Virtual Cameras
|
|
|
|
## Overview
|
|
This tutorial covers two methods for capturing rendered images from virtual cameras in Vuer, with a focus on the recommended ondemand approach.
|
|
|
|
## Methods Overview
|
|
|
|
### Method 1: Frame/Time Mode (Legacy)
|
|
Uses event handlers to collect rendered images. Only supported in `stream='frame'` or `stream='time'` mode.
|
|
|
|
**Limitations:**
|
|
- Less backend control
|
|
- Continuous rendering even when not needed
|
|
- Higher resource usage
|
|
|
|
### Method 2: OnDemand Mode (Recommended)
|
|
Uses a synchronous `grab_render` RPC API. Only available in `stream='ondemand'` mode.
|
|
|
|
**Advantages:**
|
|
- Superior backend control
|
|
- Renders only when explicitly requested
|
|
- Lower computational overhead
|
|
- Support for depth rendering
|
|
|
|
## Complete Example: OnDemand Mode
|
|
|
|
```python
|
|
import asyncio
|
|
import numpy as np
|
|
from vuer import Vuer, VuerSession
|
|
from vuer.schemas import Scene, CameraView, DefaultScene, Box, Urdf
|
|
from PIL import Image
|
|
|
|
app = Vuer()
|
|
|
|
@app.spawn(start=True)
|
|
async def main(session: VuerSession):
|
|
# Set up the scene
|
|
session.set @ Scene(
|
|
DefaultScene(),
|
|
|
|
# Add some objects to render
|
|
Box(
|
|
args=[1, 1, 1],
|
|
position=[0, 0.5, 0],
|
|
color="red",
|
|
key="box",
|
|
),
|
|
|
|
Urdf(
|
|
src="/static/robot.urdf",
|
|
position=[2, 0, 0],
|
|
key="robot",
|
|
),
|
|
|
|
# Configure camera with ondemand streaming
|
|
CameraView(
|
|
key="ego",
|
|
fov=50,
|
|
width=640,
|
|
height=480,
|
|
position=[0, 2, 5],
|
|
rotation=[0, 0, 0],
|
|
stream="ondemand",
|
|
renderDepth=True, # Enable depth rendering
|
|
near=0.1,
|
|
far=100,
|
|
),
|
|
)
|
|
|
|
# Wait for scene to initialize
|
|
await asyncio.sleep(0.5)
|
|
|
|
# Capture renders from different positions
|
|
for i in range(10):
|
|
# Update camera position
|
|
x = 5 * np.cos(i * 0.2)
|
|
z = 5 * np.sin(i * 0.2)
|
|
|
|
session.update @ CameraView(
|
|
key="ego",
|
|
position=[x, 2, z],
|
|
rotation=[0, i * 0.2, 0],
|
|
)
|
|
|
|
# Small delay for camera update
|
|
await asyncio.sleep(0.1)
|
|
|
|
# Grab the render
|
|
result = session.grab_render(downsample=1, key="ego")
|
|
|
|
if result:
|
|
# Process RGB image
|
|
rgb_data = result.get("rgb")
|
|
if rgb_data:
|
|
# Convert to numpy array
|
|
img_array = np.frombuffer(rgb_data, dtype=np.uint8)
|
|
img_array = img_array.reshape((480, 640, 3))
|
|
|
|
# Save image
|
|
img = Image.fromarray(img_array)
|
|
img.save(f"render_{i:03d}.png")
|
|
print(f"Saved render_{i:03d}.png")
|
|
|
|
# Process depth map
|
|
depth_data = result.get("depth")
|
|
if depth_data:
|
|
depth_array = np.frombuffer(depth_data, dtype=np.float32)
|
|
depth_array = depth_array.reshape((480, 640))
|
|
|
|
# Save depth map
|
|
depth_img = Image.fromarray(
|
|
(depth_array * 255).astype(np.uint8)
|
|
)
|
|
depth_img.save(f"depth_{i:03d}.png")
|
|
print(f"Saved depth_{i:03d}.png")
|
|
|
|
print("Finished capturing renders")
|
|
|
|
# Keep session alive
|
|
while True:
|
|
await asyncio.sleep(1.0)
|
|
|
|
app.run()
|
|
```
|
|
|
|
## Key API: `grab_render()`
|
|
|
|
```python
|
|
result = session.grab_render(downsample=1, key="ego")
|
|
```
|
|
|
|
### Parameters
|
|
- **downsample**: Downsample factor (1 = no downsampling, 2 = half resolution)
|
|
- **key**: Camera key to capture from
|
|
|
|
### Returns
|
|
Dictionary containing:
|
|
- **rgb**: RGB image data as bytes
|
|
- **depth**: Depth map data as float32 array (if `renderDepth=True`)
|
|
|
|
## Depth Rendering
|
|
|
|
Enable depth map capture by setting `renderDepth=True`:
|
|
|
|
```python
|
|
CameraView(
|
|
key="ego",
|
|
renderDepth=True,
|
|
stream="ondemand",
|
|
# ... other parameters
|
|
)
|
|
```
|
|
|
|
**Benefits:**
|
|
- Captures depth without changing object materials
|
|
- Available since 2024 update
|
|
- Minimal computational overhead
|
|
|
|
## Legacy Method: Event Handler
|
|
|
|
For `stream='frame'` or `stream='time'` mode:
|
|
|
|
```python
|
|
async def handle_camera_view(event, session):
|
|
"""Handle CAMERA_VIEW events"""
|
|
if event.key != "ego":
|
|
return
|
|
|
|
# Access rendered image
|
|
image_data = event.value.get("image")
|
|
|
|
# Process image data
|
|
print(f"Received image: {len(image_data)} bytes")
|
|
|
|
app.add_handler("CAMERA_VIEW", handle_camera_view)
|
|
```
|
|
|
|
## Multi-Camera Capture
|
|
|
|
Capture from multiple cameras:
|
|
|
|
```python
|
|
@app.spawn(start=True)
|
|
async def main(session: VuerSession):
|
|
# Set up multiple cameras
|
|
session.set @ Scene(
|
|
DefaultScene(),
|
|
|
|
CameraView(
|
|
key="front-camera",
|
|
position=[0, 1, 5],
|
|
stream="ondemand",
|
|
width=640,
|
|
height=480,
|
|
),
|
|
|
|
CameraView(
|
|
key="top-camera",
|
|
position=[0, 10, 0],
|
|
rotation=[-1.57, 0, 0],
|
|
stream="ondemand",
|
|
width=640,
|
|
height=480,
|
|
),
|
|
)
|
|
|
|
await asyncio.sleep(0.5)
|
|
|
|
# Capture from both cameras
|
|
front_render = session.grab_render(key="front-camera")
|
|
top_render = session.grab_render(key="top-camera")
|
|
|
|
# Process renders...
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
1. **Use ondemand mode** - More efficient for programmatic rendering
|
|
2. **Enable depth rendering** - Get depth maps without material changes
|
|
3. **Add small delays** - Wait for camera updates before grabbing
|
|
4. **Set appropriate resolution** - Balance quality and performance
|
|
5. **Use downsampling** - Reduce data size when full resolution isn't needed
|
|
|
|
## Performance Considerations
|
|
|
|
The ondemand approach:
|
|
- Minimizes computational overhead
|
|
- Only renders when explicitly requested
|
|
- Ideal for resource-constrained applications
|
|
- Perfect for dataset generation and batch processing
|
|
|
|
## Source
|
|
|
|
Documentation: https://docs.vuer.ai/en/latest/tutorials/camera/grab_render_virtual_camera.html
|