13.4.1.6. API 参考#

openmv 包对外暴露的接口主要是用于与摄像头通信的 Camera 类,以及用于表示协议错误的 OMVException 异常体系。两者均在本页中说明。

13.4.1.6.1. Camera 类#

class openmv.Camera(port: str, *, baudrate: int = 921600, crc: bool = True, seq: bool = True, ack: bool = True, events: bool = True, timeout: float = 1.0, max_retry: int = 3, max_payload: int = 4096, drop_rate: float = 0.0)#

通过 USB 串口与 OpenMV 摄像头通信的主机端代理。

参数:
  • port -- 串口设备路径。在 Linux 上,USB CDC 设备为 /dev/ttyACMx,USB 转 UART 桥接设备为 /dev/ttyUSBx。在 macOS 上为 /dev/tty.usbmodem.../dev/cu.usbmodem...。在 Windows 上为 COMx

  • baudrate -- 串口波特率。通过 USB 连接时,921600 是一个魔法值,它会将摄像头从 MicroPython REPL 切换到 OpenMV 协议——在 USB 链路上使用其他任何值都会让摄像头停留在 REPL 模式,因此必须使用默认值。通过 UART 链路时,该值是实际的线路波特率,可以在两端自由设置。

  • crc -- 对每个数据包启用 CRC 校验。

  • seq -- 启用每个数据包的序列号。

  • ack -- 要求数据包确认。

  • events -- 启用来自摄像头的事件通知。

  • timeout -- 每次操作的超时时间,单位为秒。

  • max_retry -- 数据包失败后引发异常前的重试次数。

  • max_payload -- 协商的最大有效负载大小,单位为字节。摄像头可能会向下协商。

  • drop_rate -- 仅供测试使用的丢包概率,取值范围为 [0.0, 1.0]。生产环境中请保持为 0.0

该类支持上下文管理器协议;with Camera(port) as cam: 会在进入时调用 connect(),在退出时调用 disconnect()

13.4.1.6.2. 连接#

Camera.connect() None#

打开串口并执行协议握手。作为副作用,会填充缓存状态(通道列表、系统信息、版本信息)。由上下文管理器自动调用。

Camera.disconnect() None#

关闭串口并释放传输层。在上下文管理器退出时自动调用。

Camera.is_connected() bool#
返回:

如果串口已打开,则返回 True

Camera.reset() None#

复位摄像头。由于摄像头会重新启动,连接将被断开。

Camera.boot() None#

让摄像头跳转到其引导加载程序。由于摄像头会重新启动,连接将被断开。

Camera.update_capabilities() None#

与摄像头重新协商协议能力(CRC、序列检查、ACK、事件、最大有效负载)。摄像头会报告它能处理的最大有效负载;主机的请求会被裁剪到该值,并将商定的设置推送回去。由 connect() 自动调用——除非需要在已有连接上重新协商构造函数标志,否则没有理由从用户代码中调用它。

Camera.poll_events() None#

运行一次传输层的接收路径,以便在不发送命令的情况下消费来自摄像头的任何待处理事件。这在长时间运行、可能数分钟内没有其他 I/O,又希望及时呈现通道注册事件的程序中很有用。

13.4.1.6.3. 脚本执行#

Camera.exec(script: str) None#

script(一段 Python 源码字符串)上传到摄像头的标准输入缓冲区并开始运行。

参数:

script -- 要执行的 MicroPython 源码。

Camera.stop() None#

中断正在运行的脚本。等同于 IDE 的停止按钮。

Camera.read_stdout() str | None#

读取正在运行的脚本自上次调用以来写入 stdout 的所有字节。

返回:

解码后的输出字符串,如果没有等待的数据则为 None

13.4.1.6.4. 流式传输#

Camera.streaming(enable: bool, raw: bool = False, resolution: tuple[int, int] | None = None) None#

打开或关闭帧流,并选择传输时使用的格式。

参数:
  • enable -- True 启用流式传输,False 禁用它。

  • raw -- 当为 False(默认)时,摄像头会在将每一帧放入流通道之前对其进行 JPEG 压缩,并由 read_frame() 在主机端解压缩。当为 True 时,摄像头会发送未经压缩的已捕获像素缓冲区——对于没有硬件 JPEG 支持的摄像头,这是正确的选择,因为在这种情况下软件压缩是循环中最慢的环节。

  • resolution -- (width, height) 指定摄像头在发送前将每一帧原始图像缩小到的目标尺寸,因为未压缩的帧比 JPEG 压缩后的帧大得多。当 raw=True 时为必填项;否则将被忽略。

Camera.read_frame() dict | None#

从流通道读取最新的帧。

返回:

如果没有等待的帧,则为 None;否则为一个字典,包含以下键:widthint,像素),heightint,像素),formatint,摄像头声明的像素格式标识符),depthint,JPEG / PNG 帧的压缩图像大小,单位为字节;未压缩格式不使用此项),databytes,长度为 width * height * 3 的 RGB888 数据),以及 raw_sizeint,摄像头在解码前通过 USB 发送的字节数)。

13.4.1.6.5. 自定义通道#

Camera.has_channel(name: str) bool#
返回:

如果摄像头上存在以 name 注册的通道,则为 True

Camera.channel_size(name: str) int#
返回:

指定名称的通道当前可用的字节数,当通道为空或不存在时为 0

Camera.channel_read(name: str, size: int | None = None) bytes | None#

从自定义通道读取数据。

参数:
  • name -- 由摄像头端脚本注册的通道名称。

  • size -- 要读取的字节数,或为 None 以读取所有可用数据。

返回:

读取到的字节,如果通道不存在则为 None

Camera.channel_write(name: str, data: bytes) bool#

data 写入自定义通道。超过有效负载大小的写入会自动拆分到多个数据包中。

参数:
  • name -- 由摄像头端脚本注册的通道名称。

  • data -- 要发送的类字节有效负载。

返回:

如果通道存在且写入已发送,则为 True,否则为 False

Camera.read_status() dict[str, bool]#

轮询每个已注册的通道。

返回:

将通道名称映射到“数据已就绪可读取”布尔值的字典。

Camera.update_channels() None#

从摄像头刷新缓存的通道列表。当某个通道注册事件到达后,下次按名称查找通道时会自动运行;想要立即获知新注册通道的应用程序可以直接调用此方法。

Camera.get_channel(name: str | None = None, channel_id: int | None = None) int | str | None#

通过名称查找通道(返回其数字 ID)或通过 ID 查找通道(返回其名称)。如果有待处理的通道注册事件,会先通过 update_channels() 刷新通道缓存。

参数:
  • name -- 要解析为 ID 的通道名称。

  • channel_id -- 要解析为名称的通道 ID。

返回:

对应的 ID 或名称,如果通道不存在则为 None。必须提供 namechannel_id 之一。

13.4.1.6.6. 设备自省#

Camera.version() dict#

返回摄像头的协议、引导加载程序和固件版本三元组。在 connect() 之后缓存。每个三元组都是一个由 int 组成的 (major, minor, patch) 元组:

  • protocol_version —— 摄像头实现的 OpenMV 线路协议版本。

  • bootloader_version —— 驻留在闪存中的引导加载程序映像。

  • firmware_version —— 当前运行的 MicroPython 固件。

Camera.system_info() dict#

返回摄像头的硬件能力和内存信息。在 connect() 之后缓存。返回字典的键分为四组。

标识

  • cpu_id —— 32 位 CPU 标识符。

  • device_id —— 由 3 个 32 位字组成的元组,即固化在芯片中的唯一设备序列号。

  • chip_id —— 由 3 个 32 位字组成的元组,连接到摄像头的每个图像传感器对应一项。

  • usb_vid —— USB 厂商 ID。

  • usb_pid —— USB 产品 ID。

