Perception: Cameras, Lidar and IMUs

The pinhole camera model, stereo depth and its error, lidar, IMUs, calibration, fiducial markers and data volumes.

The sensors compared

SensorMeasuresStrengthsLimits
Wheel encodersWheel rotationCheap, precise in the short term (see Mobile Robot Kinematics and Odometry)Drift with slip and wheel errors
IMU (gyroscope, accelerometer, magnetometer)Rotation rate, acceleration, field directionFast (100–1000 Hz), works in the darkBias drift; the magnetometer is disturbed by motors and steel
Camera (mono)Light: color and textureRich, cheap, high resolutionNo direct depth; sensitive to lighting
Stereo / depth cameraDepth for each pixelDense depth and colorShort range; error grows with distance squared; fails in sunlight (active IR)
2D lidarRange in a planeAccurate, long range, works in the darkOne plane only; glass and black surfaces confuse it
3D lidarA 3D point cloudLong range, accurate 3D structureCost, power, data volume
Ultrasonic and ToFRange along a beamCheap obstacle detectionWide beams, soft surfaces, crosstalk
RadarRange and speedAll weather, long rangeCoarse angular resolution
GNSS (GPS)Global positionAbsolute position outdoorsMeters of error (centimeters with RTK); no use indoors
Force, torque and tactileContact forcesSafe interaction, assemblyNeeds calibration and drift compensation (see Sensors and Field Devices)

The pinhole camera model

The pinhole camera modelsensorpinhole (lens center)object Hdistance Zfh = f H ÷ ZExample: f = 4 mm, sensor 6.4 mm widehorizontal field of view = 2 atan(w ÷ 2f)= 2 atan(6.4 ÷ 8) = 77.3°
Light from a point passes through the pinhole and lands on the sensor: the image is inverted and scaled by f ÷ Z.

A camera maps a 3D point (X, Y, Z) in the camera frame to a pixel (u, v) by perspective projection:

u = fx X ÷ Z + cx    v = fy Y ÷ Z + cy    (f in pixels, (cx, cy) the image center)

Worked example: focal length, field of view and size

  • Focal length in pixels. A 4 mm lens on a sensor with 3 µm pixels has f = 4 ÷ 0.003 = 1 333 pixels.
  • Field of view. For a 1 920 pixel wide image: 2 atan(1 920 ÷ (2 × 1 333)) = 71.5° (the same as using the sensor width of 5.76 mm: 71.5°). A shorter focal length gives a wider view and smaller objects.
  • Size in the image. A 0.30 m object at 2.0 m appears 200.00 pixels tall: h = f H ÷ Z. Inversely, an object known to be 0.15 m high that appears 50 pixels tall is at Z = f H ÷ h = 1 333 × 0.15 ÷ 50 = 4.00 m.
  • Resolution on the scene (ground sample distance) = Z ÷ f = 2000.0 mm ÷ 1 333 = 1.50 mm per pixel at 2.0 m: the smallest feature you can resolve is a few times that.
  • From a pixel back to a 3D point when the depth is known (a depth camera): X = (u − cx) Z ÷ f = (1 260 − 960) × 2.5 ÷ 1 333 = 0.563 m, Y = (400 − 540) × 2.5 ÷ 1 333 = -0.263 m, Z = 2.5 m.
  • Exposure and motion blur: a robot moving at 1.0 m/s past an object 2.0 m away sweeps 667 pixels per second across the image: a 10 ms exposure blurs it by 6.7 pixels, a 1 ms exposure by 0.7. Use a short exposure (and enough light) on a moving robot; a global shutter avoids the skew that a rolling shutter gives to fast motion.
  • Frame rate and latency: at 30 fps a frame takes 33 ms to capture before it is processed (see Robot Software Architecture for the latency budget).
  • Lighting matters more than resolution: even, bright, diffuse light, and no direct sun in the lens.

Camera calibration and distortion

