6.4. Dtypes

Тип элементов ndarray называется его dtype. Dtype определяет сразу три вещи: сколько байт занимает каждый элемент, как эти байты интерпретируются и какой диапазон значений может хранить массив. Выбор правильного dtype – это единственное важнейшее решение, влияющее на использование RAM на камере.

6.4.1. Поддерживаемые dtype

numpy на камере поддерживает небольшой набор dtype:

dtype

байты

диапазон

uint8

1

от 0 до 255

int8

1

от -128 до 127

uint16

2

от 0 до 65 535

int16

2

от -32 768 до 32 767

float

4

IEEE 754 одинарной точности

bool

1

True / False

Здесь нет int32 или int64, а сборка ulab от OpenMV не включает необязательный dtype complex.

Выбирайте тип, соответствующий оборудованию, которое произвело данные. 8-битному отсчёту ADC нужен uint8; 12-битный отсчёт ADC помещается в uint16; пиксель яркости с камеры в оттенках серого помещается в uint8 – экономя в четыре раза больше RAM, чем потребовал бы используемый по умолчанию float.

6.4.2. Используемый по умолчанию dtype

Используемый по умолчанию dtype у каждого конструктора на странице Создание массивов – это float. Это редко то, что нужно приложению при работе с данными датчика. Явно передавайте dtype= всякий раз, когда естественная ширина меньше:

sensor = np.array(samples, dtype=np.uint16)

Повторное оборачивание целочисленного массива без аргумента dtype= копирует и преобразует в float, что стоит и времени, и RAM. Когда производительность важна, указывайте dtype.

6.4.3. Dtype существующего массива

dtype считывает dtype массива в виде целочисленного кода типа, который массив несёт внутри себя:

a = np.array([1, 2, 3], dtype=np.uint8)
print(a.dtype)            # 66 (the integer value of ``'B'``)

Целые числа кода типа соответствуют константам, доступным в модуле numpynumpy.uint8, numpy.int8, numpy.uint16, numpy.int16, numpy.float, numpy.bool – поэтому сравнение dtype с константой модуля – это способ, которым скрипт ветвится по тому, что содержит массив:

if a.dtype == np.uint8:
    ...  # uint8 branch

6.4.4. Правила повышения типа

Два массива разных dtype могут быть операндами одного и того же оператора. numpy выбирает тип результата по короткой таблице:

слева

справа

результат

uint8

int8

int16

uint8

int16

int16

uint8

uint16

uint16

int8

int16

int16

int8

uint16

uint16

uint16

int16

float

любой

float

float

Строка uint16 / int16 повышается сразу до float, потому что numpy на камере не имеет 32-битного целочисленного dtype.

Когда у бинарного оператора с одной стороны находится скаляр Python, скаляр преобразуется в одноэлементный массив наименьшего подходящего dtype: 123 становится массивом uint8, -1000 становится int16, а Python float становится float.

6.4.5. Целочисленное переполнение заворачивается

Операции над двумя массивами одного целочисленного dtype сохраняют этот dtype, даже когда результат переполняется. Перенос молча отбрасывается:

a = np.array([200, 200], dtype=np.uint8)
b = np.array([100, 100], dtype=np.uint8)
print(a + b)

Вывод:

array([44, 44], dtype=uint8)

Результат равен 300 mod 256 == 44. Когда промежуточному значению нужен больший диапазон, чем позволяет dtype входных данных, сначала выполните приведение:

c = np.array(a, dtype=np.uint16) + b
# array([300, 300], dtype=uint16)

Это правило применяется к каждому целочисленному оператору – +, -, *, //, %, &, |, ^. Массивы float никогда не переполняются (вместо этого они повышаются до бесконечности), поэтому трюк с приведением нужен только в целочисленном случае.