内存大小(均以千字节为单位)

  • flash_size_kb —— 内部闪存总量。

  • ram_size_kb —— RAM 总量。

  • framebuffer_size_kb —— 为图像捕获保留的 RAM。

  • stream_buffer_size_kb —— 为向主机传送帧的流通道保留的 RAM。

能力标志(每个功能对应一个布尔值,均命名为 <feature>_present

  • gpu_present —— 图形处理单元。

  • npu_present —— 神经处理单元。

  • isp_present —— 图像信号处理器。

  • venc_present —— 视频编码器。

  • jpeg_present —— JPEG 硬件编码器。

  • dram_present —— 外部 DRAM。

  • crc_present —— CRC 加速器。

  • pmu_present —— 性能监控单元。

  • wifi_present —— Wi-Fi 无线电。

  • bt_present —— 蓝牙无线电。

  • sd_present —— SD 卡插槽。

  • eth_present —— 以太网 PHY。

  • multicore_present —— 多个 CPU 内核。

其他

  • usb_highspeed —— 布尔值,当 USB 以高速模式(USB 2.0 HS,480 Mbps)枚举时为 True

  • pmu_eventcnt —— 可用的 PMU 事件计数器数量;没有 PMU 时为 0

Camera.print_system_info() None#

将格式化的系统信息块以 INFO 级别记录到 logging。CLI 在连接时会使用此方法。

13.4.1.6.7. 诊断#

Camera.host_stats() dict#
返回:

主机端跟踪的传输层计数器:sentreceivedchecksumsequence

Camera.device_stats() dict#
返回:

摄像头端跟踪的传输层计数器:sentreceivedchecksumsequenceretransmittransportsent_eventsmax_ack_queue_depth

13.4.1.6.8. 性能分析器#

性能分析器会针对已插桩的固件模块——目前为 imagemlulab——报告每个函数的调用次数以及最小 / 最大 / 总执行时间。函数的进入和退出会在编译期被拦截;运行时在每次进出时采样一个单调微秒计数器,按函数累加结果,并通过 profile 通道将该表暴露给主机。

只有在向 make 传入 PROFILE_ENABLE=1 时,性能分析器才会被编译进固件。出厂固件映像不包含它——构建系统为被跟踪模块添加的 -finstrument-functions 标志会带来不小的运行时开销,因此性能分析构建是针对特定调试会话从源码专门生成的。当固件未使用该标志构建时,profile 通道不会被注册,本页中的每个性能分析器方法都会静默返回而不执行任何操作。

Arm 性能监控单元(PMU)是 Cortex-M55 的硬件计数器块——一小组可配置的计数器,可在不拖慢被测代码的情况下跟踪周期数、缓存命中与未命中、分支行为以及其他架构定义的事件。在配备了 PMU 的摄像头上——即 AE3 和 N6,这两款是 OpenMV 产品线中基于 M55 构建的摄像头——性能分析器会在采集时序数据的同时采样这些计数器,事件总数会出现在每个按函数记录的条目中。没有 PMU 的摄像头仍会产生时序记录;事件字段会返回零,而 profiler_event() 是一个空操作。

Camera.profiler_mode(exclusive: bool = False) None#

在包含式和排他式计时之间切换。包含式计时会将被调用者的时间计入调用者;排他式计时则不会。

参数:

exclusive -- True 选择排他式计时,False 选择包含式计时。

Camera.profiler_reset(config: list | None = None) None#

清除所有性能分析计数器。config=None 还会恢复默认的 PMU 事件分配。

参数:

config -- 为将来的每计数器配置覆盖预留。传入 None 以保留默认值。

Camera.profiler_event(counter_num: int, event_id: int) None#

将某个 PMU 计数器槽绑定到特定的硬件事件。

参数:
  • counter_num -- 计数器索引。

  • event_id -- 架构定义的事件标识符。

Camera.read_profile() list[dict] | None#

返回自上次复位以来收集的每个函数的性能分析记录。每条记录都是一个字典,包含 addresscallercall_countmin_ticksmax_tickstotal_tickstotal_cycles,以及一个大小与摄像头的 pmu_eventcnt 一致的 events 元组。

返回:

记录字典的列表,如果性能分析通道不可用或尚未收集到数据,则为 None

13.4.1.6.9. 子类化与通道内部机制#

上面记录的方法涵盖了本包的所有常见用法。少数模式——处理主机希望响应的摄像头端事件、为多步交换锁定通道、与承载结构化数据而非字节流的通道通信,或驱动通道特定的控制命令——需要用到 Camera 以下划线为前缀保留的方法。这些名称按约定是私有的(Python 并不会对它们进行名称修饰),需要使用它们的应用程序应当对 Camera 进行子类化,或直接调用这些方法。

通过子类化来响应事件。 摄像头发出的每个事件都会经由 Camera._handle_event() 到达。对 Camera 进行子类化并重写该方法,是应用程序响应其摄像头端脚本所引发事件的方式;事件 页面完整地介绍了这一模式。

Camera._handle_event(channel_id: int, event: int) None#

分派来自摄像头的一个事件。每当事件数据包到达时,由传输层调用。在子类中重写以添加应用程序特定的处理;调用 super()._handle_event(...) 以保留默认行为(在 CHANNEL_REGISTERED 时刷新通道列表、在 stream 通道上跟踪帧就绪状态、stdin 通道的启动 / 停止日志记录)。

参数:
  • channel_id -- 系统事件为 0,否则为已注册的通道 ID。

  • event -- 事件标识符;系统事件的值来自 EventType 枚举,通道事件的值则来自摄像头端通道后端所选定的值。

添加自身协议通信方法的子类应当用 retry_if_failed() 装饰它们,以便它们继承本页所有出厂方法都具有的同样的重新同步与重试行为。

static Camera.retry_if_failed(func)#

装饰器。包装一个实例方法,使其在传输层引发 ResyncException 时重试一次。任何调用 _send_cmd_wait_resp()(直接调用或通过某个 _channel_* 包装器调用)的方法都应携带此装饰器:

class MyCamera(Camera):
    @Camera.retry_if_failed
    def my_custom_command(self, payload):
        return self._send_cmd_wait_resp(Opcode.MY_CMD,
                                        0, payload)

通道锁定可确保通道状态在两个相关操作之间不会发生变化(例如,在一个不断追加数据的通道上,一次 _channel_size() 之后跟着一次 _channel_read())。read_frame()read_profile() 在内部使用了它;以多步访问驱动自定义通道的应用程序也是如此。

Camera._channel_lock(channel_id: int) bool#

获取通道的独占锁。在锁被释放之前,对同一通道的其他主机操作将被阻塞。

参数:

channel_id -- 数字通道 ID,通常通过 get_channel() 解析得到。

返回:

授予锁时为 True

Camera._channel_unlock(channel_id: int) bool#

释放先前通过 _channel_lock() 获取的锁。始终与加锁调用配对;使用 try / finally 以确保即使其间的读取引发异常,解锁也会发生。

参数:

channel_id -- 数字通道 ID,通常通过 get_channel() 解析得到。

结构化通道承载的是结构化记录而非扁平的字节流。性能分析器通道就是出厂示例:其形状为 (record_count, record_size),想要知道有多少条记录在等待的主机读取的是形状而非字节大小。

Camera._channel_shape(channel_id: int) tuple[int, ...]#

读取通道的形状描述符。

参数:

channel_id -- 数字通道 ID,通常通过 get_channel() 解析得到。

返回:

一个由无符号 32 位整数组成的元组,描述通道的布局。其含义因通道而异。

通道特定的控制命令——启动、停止、复位、配置——通过单个操作码(CHANNEL_IOCTL)传输,并附带一个通道特定的命令编号和一个可选的 struct.pack 有效负载。诸如 stop()exec()streaming() 等出厂方法都是对针对 stdinstream 通道的 _channel_ioctl() 调用的薄封装;定义了自身 ioctl 菜单的自定义摄像头端通道也以同样的方式驱动。

Camera._channel_ioctl(channel_id: int, cmd: int, fmt: str | None = None, *args) bytes | None#

对通道发出一个 ioctl 命令。

参数:
  • channel_id -- 数字通道 ID,通常通过 get_channel() 解析得到。

  • cmd -- 由摄像头端通道后端定义的命令编号。

  • fmt -- 可选的 struct 格式字符串,用于参数元组。对于不接受参数的 ioctl,传入 None

  • args -- 与 fmt 匹配的值。

返回:

通道返回的任何有效负载,或为 None

公共通道方法的按 ID 字节流变体会跳过名称到 ID 的查找,并接受一个显式的字节 offset——这对于从大缓冲区中间读取一块数据很有用(例如 profile 通道的记录)。

Camera._channel_size(channel_id: int) int#
参数:

channel_id -- 数字通道 ID,通常通过 get_channel() 解析得到。

返回:

通道上当前可用的字节数。

Camera._channel_read(channel_id: int, offset: int, length: int) bytes#

offset 开始读取 length 个字节。多数据包读取会自动重新组装。

参数:
  • channel_id -- 数字通道 ID,通常通过 get_channel() 解析得到。

  • offset -- 开始读取的字节偏移量。

  • length -- 要读取的字节数。

Camera._channel_write(channel_id: int, data: bytes, offset: int = 0) None#

在给定的 offset 处写入 data。多数据包写入会自动拆分到多个数据包中。

参数:
  • channel_id -- 数字通道 ID,通常通过 get_channel() 解析得到。

  • data -- 要写入的类字节有效负载。

  • offset -- 开始写入的字节偏移量。

协议原语是该类暴露的最底层接口——原始的发送命令、获取原始通道列表以及手动重新同步等条目,上面的一切最终都建立在它们之上。当发送该类尚未封装的操作码,或在子类中实现自定义恢复逻辑时,应用程序会用到它们。

Camera._send_cmd_wait_resp(opcode: int, channel: int = 0, data: bytes = b'') bytes | None#

发送一个协议命令并等待摄像头的响应。本节中其他每个方法都建立在这个原语之上。

参数:
  • opcode -- 命令编号。出厂的 Opcode 枚举列出了固件附带的代码,但该参数只是一个整数——自定义固件构建可以定义并响应其自己的命令。

  • channel -- 通道 ID,系统命令则为 0

  • data -- 命令特定的有效负载。

返回:

响应有效负载,对于像 Opcode.SYS_RESETOpcode.SYS_BOOT 这类会断开连接的命令则为 None

Camera._channel_list() dict#

从摄像头获取当前通道列表,而不触及 update_channels() 所填充的缓存字典 channels_by_idchannels_by_name。这对于想要直接检查摄像头通道状态的子类很有用。

返回:

将通道 ID 映射到 {'name': str, 'flags': int} 的字典。

Camera._resync() None#

从头重新运行协议握手。在初始连接时由 connect() 自动调用,并由每个捕获传输层 OMVException 的公共方法自动调用。在子类中实现自身恢复循环的应用程序可以在处理完底层错误后直接调用此方法。

13.4.1.6.10. 异常#

exception openmv.OMVException#

所有协议级错误的基类。下面的三个子类都继承自它,因此单个 except OMVException 即可覆盖整个错误范围。

exception openmv.TimeoutException#

摄像头未在配置的超时时间内响应。是 OMVException 的子类。

exception openmv.ChecksumException#

数据包的 CRC 不匹配。在协议耗尽其重试预算后引发。是 OMVException 的子类。

exception openmv.SequenceException#

重试后到达的数据包带有意外的序列号。是 OMVException 的子类。