应用笔记09-Vzense 3D ToF相机的时间戳、帧号和图像延时分析与测试方法的介绍

编辑:Sophia.feng 发布: 2024-10-16

ToF相机一帧图像处理过程

Vzense ToF(Time of Flight)相机的一帧图像从产生到传输完成的过程可以概括为如下工作环节:

  • 曝光(Exposure)
  • 图像处理(ISP processing)
  • 网络传输(Transmition)
  • SDK处理(SDK processing)
  • 应用获取(Program readout)

其中曝光和图像处理是在相机内完成的,SDK处理和应用获取是在主机内完成的。具体过程如下图所示:

曝光时间

相机的曝光时间包含两部分:曝光和数据传输。
DS系列产品的曝光时间构成
1、不开启HDR,曝光时间构成如下图所示:

用户可以根据需求在ScepterGUITool工具内进行调节,设置的曝光时间如下所示:

2、开启HDR(参考产品介绍是否支持),曝光时间构成如下图所示:

用户可以根据需求在ScepterGUITool工具内进行调节,设置的曝光时间如下所示:

NYX系列产品的曝光时间构成

1、不开启HDR,曝光时间构成如下图所示:

用户可以根据需求在ScepterGUITool工具内进行调节,设置的曝光时间如下所示:

2、开启HDR(参考产品介绍是否支持),曝光时间构成如下图所示:

用户可以根据需求在ScepterGUITool工具内进行调节,设置的曝光时间如下所示:

时间戳和帧号

时间戳

当Vzense ToF相机上电时,系统会启动一个从0开始的本地计时器,时间自动增加,不受相机工作状态的影响,计时器的时间即为相机的本地系统时间。
注意:相机系统时间默认并不是和主机时间对齐的实时时钟real time clock,如需对齐则需要相机和主机之间配置好NTP/PTP模式。请参考《 应用笔记05-Vzense 3D ToF相机的NTP功能设置方式介绍 》和《 应用笔记06-Vzense 3D ToF相机的PTP功能设置方法介绍 》。
相机在每帧曝光结束时,会给每一帧图像打一个时间戳t(Time Stamp),单位为1ms。该时间戳会和该帧图像的数据一起发送到主机端,并可通过API获得。

时间戳可以用作相机和主机系统之间的对齐和校准,特别是在触发模式下,可以在应用端知道某一帧的发生时间,避免在计算和传输过程中的不确定延时,从而和系统时间对齐。

帧号

当图像被传送到主机时,主机SDK会给每一帧图像标记一个唯一的帧号(Frame Index),帧号从0开始,按照顺序逐次递加。帧号是由SDK赋值,表示的是SDK接收到图像数据包的索引号。因此与曝光、ISP 处理和传输阶段无关,这意味着,即使在曝光、ISP 处理和传输阶段丢失了帧,帧号仍然是按顺序进行的。

SDK在获取一帧图像后,会把该帧图像暂存在缓存区等待应用读取。如果应用读取不及时,SDK会使用新帧覆盖旧帧,从而产生丢帧。 通过帧号,用户可以判断是否由于主机进程调度、CPU负载过高等原因导致数据丢帧。

如上图所示,SDK连续接收到了帧号为1,2,4的帧,因此我们就可以知道中间有帧号为3的一帧没有被正确读取到。从帧号的产生原理可以知道,如果发生丢帧,一定是主机端的应用读取发生了问题。

时间戳和帧号的获取

Vzense相机图像的时间戳和帧号都是在ScFrame结构体中

typedef struct
{
    uint32_t      frameIndex;        //表示图像帧索引号
    ScFrameType   frameType;         //表示图像数据类型
    ScPixelFormat pixelFormat;       //表示像素类型
    uint8_t*      pFrameData;        //表示指向图像数据缓存的指针
    uint32_t      dataLen;           //表示图像数据的长度,单位为字节
    uint8_t       depthRange;        //表示当前帧的深度范围,仅对深度图像有效
    uint16_t      width;             //表示图像宽度
    uint16_t      height;            //表示图像高度
    uint64_t      deviceTimestamp;   //表示帧在设备上生成时的时间戳,不包括帧处理和传输时间
} ScFrame;

可以通过scGetFrame接口获取。示例代码如下:

//深度图举例
		if (1 == FrameReady.depth)
		{
			status = scGetFrame(deviceHandle, SC_DEPTH_FRAME, &depthFrame);
			if (depthFrame.pFrameData != NULL)
			{
				cout << "scGetFrame status:" << status << "  "
				<< "frameType:" << depthFrame.frameType << "  "
				<< "deviceTimestamp:" << depthFrame.deviceTimestamp << "  "
				<< "frameIndex:" << depthFrame.frameIndex << endl;
			}
		}

