◉ COMP90086 / WEEK 08
0 / 8 checkpoints
Computer vision / Interactive workshop

Two views.
A third dimension.

A pixel tells you a direction. A second view lets you recover depth. Discover how camera geometry and careful matching turn two photographs into a 3D measurement.

60–90 min guided pathReal Week 8 imagesWorks offline
Start the workshop ↗
Left Tsukuba view of a desk, lamp and sculpture
01 / Left view
Right Tsukuba view, with objects shifted by depth
02 / Right view
Computed Tsukuba disparity map, with rejected pixels in magenta
03 / Measured disparity

By the end, you should be able to explain each arrow: what calibration estimates, where a correspondence can lie, why stereo images must be rectified, and when disparity can become depth in metres.

01CalibrateEstimate intrinsics and lens distortion.
02Match & rectifyEstimate geometry; align epipolar lines.
03Estimate disparitySearch corresponding image rows.
04ReconstructUse calibrated geometry and scale.
Three datasets, three roles. The checkerboards calibrate one camera; left.jpeg / right.jpeg demonstrate epipolar geometry; Tsukuba and Aloe demonstrate rectified stereo. The checkerboard calibration is not the calibration of the other pairs. Their supplied files contain no metric stereo calibration or depth ground truth.

Live calculated in your browser   Measured precomputed from the supplied images with OpenCV 4.13.0   Illustration controlled geometry, not a measurement of the images.

3D
Geometric intuition

See the cameras in space.

A photograph is a 2D slice of a 3D world. Drag the scene below. Each camera is a pinhole plus an image plane; each pixel is a ray. Depth appears only when two rays from different viewpoints meet.

A stereo rig looking at a desk

Live 3D illustration

This is the physical situation behind Tsukuba: two cameras, a baseline, and objects at different depths. Choose an object. Both cameras send a ray through it. The left and right image points pL and pR are simply where those rays punch the image planes.

Drag to orbit · scroll to zoom · arrows also work

Left camera / left rayRight camera / right ray3D scene point PImage projections

Illustration, not a reconstruction of Tsukuba. Y is up. Image planes are drawn in front of the pinholes (the usual computer-vision convention). OpenCV’s camera frame is X right, Y down, Z forward along the optical axis — the same pinhole, a different axis label.

What to notice. One pixel only names a direction. The lamp and the far shelf can project to nearby pixels if you pick the wrong depth. The second camera’s ray is what pins P down. That is the whole course in one picture: calibration describes each camera; epipolar geometry says where the second projection may lie; matching picks the point; Z = fB/d converts the leftover shift into metres.

1
1 Camera Calibration

Straight lines, curved images.

Calibration estimates the camera’s internal geometry and distortion. It does not require two images to contain identical features; stereo needs sufficient shared, visible scene content.

The pinhole, the image plane, and a known board

Live 3D illustration

Calibration is a 3D fitting problem. Every inner corner is a known point on a rigid plane. The camera is a pinhole a distance f in front of an image plane. Drag the board’s tilt and distance; the projected corners on the gold image plane are what calibrateCamera observes.

Drag to orbit · scroll to zoom

Increasing f pulls the image plane farther from C and magnifies the board. Increasing distance shrinks the image. A single view can trade those two effects; several tilted views cannot, which is why Exercise 1 uses more than one photograph.

Extra bonus: calibrate for your own camera. This page uses OpenCV’s own sample chessboard photos, but the same steps work for any camera:

  1. Get a chessboard pattern and print it.
  2. Put the printed pattern on something flat and rigid.
  3. Take at least 10 photos of it, varying angle and distance.

Where does the distortion formula come from?

Problem

Problem. A pinhole camera maps a 3D ray to a pixel by straight-line projection. A real lens does not: it bends light more the further a ray sits from the optical axis, so straight scene lines land on curved image lines. A correction has to be fitted from photographs alone.

Approach. Model the deviation as a function of radial distance from the principal point, in normalized (unitless) camera coordinates — the same coordinates an ideal pinhole model would predict before any pixel scaling.

  1. Remove focal length and principal point from the observed pixel to get an ideal, undistorted coordinate:

    x=u−cxfx,y=v−cyfyx=\dfrac{u-c_x}{f_x},\qquad y=\dfrac{v-c_y}{f_y}
  2. Distortion is radially symmetric around the principal point, so it can only depend on distance from the centre:

    r2=x2+y2r^2 = x^2+y^2
  3. A smooth, radially symmetric deviation is a polynomial in r2r^2. Reflection symmetry rules out odd powers of rr, so the standard model keeps only even ones, truncated at the sixth:

    xd=x (1+k1r2+k2r4+k3r6)yd=y (1+k1r2+k2r4+k3r6)\begin{aligned} x_d &= x\,(1 + k_1 r^2 + k_2 r^4 + k_3 r^6)\\ y_d &= y\,(1 + k_1 r^2 + k_2 r^4 + k_3 r^6) \end{aligned}

    Real lenses also decentre slightly, adding two tangential terms (p₁, p₂). The live illustration sets them to zero so the k₁-only mapping stays easy to read; OpenCV still fits all five together.

Solving it is the hard direction. This equation distorts an ideal point outward or inward. Undoing it — given a distorted pixel, recovering the ideal one — has no closed form, because r2r^2 on the right is measured in the coordinate you don’t have yet. OpenCV’s undistort routines solve this iteratively, or by building a dense forward map and resampling — never by dividing by the same factor.

What does radial distortion do?

Live illustration
Grid distortion illustration.

Dashed grey: ideal grid. Solid teal: forward-distorted grid. Other coefficients are zero here; using the measured k₁ alone does not reproduce the full camera model.

At k₁ = 0, the two grids coincide.

