Goal
Detect every corner of a PuzzleBoard calibration pattern in a grayscale image and assign each detected corner its absolute integer position on the pattern's grid. Input: image and the pattern's code array . Output: a set of triples where is a subpixel corner location and is that corner's grid coordinate on the pattern. The pattern is self-identifying: the absolute position of any corner is recovered from a local window, without seeing the pattern boundary, tolerating partial occlusion, severe perspective, and image resolution as low as roughly pixels per checkerboard edge.
Algorithm
Let denote the first- and second-order Gaussian-smoothed derivatives of . Let
denote the Hessian at , with shorthands , etc. A checkerboard corner is a saddle point of — a critical point where has eigenvalues of opposite sign.
Let and denote two cyclic binary sub-perfect maps in which every window is unique. The pattern's code array is their superposition,
cyclic of period in both axes. Every window of is unique — two positions sharing a window force equal windows on both factor maps, hence equal indices modulo and , hence equal indices modulo .
A Hessian-based score, large at X-junctions where the two principal curvatures have opposite sign and balanced magnitudes.
The default constant is . The first two terms equal , positive at saddles; the last term penalises the trace, suppressing blobs where both curvatures have the same sign.
Each edge of the checkerboard carries one bit of the code, rendered as a circle at the edge midpoint with diameter equal to of the edge length.
The ratio is the operational constant: under any perspective transform that does not collapse the checkerboard square into a line, the imaged midpoint of the edge lies within the imaged circle, so reading the bit reduces to a grayscale comparison at a known subpixel location.
Given the observed pattern after bit-reading and majority voting, cross-correlate against the four sign-rotations of the factor maps — , (rotated ), , — at sizes and . Let the peaks occur at and with ranges and . The detected code's top-left position on the grid is
The formula reconstructs the full position from a coarse-resolution index modulo (from each factor map) and a fine-resolution index modulo (from their disagreement).
Procedure
- Compute Gaussian-smoothed Hessian components across .
- Compute the saddle response at every pixel. Retain pixels at which exceeds a threshold and is a local maximum.
- Reject candidates that fail a centrosymmetric test on the surrounding intensities.
- Refine each surviving candidate to subpixel position by grayscale centroid over a neighborhood of the response map.
- For each corner , find its nearest neighbours. Separate direct grid neighbours from diagonal neighbours using the two Hessian eigenvector directions at , which bisect the black/white sectors.
- Build the corner graph with edge weights monotone in edge length and in the saddle response at the endpoints. Run Kruskal's algorithm with a union-find structure; at each merge, rotate one subtree so the two subtrees agree on a local axis orientation.
- For each axial edge of the reconstructed grid, read one bit: compare the average grayscale at the two endpoints with the grayscale sampled at the subpixel edge midpoint.
- Apply majority voting: the code repeats every three rows and every three columns, so each bit is observed three times along each axis.
- Cross-correlate the read grid against . Pick the two strongest peaks, yielding and .
- Compute via the position-decoding formula; assign grid coordinates to every detected corner by offsetting from the decoded anchor.
Implementation
The saddle response is a per-pixel scalar in the Hessian components; position decoding is modular arithmetic on the cross-correlation peaks.
/// Hessian-based saddle response for every pixel (default k = 1).
fn saddle_response(fxx: &[f32], fxy: &[f32], fyy: &[f32], k: f32) -> Vec<f32> {
fxx.iter().zip(fxy).zip(fyy)
.map(|((&xx, &xy), &yy)| xy * xy - xx * yy - k * (xx + yy).powi(2))
.collect()
}
/// Decode absolute position on the 501×501 grid from cross-correlation peaks.
/// x_a, y_b ∈ {0, …, 166}; x_b, y_a ∈ {0, 1, 2}.
fn decode_position(x_a: usize, y_a: usize, x_b: usize, y_b: usize) -> (usize, usize) {
const N: usize = 167;
let dx = (x_a as i32 - x_b as i32).rem_euclid(3) as usize;
let dy = (y_b as i32 - y_a as i32).rem_euclid(3) as usize;
(x_a + N * dx, y_b + N * dy)
}
The response kernel is arithmetic-only per pixel; the decoder is four modular operations per frame. The dominant cost of the full pipeline is the cross-correlation in step 9, quadratic in the detected-grid diameter.
Remarks
- The saddle response uses the Hessian rather than the structure tensor. At a checkerboard X-junction, has eigenvalues of opposite sign — so — while a blob gives same-sign eigenvalues and a flat region gives near-zero eigenvalues. The term subtracts the blob component.
- Complexity is linear in image size: Hessian filtering, response evaluation, non-maximum suppression, and neighbour lookup each run in ; Kruskal's MSF runs in near-linear time in the corner count.
- Position encoding is local: a decoded grid coordinate uses a window of bits (18 raw bits), so the pattern is self-identifying under occlusion as long as at least one such window is visible.
- Error tolerance: after majority voting across the three repetitions of every row and column, up to of the raw observed bits can be corrupted and the position still decodes correctly. The minimum Hamming distance between the correct alignment and any wrong alignment is bits.
- Minimum resolution: decoding succeeds at roughly pixels per checkerboard edge at full reliability, and recovers under error correction at pixels per edge — substantially below the resolution required by the classic checkerboard detectors.
- Backward compatibility: the edge-midpoint circles do not interfere with the saddle test, so a generic checkerboard corner detector still locates corners on a PuzzleBoard; the position decoding is an additive capability on top of standard calibration targets.
- Compared with ChESS: see When to choose ChESS over PuzzleBoard on the ChESS page, which hosts the comparison per the older-paper-hosts rule.
References
- P. Stelldinger, N. Schönherr, J. Biermann. PuzzleBoard: A New Camera Calibration Pattern with Position Encoding. arXiv.20127, 2024. arxiv.org/abs/2409.20127
- C. Harris, M. J. Stephens. A Combined Corner and Edge Detector. Alvey Vision Conference, 1988. DOI: 10.5244/c.2.23