EECE 5554
Robotics Sensing and Navigation

Lecture 13: Sensing with Cameras:
Feature Detection, Stereo

Dr. Xian Li
Assistant Teaching Professor
Department of Electrical and Computer Engineering

Inspiration

The left camera of a pair on our Mustang's roof

What the pair measures: depth for every pixel near to far

2
Simulated scene: the course rig, 1.7 m up, 5 mm lens, two cameras 0.5 m apart

Today

  1. Features: corners, descriptors, matching
  2. RANSAC: fitting when matches are wrong
  3. Two views: epipolar lines and stereo depth
  4. Designing the rig: baseline, sync, ROS 2
3

From four clicks to depth

Lecture 12

You clicked four corners of a parking stall. The DLT turned four point pairs into a homography H.

Today, part 1

The computer finds hundreds of point pairs by itself. Some of them are wrong, and RANSAC copes with that.

Today, part 2

Two cameras that see the same point from two places give its depth, for every pixel, many times a second.
The same idea runs through all of it: find the same point in two images, then use geometry.
4

Features: what makes a good one

Part 1 of 4

A good feature is a patch you can find again, in exactly one place, in the next image.
5

Harris corners

Slide the window by (Δu, Δv). The change in the patch is

E ≈ [Δu   Δv] M [Δu   Δv]T

M = Σ
Ix2IxIy
IxIyIy2
image gradients, summed over the window

R = det M − k (trace M)2,   k ≈ 0.04 to 0.06

The two eigenvalues of M say how much the patch changes in its strongest and weakest directions. A corner needs both to be large.
6
Read more: Harris Corner Detection (OpenCV). See it on a real image

Fast features: FAST and ORB

FAST: is it a corner?

Look at 16 pixels on a circle around p. If 9 in a row are all brighter, or all darker, than p by a threshold, p is a corner. Most pixels fail after 2 or 3 checks, so it is very fast.

ORB: a fingerprint for each corner

Oriented FAST, plus a rotated BRIEF descriptor: 256 yes or no brightness tests around the corner, stored in 32 bytes. ORB finds corners at 8 image sizes, so it copes with scale too.

Comparing fingerprints

The Hamming distance: count the bits that differ (XOR, then count). A modern CPU does that in one instruction.
7
Rosten and Drummond 2006 (FAST); Rublee et al. 2011 (ORB). Read more: ORB (OpenCV)

SIFT, and how they compare

SIFT (Lowe, 2004) finds blobs as peaks of a difference of Gaussians across many blur levels, then describes each with 128 numbers: gradient directions in a 4 × 4 grid of cells, 8 directions each.

MethodFindsDescriptorScale and rotationSpeed
Harriscornersnonerotation onlyfast
FASTcornersnoneneitherfastest
ORBFAST corners at 8 sizes256 bits, Hamming distancebothfast
SIFTblobs across scales128 numbers, Euclidean distanceboth, very robustslower
On a car at 10 to 30 frames a second, start with ORB. Use SIFT when accuracy matters more than speed, as in mapping offline.
8

Matching and the ratio test

Nearest neighbor

For each descriptor in image 1, find the closest one in image 2.

Ratio test (Lowe)

Keep the match only if the best distance is clearly smaller than the second best: d1 < 0.75 d2. On a fence or tiled floor, both are close, so the match is dropped.

Still some wrong

Even after the test, 10 to 50 percent of matches can be wrong. That is the next part.

See ORB matches on real images

features_ransac.py
import cv2 as cv
import numpy as np

img1 = cv.imread('left.png', cv.IMREAD_GRAYSCALE)
img2 = cv.imread('right.png', cv.IMREAD_GRAYSCALE)

orb = cv.ORB_create(nfeatures=2000)
k1, d1 = orb.detectAndCompute(img1, None)
k2, d2 = orb.detectAndCompute(img2, None)

bf = cv.BFMatcher(cv.NORM_HAMMING)
pairs = bf.knnMatch(d1, d2, k=2)          # best two for each
good = [m for m, n in pairs if m.distance < 0.75 * n.distance]

src = np.float32([k1[m.queryIdx].pt for m in good])
dst = np.float32([k2[m.trainIdx].pt for m in good])
H, mask = cv.findHomography(src, dst, cv.RANSAC, 3.0)
print(len(good), 'matches,', int(mask.sum()), 'inliers')
9

RANSAC: fitting when matches are wrong

Part 2 of 4

Ten points on a line (blue), and three wrong ones (red).

