You run ros2 run camera_calibration cameracalibrator inside your ROS 2 development container, the terminal prints four deprecated-remap warnings, the window adds a dbind-WARNING about the accessibility bus, and the Calibrate button stays grey. This page is for anyone calibrating a USB, GigE or 3D camera that way on an Ubuntu host. In ten minutes you can tell the three messages apart, rebuild the command with the current --ros-args --remap syntax, add the one bind mount the container is missing, and get the button working. The fix lives in the command line and in the container start script; the chessboard, the driver and the camera stay untouched.
Read the symptom: three messages, only one blocker
A typical GPU dev-container session prints three kinds of output. First, rcl emits a [WARN] for each legacy remap — twice for image:=/my_camera/image_raw and twice for camera:=/my_camera — saying the syntax is deprecated. Second, the tool reports that it waited for service camera/set_camera_info and got OK, with the same OK for left_camera and right_camera. Those lines mean the camera driver is up and reachable.
Third, the GUI prints dbind-WARNING **: Couldn't connect to accessibility bus: Failed to connect to socket /run/user/1000/at-spi/bus_1: No such file or directory. Meanwhile the Calibrate button never activates. Of the three messages, only the last one and the grey button describe a fault. The warnings are noise, and the OK lines confirm the camera link.
The remap warnings are harmless, but write the current syntax
The legacy form puts remaps bare on the command line. Current ROS 2 wants them behind --ros-args, and the warning text itself shows the replacement. Nothing is lost in the meantime: because the tool proceeds to find the set_camera_info services, the legacy remaps clearly took effect. Write it the current way so the output stays clean:
ros2 run camera_calibration cameracalibrator --size 6x8 --square 0.20 --ros-args --remap image:=/my_camera/image_raw --remap camera:=/my_camera
--size and --square stay in front: they are ordinary arguments of the tool itself, not ROS arguments. Only the two topic remaps move behind --ros-args.
If the bars never fill, the problem is detections, not the warnings
The Calibrate button enables only after enough varied views of the board. The tool shows four quality bars, labelled X, Y, Size and Skew, and each must fill. If your remap points at a topic that carries no frames, nothing is ever detected and no amount of board-waving fills the bars.
Test the link before blaming the GUI. ros2 topic list must show /my_camera/image_raw, and ros2 topic hz /my_camera/image_raw must print a rate close to the camera's configured frame rate. If one of the waiting-for-service lines never reaches OK, the driver is not up or the namespace does not match your remap — solve that first, because the GUI cannot reach a camera that publishes no services.
The real blocker: the container cannot see /run/user/1000
On the host, ls -l /run/user/1000/at-spi/bus_1 lists a socket owned by your user. Inside the container, the same command reports No such file or directory. That comparison is the whole diagnosis. Linux creates /run/user/<UID> at desktop login and fills it with session sockets, including the accessibility bus the GUI toolkit wants. A container sees none of it unless the docker run line mounts it, and the standard dev script mounts the workspace and /dev but not /run/user. So the window opens — the X display works — but the accessibility connect fails, dbind-WARNING scrolls past, and the button stays grey even though the camera link is fine.
The fix is one line in scripts/run_dev.sh, directly after the line that mounts /dev:
-v /dev/*:/dev/* \
-v /run/user/1000:/run/user/1000 \
Then exit the container and start it again through the script. Verify from inside the new container: ls -l /run/user/1000/at-spi/bus_1 now lists the socket, and the dbind-WARNING no longer appears. UID 1000 is the usual first account on a single-user Ubuntu desktop such as the 22.04 host in this setup; run id -u and substitute your own number if it differs.
Worked example: 6x8 board, 0.20 m squares, UID 1000 container
Inputs: a checker pattern with 6 × 8 internal corners; a square edge of 0.20 m measured on the physical board; image topic /my_camera/image_raw and camera namespace /my_camera; a host user with UID 1000 whose /run/user/1000/at-spi/bus_1 socket exists on the host but not in the container; and a start script scripts/run_dev.sh that already passes --privileged and --network host.
The rule in words: WARN-level remap messages never stop the tool, and a socket present on the host but absent in the container means a missing bind mount — a bind mount maps one host directory into the container with identical content.
One input deserves arithmetic: the square size, because it sets the metric scale of everything you calibrate.
Declared 0.20 m against a true edge of 0.19 m: 0.20 / 0.19 - 1 = 0.053, so the metric scale of the calibration would be about 5% too large, and every length derived from it inherits that 5%. The tool takes --square 0.20 in metres, so one edge is 0.20 m = 200 mm.
The four steps: rebuild the command with both remaps behind --ros-args; add -v /run/user/1000:/run/user/1000 \ after -v /dev/*:/dev/* \ in scripts/run_dev.sh; restart the container and confirm the socket is listed inside it; then move the board through all four corners, tilt it in both directions and capture from two distances until X, Y, Size and Skew all fill.
Result: the dbind-WARNING disappears, the bars fill, Calibrate activates, and the finished calibration is written back through the driver's set_camera_info service.
Decision: if the path exists on the host but not in the container, add the mount — that is the blocker. If the mount is in place and the bars still stay empty, the fault is detections, not the container; go back to ros2 topic list and your remap names.
After the button works: the result lands in camera_info
When calibration completes, the tool writes the intrinsics through set_camera_info, and a well-behaved driver starts publishing camera_info beside the image. Check the numbers and get the file published reliably — the guide on validating and publishing camera_info in ROS 2 continues exactly there, and if the parameter never shows up afterwards, the camera_info-not-published fix covers the usual path and name mistakes.
Calibrating a shop camera instead of a laptop webcam
Nothing in the Docker fix is webcam-specific: the same command calibrates any area scan camera whose ROS 2 driver publishes an image topic. Shutter type matters more than resolution on a calibration bench, because you translate and tilt a moving board; MV-CU020-90GC is a 2.5 MP GigE model with a global shutter.
- Sensor format
- 1/1.7"
- Lens mount
- C-mount
- Interface
- GigE
- Resolution
- Up to 3 MP
When the calibration also has to serve measurement, put more pixels on the board: LS-CMR-0166 captures 4000 × 3000 at 30 fps over USB3, and LS-CMR-0053 runs the same 12 MP IMX226 sensor over GigE at 9 fps.
- Sensor format
- 1/1.7"
- Lens mount
- C-mount
- Sensor format
- 1/1.7"
- Interface
- USB3
Stereo and structured-light 3D sensors, by contrast, arrive factory-calibrated; you run this chessboard workflow for 2D cameras and after lens changes.
Prevent it: start every GUI container with the mounts it needs
Put the runtime-directory mount into the template you use for every vision container, alongside the workspace, /dev and localtime mounts, and keep --network host so GigE discovery works from inside the container. Then cameracalibrator, rqt and other GUI tools start without accessibility errors — the same pattern that resolves camera discovery and streaming inside a Docker container and 3D camera control software that fails at startup in Docker. A USB3 camera that shows a dark image in ROS tools is the bandwidth cousin of the same lesson — check what the container is actually given — and live preview in GUI applications rides on this same socket set. For another worked GUI case, the walkthrough of GUI tools such as rqt_graph that fail inside ROS Docker containers follows the same mount-and-environment pattern.
Quick reference: every message and its action
| Output | Meaning | Action |
|---|---|---|
rcl [WARN] per legacy remap |
deprecated syntax; remap still applied | move each remap behind --ros-args
|
Waiting for service camera/set_camera_info then OK
|
driver services reachable | none; if no OK, fix driver or namespace |
dbind-WARNING naming /run/user/1000/at-spi/bus_1
|
accessibility socket not mounted | add -v /run/user/1000:/run/user/1000
|
| Calibrate stays grey | bars unfilled, or no frames on the topic | check ros2 topic list, then fill X, Y, Size and Skew |
If you are picking the camera this calibration will serve, the camera finder filters by interface, shutter and resolution, or start the camera configurator to spec the rest of the chain with us. More fixes of this kind sit on the systems topic page.
Questions
Do the deprecated remap warnings stop cameracalibrator from working?
No. They are WARN-level notices: the tool parses the legacy remaps, applies them, and goes on to find the camera's set_camera_info service. You can calibrate with them on screen. Cleaning up costs one edit — move both remaps behind --ros-args — and keeps the output readable for everything else you run in that terminal.
Why does a single camera produce left_camera and right_camera service lines?
cameracalibrator always waits on the camera, left_camera and right_camera set_camera_info service names, because the same tool calibrates stereo pairs. For one camera only the camera line matters; the other two print OK and are ignored. To calibrate a stereo pair you remap the left and right names instead of the plain camera name.
What does --square 0.20 mean, and why does its accuracy matter?
It is the edge length of one checker square in metres, so 0.20 means 200 mm. Corner detection itself does not need the true size, but every metric number the calibration produces scales with it: declare 0.20 for a board whose true edge is 0.19 m and the focal length inherits a 5% error. Measure the physical board, not the artwork.
How many chessboard views does the Calibrate button need?
There is no fixed count. Four indicators — X, Y, Size and Skew — must each fill, and they fill on variety: the board in every corner, tilted in different directions, captured from at least two distances. In practice two to three dozen well-spread views fill the bars; a hundred near-identical head-on frames never will.
Does the same fix apply to stereo and structured-light 3D cameras?
The container mount is identical: any GUI tool that touches the accessibility bus needs /run/user mounted. But you rarely run this tool for depth cameras — stereo and structured-light 3D sensors arrive factory-calibrated, and their depth quality is checked against the documented working-distance window, not a chessboard. Reserve this workflow for 2D area scan cameras and re-calibration after lens changes.
Why does the socket exist on my host but not inside the container?
/run/user/1000 is tmpfs that Linux populates when your desktop session logs in, and it holds session sockets such as the accessibility bus. Containers share none of it unless the docker run line bind-mounts the directory explicitly. Copying the socket file does nothing either: the directory mount must exist at container start.


