3D visionHow-to

Depth camera driver runs: view your ROS image topics

How to see frames from a running ROS depth camera: list image topics, open rqt_image_view, check the rate with rostopic hz, then subscribe in your own node.

LumaSpec Vision9 min read Share on LinkedIn

On this page 10 sections
  1. 01What a running driver actually publishes
  2. 02Step 1: list the image topics the node publishes
  3. 03Step 2: open a stream in the image viewer
  4. 04Step 3: check the rate with rostopic hz
  5. 05Worked example: proving a 30 fps depth stream on a USB link
  6. 06Step 4: subscribe from your own node
  7. 07When the viewer stays black or the rate collapses
  8. 08From a bench camera to a production 3D sensor
  9. 09Checklist before you trust the stream
  10. 10Questions

The launch log stops scrolling, the driver node and its nodelet manager are running, and no window opens anywhere. That is the normal end state: a ROS camera driver publishes its streams as topics and draws nothing on screen by itself. Getting from a running driver to frames you can see, measure and use takes three commands and one small node. This guide shows where the depth, color and infrared image topics live, how to open them in the image viewer, how to confirm the stream runs at the rate you set at launch, and how to subscribe from your own code. It is written for engineers who have just launched a depth camera driver for the first time and want proof the camera delivers before building inspection logic on top.

What a running driver actually publishes

A driver that started cleanly publishes four kinds of output, and none of it is visible until something subscribes:

  • One image topic per stream the launch enabled — depth, color, infrared — each carrying sensor_msgs/Image messages.
  • A camera_info topic next to each image topic, holding the intrinsics that turn pixels and depth values into metric coordinates. An image displays fine without it; geometry does not. ROS 2 camera calibration: validate and publish camera_info shows what to check in that file.
  • Static transforms between the camera's frames on /tf_static, which is what the launch log means when it reports publishing camera transforms.
  • On many drivers, depth also as a point cloud or a color-registered cloud, so the 2D depth image is one of several ways to inspect the same data.

The depth stream is normally 16-bit while the infrared streams are 8-bit; 16-bit depth and 8-bit infrared streams on one depth camera covers what each carries and how to tell them apart.

Step 1: list the image topics the node publishes

rostopic list prints every topic on the system, including the driver's. Narrow it with a filter:

rostopic list | grep image

You are looking for one topic per enabled stream, under the camera namespace your launch file defined, each with a camera_info sibling. Before viewing, confirm a topic really carries images and see who publishes it:

rostopic info <your-image-topic>

The output names the message type — sensor_msgs/Image — the publishing node, and any current subscribers. Substitute the exact name your own list printed: topic names do not always match the frame names set in the launch parameters, and when the name you expect is missing, finding the name the node really publishes is the discipline that locates it. That walk-through is written for ROS 2; the same lookup logic applies to ROS 1.

Step 2: open a stream in the image viewer

Start the viewer:

rosrun rqt_image_view rqt_image_view

A window opens with a dropdown of every image topic on the system. Pick the depth topic: it renders as a grayscale image whose tones track distance, so moving a hand toward the camera shifts the tones smoothly. Pick the color topic for the RGB stream and an infrared topic for the IR image. The viewer is an image_transport client, so the dropdown can also list the compressed variants the transport layer republishes; choose the raw topic when you are checking what the driver itself delivers.

If the dropdown lists your topic but the window stays black, the image is published in a form the viewer is not negotiating. The causes are ranked, with tests, in "When the viewer stays black" below — confirm the rate first, then work that list.

Step 3: check the rate with rostopic hz

Before building anything on the stream, measure it:

rostopic hz <your-image-topic>

The command subscribes and prints a running average rate; let it accumulate at least 50 messages. Compare against the frame rate your launch parameters requested — the example launch behind this guide requested 30 fps for its color stream. A rate far below the request is a transport problem, not a vision problem. On an industrial camera that runs on software trigger, the trigger mode itself can cap the rate, as 3D camera frame rate low on software trigger: mode and data streams explains.

Inputs, each with its origin:

  • Requested rate: 30 fps, the value set by the color frame-rate parameter at launch.
  • Depth pixel depth: 16 bits per pixel, the usual depth-stream format.
  • Depth image size: 640 × 480 pixels, a common depth resolution — substitute the width and height your own camera_info reports.
  • Link rates: USB 3 at 5 Gbit/s and USB 2 at 480 Mbit/s, both theoretical line rates.

The rule in words: the measured rate should sit within about one frame per second of the request, and the bits per second the stream needs must fit the link with room left for the other streams.

period = 1 / frame_rate bytes_per_frame = width × height × bytes_per_pixel wire_rate = bytes_per_frame × frame_rate × 8

The arithmetic:

  • period = 1 / 30 fps = 0.0333 s, so 33.3 ms between frames.
  • bytes_per_frame = 640 × 480 pixels × 2 B/pixel = 614,400 B, about 0.59 MB per frame.
  • wire_rate = 614,400 B × 30 fps = 18,432,000 B/s ≈ 18.4 MB/s, × 8 = 147 Mbit/s.

Take 29.8 Hz as the example rostopic hz reading after a minute: 1 / 29.8 Hz = 0.0336 s = 33.6 ms, about 0.3 ms and under 1% off the request — healthy. The decisions:

  • 29 Hz or more on a 30 fps request: subscribe and move on.
  • 25 to 28 Hz: something shares the link or the host — another subscriber, an open viewer, a compressed transport. Close what you do not need and re-measure.
  • About 15 Hz, almost exactly half: every second frame is dropped. Fix the link before writing any consumer. Depth alone claims 147 Mbit/s of the 480 Mbit/s a USB 2 link offers at best, before color and infrared join it; a 5 Gbit/s USB 3 link carries all three streams with headroom.

