System designTroubleshooting

RViz2 image topic published but not displaying: fix the QoS mismatch

rqt_graph shows the image topic published but the RViz2 preview is not displaying: set the display QoS to Best Effort, Keep Last, depth 1 and it appears.

LumaSpec Vision9 min read Share on LinkedIn

On this page 9 sections
  1. 01The QoS rule behind the blank panel
  2. 02Confirm the diagnosis in one minute
  3. 03The fix: Best Effort, Keep Last, depth 1
  4. 04Depth, latency and which frame you actually see
  5. 05Worked example: can the link carry the stream?
  6. 06Best Effort still shows nothing: the next causes
  7. 07Prevention: choose QoS per stream
  8. 08The decision, case by case
  9. 09Questions

rqt_graph shows your camera node publishing the image topic, you have added an Image display in RViz2, and the panel stays blank. The subscription edge is even visible in the graph. In ROS 2 that combination — a visible subscription, zero frames, no error — is almost always a Quality of Service (QoS) mismatch: the driver publishes best-effort sensor data, the display asks for reliable delivery, and the two never connect at the data layer. This guide gives you the one-minute test that confirms the diagnosis, the display settings that fix it, the bandwidth arithmetic that tells you whether the link carries the stream at all, and the next causes in line when QoS is not the fault. The same logic covers depth maps and point clouds from any 3D sensor or area scan camera on a ROS 2 machine.

The QoS rule behind the blank panel

A ROS 2 publisher and subscriber connect only when the subscriber requests no more than the publisher offers, for both reliability and durability. Reliability is ordered: best effort is the weaker policy, reliable the stronger. Camera drivers publish live streams with the sensor-data profile — best-effort reliability, volatile durability, keep-last history with depth 5 — because a frame that arrives late is worthless: the next one follows about 33 ms later at 30 fps. The RViz2 Image display, left at its factory settings, requests reliable delivery, the stronger policy, so the match fails at the data layer.

Discovery still succeeds, and that is what makes the fault confusing. rqt_graph draws the edge and ros2 topic info counts a subscriber while not one frame moves: discovery answers whether the nodes see each other, never whether their QoS profiles match.

Setting Camera driver (sensor-data QoS) RViz2 Image display (factory default)
Reliability Best Effort Reliable
Durability Volatile Volatile
History / depth Keep Last / 5 Keep Last / 10

One row differs. Fix the subscriber rather than strengthen a live video publisher.

Confirm the diagnosis in one minute

  1. Get the exact name from ros2 topic list, then run ros2 topic info /fisheye1/image_raw --verbose for your topic. The publisher block prints Reliability: BEST_EFFORT and the subscription block prints Reliability: RELIABLE; different values on that row are the diagnosis.
  2. Run ros2 topic hz /fisheye1/image_raw. With the mismatch in place it prints no rate at all — zero messages arrive — even though the graph looks connected.
  3. Read the signature. A subscriber visible in rqt_graph plus a silent ros2 topic hz means QoS. A subscriber that does not appear in the graph at all means the two processes sit in different ROS domains or on different middleware, which the closing checklist covers.

The fix: Best Effort, Keep Last, depth 1

  1. Select the Image display in the Displays panel.
  2. Set Reliability Policy to Best Effort.
  3. Leave History Policy at Keep Last and set Depth to 1.
  4. Watch the panel: the first frame appears within one frame period — 33 ms at 30 fps — plus a render cycle. Still blank after a full second means QoS was not the whole story; move to the next section.

The property names match ROS 2 releases from Foxy on. Apply the same three settings to every display that subscribes to a live sensor stream: the Camera display, PointCloud2, and depth or disparity views. A subscriber weaker than its publisher always connects; the only cost is tolerating lost frames, and on a healthy link you will not see them.

Depth, latency and which frame you actually see

Keep Last with depth N lets the subscriber buffer N frames, so a lagging renderer can serve a frame up to N × 33 ms old at 30 fps — about 165 ms at depth 5, roughly twice that at the display's factory depth 10. Depth 1 always renders the newest frame and cannot build backlog, which is what you want when watching live video. The same newest-frame discipline applies when your own code consumes the stream: Frame didn't arrive within timeout: one wait call per frame shows the matching pattern.

An uncompressed image topic costs a predictable bit rate:

bit rate (Mb/s) = width × height × bytes per pixel × fps × 8 / 1,000,000

For an 848 × 800 mono, 8-bit stream at 30 fps:

  • 848 × 800 = 678,400 bytes per frame
  • 678,400 × 30 = 20,352,000 bytes per second
  • 20,352,000 × 8 = 162,816,000 bit/s ≈ 163 Mb/s