xd=x (1+k1r2+k2r4+k3r6)yd=y (1+k1r2+k2r4+k3r6)\begin{aligned} x_d &= x\,(1 + k_1 r^2 + k_2 r^4 + k_3 r^6)\\ y_d &= y\,(1 + k_1 r^2 + k_2 r^4 + k_3 r^6) \end{aligned}Forward radial distortion, with r2=x2+y2r^2=x^2+y^2. Tangential terms omitted in this illustration.

x and y are ideal normalized camera coordinates, before distortion: for an ideal pixel, x=u−cxfxx=\dfrac{u-c_x}{f_x} and y=v−cyfyy=\dfrac{v-c_y}{f_y}. The origin is the principal point, which need not be the image centre.

Undistortion reverses this model. Multiplying an observed coordinate by the same radial factor is not an inverse correction. OpenCV creates a mapping between corrected output pixels and samples in the distorted input.

The grid uses a restricted radius so the k₁-only mapping stays monotonic across the slider range. Negative k₁ gives barrel distortion; positive k₁ gives pincushion distortion.

Where do fₓ, fᵧ, cₓ, cᵧ come from?

Problem

Problem. A pinhole camera’s intrinsics — focal length and principal point — are fixed properties of one physical camera, but nothing prints them on the lens. They have to be estimated from images of something whose real-world geometry is already known.

Approach. Photograph a flat checkerboard, whose corner spacing is known exactly, from several angles. Every corner supplies one known 3D point and one observed pixel; fit the camera model that explains all of them at once.

  1. The pinhole model projects a 3D point to a pixel, up to an unknown scale s:

    s[uv1]=K[R∣t][XYZ1]s\begin{bmatrix}u\\v\\1\end{bmatrix}=K\big[R\mid t\big]\begin{bmatrix}X\\Y\\Z\\1\end{bmatrix}

    K holds the intrinsics shared by every photograph; [R∣t] is that photograph’s own board pose, and changes view to view.

  2. For a candidate K, distortion, and every view’s R, t, project each known corner and compare it against where cornerSubPix actually found it. Summarise the disagreement over all N observed corners, in every view, in one number:

    RMS=1N∑i=1N∥pi−p^i∥2\mathrm{RMS}=\sqrt{\dfrac{1}{N}\sum_{i=1}^{N}\lVert p_i-\hat p_i\rVert^2}
  3. calibrateCamera adjusts K, the five distortion coefficients, and every view’s pose together to make that number as small as possible, starting from a closed-form estimate. Nine views converged to the 0.156 px figure below.

Why one photo isn’t enough. A single planar view fixes only a lower-dimensional slice of K — a board at one distance and tilt can trade focal length against board distance and still reproject almost perfectly. Multiple views at different angles are what pins the parameters apart, which is exactly what Exercise 1 asks you to confirm.

Recover K from the actual checkerboards

Measured · 13 images

(i) Set up

Detected and subpixel-refined 7 by 6 inner checkerboard cornersleft01.jpeg · 42 refined inner corners

(ii) Calibration

9 / 13complete grids detected
0.156 pxpooled reprojection RMS
K=[fx0cx0fycy001]K=\begin{bmatrix}f_x&0&c_x\\0&f_y&c_y\\0&0&1\end{bmatrix}Estimated at 640 × 480 pixels. Each view also has its own board-to-camera pose.
ParameterAll nine views
fₓ, fᵧ534.16, 534.25 px
cₓ, cᵧ341.71, 232.05 px
k₁, k₂, k₃−0.29427, 0.12325, 0.01021
p₁, p₂ (tangential)0.001139, −0.000138

OpenCV distortion-vector order is [k₁, k₂, p₁, p₂, k₃]. The 7 × 6 pattern counts inner corners, not squares. Four failed detections are retained in the viewer; a failure alone does not identify its cause.

More information on the functions used above, see: cv2.findChessboardCorners, cv2.cornerSubPix, cv2.drawChessboardCorners (set up), and cv2.calibrateCamera (calibration).

How to judge calibration, beyond one RMS number

Use sharp, complete boards at varied tilts, distances and image locations. Repeated nearly identical views add little information. Reprojection RMS measures agreement with the fitted corner observations; it is not independent evidence of correct intrinsics.

Inspect per-view residuals, their spatial pattern, plausible parameter values, stability across subsets and performance on held-out views. A new view still needs its own fitted pose to evaluate reprojection. Change of resolution, crop, focus, zoom or camera settings can require transformed intrinsics or recalibration.

Detected viewRMS (px)

(iii) UndistortionAfter undistortion

Measured · left12.jpeg

Once K and the distortion coefficients are known, every photo from this camera can be corrected the same way. Edges that the lens bent now come out straight.

Original left12.jpeg photograph, before undistortionBefore — original photograph
left12.jpeg undistorted using the nine-view calibrationAfter — undistorted with the K and distortion coefficients above

More information on the function used here, see: cv2.getOptimalNewCameraMatrix.

Worksheet exercise 1 / 10 min

Can one photo fool the error metric?

Predict: will one-image calibration always have a larger reprojection error? Choose a training view, then compare both camera parameters and the undistortion of the same test photo.

Test image left12 undistorted using calibration from all nine detected viewsAll nine views → test image left12
Same test image undistorted with a one-view calibration

Both outputs use getOptimalNewCameraMatrix with α = 1 and the full 640 × 480 canvas, without cropping. Different new camera matrices change field of view as well as the visible warp. The all-view fit includes left12; this is a sensitivity demonstration, not held-out validation.

QuantityNine viewsSelected single view
What does a low one-view training RMS establish?