Least squares listens to every point equally. A far-off point pulls hardest, because its error is squared.
RANSAC (Fischler and Bolles, 1981) ignores points that do not agree. The line goes through the blue points only.
10
Read more: Random sample consensus, Wikipedia. Another example

The RANSAC loop

1Sample

Pick the fewest points the model needs: 2 for a line, 4 pairs for a homography.

2Fit

Fit the model to just those points. For H, that is the DLT from Lecture 12.

3Count

Count the inliers: points within a threshold of the model, such as 3 px.

4Repeat

Keep the model with the most inliers. At the end, refit it to all of its inliers.
Why it works: one sample with no wrong points is enough. And it does not matter how wrong the wrong points are.
The threshold is the one number you must choose: about 2 to 3 times the pixel noise of your matches.
11

How many tries?

With a share e of wrong matches and s points per sample, a sample is clean with probability (1 − e)s. To get at least one clean sample with probability p:

N = log(1 − p) / log(1 − (1 − e)s)

p = 0.99 here. Real code updates e as it goes: once it finds many inliers, it stops early.
Modelse = 10%30%50%70%
Line2371749
Homography451772567
Fundamental8978117770188
Small samples win. Each extra point in the sample multiplies the tries. That is why solvers that need fewer points matter.
12

Try it: matches and RANSAC

Matches

Fit H

60 matches between two views of a flat scene. Dashed: the true frame of image 1 in image 2. Solid: where your H puts it.

Python demo and notebook

13

Break Time

5 Minutes

Two views and stereo depth

Part 3 of 4

One pixel in one camera is a ray: the point X could be anywhere on it. That is the depth we lost in Lecture 12.

A second camera sees the same point along a second ray. Where the rays cross is X: triangulation.
15

Epipolar geometry

  • X, O, and O′ make a plane: the epipolar plane.
  • Each camera sees the other camera's center at its epipole, e and e′.
  • The plane cuts image 2 in a line, l′. The match x′ must be on it: the epipolar line.
Search one line, not the whole image. Faster, and far fewer wrong matches.

The picture in OpenCV

16

The algebra: E and F

x′T F x = 0

For every right match, in pixels. F x is the epipolar line l′ in the right image.

Essential matrix E = [t]× R

The same rule in normalized coordinates (pixels times K−1). It holds the rotation R and the direction of t. 5 free numbers: the length of t cannot be seen. Found from 5 points (Nistér) inside RANSAC.

Fundamental matrix F = K′−T E K−1

E plus both cameras' intrinsics, for raw pixels. 7 free numbers, rank 2. Found from 8 points (Hartley's normalized algorithm) inside RANSAC.
For our rig we do not estimate R and t from every frame: we calibrate them once (Lecture 14), then rectify.
17
Read more: Szeliski, Computer Vision: Algorithms and Applications, 2nd ed., Section 11.3

Rectification: matches on the same row

What it does

Turns each image a little, in software, as if the two cameras were exactly side by side and parallel. A match now differs only in its column: the disparity d = xL − xR.

Where it lives in ROS 2

In CameraInfo from Lecture 12: r is this turn for each camera, and p is the new projection. The right camera's p holds Tx = −f B, so the baseline travels with every image.
18
Read more: Image rectification, Wikipedia. A rectified pair

Depth from disparity

Similar triangles, as in the pinhole model:

Z = f B / d

Z3 m5 m10 m15 m20 m40 m
d18511156372814

pixels, for f = 1111 px and B = 0.5 m

Near things shift a lot, far things barely move. At 40 m the whole depth hangs on 14 pixels.
19
Read more: Depth Map from Stereo Images (OpenCV). The OpenCV diagram

Finding d for every pixel

Block matching (cv.StereoBM)

Compare a small window at each shift, by the sum of absolute differences (SAD), and keep the cheapest. Fast, and noisy.

Semi-global matching (cv.StereoSGBM)

Hirschmüller, 2005. Add a cost for jumps between neighbors, along 8 or more directions. Slower, much cleaner: the usual choice.

Knobs you will set

window size, disparity range, uniqueness ratio, speckle filter.
20
Read more: Depth Map from Stereo Images (OpenCV). A real disparity map

Try it: disparity by block matching

left image

right image

disparity: near to far; dark = no match

Window

Scene: a textured far wall, a plain painted wall (left), a near box (middle), and a fence (right). Search 0 to 31 px.

Python demo and notebook

21

Where stereo fails

Plain surfaces

White walls, sky, a wet road at night: no texture, so every shift costs the same.

Repeated patterns

Fences, tiles, parking lines: several shifts match equally well.

Occlusion

Beside every near object, a strip of background that only one camera sees.

Shine and glare

Reflections in windows and car paint move with the camera, not with the surface.
Good matchers say "I don't know" instead of guessing: the uniqueness ratio, a texture threshold, and a left to right consistency check mark those pixels invalid.
22

Design the rig: depth error

Part 4 of 4

Matching is off by a fraction of a pixel, δd. Differentiate Z = f B / d:

δZ = Z2 δd / (f B)

Twice as far: four times the error.
Twice the baseline, or twice the focal length: half the error.
23

Picking the baseline

Baseline Bat 3 m5 m10 m15 m20 m40 mClosest depth
0.12 m1.7 cm4.7 cm19 cm42 cm75 cm3.0 m0.5 m
0.30 m0.7 cm1.9 cm7.5 cm17 cm30 cm1.2 m1.3 m
0.50 m0.4 cm1.1 cm4.5 cm10 cm18 cm72 cm2.2 m
0.80 m0.3 cm0.7 cm2.8 cm6.3 cm11 cm45 cm3.5 m
1.20 m0.2 cm0.5 cm1.9 cm4.2 cm7.5 cm30 cm5.2 m

f = 1111 px (5 mm lens), matching error 0.25 px, search of 256 px. Closest depth = f B / 256.

Wider helps far away

Error shrinks with B, everywhere.

Wider hurts up close

The closest depth grows: at 0.8 m, the 3 m garage test is out of range.

Wider is harder

The views differ more, so matching gets harder, and a long bar must not bend.
24

Try it: pick a baseline

Lens

Stereo

Requirement

Python demo and notebook

25

Keeping the pair in sync

Global shutter

Every row at once (Lecture 12). Our Blackfly S cameras have it, so each image is one instant.

One trigger for both

A pulse into both cameras' GPIO input starts both exposures. In ROS 2: spinnaker_synchronized_camera_driver.

What a 10 ms gap costs

Turning at 20°/s, the right camera has turned 0.2° more: about 4 px of false disparity, 16 times the matching error.
26
Our numbers: f = 1111 px, so 0.2° = 0.0035 rad × 1111 ≈ 3.9 px

Stereo in ROS 2: stereo_image_proc

stereo pipeline (ROS 2 Jazzy)
# all of it at once (see its arguments with --show-args):
ros2 launch stereo_image_proc stereo_image_proc.launch.py --show-args

# or node by node, in the shared /stereo namespace:
ros2 run image_proc rectify_node --ros-args \
    -r __ns:=/stereo/left -r image:=image_raw
ros2 run image_proc rectify_node --ros-args \
    -r __ns:=/stereo/right -r image:=image_raw
ros2 run stereo_image_proc disparity_node --ros-args -r __ns:=/stereo \
    -p stereo_algorithm:=1 -p disparity_range:=256 \
    -p correlation_window_size:=9
ros2 run stereo_image_proc point_cloud_node --ros-args -r __ns:=/stereo \
    -r left/image_rect_color:=left/image_rect
  • Inputs: the shared names, /stereo/left/image_raw, camera_info, and the same for right.
  • Outputs: /stereo/disparity and /stereo/points2, a PointCloud2.
  • stereo_algorithm: 0 is block matching, 1 is SGBM.
  • approximate_sync stays off: our pairs share a trigger, so their stamps match exactly.
27

Bonus check-in: M2 design review

Team A: Mount

  • Requirements from M1: range, accuracy, field of view
  • The baseline, picked with the table and the demo
  • CAD version 1: bar length, camera pitch, antenna spot

Team B: Calibration

  • Target size: square ≥ npx × Z / f. For 15 px at 10 m: 13.5 cm squares
  • Test distances 3, 5, 10, 15 m and the error each should show
  • Part 1 done with two webcams

Team C: ROS 2

  • The node graph, with the shared names
  • The sync plan: trigger wiring and the driver
  • A recorded bag run through stereo_image_proc
Bring numbers, not opinions. Every choice should point to a row in today's tables.
28

Readings (free)

29

Three takeaways

1Features turn pixels into matches. Detect, describe, match, and use the ratio test. Some matches will still be wrong.
2RANSAC fits through the wrong ones. Small samples, count the inliers, refit. The tries grow fast with the sample size.
3Stereo depth is Z = f B / d, and its error grows with Z2. Rectify, sync, and choose the baseline from the range you need.
30

Next

Lecture 14: Sensing with Cameras: Calibration, Mosaicing

How to measure K, R, and t for real, and how to stitch images into a map with RTK.

31