自动化数据采集与标注
前言
仿真数据集的产生需要有大量的重复工作,包括两类:一类是与仿真环境交互、获取准物理真实信息(数据采集),另一类是根据准物理真实信息、生成用于训练和测试的数据集(数据标注),两个工作都是相当繁琐的且不可能手工重复操作的,因此有必要开发自动化的数据采集插件或者程序。基于以上分析,这里简要记录利用Gazebo和ROS开发的自动化数据采集插件以及基于Python开发的自动化数据标注程序的主要流程,以供后来者参考。
自动化数据采集插件
本部分介绍使用C++调用gazebo接口完成对传感器信号的自动化采集。
中间因为遥感图像预处理的问题,耽搁了快两个月,不过最近花了一个周把它彻底解决了,具体体现在,完成了三维仿真环境的搭建和无人机轨迹规划、传感器数据自动采集以及GT数据的生成,一整套下来踩了很多个坑,好好记录一下。
插件功能
关于插件,最重要的功能是自动化获取无人机设备全局坐标与全局姿态,通过Mavlink协议获取无人机载相机和事件相机时刻拍摄得到的照片和事件流信息,以此可以在离线情况下完成包括GT图像生成、姿态确定和陨石坑编码等一系列工作,主要是工程上的问题。
插件代码
在catkin_ws
工作目录下,使用命令创建项目工程:
1 | catkin_create_pkg data_collector geometry_msgs roscpp dvs_msgs sensor_msgs |
代码没有什么技术含量,主要是调用OpenCV和Ignition的API完成信号获取与解算,稍微复杂一点的标定程序不归自动化数据采集插件管,就没有什么好放上去的(主要都是在ChatGPT指导下完成的)。亦可以直接克隆现有的远程托管代码仓库,并编译,即有:
1 | git clone git@github.com:BugBubbles/data_collector.git |
然后在绑定了事件相机的无人机模型SDF文件中,在<model>
标签中增加如下内容:
1 | <plugin name='data_collector' filename='libdata_collector.so'> |
其中<image_sub>
标签内填写的内容是无人机发布的图像订阅话题,<event_sub>
填写的是无人机发布的事件流订阅话题(注意不是事件图像),<output_dir>
填写的是保存的数据位置,如果不加根目录符号/
,则目录将自动保存在以~/.ros
开头的目录中,<label_dir>
是用于读取当前已知的陨石坑标注数据的目录。开启<enable>
标签,插件将自动获取以上信息。
采集数据格式
最终采集到的数据文件目录将以采集起始时刻为命名,结构如下所示:
1 | . |
其中获取到的图像流存放于images
子文件夹中,以采集的时刻作为命名量;事件流和位姿信息存放于csv文件中。基于位姿信息和获取到的图像流,可以完成数据自动化标注。值得注意的是,采集的图像中存在相当多的空白图像(无人机未拍摄到图像或者Mavlink尚未建立联系),因此在数据标注时需要对空白图像作筛选去除。
自动化数据标注程序
标注过程往往是繁琐又痛苦的,如果真的一张一张标注,就等着延毕吧(硕士水平就这)。事实上,利用相机坐标系与世界坐标系的转换关系、陨石坑在世界坐标系的位置等物理已知信息,可以利用射影几何的知识完成自动化标注,这其中也踩坑无数。历经多次心态崩溃,最终完成了高精度的GT图像生成程序。
标注功能解说
由于本项目中,数据采集均是通过三维仿真环境生成,因此标注也务必要通过三维点信息获取,直接在平面图像上作射影变换获取的标注信息是必然存在误差的,且误差模式复杂,不便于补偿(有补偿这闲工夫都把其他方法试了个遍了),因此只能通过三维点向二维图像的摄像机变换获取标注点信息。而已知的陨石坑信息往往又只包含中心坐标和半径、长短轴等二维信息,默认陨石坑是二维几何形状,不能通过解析的三维曲线的射影变换等形式获取解析解,已知信息与需要的信息存在不匹配,造成了第一个需求:
- 将二维的标注信息三维化,产生三维空间标注点集,简称2D转3D
假设通过一系列高端操作得到了三维坐标下的标注陨石坑信息,另一个棘手的问题便是,如何根据位置、姿态信息完成坐标系转换、摄像机变换和图像变换,尤其是坐标系转换(gazebo的位姿定义方式、Ignition的位姿角返回值以及世界坐标系三个轴的关系)又是非常大的一个坑,极其容易犯错,因此第二个需求:
- 将三维标注信息二维化,得到标注图像,简称3D转2D
有了这两个需求后,就可以对症下药完成这个宏大的标注程序工程了,下面开始贴代码。
2D转3D
2D与3D信息沟通的桥梁是DEM模型,因此如何通过DEM完成这一转换?这里提供一种比较粗野(但是很有效的方法,可以勾画出准确的陨石坑外形以高次曲线的形式,而不是简单的圆或者椭圆)。其流程叙述如下:
- 获取标注量:读取现有陨石坑表,得到中心坐标和半径(简单起见,仅用圆模型,椭圆模型也是类似的),分区前的陨石坑表格下载位置可参考NASA官网。当然也可以直接参考我下载并分区好的文件夹,可见于ModelScope的链接。
- 平面转立体:将待标注的陨石坑在DEM平面图上画出,并获取坑轮廓点坐标(DEM图像坐标系)和对应的高度,此时坐标已经变成了空间坐标,但是并不是物理空间的,而是DEM图像坐标系下的“空间”坐标;
- 空间拓展:根据DEM图像像素坐标与经纬度坐标的对应关系,可以直接把轮廓点的像素坐标转为经纬度坐标,而高度信息则以的形式,转为球面坐标系下的法向增量,由此得到了三维球坐标;
- 球面转直角:射影几何是直角坐标系下的几何关系,使用表示法将球面坐标系下的坐标,可以直接转为直角坐标系下的坐标,得到了真实的三维标注点坐标。
以上过程在程序中体现为如下成员函数:
1 | def findCraters(self, dem, data): |
这里有一个坑:
姿态角的含义?
查阅了Gazebo所使用的位姿表示库Igntion的官方文档,该文档提供了使用欧拉角和使用四元数表示两种方式,这里为了直观选用了欧拉角表示,三轴欧拉角分别记为俯仰角pitch,偏航角yaw,滚转角roll。在Gazebo中,欧拉角的表示顺序是Z-Y-X,即先绕Z轴旋转,再绕Y轴旋转,最后绕X轴旋转,更为重要的是,欧拉角与旋转顺序有关,先绕Z轴旋转后,再绕Y轴是绕的新形成的Y轴,不再是原始的旧轴(欧拉角的万向锁效应)。
3D转2D
在给定相机位置和姿态后,应当能够根据内外参矩阵完成摄像机变换。与一步类似,也需要完成坐标系的平移和旋转,此时需要平移的坐标系应当是世界坐标系,将其变换为图像坐标系,然后根据齐次坐标的完成图像坐标的解算。本步骤中可以使用以下成员函数完成:
1 | def __call__(self, t, pose, ori, output_dir, img=None): |
只需要输入当前的时间戳、位姿信息,程序将自动完成位姿解算。标注的示意图如下所示:
以上标注情况是根据三维坐标标注的,因此结果将比直接依据二维图像和二维几何形状的射影变换更加精确。图中也显示,标注陨石坑轮廓可以不呈现出椭圆,而是更加复杂曲线形式,以更加适合陨石坑轮廓在当前情况下真实走向。完整的标注程序代码如下所示:
1 | """用于从gazebo生成的仿真图像中获取GT信息 |
踩过的坑
在设计标注程序的时候,总是反复掉进同一个坑里,虽然现在已经完成了对gazebo和Blender两种仿真环境下的标注程序,为避免将来在使用更多更高级的仿真程序时仍然遇到相同的问题,这里把前期踩过的坑全部记录一遍,主要从输入端(仿真环境的参考结果)、输出端(陨石坑目录)查找问题:
- 查看姿态转角表示是否正确,如采用欧拉角,其顺序是否正确;如采用四元数,其w值放在第一位还是最后一位?
- 查看位移是否正确,尤其是否缩放正确,当前世界坐标系存在的缩放比例是否作用至位移矢量上(理论上不需要)?
- 查看相机内参数计算是否正确,尤其是等效焦距是否由焦距除以像素尺寸算得?
- 查看相机坐标系是否正确,目前已知OpenCV的坐标系与Numpy读取坐标系相反,而Blender渲染坐标系的x轴与Numpy呈镜像关系。
- 查看陨石坑目录是否正确,目录中是否包括深度值(深度值非常重要,陨石坑并不是位于月球表面深度为0的地方)。