英特尔®RealSense™深度摄像机D400系列的深度后处理
文章目录
- 英特尔®RealSense™深度摄像机D400系列的深度后处理
- Introduction
- 简单的后处理
- 利用Intel RealSense SDK API实现后处理
- 示例结果和权衡
- Conclusion
Introduction
英特尔®实感™D4xx深度摄像头可以每秒高达90帧的速度传输实时深度(即测距数据)和色彩数据,并且所有生成深度数据的处理都由嵌入式D4 ASIC在板上完成。 这实际上应该给主机处理器留下几乎为零的负担,然后主机处理器可以专注于手头应用程序对深度数据的使用。 尽管可以调整超过40个影响深度计算的参数,但应注意,ASIC不会执行任何后处理来清除深度,因为如果需要,则留给更高级别的应用程序使用。 在本文中,我们讨论了一些可以在主机上执行的简单后处理步骤,以提高针对不同应用程序的深度输出,并探讨了在主机计算和延迟方面进行的取舍。 我们已经在可以使用的英特尔实感SDK 2.0(libRealSense)中包括了开放源代码示例代码,但是请务必注意,存在许多不同类型的后处理算法,此处介绍的算法仅用作 入门基础。 librealsense后处理功能也全部包含在Intel RealSense Viewer应用程序中(如下所示),因此该应用程序可以用作快速测试依据,以确定是否值得探索Intel后处理方面的改进。
简单的后处理
如另一本白皮书中所述,要获得最佳的原始深度感,英特尔实感D4xx摄像机,我们通常建议英特尔实感D415以1280x720分辨率运行,英特尔实感D435以848x480分辨率(具有 只有少数例外)。 但是,尽管许多更高级别的应用程序确实需要以最佳高分辨率运行的深度精度和低深度噪声的好处,但对于大多数使用情况,它们实际上并不需要这么多的深度点,即高x和y分辨率。 实际上,必须处理大量数据可能会对应用程序的速度和性能造成负面影响。 因此,我们的第一个建议是考虑在收到输入后立即对输入进行采样:
- Sub-sampling :虽然可以通过例如每第n个像素简单地抽取深度图来进行子采样,但我们强烈建议对输入深度图进行更智能的子采样。 我们通常建议对像素及其附近的邻居使用“非零中位数”或“非零均值”。 考虑到计算负担,我们建议对小因子子采样(例如:2、3)使用“非零中位数”,对大因子子采样(例如:4、5 …)使用“非零均值” 。 因此,例如,当将子采样设置为4(或4x4)时,“非零均值”将需要取一个像素及其15个最近邻居的平均值,而忽略零,然后在以4 in x和y。 虽然这显然会影响深度图的xy分辨率,但应注意,所有立体声算法确实都涉及一些卷积运算,因此使用适度的二次采样(❤️)捕获后降低xy分辨率将对图像的影响降至最小。 xy深度分辨率。 X-Y分辨率降低2倍,应将随后的应用程序处理速度提高4倍,而子采样4则应将计算量降低16倍。 此外,智能子采样的一个好处是它还将使用“非零均值”或“非零中位数”函数(计算负担稍高)对数据进行一些基本的空洞填充和平滑处理。 “非零”是指以下事实:深度图上将存在应为零的值。 这些是深度图中的“孔”,代表不符合置信度指标的深度数据,并且相机提供的值不是零,而是错误的值。 最后,子采样实际上也可以帮助实现点云的可视化,因为除非将其放大,否则很难看到非常密集的深度图。
一旦深度图被压缩到较小的x-y分辨率,就应该考虑更复杂的空间和时间滤波器。 我们建议首先考虑添加一个边缘保持空间滤波器。
- Edge-preserving filtering : 这种类型的滤波器会在尝试保留边缘时平滑深度噪声。 考虑下面图1中的示例,该示例将2D范围数据发送到一个10x10mm的盒子,该盒子放在距离深度摄像头500mm的墙壁附近。 如右图所示,深度测量中会有噪音。 理想的理想测量值在左侧。 如果我们应用普通的平滑滤波器,我们将看到深度噪声将减小,但是我们还将看到盒子的不同边缘也被平滑,如图2所示。针对的是墙边的物体>
图1。靠近墙壁放置的10x10 mm盒子的概念性横截面。 深度相机将测量带有一些深度噪波的墙和盒子,如右图所示。 理想的无噪音测量如左侧所示。
图2。将平滑滤波器应用于图1中的噪声测量将平滑数据,但可能会导致不需要的伪影,例如圆角或拉长的边缘,或过冲。在左上角,我们应用左秩=5的中值滤波器。在右上角,我们应用窗口大小为13的简单移动平均值。在左下角,我们应用α=0.1的双向指数移动平均值。在右下角,我们应用本文描述的边缘保持滤波器类型,其中我们使用α=0.1的指数移动平均值,但仅在相邻像素的深度差小于δ=3的阈值步长的情况下。最后一个滤波器将作为本文采用的边缘保持滤波器的基础。
我们在“librealsense”示例中使用的边缘保持过滤器是一种简化的域转换过滤器,但我们再次强调,这只是可以应用的许多过滤器中的一种。对于该过滤器,我们在X轴和Y轴上光栅扫描深度图,然后再扫描两次,同时使用确定平滑量的α(α)参数计算一维指数移动平均值(EMA)[https://en.wikipedia.org/wiki/Moving_average]. 具体的递推公式为:
其中,α系数表示权重降低的程度。 Yt是新记录的瞬时值(视差或深度),St-1是在任何时间段t的EMA值。
-
Alpha=1表示不过滤,而Alpha=0表示过滤的无限历史。但是,我们还添加了一个名为delta(𝛿thresh)的参数。如果相邻像素之间的深度值超过此增量参数设置的深度阈值,则alpha将临时重置为1(无过滤)。这基本上意味着,如果观察到边,则会暂时禁用平滑。这将导致伪影,这取决于边是从右向左还是从左到右遍历,这就是为什么我们通常在x和y中使用两个双向通道,这往往足以减少伪影。
-
但是,使用参数时要小心,不要过度激进地删除特性。一组很好的默认值是Spatial Alpha=0.6和space Delta=8,其中Delta是以1/32差异为单位的,所以8意味着8/32差异。我们建议根据应用情况调整这些参数以获得最佳效果。在我们的实现中,我们专注于仅使用深度作为输入的过滤器。 通过使用由来自彩色图像的信息以及场景中的运动引导的滤镜,也获得了令人印象深刻的结果,但它们不在此处讨论范围。
-
这使我们进入了空间滤波英特尔实感摄像头深度图的另一个方面。 对于基于三角测量的深度传感器,测量的性质意味着深度噪声将随着距离的平方而增加。 这带来了一个小挑战,因为有必要使alpha和delta参数取决于范围,以便获得最佳结果,而不是使近距离数据过平滑,而使远距离数据不平滑。 相反,我们建议在视差域(1 / Distance)中应用边缘保留滤镜。 这就是为什么我们的实现包含以下内容的流水线:1.转换为视差空间,2.应用空间和时间滤波,以及3.转换回距离。 应当指出,RealSense传感器还可以直接在视差中输出深度,从而无需进行深度到视差转换。
-
我们提到的深度图的另一个方面是“孔”的存在。 在立体声系统中,漏洞是由于数据不可用或置信度太低所致。 漏洞通常是由以下原因造成的:A.遮挡–由于阴影(又称为遮挡),左右图像看不到同一物体。 由于我们将所有内容都引用到左侧的成像器,因此您将在对象的左侧以及沿图像的左侧看到阴影。 B.缺乏纹理–立体匹配依赖于左右图像中的纹理匹配,因此对于没有纹理的表面(如平坦的白墙),深度估计可能会很有挑战性(这就是使用投影仪生成纹理的原因); C.多次匹配–当存在多个同样好的匹配时,例如在观察非常统一的周期性结构时,就会发生这种情况。 D.无信号–如果图像曝光不足或曝光过度,则会发生这种情况; E.低于minZ –如果对象非常接近,则超出了立体算法的搜索范围,并且我们具有最小工作距离。 需要将物体比该MinZ更远才能看到。
-
我们看看有孔时该怎么办。 对于某些应用,最好不要做任何事情。 对于机器人导航和避障以及3D扫描应用程序,通常都是如此。 对于深度增强摄影等其他应用程序,人类更喜欢看没有孔的图像,“最佳猜测”比没有猜测要好。
-
Spatial Hole-filling:我们的英特尔实感SDK API中有一些非常简单的填充孔的方法。 一种简单的孔填充方法是使用指定半径内的相邻左或右有效像素填充孔。 为了效率考虑,该方法在空间过滤阶段以librealsense实现。
另一个简单的孔填充方法(和离散处理块)是使用左有效像素填充孔,因为我们的立体算法是参考左成像器的。 这在“ librealsense”库中作为单独的处理块(称为Hole_filling_filter)实现。 在SDK中,通过用1.左有效像素值填充空像素,2。有效的五个左上和下像素像素值(用于深度图)中的最大值(最远)来填充孔像素,从而实现了三种左填充方法。 。有效的左上和下五个像素值中最小的一个(用于视差图)。 所有这些条件都有利于假设应使用最近的背景图像(而不是场景的前景值)填充孔,并且对于遮挡填充效果很好.
图3。使用离散孔填充处理块应用孔填充有三个填充选项。
现在回到改进后处理中的深度噪声,我们注意到另一个非常重要的噪声贡献是时间噪声。Intel RealSense D4xx传感器不具备任何先验或历史知识,因此每个深度帧的计算完全独立于前一帧,并且是完全确定的。但是,输入图像上存在噪声,这些噪声可能来自固有的传感器噪声、环境噪声(光照变化)、运动或投影仪噪声。所有这些源在时间上都会产生一些深度噪声。一般来说,这意味着可以通过以较低的帧速率运行和更长时间地积分曝光来改进数据质量,或者,如下面将讨论的,通过对深度映射中的每个像素应用一些时间平均。
-
Temporal filtering & persistence: 如果可能,建议使用一些时间平均值来提高深度,确保不让“孔”(深度=0)影响计算。在“librealsense”实现中,我们坚持使用与空间过滤相同类型的指数移动平均(EMA)过滤器。像以前一样,会有一个alpha参数,它将代表应该平均的时间历史的范围。这种方法的好处是“分数”帧基本上可以平均。通过设置alpha=1,将进行零过滤,但将alpha降低到接近0,将增加平均值并平滑。因此,它不是一个简单的离散“平均2、3或4帧”,而是允许更细粒度的平滑。此外,就像空间平均一样,增加一个阈值参数delta是很重要的。通过这种方法,我们尝试减少边缘附近的时间平滑,并且在平均化中不包括孔(z=0)。时间过滤器的一些好的默认值是alpha=0.5和delta=20。
我们提供的另一个临时过滤器是“持久性过滤器”。这本质上是一个过滤器,可以用来看得更远。对于每个深度像素,过滤器将查看孔的历史记录,并将保留在指定帧数(0-7)内看到的最后一个有效值。持久性值为8意味着我们将无限期地保留最后一个有效值,而值为1表示如果您看到一个洞,则只查看历史中的一个帧,否则使用最新的值(即z=0)。因此,当相机在黑暗中指向一个大房间时,在一定的范围内,有效的深度值将开始让位给洞。如果使用持久性特性,您将看到更远的地方,因为它将持久化像素。换言之,这是一种空洞填充算法,但它的猜测是基于过去看到的一个很好的值,而不是基于相邻像素的填充。当然,所有的补洞算法都容易出现一些错误和伪影,应该在仔细考虑权衡后使用。在这个持久性过滤器的特定情况下,会有一个伪影,如果你在摄影机前面挥舞一个对象,你会看到一个不同的填充行为,这取决于你在场景中是向左还是向右移动对象。向右移动会将值从对象保留到遮挡区域,使对象更宽,而向左移动将保留背景中的值(这更好)。与所有的算法一样,这可以用更高级别的逻辑来修复,但超出了当前的范围和实现。
利用Intel RealSense SDK API实现后处理
在librealense API中,后处理作为一系列“处理块”进行处理。 概念相当简单,如图4所示。请参见后处理示例。(在example里面)
图4。这显示了使用RealSense的正常流程™ 摄像机初始化,开始流媒体,循环捕获数据,最后关闭所有流。橙色显示添加后处理所需的附加步骤。
首先,在开始捕获之前,需要为所有处理步骤初始化后处理“块”和队列。 通过对每个处理块使用以下三个命令集来完成此操作:
Initialization sequence:
rs2_frame_queue* rs2_create_frame_queue(int capacity=1, rs2_error** error);
rs2_processing_block* rs2_create_XXX_filter_block(rs2_error** error);
void rs2_start_processing_queue(rs2_processing_block* block, rs2_frame_queue* queue, rs2_error** error);
请注意,XXX表示不同的过滤器:
rs2_processing_block* rs2_create_spatial_filter_block(rs2_error** error);
rs2_processing_block* rs2_create_decimation_filter_block(rs2_error** error);
rs2_processing_block* rs2_create_disparity_transform_block(unsigned char transform_to_disparity, rs2_error** error);
rs2_processing_block* rs2_create_temporal_filter_block(rs2_error** error);
rs2_processing_block* rs2_create_hole_filling_filter_block(rs2_error** error);
视差转换块有一个“ transform _ to _ distances”标志,表示它是从深度到视差(= 1)还是从视差到深度(= 0)的转换。应该分别为每个处理块初始化。
初始化后,有必要设置处理过滤器参数。 可以在流式传输期间的任何时间完成此操作。
设置处理过滤器参数:
可以使用SetOption命令设置过滤器参数:
void rs2_set_option(const rs2_sensor* sensor, rs2_option option, float value, rs2_error** error);
有四个相关的枚举选项:
36 - RS2_OPTION_FILTER_MAGNITUDE 37 - RS2_OPTION_FILTER_SMOOTH_ALPHA 38 - RS2_OPTION_FILTER_SMOOTH_DELTA 39 - RS2_OPTION_HOLES_FILL
重要的是要注意,对于set_option中的“ sensor”指针,它应该是指向特定“ Processing Block”的指针。 因此,例如,如果要为时间滤波器设置ALPHA参数,则可以使用指向时间滤波器块的指针。
Filter magnitude = sub-sampling number (ex: 2, 3, 4..)
对于空间过滤器块,有四个选项:
Filter magnitude = iterations of the filter. We recommend using 2 (default). Filter smooth alpha = the spatial alpha parameters (0-1)Filter smooth delta = the spatial delta threshold in 1/32 disparities. Default is 20, but try 8 as well.Filter holes fill = the spatial radius for hole filling (0=none, 1=2 pixels, 2=4 pixels, 3=8 pixels, 4=16 pixels, 5=unlimited).
对于时间滤波器块,三个选项是:
Filter smooth alpha = the temporal alpha parameters (0-1)Filter smooth delta = the temporal delta threshold in 1/32 disparities. Default is 20.Filter holes fill = the persistence value (0=no persistence, and 8=persist indefinitely)
对于单独的孔洞填充块,只有一个选项:
Filter holes fill = additional filling of holes based on choosing nearest neighbor filled with 0=Use left pixel to fill, 1=Max of nearest pixels on left, and 2=Min value of nearest pixels on left, as shown in Figure 3.
过滤器孔洞填充=基于选择最近邻点的其他孔洞填充,填满0=使用左侧像素填充,1=左侧最近像素的最大值,以及2=左侧最近像素的最小值,如图3所示。
一旦所有过滤器都已初始化,我们就可以在流处理过程中灵活地处理帧。 循环中捕获数据的正常流程是使用wait_for_frame命令。
rs2_frame* rs2_wait_for_frame(rs2_frame_queue* queue, unsigned int timeout_ms, rs2_error** error);
这是一个阻塞呼叫。一旦一个新的帧到达,就可以分析这个帧,并在帧被释放之前处理和显示数据,然后重复这个过程。
但是现在我们可以按照我们想要的顺序向流添加后期处理。推荐的流程如图5所示。
图5。在捕获循环中,处理流可以通过使用“process frame”命令自由定义,并在结束释放之前传递帧指针。
应用过滤的命令是将最新帧作为输入,并按顺序多次执行所需的处理命令:
void rs2_process_frame(rs2_processing_block* block, rs2_frame* frame, rs2_error** error);
其中处理块指针指定特定的后处理步骤。 然后,每个步骤的输出帧将成为下一个处理块的输入帧,依此类推。 在重复循环之前,仅需要释放最终处理的帧。
示例结果和权衡
现在,我们转向探讨一些示例,设置的影响以及计算负担。
首先,我们解决计算负担的问题。 图6显示了在Intel i7笔记本电脑上完成每个步骤所需的时间(以毫秒为单位)。 我们在这里仅针对一台PC进行基准测试,以显示过滤器的相对处理时间。 显然,这些时间将取决于所使用的特定计算平台。
图6.这详细说明了在两个不同的PC平台上执行后处理所需的时间。 这主要是为了深入了解主要的处理负担在哪里。 这些都是假设在CPU上实现单线程的情况下完成的。
通过将子采样数量从1(非子采样)调整为6,我们看到总处理时间从37ms减少到5.5ms。 我们还发现,对于低子样本(高输出分辨率),空间滤波是最耗时的,但是对于高子样本(低输出分辨率),空间滤波时间从22ms减少到1.34ms。 还应该注意的是,这些过滤器没有通过使用任何特殊指令或并行处理进行优化,以保持尽可能高的代码可移植性。 对于许多应用程序,当以1280x720分辨率开始时,最好的权衡可能是3的子采样,如果不使用空洞填充,则所有后处理需要12.5ms。
时间滤波器被认为是非常有效的,并且仅使用单个完整的存储器帧。 在3的子采样因子下(花费5.8ms),时间滤波器仅花费0.76ms,而与滤波器的alpha或delta参数选择无关。
现在转到过滤器的效果,我们将展示特定场景的一些定性结果。测试场景如图7所示。它是一个白色的英特尔RealSense包装盒,和一个黑色皮革笔记本在一个平面纹理。
如果我们打开“persistence”过滤器,我们会看到笔记本上的漏洞大部分会消失,如图9所示。
如果改为打开空间过滤器孔填充块,我们会看到盒子和笔记本附近的孔消失了,并被背景替换了。 尽管这种填充似乎在笔记本电脑的中部和英特尔实感盒附近的遮盖物都可以正常工作,但在笔记本电脑的边缘附近,我们发现孔被背景的深度所取代,从而给人留下了印象。 笔记本严重撕裂。 对于许多应用程序,最好留下一个漏洞,而不是提供错误的值。
在本文的其余部分中,我们将重点讨论点云可视化,这是观察空间噪声的最佳方法,特别是如果我们不将彩色图像纹理应用于可能隐藏数据的网格。作为一个起始参考点,我们测量了背景倾斜平面拟合的均方根噪声。当投影仪关闭时,均方根噪声约为0.6mm,或0.085亚像素均方根噪声。当投影仪在70mW(低于默认值150mW)时,这实际上会降低到0.7mm或0.1sub-pixel RMS噪声(请参阅我们的其他调整文件了解为什么会出现这种情况)。
图11.显示了子采样= 1、2、3的场景点云。
图12.时间平均的效果。 左边是原始图像,右边使用时间过滤器。
要查看时间平均的效果,在实时观看或在视频上观看时最明显,这在本文中是无法做到的。 取而代之的是,图12试图显示在投影机开启的情况下进行主动时间滤波(alpha = 0.1,delta = 20)的情况。 深度噪声的改善在黑色笔记本电脑上最为明显,RMS噪声从2mm(0.32子像素)变为1.1mm(0.15子像素)。 对于关闭投影机的背景,相同的时间滤波器将RMS从0.40mm(子像素= 0.07)提高到0.29mm(子像素= 0.05)。 我们应该注意,当在实感深度摄像机可以提供优于1mm深度分辨率的近距离进行测量时,最好将“深度单位”(深度步长)设置为100um,而不是默认的1000um。 如《调音》白皮书中所述,深度的量化步骤。 如果执行此操作,则增量参数也应按10缩放。
关闭时间过滤并现在打开空间过滤器,我们在图13中看到可以平滑空间深度噪声。 在此示例中,我们设置空间alpha = 0.4,分别为delta = 100(右上图)和delta = 4(右下图)。 这清楚地表明了空间滤波器的边缘保留特性。 在delta = 100的情况下,此场景的相邻像素之间的步长大小永远不会超过100/32 = 3.12视差,因此框的边缘将变得平滑,即不会保留边缘。 对于delta = 4,我们恢复边缘,同时仍使平坦表面平滑。
最后,在图13的右下角,我们显示了启用了所有过滤的场景——空间、时间和持久性,在左上角显示了对原始场景的改进。
图13.空间过滤的效果。 右上方是原始深度图。 右上方是没有任何边缘保留的平滑深度图。 左下方是相同的平滑深度图,但空间增量= 4,最后在右下方,我们组合了时间和空间过滤器。
Conclusion
我们在此处显示了一组后处理滤镜,可以将这些滤镜应用于英特尔实感摄像头的深度图以改善深度。 这旨在仅显示一种“清理”深度图像的方法的示例,众所周知,在计算或引入某些伪像时总会存在取舍。 例如,时间滤波可以帮助减少> 2x的深度噪声,但应谨慎用于高速运动的场景。 对于空间滤波,我们展示了一种边缘保留滤波器,可以在尝试保留真实特征的同时使图像平滑。 我们还演示了持久性过滤器的使用,该过滤器可用于增加系统的范围,尤其是在黑暗中。 所有这些过滤器都是开源的,可在Intel RealSense SDK 2.0中使用,并且其结构易于在不同序列中应用。