Monty. Frameworks. Utils. EdgeDetection. EdgeDetector
(ExTbpMonty.Core v0.1.0)
Copy Markdown
View Source
One patch in, one edge out.
Mirrors tbp/monty/frameworks/utils/edge_detection.py::EdgeDetector.
The detector does not ask "is there a gradient here" — a texture has gradients everywhere. It asks whether the gradients in the patch agree on one direction, which is what a structure tensor aggregated over the patch answers. Three things can then say no:
- Nothing was seen. If every pixel in the weighted neighbourhood has no gradient at all (a flat patch), the aggregate is zero and there is no edge to report.
- The edge is not in the middle. The weighted centroid of the gradient, measured along the edge's normal, is how far from the patch centre the edge actually runs. A patch that is half background has an edge at its rim, not through its centre, and a module that moved to the edge does not want to hear about a different edge at the rim.
- It is too weak, or too muddled.
strength_thresholdandcoherence_thresholdare separate because a strong but incoherent patch (two textures crossing) is not an edge either.
Geometric or texture
An edge in brightness may be ink on a surface or the surface ending against
the background. The depth image says which: across a geometric edge, depth
jumps. Monty computes the depth gradient perpendicular to the brightness edge,
inside a band around it, and calls the edge geometric when the 95th percentile
of that gradient clears depth_edge_threshold. The percentile rather than the
mean is what makes a one-pixel jump count without a wide crease being needed
to prove it.
Summary
Functions
How far, in pixels, the edge runs from the patch centre — signed, along the edge's normal.
The weight of every pixel: how close it is to the centre, and how strong its gradient is.
Monty's __call__: the edge in this patch, or the reason there is none.
Whether the edge is a crease in the world rather than a change in ink.
A detector, as Monty's __init__ reads its arguments.
Types
Functions
How far, in pixels, the edge runs from the patch centre — signed, along the edge's normal.
The weight of every pixel: how close it is to the centre, and how strong its gradient is.
exp(-d² / 2σ_r²) with zero past radius, multiplied by Ix² + Iy². The
first factor is what makes the patch answer for its middle; the second is what
keeps a strong edge near the rim from being discarded just for being far away.
@spec detect(t(), Monty.Observations.SensorObservation.t()) :: Monty.Frameworks.Utils.EdgeDetection.EdgeFeatures.t()
Monty's __call__: the edge in this patch, or the reason there is none.
The outline, in order: grey the patch, Sobel it, build a structure tensor per pixel and blur each component, weight the tensors towards the centre and towards strong gradients, aggregate, and then ask the three questions above.
Whether the edge is a crease in the world rather than a change in ink.
The depth gradient across the edge, inside a band 1.5 pixels wide around where
the brightness edge was found, and its 95th percentile against
depth_edge_threshold.
A detector, as Monty's __init__ reads its arguments.
:radius and :sigma_r shape the neighbourhood the tensor is aggregated
over: a Gaussian that falls to nothing at radius pixels from the centre, and
a falloff of sigma_r. :max_center_offset is the one setting that changes
what "an edge" means — nil accepts an edge anywhere in the patch, and a
number accepts it only that close to the centre.
An even :kernel_size is refused: a Gaussian of even size has no centre
pixel, so the blur would be half a pixel off and the edge with it.