From worksheet08_solution.ipynb, Exercise 1 Solution: “The result varies depending on which image you use, but the calibration (and the undistortion result) generally will not be correct. It may fail to compensate for the radial distortion of the lens, or it may assume a wrong focal length so the ‘undistorted’ image will look stretched.”

Reveal the reasoning
A planar view constrains a homography. With unknown intrinsics, pose and distortion, many explanations can fit it. Diverse views share one camera model while changing pose, providing additional constraints. One view may be sufficient under strong prior constraints, but the unconstrained experiment here is unreliable. The single-view and pooled training RMS also use different observation sets, so they are not a fair held-out comparison.
2
2 Epipolar Geometry

The match lies on a line.

A point in the left image defines a ray through the left camera. Seen by the right camera, that ray projects to an epipolar line. Geometry narrows the search; appearance still has to identify the match.

Background reading from the source notebook: the Wikipedia article on epipolar geometry illustrates baseline, epipoles and epipolar planes for a general camera pair; CVonline’s explanation works through the same concepts from the ground up.

One left pixel, many possible scene depths

Live geometric illustration

Move P along the ray from the left camera. Its left-image projection stays fixed. Its right-image projection moves along one epipolar line, because both camera centres and the entire ray lie in one plane.

Oblique schematic of a parallel, horizontal pinhole pair; image planes are drawn in front of the cameras. Teal shaded triangle: part of the epipolar plane. Blue rectangles: image planes. Dashed gold lines: intersections with that plane. This special case has parallel epilines and epipoles at infinity; a general camera pose can give slanted epilines and finite epipoles.

The epipolar plane is a real triangle in 3D

Live 3D illustration

The 2D schematic above is the same geometry, flattened. Here you can walk around it. The teal triangle is the epipolar plane: it always contains both camera centres and the whole left ray. The right camera therefore sees that ray as a single line on its image plane.

Drag to orbit · scroll to zoom

Slide P farther away: pL stays put (same left ray) while pR travels along the gold epipolar line. Add toe-in and the cameras converge — the line tilts, and the epipoles become finite. Parallel stereo is the special case used for Tsukuba and Aloe.

(i) Find the Fundamental Matrix

Where does the epipolar line come from?

Problem

Problem. A pixel in the left photo could be the image of a 3D point at any depth along one ray. Searching the whole right photo for its match would be slow, and mostly spent on candidates geometry already rules out.

Approach. The two camera centres and any 3D point on that ray all lie in one plane — the epipolar plane. Turning that single fact into algebra gives a matrix equation every true match must satisfy.

  1. The same 3D point has coordinates XLX_L in the left camera’s frame and XRX_R in the right’s, related by the rigid motion between the two cameras:

    XR=R XL+tX_R = R\,X_L + t
  2. Each camera’s image ray points along that camera’s own coordinate vector to the point (xLx_L, xRx_R are normalized directions — pixels with the intrinsics already divided out):

    xL∥XL,xR∥XRx_L \parallel X_L,\qquad x_R \parallel X_R
  3. Substitute XLX_L = λL\lambda_LxLx_L and XRX_R = λR\lambda_RxRx_R into step 1 and rearrange: the baseline tt becomes a combination of xRx_R and RRxLx_L alone — the unknown depths cancel:

    λR xR−λL R xL=t\lambda_R\, x_R - \lambda_L\, R\,x_L = t
  4. A vector that is a combination of two others is coplanar with them, and three coplanar vectors have zero scalar triple product:

    xR⋅(t×(RxL))=0x_R\cdot\big(t\times(Rx_L)\big)=0
  5. Writing the cross product as multiplication by the skew-symmetric matrix [t]×[t]_\times leaves a plain bilinear equation in the two image rays — the essential matrix EE:

    E=[t]×R⟹xRTE xL=0E=[t]_\times R \qquad\Longrightarrow\qquad x_R^{\mathsf T}E\,x_L=0
  6. Real pixels also carry each camera’s own intrinsics. Substituting the normalized-to-pixel relation for both cameras and absorbing KLK_L, KRK_R into one matrix gives the pixel-level form used throughout this page:

    F=KR−TE KL−1⟹xRTF xL=0F=K_R^{-\mathsf T}E\,K_L^{-1} \qquad\Longrightarrow\qquad x_R^{\mathsf T}F\,x_L=0

Where rank two comes from. [t]×[t]_\times is skew-symmetric, so it always has rank two (tt lies in its own null space); multiplying by the full-rank rotation RR and the invertible KLK_L, KRK_R cannot raise that. That is the rank-two structure the panel on the right relies on — and because the defining equation is homogeneous, scaling FF by any nonzero constant changes nothing, which is why it is only meaningful up to scale.

Explore the fundamental matrix

Measured F · live projection

Click either photograph to draw the corresponding line in the other. Or use the stored-inlier control to inspect a matched pair and its point-to-line residual.

—ratio-test matches
—RANSAC inliers
—median right-line residual
Left image · 612 × 459
Right image · 612 × 459

xRTF xL=0⋅ℓR=FxL⋅ℓL=FTxRx_R^{\mathsf T} F\, x_L = 0 \quad\cdot\quad \ell_R = F x_L \quad\cdot\quad \ell_L = F^{\mathsf T} x_Rx=(u,v,1)Tx=(u,v,1)^{\mathsf T}. ℓ=(a,b,c)T means au+bv+c=0\ell=(a,b,c)^{\mathsf T}\ \text{means}\ au+bv+c=0.

The equality is exact for ideal corresponding pinhole projections. Estimated features, residual lens distortion and an estimated F produce nonzero residuals. A point being close to an epiline is a necessary geometric consistency check, not proof that it is the correct match.

