diff --git a/src/lerobot/cameras/realsense/camera_realsense.py b/src/lerobot/cameras/realsense/camera_realsense.py index 29cb1e5e0..cb76f93b3 100644 --- a/src/lerobot/cameras/realsense/camera_realsense.py +++ b/src/lerobot/cameras/realsense/camera_realsense.py @@ -191,6 +191,7 @@ class RealSenseCamera(Camera): ) from e self._configure_capture_settings() + self._configure_sensor_options() self._start_read_thread() # NOTE(Steven/Caroline): Enforcing at least one second of warmup as RS cameras need a bit of time before the first read. If we don't wait, the first read from the warmup will raise. @@ -339,6 +340,59 @@ class RealSenseCamera(Camera): self.new_frame_event.clear() return self._async_read(timeout_ms=10000, read_depth=read_depth) + def _get_color_sensor(self) -> "rs.sensor": + """Returns the color sensor from the active pipeline profile.""" + if self.rs_profile is None: + raise RuntimeError(f"{self}: rs_profile must be initialized before use.") + + device = self.rs_profile.get_device() + for sensor in device.query_sensors(): + if sensor.get_info(rs.camera_info.name) == "RGB Camera": + return sensor + raise RuntimeError(f"{self}: no RGB Camera sensor found on device.") + + def _configure_sensor_options(self) -> None: + """Applies manual sensor options (exposure, gain, white balance) to the color sensor. + + When exposure or gain is set, auto-exposure is disabled first. When white_balance + is set, auto white balance is disabled first. Skipped entirely if no options are set. + """ + config = self.config + if config.exposure is None and config.gain is None and config.white_balance is None: + return + + color_sensor = self._get_color_sensor() + + if (config.exposure is not None or config.gain is not None) and color_sensor.supports( + rs.option.enable_auto_exposure + ): + color_sensor.set_option(rs.option.enable_auto_exposure, 0) + logger.info(f"{self} auto-exposure disabled.") + + if config.exposure is not None: + if color_sensor.supports(rs.option.exposure): + color_sensor.set_option(rs.option.exposure, config.exposure) + logger.info(f"{self} exposure set to {config.exposure}.") + else: + logger.warning(f"{self} sensor does not support manual exposure.") + + if config.gain is not None: + if color_sensor.supports(rs.option.gain): + color_sensor.set_option(rs.option.gain, config.gain) + logger.info(f"{self} gain set to {config.gain}.") + else: + logger.warning(f"{self} sensor does not support manual gain.") + + if config.white_balance is not None: + if color_sensor.supports(rs.option.enable_auto_white_balance): + color_sensor.set_option(rs.option.enable_auto_white_balance, 0) + logger.info(f"{self} auto white balance disabled.") + if color_sensor.supports(rs.option.white_balance): + color_sensor.set_option(rs.option.white_balance, config.white_balance) + logger.info(f"{self} white balance set to {config.white_balance}.") + else: + logger.warning(f"{self} sensor does not support manual white balance.") + @check_if_not_connected def read_depth(self, timeout_ms: int = 200) -> NDArray[Any]: """ diff --git a/src/lerobot/cameras/realsense/configuration_realsense.py b/src/lerobot/cameras/realsense/configuration_realsense.py index 018675195..b4cf4d9e0 100644 --- a/src/lerobot/cameras/realsense/configuration_realsense.py +++ b/src/lerobot/cameras/realsense/configuration_realsense.py @@ -46,6 +46,15 @@ class RealSenseCameraConfig(CameraConfig): use_depth: Whether to enable depth stream. Defaults to False. rotation: Image rotation setting (0°, 90°, 180°, or 270°). Defaults to no rotation. warmup_s: Time reading frames before returning from connect (in seconds) + exposure: Manual exposure value for the color sensor. When set, auto-exposure is + disabled and this fixed value is used. Valid range depends on the camera model + (e.g., 1-10000 for D400 series). Defaults to None (auto-exposure). + gain: Manual gain value for the color sensor. When set, auto-exposure is disabled + and this fixed gain is used. Valid range depends on the camera model + (e.g., 16-248 for D400 series). Defaults to None (auto). + white_balance: Manual white balance value for the color sensor. When set, auto + white balance is disabled and this fixed value is used. Valid range depends on + the camera model (e.g., 2800-6500 for D400 series). Defaults to None (auto). Note: - Either name or serial_number must be specified. @@ -61,6 +70,9 @@ class RealSenseCameraConfig(CameraConfig): use_depth: bool = False rotation: Cv2Rotation = Cv2Rotation.NO_ROTATION warmup_s: int = 1 + exposure: int | None = None + gain: int | None = None + white_balance: int | None = None def __post_init__(self) -> None: self.color_mode = ColorMode(self.color_mode)