图像延时

图像延时的构成

根据一帧图像的处理过程可知,相机的延时由以下部分组成:

功能所在部分功能描述影响因素
曝光相机端通过曝光完成一帧图像的采集1、曝光时间设置
2、HDR功能(开启HDR,耗时增加)
3、WDR功能(开启WDR,耗时增加)
图像处理相机端深度图计算、IR图计算、RGB图像转换、
RGB图像编码、图像滤波、畸变还原等工作
1、RGB图像分辨率(分辨率越大,耗时越长)
2、深度图像分辨率(分辨率越大,耗时越长)
3、滤波开关(开启滤波,耗时增加)
4、HDR功能(开启HDR,耗时增加)
5、WDR功能(开启WDR,耗时增加)
网络传输相机和主机之间的网络设备从相机传输数据到主机1、网络设备性能(如网卡、网线等)
2、主机网络负载
SDK处理主机端点云转换、图像对齐、RGB图像解码等1、主机性能
2、用户应用程序多线程设计
应用读取读取
主机端
从SDK缓冲区读取图像1、主机性能
2、用户应用程序多线程设计

延迟测试方法

以下内容将详细展开并举例说明Vzense相机如何进行延时数据测试。
在下述测试中,会进行两个指标的测试:单帧总延时,除曝光之外的延时(曝光结束到应用获取)。
测试步骤:
1、开启相机的NTP或PTP对时功能。
详细设置方法请参考:NTP设置方法, PTP设置方法
如果只需要测试单帧总延时,可不进行本步骤。
注意:部分型号相机暂不支持NTP、PTP时钟同步。
2、时间戳打印
例程参考:
BaseSDK/Windows/Samples/Base/NYX650/SingleFrameDelayTest。
(1) Windows
使用ftime函数获取换算毫秒级UNIX时间戳。示例代码如下:

#include <time.h>

/*
省略部分代码
*/	
for (int i = 0; i < number; i++)
{
	timeb timeStart, timeEnd;
	status = scSoftwareTriggerOnce(deviceHandle);
	ftime(&timeStart);//记录时间戳开始
	if (status == ScStatus::SC_OK)
	{
	}
	else
	{
		cout << "[scSoftwareTriggerOnce] fail, ScStatus(" << status << ")." << endl;
		continue;
	}

  //如果超过1200ms没有图像传入,该函数将会返回超时
	status = scGetFrameReady(deviceHandle, 15000, &frameReady);
	if (status == ScStatus::SC_OK)
	{
	}
	else
	{
		cout << "[scGetFrameReady] fail, ScStatus(" << status << ")." << endl;
		continue;
	}
  //深度图举例
	if (1 == frameReady.depth)
	{
		status = scGetFrame(deviceHandle, SC_DEPTH_FRAME, &depthFrame);
		if (status == ScStatus::SC_OK)
		{
			if (depthFrame.frameIndex % 10 == 0)
			{
				cout << "SC_DEPTH_FRAME <frameIndex>: " << depthFrame.frameIndex << endl;
			}

			ftime(&timeEnd);//记录时间戳结束
			endTimestamp = timeEnd.time * 1000 + timeEnd.millitm;
			startTimestamp = timeStart.time * 1000 + timeStart.millitm;
			deviceTimestamp = depthFrame.deviceTimestamp;
			frameInterval = endTimestamp - startTimestamp;
			frameIntervalNTP = endTimestamp - deviceTimestamp;

			//If the delay time is greater than 2000, it is considered that NTP is not enabled.
			if (frameIntervalNTP > 2000)
			{
				csvWriter << depthFrame.frameIndex << "," << frameInterval << ",N/A" << endl;
			}
			else
			{
				csvWriter << depthFrame.frameIndex << "," << frameInterval << "," << frameIntervalNTP << endl;
			}
		}
		else
		{
			cout << "[scGetFrame] fail, ScStatus(" << status << ")." << endl;
		}
	}

	//The time interval between two triggers should be greater than 
	//the time interval between the two frames generated.
	this_thread::sleep_for(chrono::milliseconds(1000 / frameRate));
}

(2)Linux
使用clock_gettime函数获取换算毫秒级UNIX时间戳。示例代码如下:

#include <time.h>

