应用笔记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处理和应用获取是在主机内完成的。具体过程如下图所示:

时间戳和帧号

时间戳

当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、修改sample代码,增加时间戳打印
例程DeviceSWTriggerMode非常接近我们的测试方法,只需要进行轻微修改即可。由于Windows与Linux的时间获取函数不同,在此我们分别进行介绍。
例程参考:
BaseSDK/Windows/Samples/Base/NYX650/DeviceSWTriggerMode。
(1) 、Windows
使用ftime函数获取换算毫秒级UNIX时间戳。示例代码如下:

#include <sys/timeb.h>

/*
省略部分代码
*/

//图像流开始抓取
status = scStartStream(deviceHandle);
if (status != ScStatus::SC_OK)
{
    cout << "scStartStream failed status:" << status << endl;
    return -1;
}
cout << "Software trigger test begins" << endl;
//启动流前的准备延迟
this_thread::sleep_for(chrono::milliseconds(3000));

//1.软触发
//2.读取下一帧图像
//3.根据准备就绪标志和图像类型获取图像信息
//4.休眠时间 1000/帧率(毫秒)
for (int i = 0; i < frameSpace;    i++)
{
    timeb time_start, time_end;
    /*调用API函数scSoftwareTriggerOnce来触发一帧数据的发送,然后该帧数据就会被发送出去。 
      如果不调用将会返回超时*/
    status = scSoftwareTriggerOnce(deviceHandle);
    ftime(&time_start); //记录时间戳开始
    if (status != ScStatus::SC_OK)
    { 
        cout << "scSoftwareTriggerOnce failed status:" <<status<< endl;
        continue;
    }

    //如果超过1200ms没有图像传入,该函数将会返回超时
    status = scGetFrameReady(deviceHandle, 1200, &FrameReady);
    if (status != ScStatus::SC_OK)
    { 
        cout << "scGetFrameReady failed status:" << status << endl;
        //this_thread::sleep_for(chrono::seconds(1));
        continue;
    }
    //深度图举例
    if (1 == FrameReady.depth) 
    {
        status = scGetFrame(deviceHandle, SC_DEPTH_FRAME, &depthFrame);
        if (depthFrame.pFrameData != NULL)
        {
            cout << "get Frame successful,status:" << status << "  "
                 << "frameTpye:" << depthFrame.frameType << "  "
                 << "frameIndex:" << depthFrame.frameIndex << endl
                
            ftime(&time_end);//记录时间戳结束
            uint64_t unix_end = time_end.time * 1000 + time_end.millitm;
            uint64_t unix_start = time_start.time * 1000 + time_start.millitm;
            uint64_t unix_exposure_end = depthFrame.deviceTimestamp;
            
            cout << "one frame total time delay from trigger: " << unix_end - unix_start  << endl;
            
            if ((unix_end - unix_exposure_end) > 2000)
            {
                cout << "NTP/PTP is not work !!!" << endl;
            }
            else
            {
                cout << "time delay from exposure end: " << unix_end - unix_exposure_end << endl;
            }
        }
    }
    this_thread::sleep_for(chrono::milliseconds(1000 / frameRate));
}

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

#include <time.h>

/*
省略部分代码
*/

//图像流开始抓取
status = scStartStream(deviceHandle);
if (status != ScStatus::SC_OK)
{
    cout << "scStartStream failed status:" << status << endl;
    return -1;
}
cout << "Software trigger test begins" << endl;
//启动流前的准备延迟
this_thread::sleep_for(chrono::milliseconds(3000));

