Monty. Frameworks. Models. Salience. Strategies. Vocus2. OrientationSalience
(ExTbpMonty.Core v0.1.0)
Copy Markdown
View Source
How much a patch looks like an edge, at several orientations.
Mirrors
tbp/monty/frameworks/models/salience/strategies/vocus2/vocus2.py::OrientationSalience.
Four Gabor filters at 0°, 45°, 90° and 135° are run over the detail of the lightness pyramid, and the absolute response is kept. A Gabor filter answers strongly where the image has structure at its wavelength, so "this patch is an edge" becomes a number — and the four orientations together make an edge salient whichever way it runs. Filtering the Laplacian rather than the image keeps a response local to the scale it was found at.
Each kernel has its mean subtracted before use, so a large flat region answers nothing instead of answering in proportion to its brightness: the filter is meant to detect structure, not light.
Measured, not recalled
cv2.getGaborKernel is not a dependency of the port, so both of its halves
were measured against OpenCV 4.10 and reproduced: the Gaussian envelope with
sigma_y = sigma / gamma, the rotation x_r = x cos θ + y sin θ and
y_r = -x sin θ + y cos θ, and — the part that is easy to get wrong — the
modulation entering as cos(2π x_r / λ - ψ), with a minus psi. A plus psi
agrees when psi is zero and is wrong for every other phase, which is how the
mistake survived a first reading.
Filtering is correlation, not convolution, with OpenCV's default
BORDER_REFLECT_101; measured to 1.0e-6 on random images.
Summary
Functions
Correlates a plane with a kernel, as cv2.filter2D does.
The Gabor kernels: one per orientation, each centred on zero mean.
Orientation salience from :n_orientations Gabor filters of :period.
The orientation salience map for one pyramid of lightness.
Types
@type t() :: %Monty.Frameworks.Models.Salience.Strategies.Vocus2.OrientationSalience{ collapse: (Monty.Frameworks.Models.Salience.Strategies.Vocus2.Pyramids.Pyramid.t() -> [[float()]]), combine: (Monty.Frameworks.Models.Salience.Strategies.Vocus2.MapCombine.maps() -> [[float()]]), gamma: float(), kernels: %{optional(String.t()) => [[float()]]}, n_orientations: pos_integer(), period: float(), phase: float(), sigma: float() }
Functions
Correlates a plane with a kernel, as cv2.filter2D does.
The kernel is laid over the pixel unflipped — correlation, not convolution —
and the border is reflected without repeating the edge pixel
(BORDER_REFLECT_101), so [a b c d] continues [c b | a b c d | c b].
The padded plane and the kernel are turned into tuples first, because every pixel reads the same neighbourhood many times and a list is a walk from the front each time.
The Gabor kernels: one per orientation, each centred on zero mean.
The kernel is int(7*sigma + 1) | 1 pixels square — Monty's arithmetic, and
OpenCV's rule for a kernel size it is not given.
Orientation salience from :n_orientations Gabor filters of :period.
:sigma defaults to 0.3 * period, which is Monty's rule of thumb, and
:phase to a quarter turn, which finds edges; a phase of zero finds strips.
@spec process( t(), Monty.Context.t(), Monty.Frameworks.Models.Salience.Strategies.Vocus2.Pyramids.Pyramid.t() ) :: [[float()]]
The orientation salience map for one pyramid of lightness.
Each orientation answers one map — the detail of the pyramid filtered and
rectified — and the maps are combined by :combine, whose default is their
mean.