librealsense 深度后处理实战:rs-post-processing 示例源码逐段解析与六大滤镜调参指南

发布时间:2026/9/16 16:55:38
librealsense 深度后处理实战:rs-post-processing 示例源码逐段解析与六大滤镜调参指南 librealsense 深度后处理实战rs-post-processing 示例源码逐段解析与六大滤镜调参指南【免费下载链接】librealsenseRealSense SDK项目地址: https://gitcode.com/GitHub_Trending/li/librealsense导读本文以 librealsense 仓库中的 post-processing 示例文档 及其配套源码 rs-post-processing.cpp 为骨架完整拆解一个实时可调、可对比的深度图像后处理演示程序从管线配置、滤镜链装配、双线程架构到 ImGui 交互界面的全部实现细节并结合 src/proc 目录下各滤镜的底层源码逐一给出 Decimation、Disparity、Spatial、Temporal、Rotation、Threshold 六个处理块的原理、参数取值范围与调参建议。读完本文你既能照搬该示例搭建自己的深度后处理流水线也能理解每个滤镜选项背后的真实语义做到知其然亦知其所以然。一、示例概览一个可交互的深度滤镜调参台该示例演示了以下处理块processing blocks的用法Decimation抽取智能降低深度帧的分辨率密度Disparity视差在深度域与视差域之间做变换仅适用于立体深度传感器如 D400 系列Spatial空间滤波对深度数据做保边平滑Temporal时间滤波参考历史帧对深度数据进行滤波Rotation旋转旋转深度帧与红外帧。程序运行后窗口1280×720中会显示旋转中的点云其中原始深度帧与滤波后深度帧各占一半视口界面上为每个滤镜提供一个启用/禁用复选框Checkbox并为每个受支持的滤镜选项提供滑块Slider用户可实时调节参数并直观对比前后效果。从构建配置 CMakeLists.txt 可以看到该示例在BUILD_GRAPHICAL_EXAMPLES开关开启时才会被编译目标名为rs-post-processing使用 C11 标准并链接了tclap与仓库内置的 ImGui 源码${IMGUI_SOURCES}。二、依赖与辅助结构从头文件到两个关键类2.1 头文件引入程序首先包含 RealSense 跨平台 API 与示例辅助库#include librealsense2/rs.hpp // Include RealSense Cross Platform API #include example.hpp // Include short list of convenience functions for renderingexample.hpp 提供开窗window类与纹理准备等渲染辅助能力随后引入 ImGui 相关头文件用于绘制 GUI 控件#include imgui.h #include imgui_impl_glfw.h #include imgui_impl_opengl3.h #include realsense_imgui.h这些头文件来自仓库内置的 third-party/imgui 库其中realsense_imgui.h由仓库示例层提供位于 examples 目录封装了 ImGui 与示例渲染环境的桥接RsImGui::PushNewFrame等。2.2filter_slider_ui一个滤镜选项对应一个滑块struct filter_slider_ui { std::string name; std::string label; std::string description; bool is_int; float value; float step; rs2::option_range range; bool render(const float3 location, bool enabled); static bool is_all_integers(const rs2::option_range range); };该结构体把一个rs2_option滤镜选项与它的 GUI 滑块绑定在一起range保存选项的最小/最大/默认值value记录当前值is_int决定滑块按整数还是浮点数绘制render()负责在指定位置绘制滑块并返回是否发生改动。2.3filter_options滤镜与其选项的封装class filter_options { public: filter_options(const std::string name, rs2::filter filter); filter_options(filter_options other); std::string filter_name; //Friendly name of the filter rs2::filter filter; //The filter in use std::maprs2_option, filter_slider_ui supported_options; //maps from an option supported by the filter, to the corresponding slider std::atomic_bool is_enabled; //A boolean controlled by the user that determines whether to apply the filter or not };filter_options持有实际滤镜的引用rs2::filter通过它既能调用process()方法处理帧也能读取/设置选项supported_options把滤镜支持的选项映射到对应滑块is_enabled是用std::atomic_bool实现的线程安全开关供用户勾选是否启用该滤镜。在其构造函数见 rs-post-processing.cpp中程序遍历一组候选选项并逐一探测滤镜是否支持const std::arrayrs2_option, 6 possible_filter_options { RS2_OPTION_FILTER_MAGNITUDE, RS2_OPTION_FILTER_SMOOTH_ALPHA, RS2_OPTION_MIN_DISTANCE, RS2_OPTION_MAX_DISTANCE, RS2_OPTION_FILTER_SMOOTH_DELTA, RS2_OPTION_ROTATION };只要flt.supports(opt)为真就通过get_option_range(opt)、get_option_description(opt)、get_option_name(opt)拿到该选项的取值范围、说明文本与名称并用filter_slider_ui::is_all_integers(range)判断滑块类型——该函数检查range的min、max、def、step是否全部为整数若是则用整数滑块。2.4 辅助函数声明// Helper functions for rendering the UI void render_ui(float w, float h, std::vectorfilter_options filters); // Helper function for getting data from the queues and updating the view void update_data(rs2::frame_queue data, rs2::frame depth, rs2::points points, rs2::pointcloud pc, glfw_state view, rs2::colorizer color_map);render_ui负责绘制整个 GUIupdate_data负责从队列取帧、生成点云并更新视图纹理。三、六大处理块逐一定位底层实现与参数语义示例中声明的滤镜与视差变换如下rs2::decimation_filter dec_filter; // Decimation - reduces depth frame density rs2::rotation_filter rot_filter; // Rotation - rotates frames. By default, the constructor rotates depth frames. rs2::threshold_filter thr_filter; // Threshold - removes values outside recommended range rs2::spatial_filter spat_filter; // Spatial - edge-preserving spatial smoothing rs2::temporal_filter temp_filter; // Temporal - reduces temporal noise // Disparity transform from depth to disparity and vice versa const std::string disparity_filter_name Disparity; rs2::disparity_transform depth_to_disparity(true); rs2::disparity_transform disparity_to_depth(false);下面逐一对照 src/proc 目录下的实现文件说明每个处理块的真实行为与可调参数。3.1 Decimation抽取滤波源码位置decimation-filter.cpp。它注册的唯一选项是RS2_OPTION_FILTER_MAGNITUDE即界面上的 Decimation scale取值范围最小值 1最大值 8默认值 2步长 1线性抽取因子见 decimation-filter.cpp。实现上对 Z16 深度格式使用中值滤波按scale×scale的块patch滑动从块内非零像素中取中值作为输出像素对应opt_med3opt_med9等针对 3×39×9 内核的手工优化中值算法见 decimation-filter.cpp。对于偶数尺寸内核取中位以下一档的值。非 Z16 格式YUYV、RGB8、Y8、Y16 等则退化为块内均值sum / patch_size。值得注意的是输出帧的尺寸处理抽取后真实宽高为width/scale与height/scale但最终输出会被向上补齐到 4 的倍数_padded_width (_real_width 3) / 4 * 4同时内参焦距与主点按比例缩放fx / patch_size等以保证相机模型与降采样后的图像一致见 decimation-filter.cpp。调参提示scale2时输出面积约为原来的 1/4点云计算量大幅下降scale过大如 8会明显损失细节通常 23 已能满足多数降载需求。3.2 Disparity深度域 ↔ 视差域变换源码位置disparity-transform.cpp。rs2::disparity_transform(true)表示从深度变换到视差域false则反向。视差变换只在立体stereo深度传感器上才有意义例如 D400 系列因为视差定义依赖双目基线与焦距。在示例流程中Spatial 与 Temporal 滤波在视差域中执行效果更好——视差与深度呈倒数关系在视差域中近处物体的深度误差更接近线性滤波伪影更少。因此示例把深度→视差放在 Spatial/Temporal 之前处理完后再用disparity_to_depth还原回深度域详见第四节的处理线程流程。3.3 Spatial空间保边平滑源码位置spatial-filter.cpp。空间滤波基于指数移动平均思想对深度做迭代式平滑但通过梯度阈值保留边缘。其选项包括选项含义范围默认值步长RS2_OPTION_FILTER_SMOOTH_ALPHA指数移动平均的 Alpha 权重1不过滤0无限滤波0.25 ~ 1.00.50.01RS2_OPTION_FILTER_SMOOTH_DELTA保边阈值以深度层级计低于该梯度的区域被平滑1 ~ 50201RS2_OPTION_HOLES_FILL空洞填充模式0 ~ 50禁用1RS2_OPTION_FILTER_ITERATIONS迭代平滑次数1 ~ 521以上常量均定义于 spatial-filter.cpp空洞填充模式的枚举为Disabled0、2-pixel radius1、4-pixel radius2、8-pixel radius3、16-pixel radius4、Unlimited5见 spatial-filter.cpp。调参提示alpha越小平滑越强当前像素权重低delta是保边关键——它是深度梯度阈值梯度小于 delta 的区域视为平面被平滑大于 delta 的边缘被保留太小则平滑不充分、太大会抹掉真实边缘迭代次数增加会增强平滑效果但提高耗时。3.4 Temporal时间域滤波源码位置temporal-filter.cpp。时间滤波通过参考历史帧抑制时间噪声其选项选项含义范围默认值RS2_OPTION_HOLES_FILLPersistency mode持续性/空洞填充模式0 ~ 83RS2_OPTION_FILTER_SMOOTH_ALPHA指数移动平均 Alpha1完全采用当前像素0无限滤波0 ~ 10.4RS2_OPTION_FILTER_SMOOTH_DELTA保边梯度阈值1 ~ 10020见 temporal-filter.cpp。Persistency mode 的 9 档含义在源码中逐一登记见 temporal-filter.cpp0Disabled禁用1Valid in 8/8连续 8 帧有效2Valid in 2/last 33Valid in 2/last 4默认即最近 4 帧中至少 2 帧在该像素有效才采信该像素4Valid in 2/85Valid in 1/last 26Valid in 1/last 57Valid in 1/88Always on始终采用调参提示动态场景下 persistence 过严会导致运动拖影/空洞建议保持默认或放宽alpha默认 0.4 表示新帧只占 40% 权重适合抑制闪烁delta与 Spatial 类似用于在时间维度上区分真实变化与噪声波动。3.5 Rotation旋转源码位置rotation-filter.cpp。默认构造函数旋转深度流rotation_filter()委托给带std::vectorrs2_stream{ RS2_STREAM_DEPTH }的重载。其注册的选项RS2_OPTION_ROTATIONRotation angle最小值 -90最大值 180默认值 0步长 90见 rotation-filter.cpp。即旋转角度以 90° 为步进支持 -90°、0°、90°、180° 等整角度旋转。该处理块同样可作用于红外帧传入流列表即可。3.6 Threshold距离阈值源码位置threshold.cpp。它把深度值超出[min, max]范围的像素置为 0无效注册两个选项RS2_OPTION_MIN_DISTANCE最小距离范围 0 ~ 16 米默认 0.1 米步长 0.1RS2_OPTION_MAX_DISTANCE最大距离范围 0 ~ 16 米默认 4 米步长 0.1。见 threshold.cpp。处理时通过auto du orig-get_units()取深度单位把原始深度计数换算成米dist du * depth_data[i]只有_min dist _max的像素被保留见 threshold.cpp。两个选项分别用max_distance_option与min_distance_option包装保证 min 不会大于 max。调参提示桌面/机器人场景常用 0.13~4 米裁剪背景可显著降低点云噪点并聚焦有效范围。四、主流程管线配置与滤镜链装配4.1 窗口、视图与点云对象// Create a simple OpenGL window for rendering: window app(1280, 720, RealSense Post Processing Example); ImGui_ImplGlfw_Init(app, false); // Construct objects to manage view state glfw_state original_view_orientation{}; glfw_state filtered_view_orientation{}; // Declare pointcloud objects, for calculating pointclouds and texture mappings rs2::pointcloud original_pc; rs2::pointcloud filtered_pc;两个glfw_state分别保存原始/滤波两个视口的相机姿态旋转角两个rs2::pointcloud分别负责为两路数据生成点云与纹理映射。4.2 管线配置只请求深度流// Declare RealSense pipeline, encapsulating the actual device and sensors rs2::pipeline pipe; rs2::config cfg; // Use a configuration object to request only depth from the pipeline cfg.enable_stream(RS2_STREAM_DEPTH, 640, 480, RS2_FORMAT_Z16, 30); // Start streaming with the above configuration pipe.start(cfg);rs2::pipeline会自动从已连接相机中挑选匹配配置的设备并开始推流示例只请求一条 640×480、Z16 格式、30fps 的深度流文档版当前源码中实际写的是RS2_STREAM_DEPTH, 640, 0, RS2_FORMAT_Z16, 30高度传 0 表示由设备决定见 rs-post-processing.cpp二者语义等价——依赖配置对象从全部已连接相机中自动选择匹配设备。4.3 滤镜链入队顺序即执行顺序// Initialize a vector that holds filters and their options std::vectorfilter_options filters; // The following order of emplacement will dictate the orders in which filters are applied filters.emplace_back(Decimate, dec_filter); filters.emplace_back(Rotate, rot_filter); filters.emplace_back(Threshold, thr_filter); filters.emplace_back(disparity_filter_name, depth_to_disparity); filters.emplace_back(Spatial, spat_filter); filters.emplace_back(Temporal, temp_filter);由于std::vector保证迭代顺序不变后续处理线程将严格按此顺序应用滤镜Decimate → Rotate → Threshold → 深度转视差 → Spatial → Temporal。文档注释特别提醒顺序很重要——例如 Spatial/Temporal 应当放在转视差之后以利用视差域的线性特性而 Decimation 放在最前面可以降低后续所有滤镜的输入分辨率、节省计算量。4.4 队列与辅助对象// Declaring two concurrent queues that will be used to push and pop frames from different threads rs2::frame_queue original_data; rs2::frame_queue filtered_data; // Declare depth colorizer for pretty visualization of depth data rs2::colorizer color_map; // Atomic boolean to allow thread safe way to stop the thread std::atomic_bool stopped(false);两条rs2::frame_queue在处理线程与主线程之间搬运帧原始深度帧进original_data滤波后帧进filtered_datars2::colorizer用于把深度数据伪彩映射使点云带纹理stopped原子布尔量用于安全通知处理线程退出。五、双线程架构处理线程与主线程的协作为了避免wait_for_frames阻塞和长时间滤波拖慢 UI示例把取帧滤波放到独立线程主线程只做渲染。5.1 处理线程取帧 → 滤波 → 分队列std::thread processing_thread([]() { while (!stopped) //While application is running { rs2::frameset data pipe.wait_for_frames(); // Wait for next set of frames from the camera rs2::frame depth_frame data.get_depth_frame(); //Take the depth frame from the frameset if (!depth_frame) // Should not happen but if the pipeline is configured differently return; // it might not provide depth and we dont want to crash rs2::frame filtered depth_frame; // Does not copy the frame, only adds a reference bool revert_disparity false; for (auto filter : filters) { if (filter.is_enabled) { filtered filter.filter.process(filtered); if (filter.filter_name disparity_filter_name) { revert_disparity true; } } } if (revert_disparity) { filtered disparity_to_depth.process(filtered); } filtered_data.enqueue(filtered); original_data.enqueue(depth_frame); } });要点filtered depth_frame只是增加一次引用计数而非拷贝帧循环遍历滤镜向量只对is_enabled true的滤镜调用process()每个处理块都是可选的、相互独立一旦发现 Disparity 处理块被启用过就在循环结束后调用disparity_to_depth.process(filtered)把结果还原回深度域最后把滤波结果与原始帧分别入队。文档注释中明确指出一个已知细节往两条队列分别推帧可能造成原始点云与滤波点云来自不同深度帧不同时刻若需要严格同步应当把两帧打包一起推送或引入额外同步机制。5.2 主线程GUI 渲染与点云更新主线程先保存渲染所需的帧/点云对象并配置点云自动旋转参数rs2::frame colored_depth; rs2::frame colored_filtered; rs2::points original_points; rs2::points filtered_points; auto last_time std::chrono::high_resolution_clock::now(); const double max_angle 15.0; // 点云旋转的最大角度源码中为 15° float rotation_velocity 0.3f;主循环中每次迭代依次执行渲染 GUI → 从两条队列取数据更新两路点云 → 绘制左右两个视口 → 推进旋转角度。update_data的实现见 rs-post-processing.cpp是理解点云带色的关键void update_data(rs2::frame_queue data, rs2::frame colorized_depth, rs2::points points, rs2::pointcloud pc, glfw_state view, rs2::colorizer color_map) { rs2::frame f; if (data.poll_for_frame(f)) // Try to take the depth and points from the queue { points pc.calculate(f); // Generate pointcloud from the depth data colorized_depth color_map.process(f); // Colorize the depth frame with a color map pc.map_to(colorized_depth); // Map the colored depth to the point cloud view.tex.upload(colorized_depth); // and upload the texture to the view (without this the view will be BW) } }它使用非阻塞的poll_for_frame从队列取帧避免阻塞主循环随后pc.calculate(f)由深度帧生成点云 →color_map.process(f)把深度伪彩化 →pc.map_to(colorized_depth)把彩色深度作为点云纹理 →view.tex.upload上传纹理缺少这一步点云将显示为黑白。两个视口的绘制通过glViewport切分窗口完成draw_text(10, 50, Original); draw_text(static_castint(w / 2), 50, Filtered); if (colored_depth original_points) { glViewport(0, h / 2, w / 2, h / 2); draw_pointcloud(w / 2, h / 2, original_view_orientation, original_points); } if (colored_filtered filtered_points) { glViewport(w / 2, h / 2, w / 2, h / 2); draw_pointcloud(w / 2, h / 2, filtered_view_orientation, filtered_points); }5.3 平滑旋转动画auto curr std::chrono::high_resolution_clock::now(); const std::chrono::milliseconds rotation_delta(40); // 至少间隔 40ms 才转动一次 if (curr - last_time rotation_delta) { if (fabs(filtered_view_orientation.yaw) max_angle) { rotation_velocity -rotation_velocity; // 到达最大角度后反向 } original_view_orientation.yaw rotation_velocity; filtered_view_orientation.yaw rotation_velocity; last_time curr; }点云以 0.3°/帧 的速度绕 yaw 轴旋转并在达到max_angle时反向从而让观察者能从不同角度看到滤镜效果rotation_delta把旋转速率与实际渲染帧率解耦避免帧率波动导致转动忽快忽慢。5.4 优雅退出stopped true; processing_thread.join(); return EXIT_SUCCESS;主窗口关闭后循环结束设置stopped通知处理线程退出并join等待其结束。源码注释亦提醒直接使用裸std::thread并非最稳妥的做法生产代码建议用 RAII 方式如std::jthread或封装类管理线程生命周期。六、GUI 实现细节复选框 滑块的动态选项面板render_ui见 rs-post-processing.cpp为每个滤镜绘制一个复选框为每个受支持选项绘制一个滑块for (auto filter : filters) { // Draw a checkbox per filter to toggle if it should be applied ImGui::SetCursorPos({ offset_x, offset_y }); bool tmp_value filter.is_enabled; ImGui::Checkbox(filter.filter_name.c_str(), tmp_value); filter.is_enabled tmp_value; // Draw a slider for each of the filters options for (auto option_slider_pair : filter.supported_options) { filter_slider_ui slider option_slider_pair.second; if (slider.render({ offset_x offset_from_checkbox, offset_y, w / 4 }, filter.is_enabled)) { filter.filter.set_option(option_slider_pair.first, slider.value); } offset_y elements_margin; } }当滑块返回值value_changed true时调用filter.filter.set_option(option, value)把新值实时下发到滤镜——这正是调参即时生效的机制。若某滤镜没有受支持选项supported_options.size() 0则只保留复选框并跳过滑块行。filter_slider_ui::render见 rs-post-processing.cpp进一步区分整数与浮点滑块整数滑块is_int true时用RsImGui::SliderIntTofloat绘制并将值按步长取整value min ((value - min) / step) * step浮点滑块用ImGui::SliderFloat绘制值按步长四舍五入value min round((value - min) / step) * step鼠标悬停标签时通过RsImGui::CustomTooltip显示该选项的描述description来自get_option_description当滤镜被禁用enabled false时滑块以置灰透明把手、灰色文字呈现防止误操作。界面风格上复选框勾选色与滑块把手统一使用绿色系RGB 40/170/90与示例整体 UI 保持一致。七、构建与运行该示例属于图形化示例需要在构建 librealsense 时开启图形示例开关cmake .. -DBUILD_GRAPHICAL_EXAMPLEStrue cmake --build . --target rs-post-processing仓库根 CMakeLists.txt 中的BUILD_GRAPHICAL_EXAMPLES控制包括 examples/gl、examples/pointcloud 等在内的图形示例编译。由于示例依赖 OpenGL GLFW ImGuiLinux 下通常还需安装相应依赖可参考 doc/installation.md 与 scripts/install_glfw3.sh。运行前请连接一台 RealSense 深度相机或使用支持深度流的设备如 D400 系列然后执行编译出的rs-post-processing可执行程序。启动后即可看到左侧 Original、右侧 Filtered 两个旋转点云视口勾选/取消复选框或拖动滑块即可实时观察各滤镜及其参数对深度图像质量的影响。建议按文档推荐的顺序实验先开 Decimation 降低分辨率再依次叠加 Threshold、Disparity Spatial Temporal体验视差域滤波与深度域滤波在近处物体边缘表现上的差异。八、小结rs-post-processing示例的价值在于它把 librealsense 中最常用的六个深度后处理块组装成一条可实时调节、可并排对比的参考流水线代码结构清晰filter_options抽象了滤镜选项开关处理线程与主线程通过frame_queue解耦ImGui 面板让所有参数可视化。结合 src/proc 下的底层实现你可以确认每个选项的真实取值范围与算法行为如 Decimation 的中值降采样与 4 对齐、Spatial/Temporal 的 Alpha-Delta 保边机制、Threshold 的米制距离裁剪等从而把这条流水线移植到自己的项目中或在 rs-post-processing.cpp 基础上替换滤镜顺序、增加处理块如仓库中同样位于 src/proc 的 hole-filling-filter.cpp、occlusion-filter.cpp 等定制属于自己的深度后处理链路。【免费下载链接】librealsenseRealSense SDK项目地址: https://gitcode.com/GitHub_Trending/li/librealsense创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考