/*
省略部分代码
*/
for (int i = 0; i < number; i++)
{
	timespec timeStart, timeEnd;
	status = scSoftwareTriggerOnce(deviceHandle);
	clock_gettime(CLOCK_REALTIME, &timeStart);//记录时间戳开始
	if (status == ScStatus::SC_OK)
	{
	}
	else
	{
		cout << "[scSoftwareTriggerOnce] fail, ScStatus(" << status << ")." << endl;
		continue;
	}

  //如果超过1200ms没有图像传入,该函数将会返回超时
	status = scGetFrameReady(deviceHandle, 15000, &frameReady);
	if (status == ScStatus::SC_OK)
	{
	}
	else
	{
		cout << "[scGetFrameReady] fail, ScStatus(" << status << ")." << endl;
		continue;
	}
	//深度图举例
	if (1 == frameReady.depth)
	{
		status = scGetFrame(deviceHandle, SC_DEPTH_FRAME, &depthFrame);
		if (status == ScStatus::SC_OK)
		{
			if (depthFrame.frameIndex % 10 == 0)
			{
				cout << "SC_DEPTH_FRAME <frameIndex>: " << depthFrame.frameIndex << endl;
			}

			clock_gettime(CLOCK_REALTIME, &timeEnd);//记录时间戳结束
			endTimestamp = timeEnd.tv_sec * 1000 + timeEnd.tv_nsec/1000000;
			startTimestamp = timeStart.tv_sec * 1000 + timeStart.tv_nsec/1000000;
			deviceTimestamp = depthFrame.deviceTimestamp;
			frameInterval = endTimestamp - startTimestamp;
			frameIntervalNTP = endTimestamp - deviceTimestamp;

			//If the delay time is greater than 2000, it is considered that NTP is not enabled.
			if (frameIntervalNTP > 2000)
			{
				csvWriter << depthFrame.frameIndex << "," << frameInterval << ",N/A" << endl;
			}
			else
			{
				csvWriter << depthFrame.frameIndex << "," << frameInterval << "," << frameIntervalNTP << endl;
			}
		}
		else
		{
			cout << "[scGetFrame] fail, ScStatus(" << status << ")." << endl;
		}
	}

	//The time interval between two triggers should be greater than 
	//the time interval between the two frames generated.
	this_thread::sleep_for(chrono::milliseconds(1000 / frameRate));
}

执行结果如下:
通过上述方法可以多次测试并统计当前系统的延时,可以在生成的”SingleFrameDelayTest.csv“文件中进行查看,如下表所示:

frameIndexTotalDelayExcludeDelayofExposure
19842
210752
311762
411257
511055
69338
710347
88934
911357
1011155

通过生成数据得知此次测试(10帧图像)的两个测试指标如下:
(a)单帧总延时:105ms左右。
(b)除曝光之外的延时时间为50ms左右。

Vzense ToF相机延时测试数据

以NYX650(B26) + ScepterSDK(v24.12.2)为例,ToF曝光时间5ms, RGB分辨率640 x 480,开Depth2RGB对齐,获取对齐后Depth与RGB图像延时测试数据如下表所示:

平台曝光+数据传输图像处理
(固定)
网络传输SDK处理总延时
RK358813ms65ms20ms36ms134ms
Nvidia Xavior NX13ms65ms10ms30ms118ms

DS系列产品的延迟相对于NYX会更高一些。以DS86(B21) + ScepterSDK(v24.12.2)为例,ToF曝光时间4ms, RGB分辨率640 x 480,开Depth2RGB对齐,获取对齐后Depth与RGB图像延时测试数据如下表所示:

平台曝光+数据传输图像处理
(固定)
网络传输SDK处理总延时
RK358856ms84ms38ms47ms225ms
Nvidia Xavior56ms84ms22ms41ms203ms

延时优化

如果在测试后发现,整体延时无法满足应用需求,可以从如下几个角度进行调节优化:
1、图像分辨率:图像分辨率越大,需要处理的数据量越大,因此分辨率的不同对延时影响最为巨大。建议在业务允许的情况下,选择较小的分辨率,如640×480。
2、系统平台:系统平台的性能直接影响SDK的运行效率与性能,因此尽量选择符合要求的平台,以及尽量平衡其他进程对相机SDK资源的抢占。
3、点云转换:点云转换涉及大量float运算,消耗较大,尽量减少点云转换的数量可以较大的提高效率。如只转换ROI(region of interest)区域点云,不做全图转换。
4、滤波开关:根据业务需求,可以关闭部分滤波开关,以减少资源占用与提高延时。
5、相机型号:Vzense相机有不同型号,不同型号相机因为原理不同,实现方式不同,也会存在延时差异。

相关文章

How can we support you?

We will be happy to advise you on product selection and find the right solution for your application.