SIFT and FLANN results are precomputed, not rerun in JavaScript. Each threshold has its own RANSAC-estimated F: 1 px threshold, confidence 0.999, at most 10,000 iterations, RNG seed 8. These example F matrices fit the supplied raw photographs; unknown lens distortion is not corrected using the unrelated checkerboard K. Counts vary by OpenCV version and matching settings.

Inspect F and understand its scale

F has rank two in the nondegenerate model and is only defined up to a nonzero scalar. Compare epilines or scale-invariant residuals, not raw matrix entries. cv2.findFundamentalMat computed F above; cv2.computeCorrespondEpilines, cv2.line and cv2.circle draw the lines you clicked into existence above. Point-to-line distance is ∣au+bv+c∣a2+b2\dfrac{|au+bv+c|}{\sqrt{a^2+b^2}}, in pixels.

Worksheet exercise 2 / 10 min

Filter appearance first, geometry second.

  1. Create SIFT descriptors and two-nearest-neighbour FLANN matches.
  2. Keep a match when distance₁ < τ × distance₂. Increase τ from 0.60 to 0.90 and record counts.
  3. Fit F robustly. Inspect inlier residuals and whether features cover the image.

The ratio test removes ambiguous descriptor matches; RANSAC rejects pairs inconsistent with one epipolar geometry. A larger match count is not itself better.

For a point in the right image, which operation gives its line in the left image?
Reveal robust implementation advice
Keep feature coordinates as float32, guard missing descriptors and incomplete k-nearest-neighbour pairs, and check that F and its mask exist. This reference uses RANSAC instead of the worksheet’s LMedS; LMedS relies on a majority of inliers. RANSAC still needs enough well-distributed correct matches and can fail in degenerate scenes. Drawing a nearly vertical epiline by dividing by b is unstable: intersect the line with all four image borders instead.
3
Beyond the worksheet

Undistorted is not rectified.

Undistortion corrects the lens. For the horizontal stereo setup used here, rectification warps a pair so corresponding epipolar lines share image rows, enabling a horizontal disparity search.

Rectification is a virtual rotation

Live 3D illustration

Nothing in the room moves. Rectification rotates each camera so the image planes become coplanar and the baseline is parallel to the image x-axis. Correspondences that used to lie on tilted lines then share a row — which is why stereo matching can search horizontally.

Drag to orbit · use the slider to rectify

Four scene points stay fixed. Watch their image-plane projections: before rectification the dashed joins are skewed; after, they become horizontal. Undistortion (Section 1) is a different warp — it straightens the lens, not the stereo geometry.

What has to be true for rows to line up?

Problem

Problem. The fundamental matrix only promises a match lies on some line. In general that line is tilted, and a different tilt for every point — expensive and awkward to search directly.

Approach. Warp each image with its own homography so every epipolar line becomes horizontal and lands on the same row in both images. “Search the epiline” then becomes “search this one row.”

  1. Each rectifying warp is a homography — a linear map on homogeneous pixel coordinates:

    x′=Hxx' = Hx
  2. Every epipolar line in an image passes through that image’s epipole ee. For every epipolar line to become horizontal, ee itself has to map to the point at infinity in the horizontal direction — the one direction with no finite image:

    H e ∝ (1,0,0)TH\,e \ \propto\ (1,0,0)^{\mathsf T}
  3. Choosing HHL and HHR this way, while keeping the two warps consistent with each other, is exactly what leaves matching points on the same row:

    yL′=yR′y'_L = y'_R

What this does and doesn’t fix. stereoRectifyUncalibrated only has FF and the matched pixels to work with, so it can straighten and align rows — it has no way to recover focal length, baseline or scale. Do that first with paired calibration and stereoRectify if metric depth is the goal, not just an aligned search direction.

From general epilines to shared scanlines

Measured projective warps
Left
Right

The black regions are areas without source-image support after warping. They must not be treated as ordinary texture. Compare the same scene feature across the images, not the borders of the two canvases.

This demonstration uses uncalibrated rectification. Homographies from F align the views, but do not establish the focal length, baseline or metric reconstruction. For a calibrated rig, use paired calibration, stereoRectify and initUndistortRectifyMap; retain the returned projection matrices or Q.
How does the physical camera setup matter?
The cameras do not have to be mounted at exactly the same height: rectification can account for relative pose within usable overlap. A predominantly vertical rig may instead rectify to columns; horizontal BM/SGBM needs a compatible image orientation. Strong viewpoint changes increase occlusion and matching difficulty. For a single camera moved between exposures, the scene must remain static for the standard two-view rigid-scene model. Pure rotation provides no triangulation baseline.
Why can horizontal block matching fail after lens undistortion alone?
3
3 Stereo Depth Map

Why did this pixel choose that shift?

Compare a left patch with candidate right patches along the same row. The sum of absolute intensity differences (SAD) gives a cost; the smallest cost wins.

From the source notebook: the goal is a greyscale heat map where lighter greys mean objects close to the lens and darker greys mean objects further away. Two of its practical notes carry over directly — calibrate and undistort your own images first, and keep both cameras at the same height, as your eyes are; this blog post covers building your own stereo rig.

More information on the functions used below, see: cv2.StereoBM_create, cv2.StereoSGBM_create.

Where does the SAD cost come from?

Problem

Problem. Given a left pixel and a candidate shift dd, we need a single number that says how well the two neighbourhoods agree — low for the same surface point, high otherwise.

Approach. Comparing single pixel intensities is too noisy on its own — sensor noise or a slight exposure difference can make a correct match look wrong. Compare small windows instead, and sum the disagreement.

  1. Take a bb×bb window WW around the left pixel, and the same-shaped window shifted by dd pixels in the right image. Sum the absolute intensity difference over the whole window:

    C(x,y,d)=∑(i,j)∈W∣ L(x+i, y+j)−R(x+i−d, y+j) ∣C(x,y,d)=\sum_{(i,j)\in W}\big|\,L(x{+}i,\,y{+}j)-R(x{+}i{-}d,\,y{+}j)\,\big|
  2. Repeat for every candidate shift from 0 to D−1, and keep whichever dd produced the lowest cost — a winner-take-all rule:

    d^(x,y)=arg min⁡0 ≤ d < D C(x,y,d)\hat d(x,y)=\operatorname*{arg\,min}_{0\,\le\, d\,<\,D}\ C(x,y,d)

Why this can still be wrong. Arg-min always returns some disparity, even over a flat wall where every shift scores about the same. A low cost is necessary for a good match; it is not sufficient — exactly what the ambiguity filter in the lab below is checking for.

The live SAD laboratory

Computed in your browser

Choose a textured point or a flat region. Move the candidate disparity and watch both the patch location and its cost. Then change the window or search range to recompute the whole map.

Left reference patch
Right candidate patch · xR = xL − d

0 px47 pxRejected / outside ROI

Preparing the stereo pair…

Exactly what is this browser implementation doing?

Tsukuba is area-averaged to 192 × 144 pixels. All disparity and patch coordinates here refer to that half-resolution grid; a shift of 10 px here corresponds to 20 px at the original size.

The matcher evaluates integer disparities d = 0,…,D−1 using a full b × b window and integral-image SAD sums. Every valid pixel uses the full search range: the common ROI excludes h = (b−1)/2 pixels at top, bottom and right, and h+D−1 pixels at left. Missing image support is masked instead of being filled with invented intensity values.

The optional educational filter rejects patches with intensity standard deviation < 2 (on 0–255 intensity) or a best-vs-second-best cost gap ≤ 5% of the second-best cost. Ties are therefore rejected. This is not OpenCV’s uniqueness implementation, and neither this score gap nor the retained fraction measures accuracy.

This transparent SAD matcher does not reproduce OpenCV StereoBM: it omits prefiltering, subpixel refinement, speckle cleanup and left-right consistency checks. The next section shows actual OpenCV outputs.

Worksheet exercise 3 / 10 min

Change one parameter at a time.

  1. Compare 3 × 3 and 21 × 21 at the same disparity range. Inspect thin structures and depth boundaries.
  2. Reduce D from 32 to 16. If the true shift lies outside the search, the matcher must reject or choose a wrong candidate; it need not return the maximum shift.
  3. Compare raw winners with the ambiguity filter on. Explain why a filled-in map is not automatically better.
A perfectly uniform patch has equal costs at many disparities. What should you conclude?
3
3 Stereo Depth Map

Local matching meets spatial regularity.

StereoBM uses local block evidence plus filtering. StereoSGBM aggregates matching costs along multiple paths and penalizes disparity changes. Neither guarantees a correct or complete map.

Lift the disparity map into a point cloud

Live 3D from the SAD map

A disparity map is already 3D information, flattened into colours. Here every valid SAD match is back-projected with an illustrative pinhole: X = (u − cₓ) Z / f, Z = fB / d, and coloured by the left Tsukuba pixel. Orbit it: the lamp, statue and desk separate in depth.

Drag to orbit · rebuilds when the SAD settings change

Pedagogical geometry, not Tsukuba’s missing metric calibration. Rejected (magenta) pixels never become points — that is why a “complete-looking” filled map is not automatically a better reconstruction. Change block size or the disparity range in the SAD laboratory and watch thin structures appear or collapse here.

OpenCV results explorer

36 precomputed maps
Left source view for selected OpenCV comparisonLeft input
Right source view for selected OpenCV comparisonRight input
OpenCV StereoBM disparity result with rejected pixels in magenta
OpenCV StereoSGBM disparity result using the selected parameters
0 px63 pxRejected

Keep the color scale fixed. Each dataset uses one fixed disparity scale across all settings, so colors remain comparable. Magenta is invalid, not far away. The retained-pixel fraction is coverage after filtering, not ground-truth accuracy.

Where do the smoothness penalties come from?

Problem

Problem. Block matching scores every pixel on its own. Nothing stops a neighbourhood of nearly tied costs from picking wildly different winners pixel to pixel — the noisy, speckled BM column on the left.

Approach. Add a second term to the cost that penalizes a pixel’s disparity for disagreeing with its neighbours, then minimize both together.

  1. The energy SGBM approximately minimizes, over the whole map D, combines the per-pixel matching cost with a penalty on how much neighbouring disparities differ:

    E(D)=∑pC(p,Dp) + ⁣ ⁣∑q∈Np ⁣ ⁣P1[ ∣Dp−Dq∣=1 ] + ⁣ ⁣∑q∈Np ⁣ ⁣P2[ ∣Dp−Dq∣>1 ]E(D)=\sum_{p}C\big(p,D_p\big)\ +\!\!\sum_{q\in \mathcal N_p}\!\!P_1\big[\,|D_p-D_q|=1\,\big]\ +\!\!\sum_{q\in \mathcal N_p}\!\!P_2\big[\,|D_p-D_q|>1\,\big]
  2. The two penalties are deliberately unequal:

    P2>P1P_2>P_1

    A one-step change (a sloped surface) costs little; a bigger jump (a real depth edge) costs more but is still finite — so genuine discontinuities survive instead of being smoothed away.

  3. Exact minimization over a full 2-D image is intractable, so SGM aggregates cost along several 1-D paths through each pixel — exact along any single path by dynamic programming — and sums the results. Semi-global, not global.

  4. This explorer’s fixed rule scales both penalties with window area and channel count, so a bigger window does not automatically make disparity jumps look proportionally cheaper:

    P1=8 C b2,P2=32 C b2P_1=8\,C\,b^2,\qquad P_2=32\,C\,b^2
