# Cámaras

LeRobot ofrece varias opciones para la captura de video:

| Clase             | Cámaras compatibles                   |
| ----------------- | ----------------------------------- |
| `OpenCVCamera`    | Teléfono, laptop integrada, cámaras web USB |
| `ZMQCamera`       | Cámaras conectadas a la red           |
| `RealSenseCamera` | Intel RealSense (con profundidad)        |
| `Reachy2Camera`   | Cámaras del robot Reachy 2              |

> [!TIP]
> Para obtener detalles de compatibilidad de `OpenCVCamera`, consulte el [Resumen de E/S de vídeo con OpenCV](https://docs.opencv.org/4.x/d0/da7/videoio_overview.html).

### Encuentra tu cámara

Cada cámara requiere un identificador único para ser instanciada, lo que permite distinguir entre varios dispositivos conectados.

`OpenCVCamera` y `RealSenseCamera` admiten la detección automática. Ejecute el siguiente comando para listar los dispositivos disponibles y sus identificadores. Tenga en cuenta que estos identificadores pueden cambiar tras reiniciar su computadora o volver a conectar la cámara, dependiendo de su sistema operativo.

```bash
lerobot-find-cameras opencv # or realsense for Intel Realsense cameras
```

La salida se parecerá a esto si tienes dos cámaras conectadas:

```bash
--- Detected Cameras ---
Camera #0:
  Name: OpenCV Camera @ 0
  Type: OpenCV
  Id: 0
  Backend api: AVFOUNDATION
  Default stream profile:
    Format: 16.0
    Width: 1920
    Height: 1080
    Fps: 15.0
--------------------
(more cameras ...)
```

> [!WARNING]
> Al usar cámaras Intel RealSense en `macOS`, es posible que aparezca este [error](https://github.com/realsenseai/librealsense/issues/12307): `Error finding RealSense cameras: failed to set power state`. Esto se puede resolver ejecutando el mismo comando con permisos de `sudo`. Ten en cuenta que el uso de cámaras RealSense en `macOS` no es estable.

`ZMQCamera` y `Reachy2Camera` no admiten la detección automática. Deben configurarse manualmente proporcionando su dirección de red y puerto o la configuración del SDK del robot.

## Colocación de la cámara e iluminación

Las imágenes de la cámara son de lo que aprende tu política, por lo que una buena configuración de la cámara es más importante que una alta resolución:

- **Ilumina la escena de forma uniforme.** Utiliza una luz difusa y constante, y evita la luz solar directa, los reflejos intensos y las sombras en movimiento. Una iluminación que difiera entre la grabación y la implementación puede hacer que la política falle.
- **Fije las cámaras en su sitio.** Montelas de forma rígida y no las mueva entre sesiones de grabación, ni entre la grabación y la implementación.
- **Defina la tarea.** El objeto, la pinza y el objetivo deben ser visibles en al menos una cámara. Una buena prueba: ¿podría realizar la tarea usted mismo mirando únicamente las imágenes de las cámaras?
- **Mantén la resolución modesta.** 640×480 a 30 fps es suficiente para la mayoría de las tareas y mantiene la grabación y el entrenamiento rápidos.

## Usar cámaras

### Modos de acceso a fotogramas

Todas las clases de cámara implementan tres modos de acceso para capturar fotogramas:

| Método                    | Comportamiento                                                                                                                                                   | ¿Bloques?        | Más adecuado para                                 |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | ---------------------------------------- |
| `read()`                  | Espera a que el hardware de la cámara devuelva un fotograma. Puede bloquearse durante mucho tiempo dependiendo de la cámara y del SDK.                                                | Sí            | Scripts simples, captura secuencial       |
| `async_read(timeout_ms)`  | Devuelve el último fotograma no consumido del hilo en segundo plano. Solo se bloquea si el búfer está vacío, hasta `timeout_ms`. Lanza `TimeoutError` si no llega ningún fotograma. | Con un tiempo de espera | Bucles de control sincronizados con los FPS de la cámara |
| `read_latest(max_age_ms)` | Muestra el fotograma más reciente del búfer (puede estar desactualizado). Lanza `TimeoutError` si el fotograma es anterior a `max_age_ms`.                                        | No             | Visualización de la interfaz de usuario, registro, monitoreo    |

### Ejemplos de uso

Los siguientes ejemplos muestran cómo usar la API de cámara para configurar y capturar fotogramas de diferentes tipos de cámara.

- **Captura de fotogramas bloqueante y no bloqueante** con una cámara basada en OpenCV
- **Captura de color y profundidad** con una cámara Intel RealSense

> [!WARNING]
> No desconectar las cámaras de forma limpia puede provocar fugas de recursos. Utilice el protocolo de gestor de contexto para garantizar la limpieza automática:
>
> ```python
> with OpenCVCamera(config) as camera:
>     ...
> ```
>
> También puedes llamar a `connect()` y `disconnect()` manualmente, pero usa siempre un bloque `finally` para este último.

```python
from lerobot.cameras.opencv import OpenCVCamera, OpenCVCameraConfig
from lerobot.cameras import ColorMode, Cv2Rotation

# Construct an `OpenCVCameraConfig` with your desired FPS, resolution, color mode, and rotation.
config = OpenCVCameraConfig(
    index_or_path=0,
    fps=15,
    width=1920,
    height=1080,
    color_mode=ColorMode.RGB,
    rotation=Cv2Rotation.NO_ROTATION
)

# Instantiate and connect an `OpenCVCamera`, performing a warm-up read (default).
with OpenCVCamera(config) as camera:

    # Read a frame synchronously — blocks until hardware delivers a new frame
    frame = camera.read()
    print(f"read() call returned frame with shape:", frame.shape)

    # Read a frame asynchronously with a timeout — returns the latest unconsumed frame or waits up to timeout_ms for a new one
    try:
        for i in range(10):
            frame = camera.async_read(timeout_ms=200)
            print(f"async_read call returned frame {i} with shape:", frame.shape)
    except TimeoutError as e:
        print(f"No frame received within timeout: {e}")

    # Instantly return a frame - returns the most recent frame captured by the camera
    try:
        initial_frame = camera.read_latest(max_age_ms=1000)
        for i in range(10):
            frame = camera.read_latest(max_age_ms=1000)
            print(f"read_latest call returned frame {i} with shape:", frame.shape)
            print(f"Was a new frame received by the camera? {not (initial_frame == frame).any()}")
    except TimeoutError as e:
        print(f"Frame too old: {e}")

```

```python
from lerobot.cameras.realsense import RealSenseCamera, RealSenseCameraConfig
from lerobot.cameras import ColorMode, Cv2Rotation

# Create a `RealSenseCameraConfig` specifying your camera’s serial number and enabling depth.
config = RealSenseCameraConfig(
    serial_number_or_name="233522074606",
    fps=15,
    width=640,
    height=480,
    color_mode=ColorMode.RGB,
    use_depth=True,
    # Optional fixed color controls. Omit them to leave the current sensor settings unchanged.
    exposure=120,
    gain=64,
    white_balance=4600,
    rotation=Cv2Rotation.NO_ROTATION
)

# Instantiate and connect a `RealSenseCamera` with warm-up read (default).
camera = RealSenseCamera(config)
camera.connect()

# Capture a color frame via `read()` and a depth map via `read_depth()`.
try:
    color_frame = camera.read()
    depth_map = camera.read_depth()
    print("Color frame shape:", color_frame.shape)
    print("Depth map shape:", depth_map.shape)
finally:
    camera.disconnect()
```

Los controles de color manuales desactivan el modo de exposición automática o de balance de blancos correspondiente. Sus rangos de valores admitidos varían según el modelo de cámara; un valor no válido genera un error en el momento de la conexión que incluye el rango reportado por el sensor. Solicitar un control no admitido también genera un error. Los controles omitidos dejan sin cambios la configuración automática o manual existente del sensor. Estas opciones requieren `use_rgb=True`.

Los controles de color manuales requieren un módulo RGB dedicado. Las cámaras que no lo tienen, como la RealSense D405, no los admiten y generan un error en el momento de la conexión.

### Trabajar con profundidad

Las cámaras Intel RealSense y Reachy 2 pueden capturar color y profundidad de forma sincronizada. La llamada a `read()` devuelve el fotograma de **color** como `(H, W, 3)` `uint8`. La llamada a `read_depth()` devuelve el **mapa de profundidad** como `(H, W, 1)` `uint16`, donde cada valor de píxel es la distancia desde el sensor expresada en **milímetros**. Un valor de píxel de `0` suele significar "no hay medición disponible" (fuera de rango, ocluido o de baja confianza).

Durante la grabación, el bucle de control consulta de forma no bloqueante los fotogramas más recientes en el búfer mediante `read_latest()` (color) y `read_latest_depth()` (profundidad), añadiendo el mapa de profundidad como una característica hermana (p. ej. `front_depth` junto a `front`).

Para saber cómo se almacenan y codifican los flujos de profundidad al grabar un conjunto de datos, consulte la sección [Flujos de profundidad](./video_encoding_parameters#depth-streams) de la guía de codificación de video.

## Usa la cámara de tu teléfono

Para usar tu iPhone como cámara en macOS, activa la función Continuity Camera:

- Asegúrate de que tu Mac esté ejecutando macOS 13 o posterior y que tu iPhone esté en iOS 16 o posterior.
- Inicia sesión en ambos dispositivos con el mismo Apple ID.
- Conecte sus dispositivos con un cable USB o active el Wi-Fi y el Bluetooth para una conexión inalámbrica.

Para más detalles, visite [Apple support](https://support.apple.com/en-gb/guide/mac-help/mchl77879b8a/mac).

Si quieres usar tu teléfono como cámara con OBS, sigue estos pasos para configurar una cámara virtual.

1. _(Solo Linux) Instala `v4l2loopback-dkms` y `v4l-utils`_. Estos paquetes crean dispositivos de cámara virtuales y verifican su configuración. Instálalos con:

```bash
sudo apt install v4l2loopback-dkms v4l-utils
```

2. _Instala la [aplicación DroidCam](https://droidcam.app) en tu teléfono_. Esta aplicación está disponible para iOS y Android.
3. _Descarga e instala [OBS Studio](https://obsproject.com)_.
4. _Descarga e instala el [plugin de OBS para DroidCam](https://droidcam.app/obs)_.
5. _Iniciar OBS Studio_.

6. _Añade tu teléfono como fuente_. Sigue las instrucciones [aquí](https://droidcam.app/obs/usage). Asegúrate de establecer la resolución en `640x480` para evitar las marcas de agua.
7. _Ajustar la resolución_. En OBS Studio, ve a `File > Settings > Video` o `OBS > Preferences... > Video`. Cambia la `Base(Canvas) Resolution` y la `Output(Scaled) Resolution` a `640x480` escribiéndolo manualmente.
8. _Iniciar la cámara virtual_. En OBS Studio, sigue las instrucciones [aquí](https://obsproject.com/kb/virtual-camera-guide).
9. _Verifique la configuración de la cámara virtual y la resolución_.
   - **Linux**: usa `v4l2-ctl` para listar los dispositivos y comprobar la resolución:
     ```bash
     v4l2-ctl --list-devices  # find VirtualCam and note its /dev/videoX path
     v4l2-ctl -d /dev/videoX --get-fmt-video  # replace with your VirtualCam path
     ```
     Deberías ver `VirtualCam` en la lista y la resolución `640x480`.
   - **macOS**: Abra Photo Booth o FaceTime y seleccione "OBS Virtual Camera" como entrada.
   - **Windows**: La aplicación de cámara nativa no admite cámaras virtuales. Utilice una aplicación de videollamadas (Zoom, Teams) o ejecute `lerobot-find-cameras opencv` directamente para verificarlo.

Solución de problemas

> La resolución de la cámara virtual es incorrecta.

Elimine la fuente de la cámara virtual y créela de nuevo. La resolución no se puede cambiar después de la creación.

> Error al leer el fotograma en el hilo de fondo para OpenCVCamera(X): el ancho=640 o la altura=480 del fotograma de OpenCVCamera(X) no coinciden con el ancho=1920 o la altura=1080 configurados.

Este error se debe a que OBS Virtual Camera anuncia una resolución de `1920x1080` a pesar del reescalado. La única solución por ahora es comentar la comprobación de ancho y alto en `_postprocess_image()`.

Si todo está configurado correctamente, tu teléfono aparecerá como una cámara estándar de OpenCV y podrá usarse con `OpenCVCamera`.

