6.18. Изображения и ndarray

Класс Image – это быстрый интерфейс для работы с пикселями в нативном формате камеры: каждый его метод работает напрямую с буфером кадра в нативном формате пикселей камеры. numpy – это универсальный числовой интерфейс для всего остального. Два метода связывают их между собой:

Вместе они позволяют приложению захватить кадр, передать его в numpy для произвольного преобразования, а затем вернуть результат обратно в изображение, чтобы отобразить его, сохранить или передать дальше в остальную часть библиотеки изображений.

6.18.1. Изображение в ndarray

to_ndarray() выделяет новый ndarray и копирует в него данные пикселей изображения (с приведением типов согласно таблице ниже). Это никогда не представление на буфер кадра изображения – массив numpy всегда владеет собственными байтами. Сигнатура имеет вид to_ndarray(dtype, *, buffer=None), а форма результата зависит от формата изображения:

  • GRAYSCALE – двумерный массив формы (height, width).

  • RGB565 – трёхмерный массив формы (height, width, 3), плоскости в порядке R/G/B.

Аргумент dtype определяет, как отображается каждое 8-битное значение пикселя v:

dtype

элемент

отображение для 8-битного значения пикселя v

'B'

uint8

v (без изменений)

'b'

int8

v - 128 (центрировано вокруг нуля)

'f'

float32

float(v) (0.0 … 255.0)

Пример – представление кадра в оттенках серого как матрицы uint8:

import csi
from ulab import numpy as np

csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.GRAYSCALE)
csi0.framesize(csi.QVGA)

img = csi0.snapshot()
a   = img.to_ndarray('B')        # shape (240, 320), dtype=uint8

print(a.shape, a.dtype)
print("mean brightness:", np.mean(a))

Ключевое слово buffer= позволяет приложению повторно использовать уже выделенный bytearray, чтобы камере не приходилось выделять новый на каждый кадр:

buf = bytearray(320 * 240)
while True:
    img = csi0.snapshot()
    a   = img.to_ndarray('B', buffer=buf)
    # ... process a ...

6.18.2. ndarray в изображение

Для обратного преобразования передайте ndarray в качестве первого аргумента в image.Image. Конструктор выделяет новый буфер изображения и копирует в него значения массива, ограниченные и округлённые до диапазона 0..255:

image.Image(arr, *, buffer=None, copy_to_fb=False)

Конструктор определяет геометрию и формат пикселей по форме массива:

  • форма (h, w) – изображение GRAYSCALE.

  • форма (h, w, 3) – изображение RGB565.

ndarray должен иметь dtype float; на сегодня конструктор поддерживает только этот случай. Значения округляются и ограничиваются диапазоном 0..255.

buffer= позволяет приложению передать уже выделенный bytearray для результирующего изображения. copy_to_fb=True записывает результат в буфер кадра камеры, что является правильным выбором, когда результат должен появиться в предпросмотре IDE.

6.18.3. Полный цикл

import csi
import image
from ulab import numpy as np

csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.GRAYSCALE)
csi0.framesize(csi.QVGA)

img = csi0.snapshot()
a   = img.to_ndarray('f')                  # work in float space

a = 255.0 * (a / 255.0) ** 0.5             # gamma correction

out = image.Image(a, copy_to_fb=True)      # back to an image

6.18.4. Когда использовать мост

Этот мост – правильный ответ, когда приложению требуется универсальная числовая операция, которую не предоставляют встроенные методы image – произвольные фильтры, произвольное смешивание, нестандартные нелинейности – или когда данные пикселей нужно объединить с не-изображенческими данными (оси IMU, аудиосэмплы) в одном вычислении.

Это не правильный ответ для высокопроизводительной обработки пикселей, которую класс Image уже покрывает. Встроенные методы работают напрямую с буфером кадра в нативном формате пикселей камеры и работают намного быстрее, чем эквивалентное выражение numpy. Используйте мост для операций, которые библиотека изображений ещё не предоставляет.