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

不存在 int32int64,并且 OpenMV 的 ulab 构建未启用可选的 complex dtype。

选择与产生数据的硬件相匹配的类型。8 位 ADC 采样应使用 uint8;12 位 ADC 采样可放入 uint16;来自灰度摄像头的亮度像素可放入 uint8 ——这比默认 float 节省四倍的 RAM。

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.uint8numpy.int8numpy.uint16numpy.int16numpy.floatnumpy.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 数组永远不会溢出(它们会改为提升为无穷大),所以只有在整数情形下才需要这个转换技巧。