Monty.Frameworks.Utils.EdgeDetection.ImageOps (ExTbpMonty.Core v0.1.0)

Copy Markdown View Source

The three OpenCV calls Monty's edge detector is built on, written out.

Monty reads pixels with cv2.cvtColor, cv2.Sobel and cv2.GaussianBlur. This application has no OpenCV and no Python in the matching path, so the three are here, with the properties that matter for an edge:

  • Colour to grey is OpenCV's fixed-point luma, (4899 R + 9617 G + 1868 B + 8192) >> 14, scaled to [0, 1]. The coefficients are the usual 0.299/0.587/0.114 in 14-bit fixed point, and the rounding term is what makes a half step round up rather than down.
  • Sobel 3×3 is the separable [-1, 0, 1] × [1, 2, 1] derivative, with pixels outside the patch reflected without repeating the edge (gfedcb | abcdefgh | gfedcba) — OpenCV's BORDER_REFLECT_101, which is what Sobel and GaussianBlur default to. Reflecting rather than zeroing is why a patch's border does not read as a bright edge of its own.
  • Gaussian blur is a separable kernel of the requested size and sigma, centred on the pixel, normalised to sum to one, with the same reflection.

Pixels outside the patch matter more than they look: an image is a window onto a surface that continues past it, and a filter that pretends otherwise invents an edge at the window's rim.

Summary

Functions

A symmetric Gaussian blur of the given kernel size and sigma.

A normalised Gaussian kernel of size weights, centred on the middle one.

The percentile-th value of values, interpolating between neighbours.

An index folded back into 0..(length - 1) without repeating the edge.

The derivative of an image along :x or :y, border reflected.

The patch's brightness, in [0, 1], from its red, green and blue channels.

Functions

gaussian_blur(image, size, sigma)

@spec gaussian_blur([[number()]], pos_integer(), float()) :: [[float()]]

A symmetric Gaussian blur of the given kernel size and sigma.

Two one-dimensional passes, which is what OpenCV does: a size × size kernel is the outer product of one vector of weights with itself, so the passes give the same answer for 2 size multiplications a pixel instead of size².

gaussian_kernel(size, sigma)

@spec gaussian_kernel(pos_integer(), float()) :: [float()]

A normalised Gaussian kernel of size weights, centred on the middle one.

percentile(values, percentile)

@spec percentile([number()], float()) :: float()

The percentile-th value of values, interpolating between neighbours.

numpy's default, which is what Monty asks for: position (n - 1) * p / 100 in the sorted list, then a straight line between the two values it falls between.

reflected(index, length)

@spec reflected(integer(), pos_integer()) :: non_neg_integer()

An index folded back into 0..(length - 1) without repeating the edge.

-1 reads 1 and length reads length - 2 — OpenCV's BORDER_REFLECT_101.

sobel(image, axis)

@spec sobel([[number()]], :x | :y) :: [[float()]]

The derivative of an image along :x or :y, border reflected.

:x finds vertical edges (brightness changing left to right) and :y horizontal ones — OpenCV's order, and the order every caller assumes.

to_gray(rgba)

@spec to_gray([[number()]]) :: [[float()]]

The patch's brightness, in [0, 1], from its red, green and blue channels.

Alpha is ignored: OpenCV's COLOR_RGB2GRAY reads three channels and this reads the same three.

The fixed point is OpenCV's, which takes an 8-bit image. A world that renders fractions of one instead — the stand-in scene does — is scaled here rather than in every consumer: the two conventions meet once, at the fixed point that only speaks the first.