9.1. Why arrays¶
The Image class is the right tool for
pixel work because every method on it operates directly
on the camera’s native pixel buffer in a single fast
call. Most of what the application does to a frame –
thresholding, blob finding, AprilTag detection, edge
filters – already lives there.
What the image library does not expose is the rest of the numerical work an OpenMV application runs into:
sensor buffers that are not pixels – ADC samples, IMU axes, microphone audio,
derived numbers from the image that no built-in method returns – a histogram column, a custom blend of two frames, a per-pixel transform the catalogue does not cover,
small linear algebra – the calibration matrix that rectifies the lens, the rotation that fuses the IMU,
signal-processing math – the FFT of a vibration buffer, an IIR filter applied to a sensor’s output, a spectrogram a classifier wants as input.
All of these want the same shape: a buffer of numbers
with one operation applied to every element. A Python
for loop is the obvious way to write it:
for i in range(len(samples)):
samples[i] = samples[i] * cal
The loop works. It is also slow. Python is an interpreted
language, and every iteration of a Python loop carries
the cost of running the interpreter once: look up
samples, read element i, multiply, write back,
advance the loop counter, check the loop condition. On a
buffer of a thousand sensor samples those interpreter
costs add up to tens of milliseconds for what is
fundamentally a quick operation.
That overhead bites every time a script reaches a buffer.
A QVGA grayscale frame is 76 800 pixels; an
accelerometer at 100 Hz delivers a hundred three-axis
samples a second; a microphone fills a 1024-sample
buffer every 64 ms. A pure-Python for loop over any
of those turns a job that should take a few microseconds
into one that takes tens of milliseconds.
9.1.1. Library functions are faster than loops¶
The fix is to express the operation as a single function
call against the whole buffer, instead of a Python loop
over its elements. numpy is exactly that: a
library of array math where every operation is one
already-optimised function that walks the buffer once
from start to finish. np.multiply(samples, cal)
multiplies every element of samples by cal
inside a single call – the same arithmetic the loop did,
without the per-iteration interpreter cost. The same
1000-element multiply that took tens of milliseconds as
a Python loop takes tens of microseconds as a numpy
call.
This is the deal numpy offers across the board:
sum, mean, sin, exp, matrix multiply, FFT, IIR filter –
each one is a single library function that operates on a
whole buffer at once. The trade is that the data has to
live in numpy’s array type and the operation has to be
expressed against that array, not against its elements
one at a time.
9.1.2. Why a list will not do¶
A Python list cannot stand in. A list can hold
any mix of objects – integers, floats, strings, other
lists – so each element of a list of “1000 sensor
samples” is actually a separate int object stored
elsewhere, with the list keeping a reference to it. A
library function looking at a list would still have to
unpack each element through that reference and check its
type – exactly the cost the loop pays. Lists are the
wrong shape for fast array math.
9.1.3. Why bytearray is not enough either¶
A bytearray is the right shape – one
typed buffer, one byte per element, all in one
contiguous block. It is what most byte-oriented
peripheral APIs hand back. What it lacks is the math.
bytearray * 2 repeats the buffer rather than
doubling each value, and there is no sensible meaning
for bytearray + bytearray element by element.
The data structure that combines a typed buffer with
element-wise math is the ndarray.
The next page opens the box.