Parameters, matching penalties and what these results establish

SGBM’s P₁ penalizes a one-pixel disparity change and P₂ penalizes larger changes along a path. P₂ > P₁. Here P1=8Cb2 and P2=32Cb2P_1=8Cb^2\text{ and }P_2=32Cb^2, with C the channel count. As b changes, these penalties also change following that rule; this explorer is a practical configuration comparison, not an isolated causal test of window size.

Aloe uses color for SGBM and grayscale for BM, as in the worksheet. Thus differences there include both matcher choice and color information. Tsukuba is grayscale for both. No provided disparity ground truth means we can discuss apparent artifacts, but cannot certify one setting as most accurate.

Current SGBM configuration · runs on the selected input arrays
Worksheet exercise 4 / 10 min

Make Aloe’s leaves survive the reconstruction.

Select Aloe. Compare D = 64, 96 and 128, then b = 5, 9 and 15. Inspect thin leaves, the background, textureless surfaces and occlusion boundaries. Explain the trade-offs rather than chasing a visually smooth target.

dpx=draw16d_{px}=\dfrac{d_{raw}}{16}OpenCV BM/SGBM’s signed fixed-point output uses four fractional bits (Python: raw.astype(np.float32) / 16). Preserve it before making any display image.

For minDisparity = 0, invalid output uses raw = −16. Keep an explicit validity mask. An accepted zero disparity still cannot produce a finite Z via fB/d. Do not apply min–max image normalization before reconstruction.

OpenCV returns raw = 320 at an accepted pixel. What disparity belongs in the depth formula?
Reveal a practical answer outline
A larger range can include nearby surfaces with large disparity, but costs more computation, admits more false candidates and increases the unsupported left border. Small windows may retain thin leaves but have weak or ambiguous evidence; large windows combine different depths and can blur or corrupt boundaries. Spatial penalties help in ambiguous regions but can oversmooth. Occlusions have no true match, regardless of tuning. Changing the range can alter the global SGBM solution, not merely extend a display scale.

Exercise 4 Solution

worksheet08_solution.ipynb

The worksheet’s own solution tunes StereoSGBM specifically for this pair: minDisparity=16, numDisparities=96 (112 − 16, divisible by 16), blockSize=16, with P₁/P₂ scaled from a window size of 3 across 3 colour channels (P1 = 8×3×3² = 216, P2 = 32×3×3² = 864), disp12MaxDiff=1, uniquenessRatio=10, speckleWindowSize=100, speckleRange=32.

Aloe disparity map computed with the worksheet solution’s exact StereoSGBM parametersComputed with the parameters above — compare it to your own settings in the explorer

Reference source for these tuned parameters (cited in the worksheet solution): OpenCV’s own stereo_match.py sample.

+
Beyond the worksheet

A small shift can mean a long way.

For a calibrated horizontal stereo pair with aligned principal points, disparity d=uL−uRd = u_L - u_R and depth Z=fBdZ=\dfrac{fB}{d}. The focal length and disparity must use the same pixel scale.

Where does Z = fB/d come from?

Problem

Problem. Rectification turns matching into a 1-D search and returns a pixel shift, dd. A pixel shift is not a distance — it has to become metres.

Approach. Similar triangles. Two rectified cameras share a focal length ff and image row, offset by baseline BB along one axis; project the same 3D point into both and compare.

  1. Place the left camera at the origin and the right camera at (B, 0, 0), both looking down +Z. A point at (X, Y, Z) projects in the left image at:

    xL=f XZx_L=f\,\dfrac{X}{Z}
  2. In the right camera’s own frame the same point’s X-coordinate is shifted by −B:

    xR=f X−BZx_R=f\,\dfrac{X-B}{Z}
  3. Disparity is the difference between the two projections. Z and X cancel out of everything except the shift:

    d=xL−xR=fBZd=x_L-x_R=\dfrac{fB}{Z}
  4. Solve for Z:

    Z=fBdZ=\dfrac{fB}{d}
  5. To see how a small error in dd moves Z, differentiate:

    dZdd=−fBd2\dfrac{dZ}{dd}=-\dfrac{fB}{d^2}

    For a disparity uncertain by ±ε\varepsilon (with ε\varepsilon much smaller than dd), that slope gives the error bound used in the calculator:

    ∣ΔZ∣≈fBd2 ε|\Delta Z|\approx\dfrac{fB}{d^2}\,\varepsilon

Why this gets sensitive at range. The 1/dd² in that last line is the whole story: as objects get farther away, dd shrinks, and the same one-pixel matching error swings Z by much more. Try it in the calculator below — halve the disparity and watch the same ε open a much wider interval.

Similar triangles, standing in the room

Live 3D illustration

The formula Z = fB / d is two similar triangles: the baseline B at the cameras, and the leftover shift d on the image planes. This view is wired to the sliders in the calculator below. Move disparity and watch P fly away; the gold segment on the image planes is d.

Drag to orbit · uses the f, B and d sliders below

When d is small, P is far and a 1 px matching error becomes a large error in Z. That is why distant, textureless regions on Aloe and Tsukuba are the first to look geometrically implausible.

A stereo depth calculator

Live · synthetic camera

“Halve resolution” halves f, d and ε together; B stays fixed. It demonstrates the coordinate scaling law, not the noise characteristics of a resampling algorithm.

Axial depth Z

2.400metres

Z=fBd∣ΔZ∣≈fBd2 εZ = \dfrac{fB}{d} \qquad\qquad |\Delta Z| \approx \dfrac{fB}{d^2}\,\varepsilonThe second expression is a first-order error estimate, valid for ε≪d\varepsilon \ll d.

