0.3.1 |
|
---|
#6 in #tether
68KB
1.5K SLoC
Tether Lidar2D Consolidator,用Rust编写
这基本上是将原始NodeJS Agent(此处称为“原始Agent”✌️)直接移植到Rust 🦀。
实现了原始Agent的所有基本功能,但应用程序的结构并不试图复制原始版本。
两个主要目标是
- 学习在真实世界应用中使用Rust,遵循一个已知且基本工程挑战已被“解决”的现有代码库
- 产生一个更快速、更轻量级的Lidar Consolidator Agent版本,可以在资源较少的平台(如Raspberry Pi)上部署,而不对系统造成太大的压力
开发依赖
Paho Eclipse MQTT有一些(非Rust)依赖。在Mac上,你可能需要以下内容
brew install openssh cmake
和在Linux上
sudo apt install libssl-dev build-essential cmake
基本用法
如果使用cargo,你可以简单地使用
cargo run
或者,例如,以发布版(推荐)和某些命令行参数运行
cargo run --release -- --loglevel debug
或者,运行编译的二进制文件(假设你已经运行了cargo build --release
),通常位于./target/release/tether-lidar2d-consolidation-rs
命令行配置
你可以通过在你的执行命令后附加--help
来查看所有可用的命令行参数,例如cargo run -- --help
选项故意做得几乎与原始Agent中使用的选项相同,只有一些值得注意的例外
- 这里我们使用了一个标志
--perspectiveTransform.includeOutside
而不是设置一个布尔值ignoreOutside
。这更好地反映了默认用法,并在使用标志时更有意义(如果不显式附加,默认为false
) - 对于Tether,你可以提供
--tether.host
、--tether.username
、--tether.password
,如果这些与默认值不同 - 没有与
autoBroadcastConfig
相关的选项,因为不需要这种行为;我们每次启动和请求时都保存和(重新)发布配置,基本上每次都改变
图书馆注意事项
MQTT 客户端
最初我们尝试使用 mqtt-rs,因为它看起来相对简单易用,但将来可能会考虑使用 mqttrs,它可能“更好”。
目前我们选择了 paho-mqtt,因为它似乎得到了很好的支持,并提供了一些实际场景(尤其是异步)的示例。
MessagePack 编码/解码
rmp_serde 适用于 JSON 和 MsgPack 序列化/反序列化。我们可能没有充分利用零拷贝操作,但这需要更多时间来弄清楚。
最初我们尝试了 msgpack-simple,它警告说“性能不如静态解决方案”,但作为一个起点使用起来要容易得多。
集群
我们尝试了库 kddbscan,尽管这可能会更“准确”,但它似乎运行得太慢。无论如何,这和 OG Agent 使用的 DBSCAN 算法非常不同。
然后我们选择了更朴实的(但据称性能更好)的 petal-clustering。这反过来又需要一种叫做 ndarray 的东西,它看起来非常相似(并且可能基于)Python 的 numpy。
目前,我们使用 OG Agent 中的 DBSCAN 方法,但将来可能值得测试这个库中其他支持的模式,即 HDbscan,它可能更快(参见 这篇论文)。
另一种可能性可能是库 linfa-clustering。
JSON 序列化/反序列化
我们使用库 serde 和 serde_json 的组合,这使得以各种方式处理 JSON 变得容易,包括与 Rust 类型/结构体对应的有类型强类型,这正是我们在加载/保存 Config 时的需求。
透视变换
我们正在尝试从 ROI 到标准化“正方形”输出四边形的“四边形到四边形投影”,类似于 OG Agent 中的 perspective-transform。
到目前为止
- https://www.physicsforums.com/threads/transform-that-maps-points-from-any-quad-to-an-reactangle.833996/
- https://docs.rs/projective/0.3.0/projective/trait.Projective.html 提供了必要的 API - 我认为需要应用给任何给定点的 3x3(或是否是 4x4)矩阵。可能是通过复制 https://github.com/jlouthan/perspective-transform/blob/master/dist/perspective-transform.js 来实现的?
- https://math.stackexchange.com/questions/296794/finding-the-transform-matrix-from-4-projected-points-with-javascript/339033#339033
- https://stackoverflow.com/questions/14244032/redraw-image-from-3d-perspective-to-2d/14244616#14244616
- https://blog.mbedded.ninja/mathematics/geometry/projective-transformations/
- https://en.wikipedia.org/wiki/Homography#Mathematical_definition
- https://docs.rs/cgmath/0.18.0/cgmath/struct.Perspective.html
- https://franklinta.com/2014/09/08/computing-css-matrix3d-transforms/
- https://yasenh.github.io/post/homogeneous-coordinates/
最终,我们使用了 ndarray
(已安装以支持集群计算)和 nalgebra
的组合。
日志记录
我们使用 log 和 env-logger。默认日志级别设置为 INFO,但可以被覆盖,例如通过环境变量前缀,例如。
RUST_LOG=debug cargo run
命令行配置
我们使用 clap,它仅进行命令行参数解析(不使用文件、环境变量等)。
像 more-config 这样的工具可能很有用,因为它包含了与 NodeJS 的 rc 包类似的功能。
实现说明
- 与 OG 代理一样,LIDAR 设备的序列字符串是从主题中提取的,具体是从
agentIdOrGroup
部分,位于lidar2d/{agentIdOrGroup}/scans
- 样本从数组数组中复制;每个“样本”是一个包含
[angle, distance]
元素的数组,有时还有[angle, distance, quality]
(尽管后者尚未处理)。样本被转换为点,所有转换后的点的列表(向量)被“插入”(即替换,如果键已存在)到表示所有 LIDAR 设备(以序列字符串为键)的哈希表中。- 可能存在一些数据分配/复制操作;需要尽可能减少这些操作
- 在此过程中有一些
.unwrap()
的实例;在某些情况下应更仔细地处理错误
待办事项
- 添加(可选)tether-tracking-smooth 功能,内置
- 存在区域应在实际空间中定义(更合理的校准),而不是标准化跟踪空间
- 存在区域应可定义为除矩形之外的形状
- 平滑还应删除应合并的重复现有点(不要留下“过时”的点)
- 确保在 Raspberry Pi 上编译,然后交叉编译(例如从 Mac)
- 保留消息,用于配置发布 - 移除“请求配置”主题的需求
- 调试日志级别应抑制 MQTT 日志消息(太冗长)
- 允许所有关键设置(聚类,平滑)在命令中实时更新
- 如果在超时后没有从已知设备接收到数据,则打印警告/错误(以及可选的恐慌?)
- 如果没有找到 Lidar 设备配置文件,则创建一个
- 维护“状态”(配置数据),并在启动时发布,这应该允许可视化器开始显示扫描数据
- 使用 rmp-serde 替代 msgpack-simple 进行 MessagePack
- 从磁盘读取/写入配置数据
- 转换传入的扫描点:旋转和位置/偏移
- 对传入样本应用扫描掩码阈值
- 应用最大聚类大小过滤
- 处理 ROI、变换(扭曲)
- 允许通过“saveLidarConfig”保存传入的配置(设备,ROI)
- 允许按请求创建 AutoMaskSampler
- 应使用多个传感器进行测试
- 跟踪输出应应用 ignoreOutside、ignoreOutsideMargin 逻辑
- 如果收到未知/未配置的设备,则将其添加到配置中,并使用一些默认值
- 从命令行参数/默认值加载/覆盖一些设置(例如 Tether、聚类)
- 自动生成代理 UUID
- 使用“真实”的 Tether Agent crate - 尽管目前尚未设置为异步
- 将潜在阻塞的过程(例如配置写入磁盘)移入单独的线程或非阻塞函数
- 退出时正确关闭客户端,以便队列也得到正确销毁
- 目前,如果“扫描样本”是形式为 (f64,f64) 的元组,即 (角度,距离),则如果包含质量,系统将引发恐慌。这意味着我们可能需要一个没有固定长度的数组,或者简单地完全删除质量“字段”
- 清除和/或设置新的 AutoMask Samplers 时可能存在内存泄漏
有用的资源
- 一般配方,包括一些三角学:https://rust-lang-nursery.github.io/rust-cookbook/about.html
- MessagePack 规范(包括支持的类型详情):https://github.com/msgpack/msgpack/blob/master/spec.md
- Rust 示例教程,包括自定义类型(结构体):https://doc.rust-lang.net.cn/rust-by-example/custom_types/structs.html
- Rust 编程语言 "书籍",包括关于哈希表的有用信息:https://doc.rust-lang.net.cn/book/ch08-03-hash-maps.html
- 通过 VSCode + LLDB 进行调试的实用技巧
依赖项
~28–42MB
~737K SLoC