IntelRealSense 深度相机 测量物体的实际长度 —— rs-measure 官网文档翻译

  • Post author:
  • Post category:其他




总览

本教程显示了使用深度数据测量真实世界距离的简单方法。

	## Note: 测量真实世界中物体的维度是深度相机的一个明显的应用之一。此样本不是适当的测量工具,而是展示关键概念。
	使用更好的算法可以显著改善测量结果。

在这个教程中你将会学到怎样:

  • 在空间上将颜色流与深度对齐(和在

    rs-align

    中深度到颜色的对齐相反)
  • 利用后处理来处理缺失和噪声的深度数据
  • 在2D像素和3D空间中的点之间进行转换
  • 利用多核来并行化数据流
  • 使用OpenGL在深度之上叠加颜色



预期输出

在这里插入图片描述

这个例子允许用户测量物理世界中的两个点。



代码总览



深度处理流程

我们通过定义所有的处理块来开始这个例子。我们将要使用

// Colorizer is used to visualize depth data
// Colorizer 用于可视化深度数据
rs2::colorizer color_map;
// Use black to white color map
// 使用黑色到白色颜色映射
color_map.set_option(RS2_OPTION_COLOR_SCHEME, 2.f);
// Decimation filter reduces the amount of data (while preserving best samples)
// 抽取滤波器减小数据的数量(同时保持最佳样本)
rs2::decimation_filter dec;
// If the demo is too slow, make sure you run in Release (-DCMAKE_BUILD_TYPE=Release)
// but you can also increase the following parameter to decimate depth more (reducing quality)
// 如果这个demo太慢,确保你在release中运行(-DCMAKE_BUILD_TYPE=Release)
// 但是你可以同样增加下面的参数来更多的减少深度(降低质量)
dec.set_option(RS2_OPTION_FILTER_MAGNITUDE, 2);
// Define transformations from and to Disparity domain
// 定义从和到时差域的转换
rs2::disparity_transform depth2disparity;
rs2::disparity_transform disparity2depth(false);
// Define spatial filter (edge-preserving)
// 定于空间过滤器(保留边)
rs2::spatial_filter spat;
// Enable hole-filling 
// Hole filling is an aggressive heuristic and it gets the depth wrong many times
// However, this demo is not built to handle holes 
// (the shortest-path will always prefer to "cut" through the holes since they have zero 3D distance)
// 开启填孔
// 填孔是一个积极的启发性策略,它多次错误地获得深度
// 然而,这个demo不是用于处理填孔的
// (最短的路径总是喜欢切穿这些孔,因为它们的3D距离为零。)
spat.set_option(RS2_OPTION_HOLES_FILL, 5); // 5 = fill all the zero pixels 5 = 填充所有的零像素
// Define temporal filter 
// 定义时间过滤器
rs2::temporal_filter temp;
// Spatially align all streams to depth viewport
// We do this because:
//   a. Usually depth has wider FOV, and we only really need depth for this demo
//   b. We don't want to introduce new holes
// 将所有流在空间上对齐到深度视口
// 我们这样做是因为:
//   a. 通常深度有一个更广的FOV,并且对于这个demo我们只需要深度
//   b. 我们不想引入新的洞
rs2::align align_to(RS2_STREAM_DEPTH);

下面,我们为深度+颜色流配置相机管道

// Declare RealSense pipeline, encapsulating the actual device and sensors
// 定义RealSense管道,封装真正的设备和传感器
rs2::pipeline pipe;

rs2::config cfg;
cfg.enable_stream(RS2_STREAM_DEPTH); // Enable default depth //启用默认深度
// For the color stream, set format to RGBA
// To allow blending of the color frame on top of the depth frame
// 对于颜色流,设置格式为RGBA
// 允许在深度帧顶部混合颜色帧
cfg.enable_stream(RS2_STREAM_COLOR, RS2_FORMAT_RGBA8);
auto profile = pipe.start(cfg);

我们的目标是生成没有任何孔的深度,因为这将会对我们的算法构成直接问题。

减少丢失像素的最好方法是让硬件来处理。

D400相机具有我们可以利用的高密度预设。

auto sensor = profile.get_device().first<rs2::depth_sensor>();

// Set the device to High Accuracy preset
// 将设备设置为高密度预设
auto sensor = profile.get_device().first<rs2::depth_sensor>();
sensor.set_option(RS2_OPTION_VISUAL_PRESET, RS2_RS400_VISUAL_PRESET_HIGH_ACCURACY);

给定一个帧集,我们将按顺序应用所有的处理块。

首先我们应用

align

处理块来将颜色帧对其到深度视口:

// First make the frames spatially aligned
// 首先使帧空间对齐
data = align_to.process(data);

然后,我们应用深度后处理流:

rs2::frame depth = data.get_depth_frame();
// Decimation will reduce the resultion of the depth image,
// closing small holes and speeding-up the algorithm
// 抽取将减少深度图像的结果
// 关闭小孔,并且加速算法
depth = dec.process(depth); 
// To make sure far-away objects are filtered proportionally
// we try to switch to disparity domain
// 确保按照比例过滤远处的物体
// 我们尝试转换到视差域
depth = depth2disparity.process(depth);
// Apply spatial filtering
// 应用空间过滤器
depth = spat.process(depth);
// Apply temporal filtering
// 应用时间过滤
depth = temp.process(depth);
// If we are in disparity domain, switch back to depth
// 如果我们在视差域,切回到深度
depth = disparity2depth.process(depth);
// Send the post-processed depth for path-finding
// 发送后处理深度用于路径查找
pathfinding_queue.enqueue(depth);
	所有基于立体的3D相机都具有噪声与距离平方成正比的特性。
	为了抵消这一点,我们将帧转换为视差域,使噪声在距离上更均匀。
	这对我们的结构光相机没有任何作用(因为他们没有这个属性)

