因为我需要同时得到它event和frame,所以需要在Loop在模式下获取两个数据的时间戳。这两天折腾了很久,发现CeleX传感器缺乏相关性API,还有一些问题。在这里做记录总结和分享。
Loop模式的时序
根据手册,Loop模式下,目前处于Mode1-3的任意一个模式,且这个模式完成后切换到下一个模式。所谓“完成”,对于fullframe就是收集足够设定的图片数量,event模式,则是持续了足够的时间。
可根据手册使用
setPictureNumber()
设置图片的数量,以及 setEventDuration()
设定event模型的持续时间。我不知道是芯仑硬件的问题,还是我的设置方法不正确。
Event In-Pixel Timestamp Mode的时间戳
CeleXMP有多种event但只有模式In-Pixel
该模式可以输出相机收集到的事件的时间戳,而不仅仅是传输的时间戳。但在使用该模式时,芯仑的库代码强制将系统时钟上限设置为70MHz,这也让我坑了很久,导致我一直以为的是100MHz的时钟。
但问题是,。
下图最左侧sys是PC时间戳,来自ROS的Time,中间的In是我把每次Mode2下一组events数据输出后,第一查看event的in_pixel_timestamp。通过比较两列数据,可以发现传感器内部时间戳比实际时间慢一个量级,而不是严格的量级(一开始:sys 4.71, In 462,结束时:sys 5.43, In 549)。我TM,谁知道这到底是什么?另外off_pixel_timestamp不是增加,每组events输出从0开始,所以不需要。 此外,需要注意的是,芯仑在输出每组事件数据时,每行从左到右依次输出,但起始线不是从0开始,而是这段时间内第一次事件发生的线,然后依次访问。在这种策略下,(例如:0us第一件事发生在100行,所以从100行输出;50us有一个事件发生在200行,但此时刚刚输出到第120行的数据,在100行us当第150行发生事件时。然后,当输出到第150行时,首先输出150行的数据,但时间戳比即将输出的200行晚)。(比如上一个loop时间生成的数据无法输出,因为它已经扫描了这条线,但它存在于缓存中。loop读取输出时)。
Full frame的时间戳
因为我需要判断,events在哪个full frame前后,所以需要得到 full frame 时间戳。但是查阅手册后,芯仑没有提供获取信息 full frame 内部时间戳API,只有一个函数getFullPicBuffer
收到图像时可以获得PC的时间戳。MD,系统的时间戳我自己得不到吗?API完全没有卵用。
由于传感器内部时间戳无法获得,内部时间戳只能通过外部时间戳进行推测。但是,没有任何方法可以随时获得内部时间戳,只能通过阅读events的buf获得events生成时的时间戳。但根本不明白这个时间戳的物理含义。所以我采用了以下方法:
最终的解决方案
所有时间戳PC上ROS以时间戳为准。
full frame很容易处理,只需收到full frame时读取一下ros然后修改时间msg的header,再次发布。
而对于event比较棘手的问题有两个. event生成时间戳含义不明;2. 一组event到达时间戳不会增加。
为了解决第一个问题,我不需要知道准确的时间,只需要大致准确,所以我会event时间戳,和ros是时间戳形成比例,重新形成比例event生成时间戳插值映射到两个mode两者之间的这段时间。
只需要解决第二个问题sort就这样。计算比例也很方便。
()核心代码如下:
std::vector<EventData> eventData; pCeleX5->getEventDataVector(eventData); // 读取数据 // 根据生成时间对每组事件进行排序,并获得内部时间戳的差PC时间戳的差值 std::sort(eventData.begin(), eventData.end(), cmp);
uint32_t sensor_ts_begin = eventData[0].tInPixelIncreasing;
uint32_t sensor_ts_end = eventData[eventData.size() - 1].tInPixelIncreasing;
double pc_ts_diff = (ros::Time::now() - g_last_events_ts).toSec(); // pc ts between two events group
// 转化为msg消息
...
for (int i = 0; i < eventData.size(); ++i){
...
// 计算增量(内部时间戳的增量)
double delta = ((double)data.tInPixelIncreasing - sensor_ts_begin) / ((double)sensor_ts_end - sensor_ts_begin) * pc_ts_diff;
// 根据增量,重新映射事件的生成时间(内部时间戳的增量,与外部时间戳的增量的比值,确定实际的内部时间)
e.in_pixel_timestamp = ((double)g_last_events_ts.toSec() + delta) * 1e6;
msg.events[i] = e;
}
g_last_events_ts = ros::Time::now(); // 重置当前loop模式的时间戳
pubEvents.publish(msg); // 发布消息
下图左侧是发送节点,右侧是接受节点。左侧发送时,Image和Events都是对齐到了PC的系统时钟,右侧为接受节点截取的结果。同时可以注意到,由于我才用了映射的方式,使得事件的时间戳严格递增,而不会出现这一帧最早的事件比上一帧最晚的要早。 (解释一下,由于对时间戳进行了映射,所以图中的dur基本是两个Mode2之间的时间差,其中包括Mode1的一张图,还有Mode2和3的时间)
小结
- 想MR。想用的功能都没有,二次开发很不友好;
- 注意:ROS的时间戳是很大的一个数值,所以在计算时,一定要用double类型,而不能用float。我被
*0.1f
这种表示坑了半天,时间戳总是对不上,才发现是float精度问题。 - 同时要注意,EventData,celex5_msgs/Event 当中的时间戳一个是uint32,一个是uint64,而系统的时间戳是double。所以量及转换时需要注意。我也搞不懂为啥这俩还不一样。
- 在显示数据时,ROS_INFO/cout等方式对于很大的数显示不全,可以采用
printf
,但printf
好久不用了:%d
整数,%f
浮点数,%lu
是unsigned long,为uint32的定义,%llu
是uint64。
微信公众号:【事件相机】,交流事件相机的相关科研与应用。欢迎大家关注