A real lens bends straight lines (distortion), and its focal length and center are not exactly as specified. Calibration finds the intrinsic matrix K (fx, fy, cx, cy) and the distortion coefficients from several photographs of a checkerboard or circle grid, with OpenCV or the ROS camera_calibration package. The result is saved with the camera and used to undistort every image.

import cv2, numpy as np, glob
pattern = (9, 6)                                              # inner corners of the checkerboard
objp = np.zeros((pattern[0] * pattern[1], 3), np.float32)
objp[:, :2] = np.mgrid[0:pattern[0], 0:pattern[1]].T.reshape(-1, 2) * 0.025     # square size 25 mm
obj, img = [], []
for name in glob.glob("calib/*.png"):
    gray = cv2.imread(name, cv2.IMREAD_GRAYSCALE)
    ok, corners = cv2.findChessboardCorners(gray, pattern)
    if ok:
        obj.append(objp); img.append(cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1),
                   (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)))
rms, K, dist, _, _ = cv2.calibrateCamera(obj, img, gray.shape[::-1], None, None)
print(rms)                                                    # reprojection error in pixels: under 0.5 is good
  • Take 20 or more images with the board at many positions, angles and distances, filling the frame and the corners.
  • The reprojection error (how far the projected board corners land from the detected ones) should be well under a pixel; 0.3 px is good.
  • Recalibrate after any change of focus, zoom or lens; fix the focus mechanically.
  • Extrinsic calibration finds where the camera sits on the robot (a transform for the URDF); see below.

Stereo and depth cameras

Two cameras a baseline B apart see a point at slightly different positions; the difference is the disparity d (pixels), and the depth follows from similar triangles: Z = f B ÷ d.

Stereo depth error grows with distance squared02468101200.10.20.30.40.5distance Z (m)depth error σ (m)2 m: 1.2 cm5 m: 7.4 cm10 m: 29.8 cmσZ = Z² ÷ (f B) × σd. Doubling the baseline or the focal length halves the error.
Stereo depth error grows with the square of the distance.

Worked example: a stereo camera

  • With f = 700 px, B = 0.12 m and a disparity of 42 px, Z = 700 × 0.12 ÷ 42 = 2.00 m.
  • Error. A disparity error of 0.25 px changes the depth by σZ = Z² ÷ (f B) × σd = 2.0² ÷ (700 × 0.12) × 0.25 = 1.2 cm at 2.0 m, but at 10 m (disparity only 8.4 px) it is 29.8 cm: the error grows with the square of the distance.
  • Minimum range. If the matcher searches at most 128 px of disparity the nearest measurable point is f B ÷ dmax = 0.66 m.
  • To improve far accuracy widen the baseline or use a longer focal length (both double the accuracy when doubled), at the cost of the near range and field of view.
Depth sensorHow it worksRangeNotes
Passive stereoMatches texture between two cameras0.3–20 m, depending on baselineNeeds texture; works outdoors; computing for matching (often on the camera)
Active stereo (IR projector)Adds a projected pattern to give texture0.2–10 mRealSense D400 series: works on blank walls; sunlight washes out the pattern outdoors
Time of flight (ToF)Measures the delay of modulated infrared light0.2–8 mSmall, dense depth; multipath and sunlight errors
Structured lightProjects a known pattern, triangulates0.3–3 mHigh precision at close range: scanning and picking

Lidar

A lidar measures range by the time of flight of a laser pulse, while a mirror or an array sweeps the beam. A 2D lidar (RPLidar, Hokuyo, Sick) scans a plane and is the standard sensor for indoor mobile robots; a 3D lidar (Ouster, Velodyne, Livox) gives a point cloud.

Worked example: scan resolution

  • A 2D lidar with an angular resolution of 0.5° returns 720 points per rotation; at 10 rotations a second that is 7 200 points/s. The spacing between neighboring points grows with the range (r × Δθ):
RangePoint spacing
1 m0.9 cm
5 m4.4 cm
10 m8.7 cm
20 m17.5 cm

A thin object (a chair leg of 3 cm) is hit by about three points at 3 m but may be missed entirely at 10 m, where the spacing is 8.7 cm.

  • A 3D lidar with 64 channels and 1 024 points per revolution gives 65 536 points per scan; at 10 Hz that is 655 thousand points/s, and at 16 bytes a point 1.0 MB per scan, 10.5 MB/s: heavy for a network (see ROS 2 in Practice).
  • Range and reflectivity: the maximum range is given for a white target; a dark one (10 % reflective) is seen at a fraction of it.
  • Problem surfaces: glass and mirrors (the beam passes or reflects away), black matt, rain, fog and dust; sunlight at some wavelengths.
  • Motion distortion: a scan takes 100 ms, during which the robot moves; at 1 m/s that is 10 cm: de-skew the scan using odometry.
  • Mounting height and tilt: a 2D lidar sees only one plane; mount it where obstacles are, and add a second sensor for tables and overhangs.
  • Eye safety: look for class 1 laser products; never look into an unknown beam.

IMUs: gyroscopes and accelerometers

Worked example: why integrating an IMU fails

  • Gyroscope. Integrating rotation rate gives the angle. A noise density of 0.01 °/s/√Hz produces an angle random walk of 0.01 × √100 = 0.10° after 100 s (small), but a constant bias of 0.1 °/s accumulates 0.1 × 60 = 6° in a minute.
  • Accelerometer. Integrating acceleration twice gives position, and a bias of 0.01 m/s² (1 mg) grows as ½ b t²: after 100 s the error is ½ × 0.01 × 100² = 50 m. An IMU alone cannot give position for more than a few seconds.
  • What an IMU is good for: short-term rotation (heading changes between wheel or lidar updates), the direction of gravity (roll and pitch from the accelerometer when still), vibration, and detecting motion. Fuse it with other sensors (see Localization, Mapping and Navigation and Mobile Robot Kinematics and Odometry).
  • Calibrate at rest: average the gyroscope while the robot is still to find its bias; recalibrate with temperature.
  • The magnetometer gives an absolute heading outdoors but is distorted by motors, batteries and steel: calibrate it in place and distrust it indoors.
  • Mount rigidly and away from vibration, and note its axes in the URDF (REP 145 gives the IMU conventions).
  • Sample fast and time-stamp precisely: an IMU at 200 Hz or more, with the stamp from the sample time, not the arrival time.

Fiducial markers

AprilTag and ArUco markers are printed black-and-white squares that a camera detects and identifies with a single, reliable pose estimate: ideal for docking stations, calibration targets, localization landmarks and ground-truth in experiments (apriltag_ros, OpenCV aruco).

Worked example: how far can a tag be read?

A tag needs about 20 pixels across its width to be detected reliably. A 10 cm tag with the camera above (f = 1 333 px) is that size at Z = f s ÷ 20 = 1 333 × 0.10 ÷ 20 = 6.7 m; at 2 m it is 67 px wide. To read a tag from 20 m you need a tag three times larger or a longer lens. Pose accuracy is best face-on and close, and worst at glancing angles, where the tag’s orientation is ambiguous.

Vision and point-cloud pipelines

import cv2, numpy as np
frame = cv2.imread("scene.png")
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv, (35, 80, 60), (85, 255, 255))               # a colour threshold: green objects
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, np.ones((5, 5), np.uint8))     # remove noise
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
best = max(contours, key=cv2.contourArea)
x, y, w, h = cv2.boundingRect(best)                                 # the target in pixels
TaskTypical methodNotes
Find a colored or high-contrast objectColor thresholds, edges, contours (OpenCV)Fast and simple; depends on lighting
Find known objectsNeural detectors (YOLO family), run on a GPU or acceleratorNeeds training data; see Machine Learning
Track motionOptical flow, Kalman trackingNeeds a steady frame rate and time stamps
Estimate camera motionVisual odometry and visual-inertial odometry (ORB-SLAM, VINS)Features and texture needed; scale from stereo or IMU
Clean a point cloudVoxel downsampling, ground-plane removal, outlier filters (PCL, Open3D)Reduces the data by an order of magnitude
Match two scansICP (iterative closest point), NDTNeeds a decent starting guess
Segment and cluster objectsEuclidean clustering, RANSAC planesFast and robust for tabletop scenes

