图像传感器往往面临的挑战是明暗亮度相差较大场景下的物体细节显示能力,例如拍摄室内窗边的人或物体,如图1所示。在机器人方向,类似的场景也会出现,比如在工厂走廊(阳光和阴影共存的场景)运行的自主移动机器人(AMR);在落地窗旁工作的清洁机器人面临着过曝和背光物体过暗的问题。在这些场景中,长时间曝光可能会导致亮区过度曝光,而短时间曝光可能无法捕捉暗区的细节。Orbbec Gemini 330 Series 立体相机也面临着类似的挑战,因为深度图像的本质是根据左右IR计算得到。
图1 半户外场景
高动态范围的成像是在计算机图形学与电影摄影术中,是用来实现比普通数位图像技术更大曝光动态范围(即更大的明暗差别)的一组技术,利用多帧合并的方式来克服普通传感器的极限。对于3D深度摄像头,高动态范围意味着更高的深度填充率和更高的准确性,这将显著增强机器人的避障能力。本文介绍了与HDR功能相关的基本概念,并介绍了HDR在Orbbec Gemini 330 Series 相机的应用。
曝光和增益是摄影和成像技术中的两个主要参数,直接影响图像的质量和清晰度。
ORBBEC Gemini 330 Series 分别控制可见光RGB相机、红外相机的曝光。深度或红外摄像头,曝光以微秒(µs)为单位进行测量。曝光时间范围从1µs到约166 ms。较长的曝光时间可以增加低光环境中图像亮度和信噪比(SNR),从而提高图像清晰度,反之亦然。室内场景通常需要较长的曝光时间,而户外场景则需要较短的曝光时间。
图2 图像亮度随曝光时间的变化趋势
增益不会影响传感器的进光量,而是放大现有的电信号。增加增益可以增强低光条件下的图像亮度,但也会增加噪声并降低图像质量。此外,将较短的曝光时间与较高的增益设置相结合,可以有效减小由快速运动引起的运动模糊等效应。
图3 图像亮度随时间的变化
当电信号转换为数字图像过程中,它们根据传感器的有效动态范围和可用的比特深度进行量化,对于一个k位成像传感器,通常预期的灰度值范围从0到 2k-1, 小于最小阈值或大于最大阈值将被截断。这可能导致出现过曝和欠曝的区域。曝光过度的区域通常在IR图像中呈现白色,而曝光不足IR图像会呈现黑色。在3D相机中该问题也会存在,过曝和欠曝会导致深度图出现缺失或噪声,如下图所示。
图4 过曝(上)和欠曝(下)两种不良情况:左测为红外图像,右侧为深度图像
为了解决欠曝或者过曝的问题,常见的方法包括:手动调整相机曝光、自动曝光策略、HDR技术 或 图像增强等。本文不讨论图像增强技术。
Orbbec Gemini 330 Series 支持手动曝光调整,即允许用户调整曝光时间和增益来保证图像亮度。该方法不会根据环境光照的变化自动调整。一般应用在需要执行精确的曝光控制的场景,例如,为了拍摄快速移动的物体,可以设置较小的固定曝光时间以防止运动模糊。
图5 用短曝光(左图)和长曝光(右图)拍摄相同的场景
用户可以在Orbbec Viewer中设置左右IR相机或深度的曝光时间和增益。设置前先将关闭自动曝光。其中左右IR和Depth相机的曝光和增益是同步调整,三者保持一致。
图6 在Orbbec Viewer中手动曝光参数设置
代码参考如下:
// Create a pipeline with default device
ob::Pipeline pipe;
// Get the device from the pipeline
auto device = pipe.getDevice();
// set ir exposure value to 3000
device->setIntProperty(OB_PROP_IR_EXPOSURE_INT, 3000);
// set ir gain value to 16
device->setIntProperty(OB_PROP_IR_GAIN_INT, 16);
自动曝光是相机根据光线的强弱自动调整曝光量,防止曝光过度或者不足,保证图像信噪比满足用户需求。例如,机器人需要在室内和室外工作,通常将摄像头设置为自动曝光模式。
图7 自动曝光效果,室内(上图)和室外(下图),左边是IR图,右边是深度图
默认情况下,Orbbec Gemini 330 Series 相机配置为自动曝光模式。在这种模式中,相机会自动调整曝光和增益值,直到平均图像强度达到某个预设值。在Orbbec Viewer中也可以开启或者关闭自动曝光。
用户可以根据场景调整自动曝光参数,即目标亮度预设值(Mean Intensity Set Point),设置方式如下图所示:
图8 在Orbbec Viewer中自动曝光参数配置
自动曝光配置代码如下:
// Create a pipeline with default device
ob::Pipeline pipe;
// Get the device from the pipeline
auto device = pipe.getDevice();
// Enable IR sensor auto exposue
device->setBoolProperty(OB_PROP_IR_AUTO_EXPOSURE_BOOL, true);
// set the IR sensor mean intensity set point value to 60
device->setIntProperty(OB_PROP_IR_BRIGHTNESS_INT, 60);
自动曝光可以满足用户的绝大部分场景的使用,但是对于极端的高低反场景,可以考虑第3节的HDR功能。
相机的最大曝光时间受到帧率的限制,如表1所示。在自动曝光模式下,最大曝光时间小于该帧率对应的最大曝光(表1所示最大曝光时间)。在手动曝光模式下,允许曝光曝光时间大于该帧率对应的最大曝光,但会导致帧率降低。例如,如果相机的帧率设置为30fps,并且手动曝光时间设置为30ms,但某些室内场景仍然显得太暗,曝光时间可以延长到60ms, 但此时输出帧率降低到15fps。
表1 每个帧率对应的最大曝光时间
HDR 高动态范围成像的目的是可以显示更大的范围亮度。为了克服传统动态范围的限制,可以采用采集多帧不同曝光时间的图像,并将它们合并成一张HDR图像。 因为Orbbec Gemini 330 Series 硬件(MX6800)的限制,无法在硬件ASIC上实现多帧融合。我们提出了一种运行在主机CPU上的软件解决方案来实现此功能。利用两个连续帧的数据直接合成一帧深度图像,从而增强了16位深度图像的动态范围。此功能需要固件版本v1.2.01+和SDKv1.9.0+。
如上所述,HDR可调整固定的曝光和增益值,用户根据实际应用场景来进行设置。深度的HDR即为合并不同的曝光和增益设置下的深度图。对于给定的n帧的深度图,定义 d_i (i=0, \ldots, n-1)为任意像素(u, v)的第i帧深度值,则该点(u, v)融合后的深度值可表达为:
d=\sum_{i=0}^{n-1}\mathscr{W}(d_i)\cdot d_i, (1)
其中,\mathscr{W}是权重函数。在Orbbec的HDR功能n=2。\mathscr{W}函数的取值为0或1,因此上述函数也可以表示为:
d=\begin{cases} d_0 &\text{if } d_0\neq0 \\ d_1 & \text{elif } d_1\neq0 \\ 0&\text{otherwise} \end{cases} (2)
注: 如前所述,该融合算法在OrbbecSDK中实现。
但用户使用Orbbec Viewer工具中高动态范围(HDR)控制功能时,需要开启深度或红外数据流。因为HDR涉及连续两帧数据的融合,为了避免图像拖影,可以将帧率提高到30fps及以上。 每帧HDR输出结果为合并相邻两帧后的结果,即“高曝光+低曝光”融合和“低曝光+高曝光”融合方式。这种方式会导致帧延迟,但帧率可以保持不变。用户可以按照如下设置方式手动调整高曝光和低曝光帧的曝光和增益设置,以优化HDR性能。 为了更准确设置参数,可以先关闭HDR-Merge功能,先检查高/低曝光下的所感兴趣的物体是否有较为完整的深度(高反物体和低反物体)。
图9 在Orbbec Viewer中配置HDR的硬件
开启HDR后,数据流会在两个预定的曝光设置之间交替。可以查看工具面板的metadata信息列表。
图10 检查HDR流的帧元数据
其中,metadata 相关内容说明如下:
● Hdr Sequence Name:HDR序列的名称
● Hdr Sequence Size:HDR序列的长度
● Hdr Sequence Index:当前帧在HDR序列中的编号(从零开始)
● Exposure:用于当前帧的曝光
● Gain:用于当前帧的增益
HDR融合后的深度需要启用HDRMerge后处理开关,如下图所示:
图11 开启深度HDRMerge
开启HDRMerge后,深度不会闪烁,但是IR图仍然会闪烁。OrbbecSDK不支持对IR图像的融合,IR图像的融合用户可以根据自身的需要按照简单的图像处理即可完成。除了HDRMerge之外,OrbbecSDK还可以通过数据帧ID,将闪烁流拆分为两个独立数据流(需要关闭HDRMerge)。通过ID来过滤数据流,此时深度也不再闪烁,输出的深度为高曝光帧或者低曝光帧。
图12 通过序列ID对流进行过滤
HDR使能和HDRMerge功能生效,相关配置可以参考如下示例代码:
#include "window.hpp"
#include "libobsensor/hpp/Pipeline.hpp"
#include "libobsensor/hpp/Error.hpp"
int main(int argc, char **argv) try {
// Create a pipeline with default device
ob::Pipeline pipe;
// Get the device from the pipeline
auto device = pipe.getDevice();
// Check if the device supports HDR merge
if(!device->isPropertySupported(OB_STRUCT_DEPTH_HDR_CONFIG, OB_PERMISSION_READ_WRITE)) {
std::cerr << "Current default device does not support HDR merge" << std::endl;
return -1;
}
// Configure which streams to enable or disable for the Pipeline by creating a Config
std::shared_ptr<ob::Config> config = std::make_shared<ob::Config>();
// Get all stream profiles of the depth camera, including stream resolution, frame rate, and frame format
auto depthProfiles = pipe.getStreamProfileList(OB_SENSOR_DEPTH);
auto depthProfile = depthProfiles->getProfile(OB_PROFILE_DEFAULT);
config->enableStream(depthProfile);
// Create HdrMerge post processor to merge depth frames betweens different hdr sequence ids.
// The HdrMerge also supports processing of infrared frames.
ob::HdrMerge hdrMerge;
// configure and enable Hdr stream
OBHdrConfig obHdrConfig;
obHdrConfig.enable = true; // enable HDR merge
obHdrConfig.exposure_1 = 7500;
obHdrConfig.gain_1 = 24;
obHdrConfig.exposure_2 = 100;
obHdrConfig.gain_2 = 16;
device->setStructuredData(OB_STRUCT_DEPTH_HDR_CONFIG, &obHdrConfig, sizeof(OBHdrConfig));
// Start the pipeline with config
pipe.start(config);
// Create a window for rendering and set the resolution of the window
bool resizeWindows = true;
Window app("HDR-Merge", 1280, 720);
bool mergeRequired = true;
std::cout << "Press 'M' to toggle HDR merge." << std::endl;
while(app) {
auto key = app.waitKey(10);
if(key == 'M' || key == 'm') {
mergeRequired = !mergeRequired;
if(mergeRequired) {
std::cout << "HDR merge enabled." << std::endl;
}
else {
std::cout << "HDR merge disabled." << std::endl;
}
}
auto frameSet = pipe.waitForFrames(100);
if(frameSet == nullptr) {
continue;
}
auto depthFrame = frameSet->depthFrame();
if(depthFrame == nullptr) {
continue;
}
if(mergeRequired) {
// Using HdrMerge post processor to merge depth frames
auto mergedDepthFrame = hdrMerge.process(depthFrame);
if(mergedDepthFrame == nullptr) {
continue;
}
// add merged depth frame to render queue
app.addToRender(mergedDepthFrame);
}
else {
// add original depth frame to render queue
app.addToRender(depthFrame);
}
}
// Stop the Pipeline, no frame data will be generated
pipe.stop();
// close hdr merge
obHdrConfig.enable = false;
device->setStructuredData(OB_STRUCT_DEPTH_HDR_CONFIG, &obHdrConfig, sizeof(OBHdrConfig));
return 0;
}
catch(ob::Error &e) {
std::cerr << "function:" << e.getName() << "\nargs:" << e.getArgs() << "\nmessage:" << e.getMessage() << "\ntype:" << e.getExceptionType() << std::endl;
exit(EXIT_FAILURE);
}
Orbbec Gemini 330 Series 相机的HDR功能提高了深度填充率,降低了深度空洞率。我们可以在如下场景下对比AE和HDR效果,场景包含一个矩形高反面板和一个低反射支架, 当采用自动曝光时, 从深度图可知,矩形白色高反贴已过曝,深度出现了明显了缺失。
图13 自动曝光模式下的深度效果(高低反场景,高反贴深度缺失)
如果使用HDR功能,分别设置曝光时间为7000ms和100ms, 得到如下深度效果,以及融合后的深度效果,从深度效果来看,整幅图像中的物体深度完整,不管是低反黑色支架还是白色高反贴。
图14. HDR效果示意:高曝光帧(第一行),低曝光帧(第二行),融合后的HDR深度(第三行)
HDR另一个关注点是其对深度准确性的影响。如下所示,在距离Orbbec Gemini 335相机约1043mm的墙上粘贴几块高反射面板,如左上方的RGB图像所示。从右上方图上可知,高反区域的散斑点已过曝,因此这些区域深度无法准确估算。左下方采用自动曝光采集的深度图,右下方是开启HDR及HDRMerge后的深度。
图15. 将高度反射的面板紧贴在平坦的墙壁上以测试准确性
比较高反贴在自动曝光下的深度和HDR融合后的深度,选择深度图高反贴区域,采用Imagej 工具显示某一行的深度波动情况,黑色表示开启自动曝光和红色线表示HDR融合后的深度,从下面数据可以看到HDR融合后的深度波动会略小,不过在有些情况下,两者精度波动趋势也不明显。
图16. 在与图15相同的场景中捕获的深度图像的部分剖面
Orbbec Gemini 330 Series 相机能提供高质量的深度成像,其中自动曝光可以满足大多数情况下的用户需求,包括室内和室外。对于自主移动机器人,自动曝光模式能够有效处理不断变化的光照条件。自动曝光算法在固件中实现的,因此不会消耗上位机资源,用户操作简单。而手动曝光通常用于需要精确曝光控制的场景。
深度相机的HDR主要是为了解决自动曝光和手动曝光都无法解决的一些挑战。它显着提高了在一些极线光照环境下的深度填充率,可能提升深度准确性。它能帮助减少了机器人视觉系统中两种常见且严重的错误:“漏检”和“误检”。
深度HDR也有自身的局限性,因为需要两帧数据帧的融合,需要消耗上位机额外的算力,且还会导致帧延迟;确定适当的高曝光时间和低曝光时间需要一定的经验;对于高速移动物体会因为融合而产生运动伪影。
在相机快速移动的场景中,可以调整自动曝光的平均强度来提高深度质量。如果需要使用HDR功能,可以考虑更高帧率,可以改善运动伪影。对于需要精确深度数据的应用场景,可以使用HDR开启,但不使用多帧融合(HDRMerge 关闭),即采用高曝光和低曝光帧以满足特定算法需求。