Your camera node streams clean images, yet /camera_info arrives with height and width filled in and every intrinsic — K, R, P, D — at 0.0, so ArUco pose estimation cannot work. This is the classic missing-calibration-file failure: the node never found a YAML at the path it derives from the camera name. Here you will read the exact expected path out of your own log, put the file there, and prove the numbers landed in the message.
Why does the node publish a camera_info full of zeros?
The zeros mean the node never opened a calibration file: it logs using default calibration URL, then Unable to open camera calibration file, and fills every K, R and P entry with 0.0 while images keep streaming normally.
The stream and the intrinsics travel on separate paths. The driver opens the device, starts publishing frames and fills height and width — 480 and 640 in your dump — from the stream itself. Calibration comes from a side file; when that lookup fails, distortion_model stays empty, D stays an empty list, and the nine K entries, nine R entries and twelve P entries all publish as 0.0. aruco_ros needs K for every pose it solves, so zeros give you no poses, not wrong poses.
The rest of that log block is noise. Failed getting value for control 10092545: Permission denied (13) is an optional V4L2 control the device declines to report; the node logs it and moves on.
Decision: if images flow and K is zeros, go straight to the file path — the camera itself is fine. A node that cannot open the device at all is a different failure, covered in Uvcvideo module not loaded: check for a built-in driver.
What file name and path does the node look for?
By default the node looks for a YAML inside ~/.ros/camera_info, named from the driver's device string: your log shows Device: Integrated Camera: Integrated C becoming integrated_camera:_integrated_c.yaml — lower case, spaces replaced by underscores.
Run the node once and copy the name out of its own log instead of reconstructing it. The line that starts with camera calibration URL: file:// ends with the exact file name the node wants — here, integrated_camera:_integrated_c.yaml inside ~/.ros/camera_info. Retyping it as integrated_camera.yaml is the most common way to keep the error alive. On a fresh account the folder does not exist yet, and camera_calibration_parsers then logs Unable to open camera calibration file before the node's not-found warning.
If the device string itself never appears, the node has no camera to name — that is a driver-layer problem, covered in Vision software can't find your camera: the missing driver layer.
Decision: treat the logged URL as the specification. The file must exist at exactly that path and name.
How do you create the folder and put the file in place?
Create the folder once with mkdir -p ~/.ros/camera_info, then save your calibration under the exact logged name — the calibration tool writes its results to /tmp/calibrationdata.tar.gz, and the ost.yaml inside is the file camera_calibration_parsers knows how to read.
- Start the node once and copy the file name from the
camera calibration URL:log line. - Create the directory:
mkdir -p ~/.ros/camera_info. - Unpack the results the calibrator saved:
tar -xzf /tmp/calibrationdata.tar.gz. Inside isost.yaml, the# oST version 5.0 parametersfile with[image]width 640 and height 480 above the[camera]matrix, distortion, rectification and projection blocks. - Copy it under the logged name:
cp ost.yaml ~/.ros/camera_info/integrated_camera:_integrated_c.yaml. - Restart the node and watch the warning disappear.
Ship the file the calibrator wrote rather than one rebuilt by hand; a structurally different YAML gives camera_calibration_parsers nothing to match.
Decision: five minutes of copy-paste fixes the default-lookup case until you change cameras.
How do you point the node at any calibration file you choose?
Skip the naming problem entirely by setting the camera_info_url parameter to a file:// URL of your own — for example file:///home/ros/calibration/front_cam_640x480.yaml — and the node loads that file instead of the default.
ros2 run v4l2_camera v4l2_camera_node --ros-args -p camera_info_url:=file:///home/ros/calibration/front_cam_640x480.yaml
The convention is shared across ROS 2 camera drivers: when camera_info_url is unset, each driver falls back to a camera-named file inside ~/.ros/camera_info, and setting the parameter overrides that default. Name files by station, camera and resolution — front_cam_640x480.yaml says what it belongs to a year later. For the workflow that produces and validates the file itself, see ROS 2 camera calibration: validate and publish camera_info.
Decision: set camera_info_url in every deployed launch file. The default name lookup is a bench-test convenience.
Why does usb_cam fail with the same message?
usb_cam reports Invalid camera calibration URL for ~/.ros/camera_info/camera_info.yaml for the same reason: the folder or that default file does not exist, so the node drops camera_info content and the error repeats.
The reader is shared. Both packages hand the YAML to camera_calibration_parsers, so the ost.yaml you already have works in either node unchanged, and the cure is identical — create ~/.ros/camera_info, copy the file in under the name each node requests, or set that node's own camera_info_url.
Decision: same folder, same format, different default name; fix it once and both drivers find it.
The folder exists and it still fails — what now?
When the folder exists and the warning persists, check three things in order: the file name character for character, the YAML layout, and the declared resolution — in practice the name mismatch causes most repeats.
-
Name off by one character. The warning prints the path the node tried; run
ls ~/.ros/camera_infoand compare it against that line. The colon in integrated_camera:_integrated_c.yaml is the usual casualty. Copy, never retype. -
Wrong file layout. Only the oST version 5.0 structure parses here:
[image]width 640 / height 480 above the[camera]matrix, distortion, rectification and projection blocks. Re-extract /tmp/calibrationdata.tar.gz rather than assembling values into a new file. - Resolution moved. The file declares 640 x 480. If the node now streams a different format, the intrinsics do not transfer — fx, fy, cx and cy are pixel quantities — so calibrate again at the new format.
The node reads the file once at start-up, so edits take effect after a restart, not on the next frame.
Decision: check in that order; a mismatched name causes most repeat failures, and a restart is the cheapest test of any edit.
How do you verify the calibration actually loaded?
Echo the topic and compare the K matrix against your calibration output: this 640 x 480 pinhole calibration must publish K with fx = 674.505816, fy = 675.151213, cx = 346.959614 and cy = 198.317442 — any zero left in K means the file still is not loading.
Run ros2 topic echo /camera_info and read the k block. For the calibration in this failure report, fx is 674.505816 px and fy is 675.151213 px — focal length expressed in pixels, physical focal length divided by pixel pitch, which is the value pose estimation consumes.
Three checks tell you the numbers are trustworthy:
Focal lengths within 0.10% mean square pixels, as the pinhole model expects; past about 1% we re-run the capture with more poses. A principal point 4-9% off-center is normal on a low-cost module — the solver uses the values as published, offset included. The P matrix lists 702.385254 px where K lists 674.505816 because P is the rectified camera matrix: rectification reprojects onto a new ideal image and rescales the focal length by 4.1%. Multiply by K on raw frames, by P on the rectified stream.
Decision: all nine K entries non-zero and matching the file means loaded; any zero sends you back to the name check.
What changes when the camera moves onto industrial hardware?
On the line, keep the same rule and make it deterministic: one YAML per camera-and-lens pair, named after the station rather than the device string, loaded through camera_info_url from your launch file or by your own node.
The calibration belongs to the camera and lens, not to the USB port. A C-mount board camera such as the LS-BCM-0003 — 3.1 MP, 1/1.8" global-shutter sensor, 120 fps over USB3 — holds its geometry once focus is locked, so the YAML saved beside it travels with the unit for its working life.
- Sensor format
- 1/1.8"
- Lens mount
- C-mount
- Interface
- USB3
- Resolution
- 5 MP
An industrial GigE camera never enumerates as a V4L2 device at all: a GenICam-based driver opens it over the network, and your own code loads the same ost.yaml — the file format is independent of how the pixels arrive. The area scan cameras page and the camera finder are the places to start when picking that hardware.
Where the node runs matters less than where the file lives. A vision controller such as the MV-VC2140-128G40-1T — Intel N-series processor, 8 GB memory with a 128 GB SSD, two USB 3 ports and three to four LAN ports — is the class of machine that sits on the line running this node and its consumers.
- LAN ports
- 3-4 LAN ports
- USB 3 ports
- 2 USB 3 ports
- Light outputs
- 4 built-in channels
- Memory
- 8 GB
Two identical UVC cameras share one device string and therefore one default file name: calibrate each unit and set camera_info_url per device. Multiple USB cameras on Linux: stop device path conflicts covers the path collisions that come with the second camera. When an industrial driver refuses a parameter instead, that is a GenICam access problem, not a file-path problem — see GenICam node not existing: diagnose feature access errors.
Keep these habits and the warning stays gone:
- Set camera_info_url explicitly in every launch file; treat the default lookup as bench-only.
- Keep one YAML per camera, lens and resolution, named by station.
- Store files at absolute paths so service accounts find them.
- Restart the node after any edit; the file is read once.
If you are standardizing the station, pick the camera on the board-level cameras page, add a vision controller, start from the build-a-system configurator, or write to us through the contact page. More driver-level fixes sit on the systems topic page.
Questions
Where should calibration files live on a robot or shared machine?
~/.ros is per-user: a node started by a service account or a different login looks in that user's home and finds nothing. Put the YAML in a fixed location such as /opt/station/calib/front_cam_640x480.yaml and pass an absolute file:// URL through camera_info_url, so every account and every start-up method loads the same intrinsics.
Can I rescale a 640 x 480 calibration to another resolution?
Only as an estimate. fx, fy, cx and cy are pixel quantities: doubling the resolution to 1280 x 960 roughly doubles them (fx ≈ 1349 px from 674.505816), and the lens distortion coefficients, given in normalized units, carry over unchanged. Real UVC cameras often change field of view or crop between modes, so treat scaling as a stopgap and calibrate at the resolution you stream.
How many checkerboard poses does a trustworthy calibration need?
Collect 25 to 40 images and spread them across the frame: marker in every corner and center, tilted at several angles, at two or three distances. Watch the calibrator's X, Y, Size and Skew indicators and drive all four high before saving. A fixed-focus module typically converges to a reprojection error around 0.1 to 0.5 px; a much larger value means more poses, not more parameters.
Does changing exposure, gain or focus require recalibrating?
Exposure and gain never move the intrinsics: K, R, P and D describe geometry, not brightness. Focus does. Focal length changes as the lens group moves, so any camera with adjustable focus needs recalibration after every focus change; lock the focus ring once the image is sharp, then calibrate once and keep the file.
Do 2D detectors also need camera_info, or only pose estimation?
Pixel-space 2D detection — bounding boxes, barcodes, blobs — works without intrinsics, which is why streaming looks fine while camera_info sits at zeros. Everything that leaves pixel space needs the file: ArUco and checkerboard pose, PnP, rectification through P, and any measurement converted to millimeters. If your pipeline stays in pixels, the zeros can hide for months; the moment you add pose, they break it.