Worked example: shrinking a point cloud

A voxel filter keeps one point per cube of side 0.05 m. A room of 10 × 8 × 3 m holds at most 1 920 000 voxels of that size, but real surfaces occupy only a thin layer: a 600 000-point cloud typically falls to 50 000 to 120 000 points, a reduction of about 5 to 10 times, with little loss of the shape. Always downsample before heavy processing.

  • Keep the pipeline in the sensor’s frame, with time stamps, and convert to the robot’s frame with tf2 (see ROS 2 in Practice).
  • Test on recorded data: replay a bag so that a change is judged on the same images every time.
  • Budget the compute: a detector that runs at 5 fps on a CPU is useless at 1 m/s (see Robot Software Architecture).
  • Plan for failure: glare, an empty scene, a covered lens: report “no detection” and let the behavior layer decide.

Time synchronization and extrinsic calibration

  • Time-stamp at the source, when the data were captured (the camera’s hardware trigger, the lidar’s own clock), and use one clock across the robot (see ROS 2 in Practice).
  • Synchronize multiple sensors by a shared trigger or PTP/NTP; a 30 ms offset between a camera and a lidar on a robot turning at 1 rad/s is a 1.7° misalignment (a point 5 m away lands 15 cm out).
  • Extrinsic calibration finds each sensor’s pose relative to the robot (or to each other): camera to lidar by a target seen by both, camera to gripper by hand-eye calibration, IMU to camera by motion (Kalibr). Put the result in the URDF.
  • Recalibrate after any knock or remount: a few millimeters or a degree is enough to ruin a fused result.
  • Use a known target (a checkerboard, a tag, a pole at a measured position) to verify the calibration at the end.

Choosing sensors

Robot taskGood starting sensorsWhy
Indoor navigation2D lidar + wheel encoders + IMU, plus a depth camera for low obstaclesReliable mapping and localization; see Localization, Mapping and Navigation
Outdoor navigationGNSS (RTK) + IMU + 3D lidar or stereo + wheel odometryAbsolute position and structure in sunlight
Picking objectsA depth camera or structured light + force-torque at the wristObject pose and contact
DockingA camera with an AprilTag, plus an IR beacon or bump sensorsA precise, reliable target
Following a line or personA camera and a short-range depth sensorSimple tracking
Safety around peopleCertified safety lidar or area scannerA safety function must use a rated sensor (see Industrial Robotics)

Related

FAQ

Lidar or camera for a mobile robot?

A 2D lidar is the most reliable for mapping and obstacle avoidance indoors; a camera adds color, objects and low obstacles. Many robots use both, with the lidar for navigation and the camera for recognition.

How accurate is stereo depth?

About 1 % of the distance at close range, getting worse with the square of the distance: centimeters at 2 m, decimeters at 10 m for a small baseline. A wider baseline improves it.

Can I get position from an accelerometer?

Not for more than a few seconds: double integration turns a tiny bias into meters of error. Use it for orientation (gravity) and short-term motion, and fuse it with other sensors.

Why does my depth camera fail outdoors?

Sunlight overwhelms the infrared pattern or time-of-flight signal. Use passive stereo, a lidar or a camera with a daylight filter, and shade the sensor.

References

  • Hartley, R. and Zisserman, A., Multiple View Geometry in Computer Vision; Szeliski, R., Computer Vision: Algorithms and Applications (free online).
  • OpenCV documentation (camera calibration, ArUco); the AprilTag papers (Olson 2011; Wang and Olson 2016).
  • Woodman, O. J., “An introduction to inertial navigation”, University of Cambridge Technical Report, 2007; sensor makers’ datasheets (Intel RealSense, Ouster, Hokuyo, Bosch, InvenSense).

Last reviewed: October 2026. Sensor numbers are typical values for the parts described: check the datasheet.