A 16-bit depth map at the same size and rate doubles that to about 326 Mb/s. A wired gigabit hop moves roughly 940 Mb/s of usable payload, so both fit with room; a 100 Mb/s hop delivers roughly 90 Mb/s, below even the mono stream. On a marginal link, best effort quietly drops frames while reliable delivery stalls — the frozen panel again. The decision that follows: wire the camera host at gigabit; if the viewing machine sits on Wi-Fi, keep Best Effort and lower resolution or frame rate rather than raising reliability. When the frame rate sags on the camera side instead, exposure time is often the ceiling — see Depth camera frame rate drops: exposure settings cap the FPS.

Best Effort still shows nothing: the next causes

Ordered by how often each turns out to be the cause:

  1. Different ROS domain or middleware. ros2 topic list in the rviz2 terminal shows no camera topics at all. Launch driver and viewer from one environment so the domain and middleware match, then re-check QoS.
  2. Only a compressed topic exists. The transport layer splits each camera topic into a raw twin and a compressed twin; if raw was disabled at startup, the display subscribes to a topic nobody publishes. Look for the compressed twin in ros2 topic list; how the two route differently is covered in ROS depth color compression mismatch: diagnose topic routing.
  3. The Camera display needs more than the image. The plain Image display draws any image topic; the Camera display additionally needs the camera_info topic and a Fixed Frame the transform tree can resolve. If Image works and Camera does not, publish camera_info and set Fixed Frame to the camera frame.
  4. The panel is not blank but wrong. Gray points at a pixel-format mismatch — Camera preview shows a gray image: fix the format mismatch. A color-conversion error on a depth map is its own fix, in Depth image 16UC1 display: fix color conversion errors.

If none of these is it and the camera reaches the host through a frame grabber or a gigabit link outside ROS, the fault may sit in the hardware layer: Camera connected but no image: check the frame grabber cable wiring walks that path.

Prevention: choose QoS per stream

Stream Publisher offers Subscriber requests
Live camera images to viewers Best Effort, Keep Last, depth 5 Best Effort, Keep Last, depth 1
Streams that must arrive complete (recording) Reliable, Keep Last, queue sized to the backlog Reliable, same
Poses, control topics, calibration results Reliable Reliable

Remember one sentence: a subscriber may always request less than the publisher offers and still connects; requesting more silently produces the blank panel this guide started from. The recording row is a deliberate trade — reliable delivery buys completeness with retransmission latency — and ROS image transport recording: keep depth data reliable covers that side in depth.

The decision, case by case

  • Subscriber visible in rqt_graph, ros2 topic hz silent: set the display to Best Effort, Keep Last, depth 1 and expect the first frame within one frame period.
  • No subscriber edge in the graph: align the ROS domain and middleware of both processes first, then re-check QoS.
  • Raw topic missing, compressed twin present: fix the transport configuration before touching QoS.
  • Image display works, Camera display blank: publish camera_info and set a resolvable Fixed Frame.
  • Gray panel or a format error rather than blank: pixel format or depth encoding, not QoS.

For the same class of fault outside ROS — a capture service returning nothing, a viewer window with no feed — the wider walk-through is Fix camera feed not displaying in a vision application. If you are specifying the camera side of a new cell while debugging, the camera finder and the configurator shortlist parts against your interface and bandwidth budget, and we answer specification questions through the contact page.

Cameras3D sensors

Laser profilers and stereo 3D cameras

170 models · from $699.00

Questions

Why does the topic work in one terminal and not another?

Each terminal inherits a ROS domain number from its environment, and processes in different domains never discover each other: the node stays invisible to ros2 topic list and rqt_graph. Launch the driver and rviz2 from shells with the same domain setting, and set it identically in any launch file or service unit that starts them. After that, check QoS.

Why did ROS 2 choose Best Effort for camera streams?

Because for live sensor data the newest frame is the only one worth sending. Reliable delivery would retransmit an expired frame and delay the fresh one behind it, adding latency without adding information. The sensor-data profile encodes that trade-off: best-effort reliability, volatile durability, keep-last history with depth 5. Consumers that truly need every frame override the publisher deliberately.

Does the same fix apply to PointCloud2 and depth displays?

Yes. Every rviz2 display that subscribes to a live stream — Image, Camera, PointCloud2, depth or disparity — must request no more reliability than the driver offers, so set Best Effort and Keep Last on each. Avoid the Keep All history option on live streams: it retains every message, grows memory without bound and adds latency instead of resolving the mismatch.

How do I set the subscriber QoS in my own code?

Each ROS 2 client library ships a ready-made sensor-data QoS preset; pass it when you create the subscription and the node matches any camera driver with no further settings. To build the profile by hand, request best-effort reliability, volatile durability and keep-last history with a small depth. A subscriber that requests less than the publisher offers always connects.

Can a QoS mismatch make the image lag instead of staying blank?

Yes, in the opposite configuration. A best-effort subscriber attached to a reliable publisher connects, but a history depth of 10 can serve frames up to about 330 ms old at 30 fps after a stall. If the picture moves in bursts or trails reality, cut the display depth to 1 before suspecting the network.