Z is distance along the rectified optical axis, not Euclidean distance from the camera centre. For metric XYZ use the calibrated projection model or Q. If rectified principal points differ, use the disparity offset or Q rather than blindly applying fB/d.

If f = 800 px, B = 0.12 m and d = 40 px, what is Z?
5–6
Exercises

What can the images really tell you?

These questions separate plausible visual impressions from the geometry needed to measure distances.

Worksheet exercise 5 / 5 min

Two cues per scene. What does each recover?

Use the original room and lake photographs from the worksheet. Choose two cues in each, then separate what you can infer from what would need additional assumptions.

Original worksheet room photograph, with a table and chairs, floorboards and converging wall edgesRoom · original worksheet photograph
Original worksheet lake photograph, showing sailboats of different apparent sizes across a water surfaceLake · original worksheet photograph

Images embedded from the image collection linked by the source notebook.

Reveal a worked answer

From worksheet08_solution.ipynb: Room — Perspective (lines on floor, walls) gives relative depth on the surface; Occlusion (chairs occlude the table) and Height in image (chairs) both give rank order, not actual depth; Real world size of known objects (chairs) could give distance if the camera is calibrated. Lake — Height in image (boats) gives rank order, not actual depth; Texture (water) could give relative distance or surface normal; Real world size of known objects (boats) could give distance if calibrated.

Scene / cueWhat it supportsWhat it does not give by itself
Room: parallel wall edges and floorboardsA vanishing point and perspective structure; with calibration and known geometry, stronger reconstruction constraints.Absolute distances without scale and camera/scene assumptions.
Room: chairs occlude the table or wallWhich visible surface is in front at the overlap.The numerical separation between the overlapping surfaces.
Lake: boats have different apparent sizesRelative distance under an assumption of comparable true boat/sail dimensions.A reliable depth ordering if the boats actually have very different sizes, or absolute distance without calibration/scale.
Lake: boat–water contact points approach the horizonOn a shared horizontal water plane, points closer to the horizon are farther away, given a suitable camera model.Metric distance without camera geometry and a scale such as camera height.

The changing apparent scale of wave texture is another possible cue, provided the physical texture is sufficiently comparable. These images do not justify claiming that a reflection or apparent size alone gives a calibrated depth measurement.

Worksheet exercise 6 / 10 min

Explain epipoles. Then recover the missing scale.

6a. Without formulas, explain why all epipolar lines in one image meet at the projection of the other camera centre.

Reveal the geometric explanation
Every epipolar plane contains the line joining the two camera centres. Each plane cuts an image plane in an epipolar line. All those intersection lines therefore pass through where the camera-centre baseline meets that image plane: the other camera centre’s projection, called the epipole. This is an image location, not the physical 3D centre. If the baseline is parallel to the image plane, that intersection is at infinity and the epipolar lines are parallel.

6b. Two calibrated cameras are 10 m apart. Their relative rotation and translation direction are unknown. Can corresponding points give you metric distances?

What extra role does the known 10 m baseline play?
Reveal the full recovery route and its conditions

From worksheet08_solution.ipynb: “Compute the essential matrix from the matched points, decompose into rotation and translation between cameras. Since the baseline is known (10 m) and the cameras are calibrated, the distances to matched points can be triangulated from their view ray in each camera. Constraints: requires a minimum number of matches (5–8 depending on algorithm) and the matches cannot be coplanar.”

Yes, generically. Undistort and normalize matched points using each camera’s own calibration. Estimate an essential matrix robustly (or transform a compatible F using E = Kᵣᵀ F Kₗ), recover the relative rotation and translation direction, and choose a physically valid pose using positive depths in both cameras.

Translation from essential-matrix recovery has unknown magnitude. Scale its unit direction to 10 m, triangulate with the calibrated projection matrices and that translation, then compute either axial depth Z or Euclidean distance √(X²+Y²+Z²), as required.

This needs a rigid/static scene, reliable and well-distributed matches, correct calibration and sufficient translational parallax. Degenerate or ambiguous arrangements, particularly planar configurations for unconstrained F estimation, need care or additional constraints; tiny parallax and distant points produce poor numerical accuracy. Knowing the baseline does not solve wrong correspondences.

+
Extra Bonus: 3D effects

A virtual object, anchored to the board.

Known 3D board corners and their 2D detections let solvePnP estimate a board-to-camera pose. Project a new object through that pose and the lens model to overlay it on the original photograph.

The camera, the board, and the cube — in the world

Live 3D illustration

The photograph overlay is a projection. This is the same pose in world coordinates: the checkerboard sits on the ground, solvePnP places the camera, and the cube is specified in board units then drawn with the same rigid motion. Change the photo or the cube height and both views stay consistent.

Drag to orbit · follows the pose controls below

Board frame: X along the squares, Y along the other side, Z out of the board (mapped to height here). The white dot is the first inner corner — the same origin used by the 2D overlay.

How does a 3D point become a drawn pixel?

Problem

Problem. solvePnP gives a pose — where the camera sits relative to the board. To draw a cube “sitting on” the board, every one of its 3D corners has to be pushed through that same camera and lens to find its pixel.

