Skip to main content
Version: QTrobot V3

QTrobot Vision and 3D Camera

QTrobot has an integrated 3D camera in the head: an Intel® RealSense™ Depth Camera D455, connected to QTPC and fully open for developers to configure and use.

architecture

Software interface

Unlike the core capabilities (TTS, face, motors, ...), the camera isn't served by the main service hub on QTRP — it's served by a separate process, qtrobot-realsense-driver, running on QTPC. That's why it's a plugin in the Python and TypeScript/Node.js SDKs: robot.camera only becomes available once you explicitly point the SDK at this separate driver. Over ROS2, it's a second, independent gateway process running alongside the main one.

The driver talks to the physical RealSense camera and republishes its data over the same Magpie/ZMQ pattern used everywhere else in QTrobot V3 — a few RPC calls (intrinsics, depth scale) plus several independent streams (color, depth, IMU), each individually enable-able.

The driver runs as its own systemd service on QTPC, independent of the main service hub:

sudo systemctl status qtrobot-realsense-driver
sudo systemctl restart qtrobot-realsense-driver
journalctl -u qtrobot-realsense-driver -f # view logs

Its base ZMQ port is 50750 — RPC calls go to that port directly, while RGB, depth, and IMU each get their own stream port (50751, 50752, 50753) under the hood. Connecting to a single base endpoint (shown below) discovers all of it automatically.

See the Camera tutorial and the Python, TypeScript/Node.js and ROS2 API references for full code.

What's streamed by default

Not every stream is turned on out of the box — enabling unused sensors costs CPU and bandwidth, so the driver ships with a conservative default:

StreamDefaultResolution / rate
RGB (color)On848×480 @ 15 fps
Depth (depth, depth_aligned)On640×480 @ 15 fps
Colorized depth (depth_color)Off
Gyroscope (gyro)Off200 Hz
Accelerometer (acceleration)Off63 Hz

What "aligned depth" means: the RGB and depth sensors sit at slightly different physical positions on the camera, so a raw depth pixel doesn't line up with the same pixel in the raw color image. The depth_aligned stream re-projects the depth data into the color camera's point of view, so pixel (x, y) in depth_aligned corresponds to the same (x, y) in color — this is what you want whenever you need to know "how far away is the thing at this point in the color image" (see Get the distance at a pixel below). The plain depth stream, by contrast, is in the depth sensor's own native viewpoint.

Enabling more features

To turn on something that's off by default (colorized depth, gyro, accelerometer, or to change resolution/frame rate), edit the driver's config on QTPC and restart it:

sudo nano /opt/luxai/qtrobot_realsense_driver/etc/config.yaml
use-color-depth: true   # enable colorized depth stream
use-gyro: true # enable gyroscope
use-accel: true # enable accelerometer
sudo systemctl restart qtrobot-realsense-driver

The same file also controls RGB/depth resolution and frame rate (rgb-width, rgb-height, rgb-fps, depth-width, depth-height, depth-fps) if you need to trade off quality for bandwidth or CPU.

Accessing the camera

Enable

robot.enable_plugin_zmq("realsense-driver", node_id="qtrobot-realsense-driver")

Intrinsics and depth scale

color_intrinsics = robot.camera.get_color_intrinsics()
depth_intrinsics = robot.camera.get_depth_intrinsics()
depth_scale = robot.camera.get_depth_scale()

depth_scale is the number of metres represented by one raw depth unit — multiply any raw depth pixel value by it to get a real-world distance.

Image streams

reader = robot.camera.stream.open_color_reader()
frame = reader.read(timeout=3.0)

The same pattern is available for depth, depth_aligned and depth_color in every language.

IMU streams

Gyroscope and accelerometer each deliver [x, y, z] — angular velocity in rad/s for gyro, linear acceleration in m/s² for acceleration:

robot.camera.stream.on_acceleration(lambda frame: print(frame.value))

Viewing the live camera feed

Each color frame's .data is a raw BGR pixel buffer — exactly the layout OpenCV expects, so no color conversion is needed:

import cv2
import numpy as np

reader = robot.camera.stream.open_color_reader()

while True:
frame = reader.read(timeout=2.0)
if frame is None:
continue
image = np.frombuffer(frame.data, dtype=np.uint8).reshape(frame.height, frame.width, frame.channels)
cv2.imshow("QTrobot Camera", image)
if cv2.waitKey(1) & 0xFF == ord("q"):
break

Get the distance at a pixel

Combining the depth_aligned stream with depth_scale tells you how far away the thing is at any given pixel of the color image — useful for "how far is the person/object I'm looking at" style logic:

import numpy as np

depth_scale = robot.camera.get_depth_scale()["scale"]
reader = robot.camera.stream.open_depth_aligned_reader()

frame = reader.read(timeout=3.0)
depth_image = np.frombuffer(frame.data, dtype=np.uint16).reshape(frame.height, frame.width)

# Distance at the center pixel, in metres
cx, cy = frame.width // 2, frame.height // 2
distance_m = depth_image[cy, cx] * depth_scale

Because depth_aligned shares the color image's coordinate system, you can reuse the exact same (x, y) from anything you detect in the color stream, such as a face or object bounding box center, to look up its distance here.