【CeleX5事件相机使用系列】传感器时间戳同步问题

由于我需要同时获得event和frame,所以需要在Loop模式下获得两种数据的时间戳。这两日折腾了好久,发现CeleX的传感器缺少相关API,且存在一定的问题。在这里做记录总结,以及分享。转载请注明出处

Loop模式的时序

根据手册,Loop模式下,当前处在Mode1-3的任意一个模式,且这个模式完成后切换到下一个模式。所谓“完成”,对于fullframe来说,就是采集够了设定的图片数量,而event模式,则是持续了足够的时间。

在这里插入图片描述
根据手册,可以采用setPictureNumber()设定图片的数量,以及 setEventDuration()设定event模式的持续时间。而我测试发现,即使修改了event的时间,实际的持续时间并不是自己设定的时间。不知道是芯仑硬件的问题,还是我设置方式不正确。

Event In-Pixel Timestamp Mode的时间戳

CeleXMP有多种event模式,但只有In-Pixel这种模式能够输出相机采集到的事件的时间戳,而不仅仅是传输出来的时间戳。但这种模式使用时,芯仑的库代码强制将系统时钟上限设定为了70MHz,这也坑了我半天,导致我一直以为用的是100MHz的时钟。

但是问题是,芯仑手册说这种模式下EventDatain_pixel_timestamp是传感器内部时间戳,且单位是us,但我实际测试时发现这个数值不是这样的

在这里插入图片描述
下图最左侧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开始,而是这段儿时间内第一个发生的事件所在行,然后依次访问。这种策略下,可能会出现先输出的某个event比后输出的某个event的产生时的时间戳更靠后(例如:0us时100行产生了第一事件,于是从从100行开始输出;在50us时200行有一个事件生成,然而此时才刚刚输出到第120行的数据,在100us时第150行产生了一个事件。那么当输出到第150行时,150行的数据先输出,但时间戳晚于将要输出的200行的事件的时间戳)。同时一个更神奇的情况是,在下一个Loop当中,最早的事件时间戳还要早于进入当前loop时的系统时间戳(例如上一个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来说,比较棘手的问题有两个:1. event的生成时间戳含义不明;2. 一组event到来时时间戳不是递增的。

为解决第1个问题,由于我不需要知道精确的时间,只需要大概准确即可,所以我将event生成的时间戳,与ros的是时间戳形成比例,重新将event的生成时间戳插值映射到两个mode2之间这段儿时间。

为解决第2个问题,只需要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的时间)
在这里插入图片描述

小结

  1. 想MR。想用的功能都没有,二次开发很不友好;
  2. 注意:ROS的时间戳是很大的一个数值,所以在计算时,一定要用double类型,而不能用float。我被*0.1f这种表示坑了半天,时间戳总是对不上,才发现是float精度问题。
  3. 同时要注意,EventData,celex5_msgs/Event 当中的时间戳一个是uint32,一个是uint64,而系统的时间戳是double。所以量及转换时需要注意。我也搞不懂为啥这俩还不一样。
  4. 在显示数据时,ROS_INFO/cout等方式对于很大的数显示不全,可以采用printf,但printf好久不用了:%d整数,%f浮点数,%lu是unsigned long,为uint32的定义,%llu是uint64。


微信公众号:【事件相机】,交流事件相机的相关科研与应用。欢迎大家关注请添加图片描述

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值