6.18. Зображення та ndarray

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

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

6.18.1. Зображення до ndarray

to_ndarray() виділяє новий ndarray і копіює піксельні дані зображення в нього (з відображенням dtype, описаним нижче). Це ніколи не є виглядом на кадровий буфер зображення – масив 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. Звертайтеся до мосту лише для операцій, які бібліотека зображень ще не надає.