Approach. Run each corner through exactly the pipeline calibration fitted, in reverse: rigid motion, perspective division, lens distortion, then intrinsics.

  1. Move the board-frame point into the camera’s own frame, using the rigid motion solvePnP estimated:

    Xc=R Xb+tX_c=R\,X_b+t
  2. Perspective projection: divide by depth to get a normalized image coordinate:

    x=Xc(1)Xc(3),y=Xc(2)Xc(3)x=\dfrac{X_c^{(1)}}{X_c^{(3)}},\qquad y=\dfrac{X_c^{(2)}}{X_c^{(3)}}
  3. Apply the same distortion model calibration fitted — forward this time, point to pixel, so it is the direct formula rather than its inverse:

    xd=x (1+k1r2+k2r4+k3r6)+2p1xy+p2(r2+2x2)yd=y (1+k1r2+k2r4+k3r6)+p1(r2+2y2)+2p2xy\begin{aligned} x_d &= x\,(1+k_1r^2+k_2r^4+k_3r^6) + 2p_1xy+p_2(r^2+2x^2)\\ y_d &= y\,(1+k_1r^2+k_2r^4+k_3r^6) + p_1(r^2+2y^2)+2p_2xy \end{aligned}
  4. Apply the intrinsics to land on an actual pixel:

    u=fx xd+cx,v=fy yd+cyu=f_x\,x_d+c_x,\qquad v=f_y\,y_d+c_y

Same code, real numbers. This is exactly the computation the live projection below runs for every cube corner and axis tip, using this camera’s own K and five distortion coefficients — not a simplified stand-in.

Project a cube and coordinate axes

Live projection · measured pose
Xc=R Xb+tX_c=R\,X_b+tTransform into the camera frame, divide by Z, apply distortion, then apply K.

For this board coordinate convention, negative board Z extends outward toward the viewer. It is not negative camera depth. The cube footprint is three squares wide.

The pose was estimated with solvePnP using the nine-view calibration. Shape height and projection are computed live, including all five distortion coefficients. Overlays do not infer occlusion or lighting from the real scene.

If checkerboard coordinates use square widths, what units does solvePnP’s translation use?
§
Take it back to the notebook

The corrected implementation.

A runnable Python reference, a compact troubleshooting guide and a record of the technical corrections.

Run the workshop yourself

  1. Extract the supplied week8.zip. Keep the image names and the chessboard folder.
  2. Download the reference script below into the extracted week8 directory. It expects that working directory.
  3. Use Python with NumPy and OpenCV 4.x; this tutorial’s saved results were computed with OpenCV 4.13.0.
python -m pip install numpy "opencv-python==4.13.0.92"
python week8-reference.py

Use one OpenCV Python package per environment. If already using a compatible OpenCV 4.x installation, do not install a second package variant. SIFT is available in the main package for these versions.

The script writes calibration parameters, rectified photographs, numerical disparity arrays with validity masks, display images and a cube overlay. It does not invent metric calibration for Tsukuba or Aloe. The worked notebook packages the same reference functions with experiment prompts.

Read or copy the complete reference script
Python / NumPy / OpenCV

When a result looks wrong

SymptomCheck firstWhy
Calibration looks good only on one viewView diversity, corner order, residuals and subset stabilityLow training error can hide uncertain intrinsics and distortion.
Epilines miss most matchesLeft/right convention, F transpose, image resizing, outliersF and point coordinates must belong to the same coordinate systems.
Horizontal search fails across the imageRectification and vertical alignmentLens correction alone does not align corresponding rows.
A band at the left is invalidSearch range and valid stereo ROISome candidate patches would be outside the right image.
Depth is wrong by a factor of 16Fixed-point conversionBM/SGBM disparities must be divided by 16 before geometry.
Depth changes after resizingScale f and d consistently; update K/QGeometry uses pixel coordinates at the actual working resolution.
Bad results near mirrors, leaves or plain wallsOcclusion, non-Lambertian appearance, ambiguity and window mixingGeometry cannot create a visible, distinctive correspondence where none exists.
Projected cube floats or flipsBoard coordinates, pose convention, distortion and image cropThe image and camera model must describe the same pixel frame.
Validation record and changes from the supplied tutorial
  • Recomputed all thirteen checkerboard detections and nine-view calibration. Reproduced the reported approximately 0.156 px RMS and k₁ ≈ −0.294.
  • Corrected the forward distortion equation and normalized-coordinate definition; bounded the illustrative grid to avoid non-monotonic radial folding.
  • Added measured single-view experiments, with explicit differences between training fit, parameter stability and held-out validation.
  • Retained subpixel matches, used guarded RANSAC estimation and replaced fixed target counts with measured, version-specific values. Added scale-invariant line residuals and safe border clipping.
  • Added the missing rectification stage and separated uncalibrated projective alignment from metric calibration.
  • Corrected disparity units, the search interval, invalid masks, depth scaling and zero-disparity handling. Kept display scales fixed across parameter choices.
  • Replaced the original live matcher’s truncated edge windows and artificial out-of-image costs with full valid windows and an explicit common ROI.
  • Added matching-cost curves, ambiguity inspection and 36 genuine OpenCV BM/SGBM outputs. No output is labelled ground truth or an accuracy target.
  • Expanded all six worksheet exercises with worked reasoning; made AR projection continuous, with consistent frame and unit conventions.
  • Added keyboard-accessible equivalents for image interactions, responsive layout, optional local notes, answer feedback, printing and downloadable code/results. All demonstrations work without external libraries or image requests.

Measured outputs may differ slightly across builds and platforms. Synthetic illustrations and live browser SAD results are explicitly labelled separately from OpenCV measurements. The original ZIP is unchanged.

Source material and API references

Primary teaching source: the supplied worksheet08.ipynb and its images. Technical definitions and implementation constraints were checked against the OpenCV documentation. The diagrams and interactive explanations are newly authored. External reference links require an internet connection; the tutorial itself does not.

Your workshop record

Complete the eight checkpoints and record evidence for the six exercises.

Notes and successful checkpoints are saved only in this browser when supported. They are not sent anywhere or treated as an assessment submission. Export a copy to keep or share your work.