Step 4: subscribe from your own node

  1. Take the exact topic name from your own rostopic list output.
  2. Write a subscriber that logs one line per frame.
  3. Run it next to the driver and read the log.
  4. Add conversion and processing in the callback once the rate checks out.

The subscriber:

#!/usr/bin/env python
# ROS 1 rospy subscriber: proves frames arrive end to end
import rospy
from sensor_msgs.msg import Image

def on_image(msg):
    rospy.loginfo('image %d x %d, encoding %s, stamp %.3f',
                  msg.width, msg.height, msg.encoding,
                  msg.header.stamp.to_sec())

topic = '/camera/depth/image'  # replace with the name rostopic list printed
rospy.init_node('depth_watcher')
sub = rospy.Subscriber(topic, Image, on_image, queue_size=1)
rospy.spin()

The test that proves it: one log line per frame, about one every 33 ms at 30 fps, and the encoding field names the format your conversion step must handle. queue_size=1 is deliberate — when processing falls behind, the node drops stale frames instead of queueing them and drifting behind the camera. Convert to an OpenCV-compatible array in the callback using the message's encoding and step fields, or the bridge library your distribution ships for exactly that job.

When the consumer is an existing detection node, remap the camera's topics onto the names that node expects; remapping depth camera topics for a ROS people detection node walks a full example. If frames must outlive the process, wrist camera image storage: where frames live and how to access them covers the recording side. For a camera riding on a robot arm, configuring ROS image access on a wrist camera covers the same pattern on mounted hardware.

When the viewer stays black or the rate collapses

Causes in order of likelihood, with the test for each:

  1. Topic listed, window black. Test with rostopic hz: if the rate is healthy, the viewer is negotiating the wrong format or quality-of-service profile — RViz2 image topic published but not displaying: fix the QoS mismatch covers the ROS 2 case.
  2. Infrared topics absent from the list entirely. The launch disabled those streams; enable them the way ROS infrared topics with no messages: check stream enable settings describes.
  3. Rate near half the request. Dropped frames on a shared link — work the arithmetic in the worked example above.
  4. Subscriber connects but the first frame never arrives, or retrieval hangs. That is a host-side USB problem: depth camera detected but frame get hangs: Jetson USB fixes.
  5. Two cameras publishing under identical names. Give each wrapper its own namespace, as ROS multi-camera topic names: give each wrapper a namespace shows.

From a bench camera to a production 3D sensor

A USB depth camera on the bench is the right tool for proving software. When the task moves to a machine, the shop's 3D sensors — time-of-flight, projected structured light, laser profiling — are built for that environment, and the checks in this guide are exactly the acceptance tests you run at commissioning. The MV-DT01WMU is a ToF sensing camera:

3D sensorsHikrobot MV-DT01WMU ToF sensing cameraMV-DT01WMU
Type
Depth / ToF camera
Price on requestView and quote

The MV-DPS200P-02 is a projected structured-light 3D camera:

3D sensorsHikrobot MV-DPS200P-02 Projected structured-light 3D cameraMV-DPS200P-02
Type
Stereo / structured light
$8,829.00 · quotedView and quote

Not every task needs depth at all: for flat parts under controlled lighting, a 2D area scan camera delivers the inspection with far less data to move. Compare the whole camera range in the camera finder, or start a matched pick with the system configurator.

Cameras3D sensors

Laser profilers and stereo 3D cameras

170 models · from $699.00

Checklist before you trust the stream

  • rostopic list shows one image topic per stream you enabled, each with a camera_info sibling — a missing topic means a disabled stream, not a silent camera.
  • The viewer renders the depth topic and tones shift when you move a hand in front of the lens — proof the sensor is live.
  • rostopic hz holds within about 1 Hz of the requested fps over 50 messages or more.
  • Your subscriber logs one line per frame at the expected spacing, and its encoding field matches what your conversion expects.
  • Transforms appear on /tf_static before you rely on any cross-frame geometry.

Once the stream passes those checks, the camera is delivering and the rest is your software. If a stream refuses to cooperate, bring the launch log and the rostopic output to the contact page and the applications team will work it with you.

Questions

Why does the depth image show up gray in the viewer?

The viewer renders the 16-bit depth values as plain grayscale and applies no color map. Tone tracks distance, so a hand moved toward the camera shifts the tones smoothly across the frame. Any false-color rendering is something you add in your own node or in a 3D visualization tool once the values reach your code; the raw stream stays monochrome.

How do I see a point cloud instead of a depth image?

Look for a topic of type PointCloud2 in the same camera namespace; many drivers publish one alongside the depth image. If yours publishes images only, you convert: the depth image plus the intrinsics in camera_info give every pixel a metric XYZ triple. Robot visualization tools render PointCloud2 directly, which is also the quickest way to eyeball how well depth and color are registered.

What rate should rostopic hz report on a healthy stream?

The frame rate your launch parameters requested, held steady over 50 messages or more: 29 to 30 Hz on a 30 fps request. A reading of almost exactly half means every second frame is dropped, which points at shared USB bandwidth or an overloaded transport, not at your subscriber code. Measure with every other image viewer closed so nothing competes for the stream.

Do I need camera_info just to view an image?

No. Displaying needs only the image topic. You need camera_info the moment pixels become geometry: registering depth onto color, building point clouds, or converting pixel coordinates into the camera frame. A default or stale camera_info still displays fine while quietly wrecking every metric result downstream, so validate it before trusting measurements, not before trusting the picture.

Can several nodes subscribe to the same image topic?

Yes. Topics are one-to-many, and each subscriber receives its own copy of every message, which is why an open viewer and your node run side by side. The cost is real: every extra subscriber adds host CPU load and, on a shared USB camera, competes for the same link. Close viewers you are not actively using when you measure rates or time a processing loop.