6.4. Dtypes¶
Тип элементов ndarray называется его dtype. Dtype определяет сразу три вещи: сколько байт занимает каждый элемент, как эти байты интерпретируются и какой диапазон значений может хранить массив. Выбор правильного dtype – это единственное важнейшее решение, влияющее на использование RAM на камере.
6.4.1. Поддерживаемые dtype¶
numpy на камере поддерживает небольшой набор dtype:
dtype |
байты |
диапазон |
|---|---|---|
|
1 |
от 0 до 255 |
|
1 |
от -128 до 127 |
|
2 |
от 0 до 65 535 |
|
2 |
от -32 768 до 32 767 |
|
4 |
IEEE 754 одинарной точности |
|
1 |
|
Здесь нет 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'``)
Целые числа кода типа соответствуют константам, доступным в модуле numpy – numpy.uint8, numpy.int8, numpy.uint16, numpy.int16, numpy.float, numpy.bool – поэтому сравнение dtype с константой модуля – это способ, которым скрипт ветвится по тому, что содержит массив:
if a.dtype == np.uint8:
... # uint8 branch
6.4.4. Правила повышения типа¶
Два массива разных dtype могут быть операндами одного и того же оператора. numpy выбирает тип результата по короткой таблице:
слева |
справа |
результат |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
любой |
|
|
Строка 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 никогда не переполняются (вместо этого они повышаются до бесконечности), поэтому трюк с приведением нужен только в целочисленном случае.