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= 让应用程序可以为生成的图像提供一个已分配的 bytearraycopy_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 表达式快得多。只有在图像库尚未提供的运算上,才应该使用这种桥接。