A checkerboard is the default stereo calibration target, and under steep tilt it is the first thing to fail: corner detectors lose the foreshortened edges, and every damaged view costs you a recapture. A circle grid, printed or etched at a known pitch, gives one labeled 3D point per dot, measured to sub-pixel accuracy in both views with almost no coupling to target rotation. This guide is for engineers building or re-verifying a stereo pair from two area scan cameras, or auditing an integrated rig. You will size the dot pitch to your pixels, detect the centers with your own code when the toolchain has no built-in circle grid function, order them into one indexed grid, run the standard stereo solve, and accept or reject the result on two numbers: a re-projection error in pixels and a depth error in millimetres.
Dot centers versus checkerboard corners
Decide the pattern first; everything downstream follows from it. A checkerboard corner is the intersection of two edges, each found by a gradient in one direction, so tilt one edge hard and the corner estimate degrades. A dot center is an area centroid, which stays well behaved while the circle is still an ellipse, so the target survives the steep tilts a stable solve needs. The recent calibration literature builds dedicated sub-pixel grid-point detectors and adopts concentric circle grids for exactly this reason: plain corner methods lose accuracy on oblique views.
The trade is structure. A corner detector walks a chessboard and indexes itself; a circle detector hands you an unordered cloud of centers, and the ordering step below is yours to write.
| Checkerboard | Circle grid | |
|---|---|---|
| Feature located | corner, two edge intersections | area centroid |
| Under tilt | degrades as edges foreshorten | holds while the dot stays elliptical |
| Indexing | the detector walks the board itself | you order the centers |
| Origin ambiguity | 180-degree flip needs asymmetric squares | one sentinel dot fixes it |
| Tooling | built-in in most toolchains | often missing in older releases |
| Choose when | tilts stay moderate, zero code wanted | steep tilts or repeatable sub-pixel audits |
Decision: use a circle grid when tilts must exceed about 30 to 40 degrees, when corner detection fails repeatedly on your optics, or when you want one auditable pipeline for every target. Stay with a checkerboard when your toolchain detects it cleanly and your tilts can stay gentle.
What you need
- Two matched cameras on a rigid bar. A pair of area scan cameras such as the MV-CU020-90GC, 2.5 MP global shutter over GigE on a C-mount, covers a bench rig. Where the dot budget gets tight, the MV-CS200-10UM moves up to 20 MP mono on a 1" sensor, still USB3 and C-mount. Rolling-shutter sensors also work while the target never moves during a capture.
- A target: matte paper for first passes, film or lithographic print for the final rig, at the pitch the next section computes.
- Even, diffuse light across the target. A specular dot is a biased dot.
- Synchronized capture from your usual API, plus a script for the ordering step.
- Sensor format
- 1/1.7"
- Lens mount
- C-mount
- Sensor format
- 1"
- Lens mount
- C-mount
If the goal is depth output rather than a rig you own, the 3D sensors category lists integrated stereo and structured-light cameras such as the MV-DPS200P-02, a projected structured-light 3D camera. For those, this guide is the audit, not the build.
- Type
- Stereo / structured light
Bolt the bar down and leave it: the calibration describes the cameras as they sit that day, and a later bump or refocus silently invalidates it. That is the drift mechanism behind static calibration error, and five minutes of bracket checking beats a re-capture.
Size the dot pitch to your pixels
Work from pixels, not from a template. Keep every dot at least 10 pixels across and land the pitch at 15 to 25 pixels, so the centroid has enough uncorrupted pixels to average quantization away. This is the coverage-first logic the checkerboard sizing guide applies to squares, and it must serve the volume you actually measure: as the stereo accuracy guide puts it, the measurement volume decides. Both are worth a read before you print: checkerboard target sizing and the measurement-volume argument.
Worked example. The LS-CMR-0166 reads 4000 × 3000 pixels over USB3 at 30 fps.
- Sensor format
- 1/1.7"
- Interface
- USB3
- Resolution
- 12 MP
- Shutter
- Rolling
Set the rig so the calibration view is 96 mm wide at the working distance. Then:
- Pixel scale: 96 mm / 4000 px = 0.024 mm/px.
- Pitch 0.60 mm gives 0.60 / 0.024 = 25 px per pitch.
- Dot diameter 0.36 mm, 60 percent of pitch, gives 0.36 / 0.024 = 15 px across.
- Detection to 0.1 px resolves 0.1 × 0.024 = 0.0024 mm, about 2 µm on the image plane.
- A 160 × 120 grid at 0.60 mm pitch covers 96 × 72 mm, the full 4:3 field at that distance, on one A4 sheet.
If the field is wider, bring the target closer or cover it in sections; never shrink dots below the 10 px floor to force coverage.
Check the print too. At 1200 dpi a printer draws one dot every 25.4 / 1200 = 0.021 mm, so a 0.36 mm dot is about 17 printer pixels, and its edge softens by a few hundredths of a millimetre, roughly one camera pixel at this scale. Under about 12 printer pixels per dot, order film output instead of an office print.
Detect the dot centers
If the call to detectCircleGridPoints resolves as an undefined function, your release predates 2021b, the release that added it; check the release notes of your installation rather than hunting for a download elsewhere. Two routes work on older releases. The imfindcircles function detects circle centers but returns them unordered, so the ordering step stays with you either way. Or skip it and run the pipeline here; it is about fifty lines against grayscale frames from any camera API.
- Capture synchronized pairs. Set exposure so dots sit mid-gray with headroom on both shoulders: a saturated dot has a flat top and a centroid that leans off-center.
- Threshold locally, not globally. A window of about twice the dot pitch in pixels follows the paper's shading across the frame.
- Label connected components and filter by area. Keep blobs between half and twice the median area; drop anything touching the frame border.
- Take a gray-weighted centroid per blob from the original frame, not from the mask. The binary boundary quantizes at whole pixels and hands back the sub-pixel advantage.
- Count the results. Detected centers must equal rows × columns exactly; any miss means threshold, blur or a stray reflection, and the capture is redone, not patched.
An outline of steps 2 to 4, with our own variable names (this is our pseudocode, not a library API):
frame : uint8 grayscale capture, e.g. 4000 x 3000
pitch_px : float, dot pitch in pixels, e.g. 25.0 from the sizing step
mask : local_threshold(frame, block = round(2 * pitch_px), offset = -10) # bool, same size
blobs : connected_components(mask) # integer label image
keep : blobs where 0.5 * median_area <= area <= 2.0 * median_area, not on the border
center : gray-weighted centroid of frame inside each kept blob # float (x, y)
The proof is visual before it is statistical: draw the centers back onto the capture, one mark per dot, no dot without a mark, no mark without a dot.
One physics trap lives in step 4. The area centroid of a tilted circle's projection sits slightly closer to the image center than the true projected circle center, and the offset grows with tilt angle and with the dot's distance from the image center. At gentle tilts it hides under ordinary noise; on steep ones it becomes the dominant error term, which is why the concentric-grid papers exist. Keep worst-case tilts near 40 degrees, or print concentric ring dots, whose inner and outer edges give the estimator two constraints and cancel much of the shift.
Order the dots into one indexed grid
Detection hands you N centers; the solver needs the same 3D label on the matching center in both views.
- Estimate the lattice directions. For each center, average the displacement vectors to its nearest neighbours; the two dominant directions are the row and column axes.
- Snap every center to integer (row, column) coordinates along those axes, then build its 3D point as (row × pitch, column × pitch, 0), using the pitch you measured, not the pitch in the print file.
- Anchor the origin identically in both views with a sentinel: one missing corner dot or one dot at twice the diameter, required at the expected image position before a solve is accepted.
The left and right views see the target from opposite sides, so anchor on the sentinel, never on screen coordinates.
Run the stereo solve
Each accepted pair now contributes two matched pixel lists and one shared list of 3D points, and the solve itself is the one you already know. The mechanics, inputs and outputs match a checkerboard session; only the point lists differ. Paired checkerboard calibration covers the solve end to end.
Cross-check the result against the bar: take the returned translation between the cameras and compare it with your caliper measurement. A pair bolted 200 mm apart should solve near 200 mm; a return of 174 mm indicts the pitch entry or the indexing, not the physics. If your tool expects per-camera intrinsics first, solve each camera alone across all its views, then hand the extrinsics stage the two intrinsic sets.
Verify with two numbers
The first number is the re-projection RMS: the mean distance between each detected center and where the calibrated model puts it. Commit to bands. Under 0.5 px passes on a bench rig with 15 px dots. Between 0.5 and 1 px, inspect tilt extremes, blur and the sentinel. Over 1 px means a systematic fault — wrong pitch, flipped labels, a moved camera — and more views will not hide it. The floor is physical: an area centroid on a 15 px dot at good contrast repeats to about 0.2 to 0.3 px, so chasing 0.05 px wastes an afternoon.
The second number is depth against a gauge, via the short-range stereo relation:
With example inputs f_px = 6000 px and B = 200 mm, a reference block at Z = 500 mm sits at disparity d = 6000 × 200 / 500 = 2400 px. Disparity noise of 0.1 px moves it 500² × 0.1 / (6000 × 200) = 0.002 mm; a full pixel moves it 0.021 mm, ten times more — which is where the RMS band above comes from. Then read the depth at a fixed image point on the block, the way the center-pixel depth guide shows, and grade the result: within ±0.1 to 0.3 mm at 500 mm on an etched or film target, within about ±1 mm on a calipered print. Several millimetres out at that range points at the pitch value you entered, because depth scale inherits target scale one to one.
Common mistakes
- Trusting the file's pitch instead of a caliper measurement. Printers scale, and depth inherits the error directly; the grid-pitch fix used for checkerboards applies unchanged here.
- Saturated dots. Flat tops slide centroids; back the exposure down until the edges carry gradient.
- Motion blur from an unsynchronized pair or a target that moved between the two captures.
- Refocusing or nudging a camera between calibration and production; the solve no longer describes the rig.
- All face-on views. Without tilts, focal length and principal point trade off, and the solve returns something smooth and wrong.
When the checkerboard is still the faster answer
If your toolchain detects chessboard corners cleanly, with flip handling already proven, a moderate-tilt checkerboard session costs no code, and code you never write never breaks. Choose it when tilts can stay gentle and the corners hold. Choose the circle grid when tilts must go steep, corners keep failing, or you want one pipeline that audits every target the same way. And because both patterns end as matched point lists, one correspondence-based solve can even mix them; the FAQ below covers the checks that make mixing safe.
To pick the pair, start at the camera step of the configurator or browse the camera finder; more stereo geometry, targets and verification live on the 3D topic page. If a rig question survives the checklist, the contact page reaches the applications team.
Questions
How many dot-grid views does a stereo calibration need?
Plan on 15 to 25 synchronized pairs: two or three face-on, tilts in every direction up to about 40 degrees, and three distances spanning the working range, with a few views pushing dots into each image corner. Below about 10 pairs, focal length and the camera-to-camera rotation start trading off, and the solve returns a plausible baseline built on a skewed focal length.
Can imfindcircles handle the detection step?
For sparse, well separated dots, yes: it returns circle centers found by a Hough transform at radii you specify. It does not index the grid, it slows on a few thousand close-packed dots, and its fixed radius assumption biases centers on tilted views, where a dot images as an ellipse rather than its nominal radius. For dense grids, the threshold-blob-centroid pipeline above is faster and stays honest under tilt.
Is an office-printed target accurate enough?
For first passes, yes: at 1200 dpi a printer resolves 0.021 mm per printer pixel, enough for dots of 0.3 mm and up. Mount the sheet flat on rigid stock, because paper curl breaks the planar assumption, and measure the printed pitch over ten pitches with a caliper instead of trusting the file. For the final rig, film or lithographic output holds pitch to a few micrometres across the grid.
Does the target have to fill the whole image?
Cover the corners in at least a few views. Lens distortion lives at the frame edges, so a calibration built only from central dots returns a flattering RMS while bending reality near the borders. If your working volume keeps the target central anyway, still capture two or three corner-heavy views during calibration: you are calibrating the lens, not just the pose.
Do I need to recalibrate after refocusing one camera?
Yes. Refocusing changes the focal length you calibrated, and any touch to a mount shifts the baseline with it. Re-run the full solve; an extrinsics-only refresh is valid only while the intrinsics are provably unchanged, which a refocus immediately breaks. Make it a rule: any mechanical or optical contact with either camera invalidates both the intrinsics and the baseline.
Can I mix checkerboard and circle-grid views in one solve?
Yes, if your solver accepts generic correspondences: ordered pixel lists per view plus 3D points, which both patterns produce. Keep each target's pitch as its own measured value, solve once, then compare per-view RMS between the two pattern types. If one pattern's views sit consistently higher, suspect its detection step, because the pattern with the higher RMS is the one feeding error into the solve.



