TinyML Image Inputs: Preserve Edge Impulse’s Packed-Pixel Signal Before DSP

A correct camera preview does not prove that an image impulse receives the intended pixels. In the Edge Impulse path examined here, each floating-point signal element carries the numeric value of one packed RGB pixel. Returning normalized channel values at that interface changes what the image extractor interprets.

The source baseline is SDK v1.95.22, commit 732b2eac85c1874746fa6fa5a2876de207c59e39, specifically its standard image extractors. Custom DSP, direct tensor input and the separate DRP-AI path are outside this scope. No camera capture, example executable, bitmap generation, model inference or prediction comparison was run.

Name the representation at each boundary

Assume capture, dimensions and pixel coordinates have already been qualified. There are still three different representations to distinguish: camera storage, the signal consumed by image DSP, and the channel values supplied to the model.

A camera might store pixels as RGB565 words. The callback’s destination type is float *, but that does not make each destination element a normalized red, green or blue channel. For this path, one element represents the whole pixel as a numeric 0xRRGGBB value. DSP subsequently expands that element into channels or a grayscale value.

Edge Impulse’s RGB565 adapter example makes the boundary concrete: it decodes channel components, constructs the packed integer value and assigns that numeric value to a floating-point callback element. It advances once per pixel. This is numeric conversion, not reinterpretation of camera bytes as a floating-point object’s bit pattern.

The example is pinned separately to commit 29a15ccf992bf2048a0f021c4b04a5b8c67878c6. It was not compiled against the newer SDK here. Its README explains on-demand conversion, while the actual program constructs synthetic pixels and writes bitmaps rather than running a classifier.

Trace the numeric cast before the tensor

In the pinned image DSP source, extract_image_features numerically casts each callback element to uint32_t. It extracts red from bits 16 through 23, green from bits 8 through 15 and blue from bits 0 through 7. The three-channel branch divides each component by 255 and emits them in RGB order.

Consider a derived fixture, not observed output:

Decoded components: R = 17, G = 34, B = 51
One signal element: numeric value 0x112233, stored as float
Float RGB DSP output: 17/255, 34/255, 51/255

Returning those three fractions directly from the callback bypasses the required packed-pixel representation. A correct tensor-scale calculation applied at the wrong boundary does not restore the missing pixel encoding. Likewise, dividing the packed integer by 255 before handing it to DSP changes the integer that will be unpacked.

Camera storage is decoded into one numeric packed RGB value per float signal element, then image DSP expands it into channels or grayscale before tensor encoding. A derived gray fixture contrasts equal-channel 0x7F7F7F with bare scalar 127, which unpacks as blue-only 0x00007F.
Original representation map for the standard Edge Impulse SDK v1.95.22 image path. Pixel counts use a separately pinned example export; RGB and grayscale values are derived arithmetic, not measured inference output.

Keep input cardinality separate from channel count

The pinned example’s generated metadata describes a 96 × 96 input, 9,216 raw samples, one raw value per sample and 27,648 neural input features. Its signal therefore contains 9,216 pixel elements, while the RGB feature representation contains three times as many channel elements.

These are that export’s dimensions, not universal SDK constants. Read the actual export before allocating buffers or assigning a signal length. Check both counts explicitly; a buffer being large enough does not establish that its elements have the right meaning.

Grayscale selection still follows RGB unpacking

The standard float extractor chooses one output channel for grayscale, but first unpacks the same RGB word. It then applies the displayed weighted luma calculation. A grayscale model does not make an unadorned grayscale byte the expected callback representation.

For a proposed midlevel fixture, the packed value 0x7F7F7F supplies equal components R = G = B = 127. In ideal arithmetic, the shown luma weights preserve the normalized level 127/255. The bare numeric value 127, however, becomes 0x00007F: red and green are zero, and only blue is 127. Its ideal float-path luma is 0.114 * (127/255).

That comparison is derived from the source. It is not a measured brightness error or a universal promise about grayscale sensors and custom blocks.

Leave downstream scaling with the selected extractor

The quantized image extractor also begins with numeric packed-pixel decoding. Its later scaling and quantization depend on the selected branch. The component - 128 shortcut is conditional on a particular scale, zero point and image-scaling mode; it is not a general callback rule.

Do not pre-quantize the packed word merely because the model input is integer-valued. Inspect the configured path and verify the tensor after image DSP, separately from verifying the signal before it.

Test the boundary before judging predictions

A proposed adapter fixture should include distinct channel values, equal-channel gray, black and white in a complete immutable image. Record decoded camera components, packed callback values and copied DSP features. Assert pixel-element count separately from model-channel count.

Use the actual export’s scaling branch for any later tensor comparison, with a justified numerical tolerance. Only after the representation checks pass should unchanged-model predictions be evaluated. A plausible label or a visually correct preview cannot replace evidence from the intervening signal-to-DSP boundary.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *