Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

A Block Transform Demo

stough 202-

Preview: Glyph Image Converter

We saw in spatial filtering that a mask of weights is placed over each pixel in the image, followed by computing the inner product (sum of products) of the corresponding mask weights and pixel values. This resulted in a filtered image that might show a blurred version of the original, or else horizontal or vertical edges, or edginess generally, or any of a number of local spatial characteristics. But the spatial neighborhood represented by the mask moves by one pixel each iteration.

However, another way of viewing an image is as a grid of non-overlapping blocks, each block containing a number of pixels. A block transform is where we replace each block from the original image with a block of our choosing. We could choose to replace the whole collection of pixels in a block with the average or median color of the block. Or, as seen in this example, we can replace the block with a combination of the original block and a glyph image (basically, just a small image) that we’d like to overlay on our original.

Imports

We add several functions from the skimage module:

  • view_as_blocks decomposes an image into blocks of a defined size.

  • montage allows us to reassemble a bunch of blocks back into a grid.

  • We use resize or rescale to ensure that our image and glyphs have a common denominator that we can use as a block size.

<Figure size 900x300 with 2 Axes>
((4, 36, 47, 3), dtype('float32'), np.float32(0.0), np.float32(1.0))
<Figure size 400x400 with 1 Axes>

Our four glyphs are little images of the two-bit strings 00, 01, 10 and 11: white digits on black, here scaled to 36×4736 \times 47 pixels each. Every glyph has the same shape, which will be our block size.

[1080, 1960]
<Figure size 800x300 with 2 Axes>

view_as_blocks needs the image dimensions to be exact multiples of the block size, so we resize the image up to the next multiple of the glyph size in each dimension. (The list comprehension in the cell before computes that new size: for each dimension d and block size s, d + (s - d % s).) The change is a few pixels at most, so the two look identical.

(31, 41, 36, 47, 3)

view is a 5D view into the image: a 31×4131 \times 41 grid of blocks, each block 36×47×336 \times 47 \times 3. It’s a view in the numpy sense: no pixels are copied, we’re just indexing the same memory differently. Reshaping to block_view flattens the grid into a single list of 31⋅41=127131 \cdot 41 = 1271 blocks that we can loop over.

The block transform

Here’s where we decide what to do with each block. For every block we pick one of the four glyphs at random, and then combine the glyph with the block. The commented-out lines are alternatives I’ve played with, such as filling the glyph’s background with the block’s mean color, or coloring its digits with the complementary color. The active version is the last line: add half the glyph’s intensity on top of the block’s original pixels and clip, so the digits appear as a bright overlay. Try commenting it out and uncommenting one of the others.

<Figure size 800x500 with 1 Axes>

montage reassembles the list of processed blocks into the original grid layout. The result is our photo seemingly rendered in binary.

This is a playful example, but the structure (split into blocks, transform each block independently, reassemble) is exactly the structure of JPEG compression. There, the “transform” re-expresses each 8×88 \times 8 block in terms of a set of basis patterns and throws away the patterns that matter least. Working block by block keeps the computation local and simple, at the cost of possible visible seams between blocks when the transform is too aggressive.