13个重大发布
0.14.0 | 2024年5月5日 |
---|---|
0.12.0 | 2024年4月11日 |
0.10.1 | 2023年12月7日 |
0.10.0 | 2023年11月25日 |
0.5.0 | 2023年7月7日 |
#95 在 硬件支持
每月 1,280 下载量
200KB
4.5K SLoC
neuromorphic_drivers 是一个用于与USB神经形态设备交互的库。驱动程序从头开始编写,考虑到可移植性和性能。
支持设备和功能
名称 | 类型 | 分辨率 | 数据类型 | 掩码 | 同步 | 速率限制器 | 温度 | 照度 |
---|---|---|---|---|---|---|---|---|
Prophesee EVK4 | 摄像头 | 1280 × 720 | DVS, 触发 | ✓ | ✓ | ✓ | ✓ | ✓ |
Prophesee EVK3 HD | 摄像头 | 1280 × 720 | DVS, 触发 | ✓ | - | ✓ | - | - |
此表列出了该库支持的特性。一些设备支持未列出的特性或标记为“未添加”的特性,这些特性尚未添加到 neuromorphic_drivers。
名称 | 链接 |
---|---|
Prophesee EVK4 | https://www.prophesee.ai/event-camera-evk4/ |
Prophesee EVK3 HD | https://www.prophesee.ai/event-based-evk-3/ |
Python
入门指南
pip install neuromorphic_drivers
在Linux上,安装包后运行以下命令以安装UDEV规则。
neuromorphic-drivers-install-udev-rules
以下脚本从连接的设备读取数据。
import neuromorphic_drivers as nd
nd.print_device_list() # print a table that lists connected devices
with nd.open() as device:
for status, packet in device:
if "dvs_events" in packet:
# packet["dvs_events"] is a structured numpy array
# with dtype [("t", "<u8"), ("x", "<u2"), ("y", "<u2"), ("on", "?")])
pass
if "trigger_events" in packet:
# packet["trigger_events"] is a structured numpy array
# with dtype [("t", "<u8"), ("id, "<u1"), ("rising", "?")])
pass
数据包中的键取决于设备,并且仅在关联数组非空时存在。数据包包含可变数量的事件(通常为几千到几百万),覆盖可变的时间量(通常为几十微秒到几毫秒)。
设备配置
配置(针对每个设备特定)定义了所有设备参数,从偏置到感兴趣的区域。它可以在打开设备时指定,并且可以在任何时候更新。设备 d
的默认配置在 python/neuromorphic_drivers/generated/devices/d.py 中定义。
import neuromorphic_drivers as nd
configuration = nd.prophesee_evk4.Configuration(
biases=nd.prophesee_evk4.Biases(
diff_off=170,
diff_on=130,
)
)
with nd.open(configuration=configuration) as device:
for status, packet in device:
...
if not_sensitive_enough:
configuration.biases.diff_on -= 10
configuration.biases.diff_off -= 10
device.update_configuration(configuration)
nd.open
可打开任何支持的设备。但是,如果指定了配置,则仅考虑匹配的设备。向 update_configuration
传递错误的配置类型会引发错误。
速率限制器
Prophesee的EVK4具有硬件事件速率限制器,当传感器每秒生成的数据超过指定数量时,会随机丢弃事件。限制器有两个参数。reference_period_us
定义了计数周期(最大200µs)。maximum_events_per_period
定义了每个周期内事件的数量,超过这个数量限制器会启动。实际限制是maximum_events_per_period / reference_period_us
事件/µs。
import neuromorphic_drivers as nd
configuration = nd.prophesee_evk4.Configuration(
rate_limiter=nd.prophesee_evk4.RateLimiter(
reference_period_us=200,
maximum_events_per_period=4000,
)
)
with nd.open(configuration=configuration) as device:
...
原始模式
如果数据速率高,将原始USB数据转换为事件可能是一个昂贵的操作。原始模式跳过解析,如果只是想将数据存储到文件以供离线处理,则可能很有用。
import neuromorphic_drivers as nd
with nd.open(raw=True) as device:
for status, packet in device:
# in raw mode, packet is a "bytes" object
其他开放选项
def open(
# initial device configuration
configuration: typing.Optional[Configuration] = None,
# timeout for each iteration (for status, packet in device)
# By default, the iterator blocks until data is available.
# In some cases (other time-driven calculations, graphics...),
# it can be desirable to run an iteration of the loop even if no data is available.
# If iterator_timeout is not None, packet may be None.
iterator_timeout: typing.Optional[float] = None,
# whether to skip data parsing
raw: bool = False,
# device serial number, None selects the first available device.
# Use nd.list_devices() to get a list of connected devices and their serials.
serial: typing.Optional[str] = None,
# USB software ring configuration, None falls back to default.
usb_configuration: typing.Optional[UsbConfiguration] = None,
# maximum number of raw USB packets merged to create a packet returned by the iterator.
# Under typical conditions, each iterator packet is built from one USB packet.
# However, if more than one USB packet has already been received,
# the library builds the iterator packet by merging the USB packets,
# to reduce the number of GIL (Global Interpreter Lock) operations.
# While merging speeds up processing in such cases, it can lead to large latency spikes if too many packets are merged at once.
# iterator_maximum_raw_packets limits the number of packets that are merged, even if more USB packets have been received.
# The remaning USB packets are not dropped but simply returned on the next iteration(s).
iterator_maximum_raw_packets: int = 64,
): ...
USB配置的字段在设备间是相同的,但默认值取决于设备。以下值适用于Prophesee EVK4。
@dataclasses.dataclass
class UsbConfiguration:
buffer_length: serde.type.uint64 = 131072 # size of each buffer in the ring, in bytes
ring_length: serde.type.uint64 = 4096 # number of buffers in the ring, the total size is ring_length * buffer_length
transfer_queue_length: serde.type.uint64 = 32 # number of libusb transfers submitted in parallel
allow_dma: bool = False # whether to enable Direct Memory Access
更多示例
有关不同用法示例,请参阅python/examples。
python/examples/any_display.py实现了一个使用GPU计算指数衰减的实时事件查看器。它需要vispy和glfw(pip install vispy glfw pyopengl
)。
python/examples/evk4_plot_hot_pixels生成图表并需要Plotly(pip install plotly pandas kaleido
)。
贡献
本地构建(首次运行)。
cd python
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install maturin numpy
maturin develop # or maturin develop --release to build with optimizations
本地构建(后续运行)。
cd python
source .venv/bin/activate
maturin develop # or maturin develop --release to build with optimizations
在推送新代码之前,运行以下命令以进行代码审查和格式化。
cd python
isort .; black .; pyright .
python/python/neuromorphic_drivers/generated中的文件由构建脚本(python/build.rs)生成,不应直接修改。这些文件包含在本存储库中,用于文档目的。
当同时修改Rust库和Python包装器时,将neuromorphic-drivers = "x.y"
更改为neuromorphic-drivers = {path = "../drivers"}
以使用最新的Rust代码版本。
Rust
文档
有关文档,请参阅https://docs.rs/neuromorphic-drivers/latest/neuromorphic_drivers/。
UDEV规则
-
将以下内容写入/etc/udev/rules.d/65-neuromorphic-drivers.rules。
SUBSYSTEM=="usb", ATTRS{idVendor}=="152a",ATTRS{idProduct}=="84[0-1]?", MODE="0666" SUBSYSTEM=="usb", ATTRS{idVendor}=="04b4",ATTRS{idProduct}=="00f[4-5]", MODE="0666"
-
运行以下命令(或重新启动机器)。
sudo udevadm control --reload-rules sudo udevadm trigger
贡献
运行特定的测试。
cd drivers
cargo test --release read -- --nocapture
在crates.io上发布。
cargo publish -p neuromorphic-types
cargo publish -p neuromorphic-drivers
性能
事件速率
在特定情况下,最近的事件相机(如Prophesee EVK4)可能产生的数据量超过了实时处理的能力。虽然数据可以始终实时移动到计算机内存中,但最简单的算法(包括将原始USB字节转换为{t, x, y, polarity}
事件表示)在数据速率峰值期间难以跟上。该库使用单独的线程进行读取(USB到内存)和加工(内存到内存或磁盘),在接口处使用环形缓冲区(ring)。短数据突增被环形缓冲区无缝吸收,通常不会造成问题,尽管它们会暂时增加延迟。然而,持续的高数据速率会导致环形缓冲区逐渐填满,最终导致程序崩溃。根据使用情况,可以应用以下解决方案之一
- 降低相机的灵敏度(通常通过更改
diff_off
和diff_on
) - 如果设备支持,启用事件速率限制器(限制器在将事件传送到计算机之前随机丢弃事件,减少了带宽问题,但会显著降低瞬态突增的质量)
- 通过屏蔽行和列来降低相机的空间分辨率
- 如果可能,请更改环境(避免闪烁的灯光、减少光流、去除背景杂乱,并将大型和快速移动的物体移出视野)
- 当
backlog
变得过大时(最大backlog是环形缓冲区大小减去传输队列大小),请调用device.clear_backlog(until=0)
(直到为0) - 使用
nd.open(raw=true)
跳过解析器,直接访问USB字节,通常用于将它们保存到文件
直接内存访问
此库依赖于libusb进行所有USB通信。libusb支持直接内存访问(目前仅限Linux,未来可能会添加其他平台),允许USB控制器直接将数据包写入内存,而不需要CPU(和操作系统内核)干预。虽然这可以提高性能并降低CPU使用率,但DMA没有我们默认禁用的警告。如果用户了解其USB控制器及其限制,可能希望启用它以在嵌入式系统中提高性能。
USB驱动程序有有限数量的DMA文件对象(128/256/512/1024)。这通常不足以容纳事件突发(EVK4默认使用4096个缓冲区,每个缓冲区131072字节)。如果所有DMA缓冲区都已被使用,代码将回退到非DMA缓冲区(例如,前128个缓冲区将是DMA,其余的是非DMA),这可能会导致性能随时间变化。
使用所有可用的DMA缓冲区可能导致其他USB传输失败(包括用于配置设备的控制传输)。
因此,使用DMA需要以下解决方案之一:
- 使用少量DMA缓冲区,在处理之前将数据包复制到更大的环形缓冲区中(这多少有些违背了DMA的目的,并增加了内存复制)。
- 使用更大的缓冲区(约1MB)来增加环形缓冲区的大小,而不增加缓冲区数量,但这会增加延迟。
- 确保处理始终能够跟上数据速率(稀疏场景/低灵敏度偏差/事件速率限制/简单处理)。请注意,简单地将向量化的EVT3事件扩展到13字节的DVS事件在数据峰值期间并不是实时处理。
依赖关系
~1.8–2.7MB
~58K SLoC