我们同样应用标准颜色映射

// Apply color map for visualization of depth
// 应用颜色映射来可视化深度
auto colorized = color_map(depth);



将像素和3D中的点进行转换

要将深度图像中的像素转换为3D点,我们调用rs2_deproject_pixel_to_point C函数(定义在rsutil.h中)

这个函数需要深度内在函数(depths intrinsics),2D像素和距离(米为单位)。这是我们怎样得到深度内部函数(depth intrinsics)的:

auto stream = profile.get_stream(RS2_STREAM_DEPTH).as<rs2::video_stream_profile>();
auto intrinsics = stream.get_intrinsics(); // Calibration data 校准数据

可以使用depth_frame类的get_distance函数获取以米为单位的距离。

		过度调用get_distance会导致性能不佳,因为编译器无法跨模块边界进行优化,因此,直接从depth_sensor读取	DEPTH_UNITS选项并使用它将原始深度像素转换为米可能是有益的:

将所有内容放在一起会产生相当冗长的dist_3d函数:

float dist_3d(const rs2_intrinsics& intr, const rs2::depth_frame& frame, pixel u, pixel v)
{
    float upixel[2]; // From pixel //从像素
    float upoint[3]; // From point (in 3D) //从点(3D中)
 
    float vpixel[2]; // To pixel //到像素
    float vpoint[3]; // To point (in 3D) //到点(3D中)

    // Copy pixels into the arrays (to match rsutil signatures)
    // 将像素拷贝到数组中(匹配rsutil签名)
    upixel[0] = u.first;
    upixel[1] = u.second;
    vpixel[0] = v.first;
    vpixel[1] = v.second;

    // Query the frame for distance 
    // Note: this can be optimized
    // It is not recommended to issue an API call for each pixel
    // (since the compiler can't inline these)
    // However, in this example it is not one of the bottlenecks
    // 查询帧的距离
    // 注意:这个可以被优化
    // 不建议为每个像素发出API调用
    // (因为编译器不能内联这些)
    // 然而,在这里例子中它不是瓶颈之一
    auto udist = frame.get_distance(upixel[0], upixel[1]);
    auto vdist = frame.get_distance(vpixel[0], vpixel[1]);

    // Deproject from pixel to point in 3D
    // 将像素反映射到3D中的点
    rs2_deproject_pixel_to_point(upoint, &intr, upixel, udist);
    rs2_deproject_pixel_to_point(vpoint, &intr, vpixel, vdist);

    // Calculate euclidean distance between the two points
    // 计算空间中两个点的欧几里得距离
    return sqrt(pow(upoint[0] - vpoint[0], 2) +
                pow(upoint[1] - vpoint[1], 2) +
                pow(upoint[2] - vpoint[2], 2));
}



在后台线程上运行处理

在此示例中的后处理运算可能相对较慢。为了不阻塞主(UI)线程,我们将有一个专门的线程进行后处理。



视频处理线程

这个线程将消耗来自相机完整帧集,并且会产生包含颜色和着色深度帧的帧集(用于在主线程上渲染):

while (alive)
{
    // Fetch frames from the pipeline and send them for processing
    // 从管道中获取帧,并且发送它们进行处理 
    rs2::frameset fs;
    if (pipe.poll_for_frames(&fs)) 
    {
        // Apply post processing
        // ...
		// 应用后处理过程
		// ...
        // Send resulting frames for visualization in the main thread
        // 发送结果帧,用于在主线程中进行可视化。
        postprocessed_frames.enqueue(data);
    }
}



主线程

主线程是唯一允许用来渲染屏幕的。

它从postprocessed_frames中获取,并且只是展示结果:

while(app) // Application still alive? // 应用仍然存在吗?
{
    postprocessed_frames.poll_for_frame(&current_frameset);

    if (current_frameset)
    {
        auto depth = current_frameset.get_depth_frame();
        auto color = current_frameset.get_color_frame();

        glEnable(GL_BLEND);
        // Use the Alpha channel for blending 
        // 使用Alpha通道进行混合
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

        // First render the colorized depth image
        // 首先渲染着色的深度图像
        depth_image.render(depth, { 0, 0, app.width(), app.height() });
        // Next, set global alpha for the color image to 90%
        // (to make it slightly translucent)
        // 下一步,将彩色图像的全局alpha设置为90%
        // (使其略微半透明)
        glColor4f(1.f, 1.f, 1.f, 0.9f);
        // Render the color frame (since we have selected RGBA format
        // pixels out of FOV will appear transparent)
        // 渲染颜色帧(因为我们选择了RGBA格式,超出FOV的像素将会显示为透明)
        color_image.render(color, { 0, 0, app.width(), app.height() });

        // Render the simple pythagorean distance
        // 渲染简单的Pythagorean距离(勾股距离)
        render_simple_distance(depth, app_state, app, intrinsics);

        // Render the ruler
        // 渲染直尺
        app_state.ruler_start.render(app);
        app_state.ruler_end.render(app);

        glDisable(GL_BLEND);
    }
}

我们使用glBlendFunc使用颜色alpha通道在深度之上覆盖对其的颜色(流必须是格式RGBA才能工作)。


这个例子演示了一个简单而复杂的处理流程。每个线程都有一些不同的速率,它们都需要同步而不是相互堵塞。

这是使用线程安全的frame_queues作为同步原语和rs2::frame引用计数来实现的,用于跨线程的对象生命周期管理。