//1.软触发
//2.读取下一帧图像
//3.根据准备就绪标志和图像类型获取图像信息
//4.休眠时间 1000/帧率(毫秒)
for (int i = 0; i < frameSpace;    i++)
{
    timespec time_start, time_end;
    /*调用API函数scSoftwareTriggerOnce来触发一帧数据的发送,然后该帧数据就会被发送出去。 
      如果不调用将会返回超时*/
    status = scSoftwareTriggerOnce(deviceHandle);
    clock_gettime(CLOCK_REALTIME, &time_start); //记录时间戳开始
    if (status != ScStatus::SC_OK)
    { 
        cout << "scSoftwareTriggerOnce failed status:" <<status<< endl;
        continue;
    }

    //如果超过1200ms没有图像传入,该函数将会返回超时
    status = scGetFrameReady(deviceHandle, 1200, &FrameReady);
    if (status != ScStatus::SC_OK)
    { 
        cout << "scGetFrameReady failed status:" << status << endl;
        //this_thread::sleep_for(chrono::seconds(1));
        continue;
    }
    //深度图举例
    if (1 == FrameReady.depth) 
    {
        status = scGetFrame(deviceHandle, SC_DEPTH_FRAME, &depthFrame);
        if (depthFrame.pFrameData != NULL)
        {
            cout << "get Frame successful,status:" << status << "  "
                 << "frameTpye:" << depthFrame.frameType << "  "
                 << "frameIndex:" << depthFrame.frameIndex << endl
                
            clock_gettime(CLOCK_REALTIME, &time_end);//记录时间戳结束
            uint64_t unix_end = time_end.tv_sec * 1000 + time_end.tv_nsec/1000000;
            uint64_t unix_start = time_start.tv_sec * 1000 + time_start.tv_nsec/1000000;
            uint64_t unix_exposure_end = depthFrame.deviceTimestamp;
            
            cout << "one frame total time delay from trigger: " << unix_end - unix_start  << endl;
            
            if ((unix_end - unix_exposure_end) > 2000)
            {
                cout << "NTP/PTP is not work !!!" << endl;
            }
            else
            {
                cout << "time delay from exposure end: " << unix_end - unix_exposure_end << endl;
            }
        }
    }
    this_thread::sleep_for(chrono::milliseconds(1000 / frameRate));
}

执行结果如下:

---DeviceSWTriggerMode---
Get device count: 1
serialNumber:GN6501CBCA3310172
ip:192.168.1.101
connectStatus:1
frameRate :15
Software trigger test begins
get Frame successful,status:0  frameTpye:0  frameIndex:1
one frame total time delay from trigger: 85
time delay from exposure end: 66
get Frame successful,status:0  frameTpye:0  frameIndex:2
one frame total time delay from trigger: 83
time delay from exposure end: 64
get Frame successful,status:0  frameTpye:0  frameIndex:3
one frame total time delay from trigger: 77
time delay from exposure end: 59
get Frame successful,status:0  frameTpye:0  frameIndex:4
one frame total time delay from trigger: 77
time delay from exposure end: 59
get Frame successful,status:0  frameTpye:0  frameIndex:5
one frame total time delay from trigger: 76
time delay from exposure end: 57
get Frame successful,status:0  frameTpye:0  frameIndex:6
one frame total time delay from trigger: 80
time delay from exposure end: 60
get Frame successful,status:0  frameTpye:0  frameIndex:7
one frame total time delay from trigger: 77
time delay from exposure end: 59
get Frame successful,status:0  frameTpye:0  frameIndex:8
one frame total time delay from trigger: 77
time delay from exposure end: 58
get Frame successful,status:0  frameTpye:0  frameIndex:9
one frame total time delay from trigger: 78
time delay from exposure end: 59
get Frame successful,status:0  frameTpye:0  frameIndex:10
one frame total time delay from trigger: 79
time delay from exposure end: 61
---end---

通过上述方法可以多次测试并统计当前系统的延时。
以如上打印结果为例,得知此次测试的两个测试指标。
(a)单帧总延时:80ms左右。
(b)除曝光之外的延时时间为60ms左右。

Vzense ToF相机延时测试数据

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

平台本机处理网络传输SDK处理总延时
RK358864ms11ms20ms64+11+20 = 95ms
Nvidia Xavior NX64ms10ms10ms64+10+10 = 84ms

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

平台
本机处理
网络传输SDK处理总延时
RK3588
Nvidia Xavior NX

延时优化

如果在测试后发现,整体延时无法满足应用需求,可以从如下几个角度进行调节优化:
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.

联系我们