Launch a second camera wrapper in ROS and one of two things goes wrong: both wrappers publish the same topic names, or the first wrapper shuts down the moment the second starts. The camera ID is not the fix. The ID only selects which device a wrapper opens; the published topic names come from the node's namespace. This guide is for anyone wiring a multi camera rig — two depth cameras, a ring of color cameras, or a mix of both — into one ROS host. You will launch one wrapper instance per camera, open each device by serial number, put each instance in its own namespace, and end with topics such as /cam0/image_raw and /cam1/image_raw that cannot collide. A worked two-camera example shows the exact topic list to expect, plus the checks that prove the split.
What names a topic, and what only selects a camera
ROS builds every topic name from two parts: the node's namespace and the wrapper's relative topic name. The device parameter in the wrapper's configuration — a camera index or a serial number — decides which physical camera that instance opens. It contributes nothing to the published names. So a wrapper opened with serial 20530512 and a wrapper opened with serial 20530519, both launched with default settings, publish identical topic names: their streams merge, and every subscriber receives frames from both cameras with no way to tell them apart.
A duplicate node name in one namespace is the harsher case. ROS 1 resolves the conflict by shutting down the older node, which is why camera A stops publishing at the exact moment wrapper B launches. ROS 2 lets both nodes live, but their publishers still merge onto the same topic names. Neither failure prints an error at launch.
Many wrappers expose a camera name and a camera namespace parameter that do both jobs at once: they rename the node and prefix every published topic. Where your wrapper has them, set them per instance; where it does not, the namespace argument on the node produces the same result. The rule underneath is the same either way: one instance, one namespace, one set of prefixed topics. Changing only the camera ID in a shared configuration file selects the right devices but leaves the names identical — that is the half-fix this guide exists to finish.
What you need
- The serial number of every camera, read off its label or listed by the vendor's SDK enumeration tool; selecting each depth camera by serial number walks that step in detail.
- The wrapper package, with the parameter that selects the device.
- A launch file you control — ROS 1 XML or ROS 2 Python — rather than the single-camera default that every instance would share.
- Two ROS commands for the checks: the topic list and the node list.
The same pattern covers 2D and depth devices alike. Most color rigs use area scan cameras on GigE, where each camera also needs a unique IP on a shared switch; two identical cameras on one PC add a second trap of duplicate names and addresses, covered in fixing two identical GigE cameras on one PC. The MV-CU020-90GC is a 2.5 MP GigE color camera with a global shutter and a 1/1.7" sensor in a C-mount.
- Sensor format
- 1/1.7"
- Lens mount
- C-mount
- Interface
- GigE
- Resolution
- Up to 3 MP
Depth cameras follow the identical scheme. The 3D sensors range spans laser profilers, stereo and ToF devices; the MV-DT01WMU is a ToF sensing camera, and it enters this pattern like any other device: one serial, one namespace, one set of prefixed topics.
- Type
- Depth / ToF camera
Worked example: two depth cameras, serials 20530512 and 20530519, one host
Inputs and their source: serials 20530512 (camera A) and 20530519 (camera B) are illustrative device serials of the kind printed on each label; namespaces cam0 and cam1 are chosen here, and any distinct pair works; the node name camera_wrapper repeats in both instances, which is safe once the namespaces differ; and the wrapper publishes three relative topics — image_raw, camera_info and depth_image — an illustrative set, so count what yours really publishes. Bench note: ToF cameras emit active infrared light, so point the window away from eye level while you test.
The rule in words: a wrapper running in namespace N that publishes a relative topic R ends up on /N/R, and the same concatenation names its node. The device serial never enters either string.
The arithmetic, by concatenation:
- Instance A: /cam0/image_raw, /cam0/camera_info, /cam0/depth_image
- Instance B: /cam1/image_raw, /cam1/camera_info, /cam1/depth_image
- Shared names: |{cam0 set} ∩ {cam1 set}| = 0
- Total distinct topics: 3 + 3 = 6
| Instance | Serial | Namespace | Node path | Published topics |
|---|---|---|---|---|
| A | 20530512 | cam0 | /cam0/camera_wrapper | /cam0/image_raw, /cam0/camera_info, /cam0/depth_image |
| B | 20530519 | cam1 | /cam1/camera_wrapper | /cam1/image_raw, /cam1/camera_info, /cam1/depth_image |
Result: the topic list shows exactly those six names, the node list shows the two node paths above, and no un-prefixed /image_raw appears. ROS 1 tools return the same six names the same way.
Decision: the pass threshold is a topic-name intersection of zero. An un-prefixed /image_raw, or a single /depth_image fed by both wrappers, means one instance launched without its namespace, and every subscriber is already receiving a mix of both cameras — fix the launch before recording or calibrating. If camera A stops the moment B starts, you have duplicate node names in one namespace instead.
The proof is one command each: filter the topic list for cam0 and cam1, confirm the two sets are disjoint, then cover camera A's lens and watch /cam0/depth_image fall silent while /cam1/depth_image keeps its rate.
Launch file, step by step
- Enumerate the devices on the host with the vendor's device-list tool or the SDK's enumeration call. Write each serial against its physical position — left camera, right camera — and label the cameras to match.
- Select by serial, never by index: an index is enumeration order, so treat it as a bench shortcut and re-check it after any hardware change. The serial on the label is fixed and stable across power cycles.
- Turn the single-camera launch into a table-driven one: one entry per camera holding serial, namespace and node name, then loop over the table. Adding a third camera becomes one new line.
- In ROS 2 the loop looks like this:
from launch import LaunchDescription
from launch_ros.actions import Node
# One entry per camera: (serial, namespace).
# Serial values are illustrative; use those printed on your cameras.
CAMERAS = [
("20530512", "cam0"),
("20530519", "cam1"),
]
def generate_launch_description():
nodes = []
for serial, ns in CAMERAS:
nodes.append(Node(
package="camera_wrapper_pkg", # your wrapper's package name
executable="camera_wrapper", # your wrapper's executable name
namespace=ns,
name="camera_wrapper",
output="screen",
parameters=[{
"device_serial": serial, # the wrapper's device-selection parameter
}],
))
return LaunchDescription(nodes)
- In ROS 1 the same structure is a group per camera, with the serial set inside:
<group ns="cam0">
<node pkg="camera_wrapper_pkg" type="camera_wrapper" name="camera_wrapper">
<param name="device_serial" value="20530512"/>
</node>
</group>
- If the wrapper loads a shared YAML configuration, give each instance its own copy with its own serial and namespace values, or override those two entries per instance in the launch. That is what the shared-file question reduces to: changing the ID there is necessary for device selection and insufficient for topic names until each instance also owns a namespace.
- Launch both instances and run the next section's checks.
Wrapper updates rename parameters, and a serial entry that silently stops matching looks exactly like a dead camera. ROS launch parameters renamed between versions shows how to catch that before the bench does.
Verify the split
Run each check on the launched system:
-
ros2 node listreturns /cam0/camera_wrapper and /cam1/camera_wrapper, with no bare /camera_wrapper. -
ros2 topic listfiltered for each namespace returns two disjoint prefixed sets — the six names from the worked example. -
ros2 topic hz /cam0/depth_image, and the same for /cam1, reports the rates you configured. - Cover one lens: its topics fall silent and the other set keeps streaming. That proves the serial-to-namespace mapping, not just the names.
When you add the streams to RViz2, subscribe by the full prefixed name. A topic that appears in the list but not in the display is usually a QoS mismatch rather than a naming problem; the RViz2 QoS mismatch fix covers that case.
Common mistakes
- Same namespace twice. Both instances look healthy, the topic list shows one set of names, and frames interleave invisibly.
- Subscribing by camera ID. The ID never appears in a topic name, so a pipeline pointed at the serial as a folder name waits forever. Subscribe by namespace.
- Remapping one topic instead of namespacing. A remap fixes a single name; camera_info, diagnostics and any topic a wrapper update adds still collide. Set the namespace once and every name follows.
- Index-based selection after a hardware change. Left and right swap after a reboot and every downstream transform inherits the error. Serials remove that failure class.
What the namespaces buy you next
With disjoint topic names, the downstream layers become straightforward. Extrinsic calibration into one shared coordinate frame needs exactly this: per-camera topics the tools can tell apart. For capture timing, exposure synchronization with a shared trigger and a shared timing event for depth cameras build on the same per-instance structure, and planning acquisition and synchronization in a multi-camera launch is the companion piece for the rig as a whole. Process structure follows the same one-per-device rule: a driver crash stays contained when each camera owns its wrapper process, as in one camera per process.
On the hardware side, the camera finder filters by interface, resolution and shutter, and the camera configurator pairs a camera with its lens and light. If the open question is whether the second camera needs depth at all, the 2D or 3D conveyor picking guide works through that decision. For rig layout, bandwidth and host sizing questions, contact the applications team; more launch-and-timing reading sits on the systems topic page.
Questions
Does the camera serial number appear in the ROS topic name?
No. The serial is a device selector: it tells the wrapper which physical camera to open. Topic names come from the namespace and node name, so a wrapper opened with a different serial but the same namespace publishes exactly the same topic names. The serial decides the source; the namespace decides the name.
Why does my first camera stop publishing when the second wrapper starts?
The classic cause is a duplicate node name in one namespace. ROS 1 resolves the conflict by shutting down the older node, so camera A dies the moment wrapper B launches. Give each instance its own namespace, or at minimum a distinct node name, and both keep running.
Can I open the cameras by index 0 and 1 instead of serial numbers?
It works on the bench, but an index is enumeration order: replug a cable, add a hub or reboot and camera '1' can become camera '0'. Serials are printed on the device and stable across power cycles, so treat index-based selection as a quick test only, not for a rig you will repackage or ship.
How do I see which topics each wrapper actually publishes?
Launch both instances, then filter the topic list by each namespace, for cam0 and cam1. The node graph view in rqt shows publishers per topic, the fastest way to spot two wrappers on one name. Physical checks work too: cover one lens and watch which topics fall silent.
How many camera wrappers can one host run?
Measure rather than guess: run one instance at the target frame rate and read its CPU, memory and interface bandwidth from the system monitor, then size the host as camera count times that, plus headroom for your processing nodes. With GigE rigs, switch and network capacity is usually the first limit to check.

