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