Skip to content

Commit 3b21945

Browse files
committed
refactor(camera): calibration interface change
- changed the interface for hardware calibration - added realsense specific calibration code
1 parent aad0ad5 commit 3b21945

4 files changed

Lines changed: 61 additions & 22 deletions

File tree

extensions/rcs_realsense/src/rcs_realsense/camera.py

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import numpy as np
77
import pyrealsense2 as rs
8+
from rcs.camera.interface import Calibration
89
from rcs.camera.hw import HardwareCamera
910
from rcs.camera.interface import CameraFrame, DataFrame, Frame, IMUFrame
1011

@@ -22,10 +23,12 @@ class RealSenseDevicePipeline:
2223
pipeline: rs.pipeline
2324
pipeline_profile: rs.pipeline_profile
2425
camera: RealSenseDeviceInfo
26+
depth_scale: float | None = None
2527

2628

2729
class RealSenseCameraSet(HardwareCamera):
2830
TIMESTAMP_FACTOR = 1e-3
31+
DEPTH_SCALE = 10000
2932

3033
def __init__(
3134
self,
@@ -41,6 +44,7 @@ def __init__(
4144
self.enable_imu = enable_imu
4245
self.cameras = cameras
4346
self._logger = logging.getLogger(__name__)
47+
# TODO: safe the last 30 recorded frames
4448
self._last_frameset: dict[rs.stream, rs.frame] | None = None
4549
self._frameset_lock = threading.Lock()
4650
assert (
@@ -168,7 +172,9 @@ def enable_device(self, device_info: RealSenseDeviceInfo, enable_ir_emitter: boo
168172
if sensor.supports(rs.option.emitter_enabled):
169173
sensor.set_option(rs.option.emitter_enabled, 1 if enable_ir_emitter else 0)
170174
sensor.set_option(rs.option.laser_power, self.laser_power)
171-
self._enabled_devices[device_info.serial] = RealSenseDevicePipeline(pipeline, pipeline_profile, device_info)
175+
self._enabled_devices[device_info.serial] = RealSenseDevicePipeline(
176+
pipeline, pipeline_profile, device_info, depth_scale=sensor.get_depth_scale()
177+
)
172178
self._logger.debug("Enabled device %s (%s)", device_info.serial, device_info.product_line)
173179

174180
@staticmethod
@@ -255,7 +261,12 @@ def to_ts(frame: rs.frame) -> float:
255261
timestamps.append(to_ts(frame))
256262

257263
assert color is not None, "Color frame not found"
258-
cf = CameraFrame(color=color, ir=ir, depth=depth)
264+
# TODO: check depth type (should be int16)
265+
cf = CameraFrame(
266+
color=color,
267+
ir=ir,
268+
depth=(depth.astype(np.float64) * device.depth_scale / self.DEPTH_SCALE).astype(np.int16),
269+
)
259270
imu = IMUFrame(accel=accel, gyro=gyro)
260271
return Frame(camera=cf, imu=imu, avg_timestamp=float(np.mean(timestamps)) if len(timestamps) > 0 else None)
261272

@@ -303,7 +314,7 @@ def get_device_intrinsics(
303314
device_intrinsics[device_name][key] = value.get_profile().as_video_stream_profile().get_intrinsics()
304315
return device_intrinsics
305316

306-
def get_depth_to_color_extrinsics(self, frames):
317+
def get_depth_to_color_extrinsics(self, frames: dict[rs.stream, rs.frame]):
307318
"""
308319
Get the extrinsics between the depth imager 1 and the color imager using its frame delivered by the realsense device
309320
@@ -353,7 +364,25 @@ def enable_emitter(self, enable_ir_emitter=True):
353364
if enable_ir_emitter:
354365
sensor.set_option(rs.option.laser_power, self.laser_power)
355366

356-
357-
def intrinsics(self, camera_name: str) -> np.ndarray[tuple[typing.Literal[3, 4]], np.dtype[np.float64]]:
358-
with self._frameset_lock:
359-
pass
367+
def calibrate(self) -> dict[str, Calibration] | None:
368+
369+
intrinsics = {}
370+
for camera_name, camera_config in self.cameras:
371+
serial = camera_config.identifier
372+
device = self._enabled_devices[serial]
373+
vp = device.pipeline_profile.get_stream(rs.stream.color).as_video_stream_profile()
374+
intrinsics[camera_name] = vp.get_intrinsics()
375+
376+
extrinsics = {}
377+
for camera_name, camera_config in self.cameras:
378+
serial = camera_config.identifier
379+
device = self._enabled_devices[serial]
380+
depth_vp = device.pipeline_profile.get_stream(rs.stream.depth).as_video_stream_profile()
381+
color_vp = device.pipeline_profile.get_stream(rs.stream.color).as_video_stream_profile()
382+
depth_to_color = depth_vp.get_extrinsics_to(color_vp)
383+
384+
# calibrate strategy: save the last 30 frames and send it
385+
# it can
386+
# - ask for more new frames
387+
# - calibration fail
388+
# - calibration successful

python/rcs/camera/hw.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def poll_frame(self, camera_name: str) -> Frame:
3535
def camera_names(self) -> list[str]:
3636
"""Returns the names of the cameras in this set."""
3737

38-
def intrinsics(self, camera_name: str) -> np.ndarray[tuple[typing.Literal[3, 4]], np.dtype[np.float64]]
38+
def calibrate(self) -> dict[str, Calibration] | None:
3939
"""Returns camera intrinsics"""
4040

4141

@@ -63,6 +63,7 @@ def __init__(
6363
self._next_ring_index = 0
6464
self._buffer_len = 0
6565
self.writer: dict[str, cv2.VideoWriter] = {}
66+
self._calibration: dict[str, Calibration] | None = None
6667

6768
@property
6869
def camera_names(self) -> list[str]:
@@ -181,14 +182,20 @@ def warm_up(self):
181182
self.poll_frame(camera_name)
182183
self.rate_limiter()
183184

184-
def get_calibration(self, camera_name) -> Calibration:
185-
# get intrinsics from the camera and check that extrinsics had been calibrated
186-
pass
185+
def get_calibration(self) -> dict[str, Calibration] | None:
186+
return self._calibration
187+
188+
def calibrate(self) -> bool:
189+
self._calibration = None
190+
cal = {}
191+
for camera in self.cameras:
192+
c = camera.calibrate()
193+
if c is None:
194+
return False
195+
cal.update(cal)
196+
self._calibration = cal
197+
return True
187198

188-
def calibrate(self, camera_name) -> bool:
189-
# calibrate extrinsics with aruca marker
190-
# lets provide a calibration function
191-
pass
192199

193200
def polling_thread(self, warm_up: bool = True):
194201
for camera in self.cameras:

python/rcs/camera/interface.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,11 @@ def close(self):
7373
def config(self, camera_name: str) -> BaseCameraConfig:
7474
"""Returns the configuration object of the cameras."""
7575

76-
def get_calibration(self, camera_name) -> Calibration:
77-
"""Returns camera calibration"""
76+
def get_calibration(self) -> dict[str, Calibration] | None:
77+
"""Returns cameras' calibration. None if not calibrated or previous calibration failed."""
7878

79-
def calibrate(self, camera_name) -> bool:
80-
"""Calibrates the camera"""
79+
def calibrate(self) -> bool:
80+
"""Calibrates the cameras. Returns calibration success"""
8181

8282
@property
8383
def camera_names(self) -> list[str]:

python/rcs/camera/sim.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,13 @@ def _extrinsics(self, camera_name) -> np.ndarray[tuple[Literal[4, 4]], np.dtype[
108108

109109
return cam.inverse().pose_matrix()
110110

111-
def get_calibration(self, camera_name) -> Calibration:
112-
return Calibration(intrinsics=self._intrinsics(camera_name), extrinsics=self._extrinsics(camera_name))
111+
def get_calibration(self) -> dict[str, Calibration]:
112+
return {
113+
camera_name: Calibration(intrinsics=self._intrinsics(camera_name), extrinsics=self._extrinsics(camera_name))
114+
for camera_name in self.camera_names
115+
}
113116

114-
def calibrate(self, camera_name) -> bool:
117+
def calibrate(self) -> bool:
115118
return True
116119

117120
def config(self, camera_name: str) -> SimCameraConfig:

0 commit comments

Comments
 (0)