A stereo depth node that compiles cleanly and then dies at launch is not a build problem. In the great majority of cases the loader cannot resolve one shared library from the GPU stack, and the console log names the exact file. This page is for anyone bringing up a GPU-accelerated stereo camera node on ROS 2: it shows you how to trace the unresolved dependency with ldd, decide whether the missing file belongs to the GPU driver or the CUDA toolkit, install the right package at the right version, and prove the fix before you rebuild the workspace. The diagnosis is one command and one ownership decision, and most bring-ups that stall for days at this point clear in under an hour.
What the failure looks like
The build finishes. The launch file starts, prints a loader error and exits before the node opens the camera. ros2 topic list shows only your system's own topics, so rviz2 and rqt have no image, depth or point cloud topic to display. The camera's status LED can be lit through all of this: a loader failure kills the process before it ever touches USB.
The line that decides everything looks like this:
error while loading shared libraries: libnvcuvid.so.1: cannot open shared object file: No such file or directory
If a message in that shape appears before any ROS log output, you have a dependency problem. If the node starts and logs normally but publishes nothing, go straight to the last section: the GPU stack is probably fine and the topic side needs the attention.
Likely causes, most probable first
- A driver runtime library is missing. libnvcuvid.so.1 — the decode library GPU-accelerated depth pipelines request at startup — ships with the GPU driver, not with the toolkit. A host with the toolkit installed but the driver missing or half-installed fails exactly here. Check this first; it is the most common cause.
- The toolkit major does not match the binary. The node was linked against one CUDA major and the host carries another. Libraries are allowed to move or disappear between majors: in a widely reported stereo wrapper case, libnppicom.so was missing under CUDA 11 on a node built for the 10.2 generation.
- The file is installed but invisible to the loader. The library sits on disk, but the loader's cache was never refreshed or the directory was never registered, so every process still sees it as absent.
- Your test shell lies. You exported the runtime path in your interactive terminal and the sample ran; the launch file, started from a clean shell or a service, inherits none of it and dies. The fix has to be system-wide, not per terminal.
Read the loader's error line first
The cheapest test costs nothing. Scroll the launch console back to the first error, not the last: the loader speaks before ROS does, and its message names the file it could not find. That name is the diagnosis; everything that follows is only deciding who owns the file.
If the console has scrolled away, start the node executable or the component container directly from a terminal, so the loader's message lands in front of you instead of inside the launch log.
Trace every dependency with ldd
ldd prints every shared library a binary or component requests and where each one resolved. Run it on the built component inside your workspace — replace <camera_package> and <component> with your wrapper's package and component names:
ldd ./install/<camera_package>/lib/<camera_package>/lib<component>.so | grep "not found"
A failing trace names the losers, and the names tell you the owner class:
libnvcuvid.so.1 => not found
libnppicom.so => not found
Both are real names from documented stereo camera failures: the first belongs to the GPU driver, the second to the CUDA toolkit. An empty result with the same symptom means the failing object is another file in the launch chain — point ldd at the executable the launch file actually starts. For the general, non-GPU walkthrough of the same tool, the Linux vision shared library errors guide covers the mechanics in depth.
Decide which package owns the missing file
| The loader asks for | The file belongs to | The decision |
|---|---|---|
| libnvcuvid.so.1 | the GPU driver | repair the driver install; the toolkit cannot supply it |
| libnppicom.so and other toolkit-side names | the CUDA toolkit | install the toolkit major the binary was linked against |
| a name that already exists on disk | the loader search path | register the directory and refresh the loader cache |
The suffix tells you the version the binary wants. Driver-side names keep a stable .so.1 suffix across driver generations, while toolkit-side names carry their version so the loader can tell a 10.2-era file from an 11-era one. Read the suffix in the error and install that major: the equality test "installed major = requested major" is the whole fix.
Fix the owner package
Fix A — the driver-side file
libnvcuvid.so.1 arrives with the GPU driver. Reinstall or complete the driver for the card in the host, refresh the loader cache, and re-run ldd until the line resolves to a real path.
Fix B — the toolkit-side file, at the major the binary wants
Match the toolkit major to the SDK the camera vendor built on; never assume a newer toolkit replaces an older one, because majors are allowed to drop libraries, and libnppicom.so is the name that vanished in exactly this way. The working combination in the documented case was CUDA 10.2 with the SDK version built for it: the package then built and ran with its main functions intact — image, depth, point cloud, positional tracking, TF and dynamic parameters.
If one host serves two projects pinned to different majors, the toolkits install side by side; point each build at its own and let the loader find the suffixes it is asked for. When a library upgrade breaks your build rather than your runtime, the camera SDK build errors after an upgrade guide is the matching build-side fix.
Fix C — the file exists; the loader is not looking
Register the library directory and refresh the loader's cache as root:
sudo ldconfig
Then re-run ldd. If the library only resolved because you exported a path in your interactive shell, move it into the loader configuration instead: a node started by a launch file from a clean shell inherits none of your terminal's exports.
Worked example: the 10.2-era binary on an 11-era host
This is the documented trace, decision by decision.
The binary was linked against a 10.2-era SDK; the host carried an 11-era toolkit. First failure under launch: libnvcuvid.so.1 not found. That is a driver-side name, so round one is the driver — install it completely and refresh the cache, and the name resolves. Second failure: the build completed but the runtime still died, and ldd reported libnppicom.so not found. That is a toolkit-side name from the 10.2 generation, gone under 11. Run the equality check: requested major 10.2, installed major 11 — unequal, so install CUDA 10.2 with the matching SDK. Re-run ldd: the not-found list is empty.
Verification then proves it end to end: the SDK's own depth sample opens and shows live depth; a Release rebuild compiles with the warning storm gone; launch brings the node up; ros2 topic list now lists the image, depth and point cloud topics, and rviz2 displays them. One equality per failure, one owner per file — that is the entire method.
Verify in this order
- SDK sample before ROS. Run the depth viewer that ships with the camera SDK. If it shows depth, the driver and toolkit pair is sound and everything left is ROS-side.
-
Pull the workspace dependencies. Run
rosdep install --from-paths src --ignore-src -r -yso the package dependencies resolve before the first build. -
Build in Release. Build with
colcon build --symlink-install --cmake-args=-DCMAKE_BUILD_TYPE=Release. The many warnings that appear without an explicit build type disappear in Release mode — the outcome on this exact wrapper — and the optimized binary is the one you want on the bench anyway. -
Launch, then list. Start the camera's launch file and run
ros2 topic list; the image, depth and point cloud topics should now exist. - View. Open rviz2 or rqt and add the image, depth and point cloud displays.
When it is not the loader
The loader message is absent and the node runs. Work down this list:
- Status LED never lights. Power and USB before software; no node can fix an unpowered camera.
- The node starts and logs normally but no camera topics appear. The publishing side is the suspect; the depth camera stuck at startup guide walks that branch.
- RGB flows but depth never arrives. On stereo cameras that is often a sensor-enable configuration issue; see stereo camera works for RGB but no depth.
- Topics exist and rviz2 stays blank. A quality-of-service mismatch between publisher and subscriber; the RViz2 image topic QoS fix covers it.
- Depth runs but the point cloud edges scatter into space. That is the flying-pixels artifact at depth discontinuities; see flying pixels in stereo depth edges.
- Depth publishes but wobbles, or the pose drifts across a shift. Mounting, vibration and IMU behavior take over from here; the stereo depth jitter and IMU drift troubleshooting guide covers that failure class.
Prevent it on the next host
- Run the SDK depth sample before building anything on a new machine; it proves the driver and toolkit pair in half a minute.
- Record the driver and toolkit pair with the workstation image, and take the toolkit major from the camera SDK's requirements rather than from whichever version installs newest. A driver newer than the toolkit's minimum serves older toolkits; the reverse does not hold.
- After any driver or toolkit change, re-run ldd on the component library — one command, before the node can surprise you on the cell.
- Build Release for bench and production alike; Debug-mode warning storms are toolchain noise, and the optimized binary is what you deploy.
- If you are specifying hardware for a new station, pick the rig from the 3D sensors category and the host from the vision controllers category, then pin this bring-up order into the station's commissioning checklist.
More bring-up guides for stereo and depth rigs sit on the 3D vision topic page. If you want a camera-and-host pair checked before you order, contact the team with the SDK's requirement sheet — the mismatch that stalls a bring-up is visible on paper.
Questions
How do I run two CUDA toolkit majors on one host?
Install both; each major lives in its own versioned directory, and a single default symlink points at whichever one you promote. Point each project's build at its own toolkit and keep the loader free to resolve the suffixes each binary requests. After switching the promoted default, re-run ldd on your component library before deploying: the loader follows the suffixes, not your intentions.
Why does a stereo node build cleanly but still fail to start?
A build only resolves the headers and linker stubs shipped with the toolkit; the loader resolves the real runtime libraries when the process starts. A clean build proves the toolchain, not the machine: the same workspace builds fine on a host whose driver was never completed. That is why the loader message, not the compiler, is the evidence that matters.
Does the CUDA toolkit installation include the GPU driver?
Treat them as separate packages. The toolkit brings the compiler and the libraries with versioned names; the driver brings the kernel-side module and runtime files such as libnvcuvid.so.1. Installing only the toolkit leaves the driver-side names unresolved, which is exactly the failure this page describes. Install the driver first, then the toolkit major your SDK requires.
Do I need to rebuild the workspace after installing the correct toolkit?
Usually no. If the build succeeded and the names the loader asks for match the toolkit you just installed, the fix is runtime-side and the existing binary runs as is. Rebuild when you change toolkit major on purpose, when you switch branch or SDK version, or when ldd still reports suffixes that differ from what you installed.
Why did the node work yesterday and fail after an update?
An update replaced one side of the pair. A toolkit upgrade changes which suffixes exist; a driver upgrade can replace runtime files; an operating system update can change registered loader paths. Re-run ldd on the component library after any update and compare its not-found list with the pair you recorded: the newly missing name tells you which side moved.