Browser tool
FAST corners
Drop an image, watch the segment test fire, then click a keypoint and read what the detector actually decided about it. Everything happens in this tab.
fovea compiled to WebAssembly, and the module hands back coordinates.
There is no upload, no server and no analytics: the wasm module is instantiated with an empty
import object, so it has no way to reach the network even if it wanted to. You can pull the
network cable and this page still works.
Drop a PNG or JPEG here, or
Large images are scaled down to 1400 px on the long edge so the detector stays interactive. The scaling happens before detection, so the coordinates you see belong to the image you see.
In intensity units on the linear-light image, not a percentage of anything. A ring pixel counts as brighter when it exceeds the centre by t.
The n in FAST-n: how many consecutive ring pixels must agree. Below 9 every
edge is a corner, above 16 nothing can ever fire, so SegmentTest refuses
both.
Corners closer than this to a stronger one are dropped. Raise it when a single feature reports as a cluster.
Ranking is a separate step from detection, which is why the count found and the count kept are reported apart.
Least squares over the Sobel images, moving each corner to where the gradients say it is. Without it, every position sits on the pixel grid.
Readable on a handful of corners, unreadable on four hundred. Useful when you are hunting one specific detection.
What the ring diagram is telling you
FAST does not look at gradients. It looks at sixteen pixels arranged in a circle of radius 3 around a candidate, and asks a yes-or-no question: is there a run of at least n consecutive ring pixels that are all brighter than the centre by more than t, or all darker by more than t? That is the entire detector, and it is Rosten and Drummond’s, from Machine learning for high-speed corner detection (ECCV 2006); their page collects both papers, the FAST-ER follow-up and the reference code. The ring diagram in the inspector is that question, drawn: each of the sixteen positions coloured by which side of the band it fell on, with the winning arc marked.
Two things are worth noticing while you play with the threshold. The first is that the response is in intensity units, so a faint corner scores like a faint corner. Harris would raise the same contrast to the fourth power and report something closer to zero. The second is that a checkerboard, which every human eye reads as wall-to-wall corners, produces almost nothing at n = 9: where four squares meet, the ring alternates in two runs of eight, and eight is one short. Try the checkerboard sample and watch the count stay at zero.
The detector is not looking for what you would call a corner. It is looking for an arc.
The same run, in Rust
The module behind this page is a thin shell. Every number you just read came out of these calls, in this order:
use fovea::border::{Clamp, Skip};
use fovea::features::detect::{fast, refine_corners, FastParams, NmsRadius, SegmentTest};
use fovea::features::retain_top_n;
use fovea::transform::{convert_image, sobel_x, sobel_y, Luminance, SrgbGamma};
// The browser hands over sRGB. Undo the transfer curve first, then take
// luminance: averaging channels in sRGB moves every corner you find.
let light: Image<RgbF32> = convert_image(&srgb, SrgbGamma);
let luma: Image<MonoF32> = convert_image(&light, Luminance);
// Both silent failure modes are errors here, not empty results.
let test = SegmentTest::try_new(0.08, 9)?;
let radius = NmsRadius::try_new(3)?;
let params = FastParams::new(test, radius);
// `Skip`: the ring does not fit within three pixels of the frame, and a
// corner reported there would be made of invented samples.
let mut corners = fast(&luma, params, &Skip);
retain_top_n(&mut corners, 400);
// Detection puts corners on the grid. Accuracy is a separate step.
let window = NmsRadius::try_new(2)?;
refine_corners(&mut corners, &sobel_x(&luma, &Clamp), &sobel_y(&luma, &Clamp), window)?;
The threshold slider is the 0.08. The arc slider is the 9. When you
drag either one past the edge of what the detector can honestly do, you do not get an empty
result that you have to interpret: you get an error, because
SegmentTest refuses to exist with an arc length of 8.
A personal project, unaffiliated with my employer.