ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

OpenNI2跨平台获取奥比中光Astra深度图完整指南

OpenNI2跨平台获取奥比中光Astra深度图完整指南 搞机器人和三维视觉的人八成绕不开深度相机。我最早拿到的就是奥比中光Astra Pro后来项目里又换了Astra Pro SM幸好手上的采集代码基本没动因为整套逻辑用的都是OpenNI2。这篇文章就是把我在 Windows x64、Linux x64 和 Linux arm64 三个平台上用 OpenNI2 获取这两款相机深度图的过程完整复盘一遍包括环境配置、核心代码、编译链接以及那些文档里不会写给你的坑。如果你正准备给机器人导航、三维重建或者体感交互项目选一套深度图采集方案这篇文章可以帮你少走不少弯路。1. 为什么用OpenNI2而不用厂商SDK1.1 OpenNI2到底是什么OpenNI2全称 Open Natural Interaction 第二版是一套跨平台的深度传感器访问框架。最早由 PrimeSense 推动后来以开放源码的形式持续维护。它定义了一套统一的 C API上层应用用同一套代码对接不同厂家的深度相机底层通过插件机制加载厂商驱动。对于奥比中光 Astra 系列相机厂商在 SDK 里提供了基于 OpenNI2 的驱动所以你可以把 OpenNI2 理解成“深度相机的通用 USB 协议层”而相机驱动就是“翻译官”。这里必须强调一个坑OpenNI2 和 2009 年那个 OpenNI 1.x 完全不是一回事API 几乎是推倒重写的。网上很多老教程、老开源项目用的都是 OpenNI 1.x 的接口什么xn::Context、xn::DepthGenerator拿到 OpenNI2 下面根本编译不过。我自己刚入门时就被这个坑过所以看教程之前先确认版本。1.2 对比厂商SDK选型依据是什么你可能会问奥比中光不是有自己的 SDK 吗为什么非要用 OpenNI2我在做方案选型时主要看三点第一跨平台一致性。同一个 C 程序Windows、Linux x64、Linux arm64 上只要换一套库文件源码一行都不用改。厂商 SDK 虽然也跨平台但接口风格、依赖方式在不同版本之间变化更大维护成本高。第二换设备成本低。今天项目里插的是 Astra Pro明天换成 Astra Pro SM甚至换 PrimeSense、华硕 Xtion 这类老设备只要底层驱动支持上层代码依然复用。我自己实际测试过Astra Pro 和 Astra Pro SM 在 OpenNI2 层面的接口完全一致程序里甚至可以通过device.getDeviceInfo().getName()拿到当前设备名来做区分但采集流程不用改。第三资料多、生态稳。OpenNI2 作为开放框架配套的教程、开源项目比厂商 SDK 多遇到问题搜索时有现成经验可以抄。尤其是 Linux 下的权限问题、USB 带宽问题网上讨论非常充分。当然 OpenNI2 也不是万能的比如官方主干版本停在 2.2很多新硬件支持要靠厂商“补丁版”SDK另外深度图和彩色图对齐这类功能OpenNI2 原生接口不提供得自己处理。我的习惯是核心深度采集用 OpenNI2彩色对齐和算法部分再用 OpenCV 或者厂商 SDK 混合补。2. 环境搭建三个平台一次配好2.1 Windows平台配置步骤Windows 下的配置一般是最省心的。首先到奥比中光官网的下载页面找到对应 Astra 系列的 OpenNI2 SDK 包。下载后解压你会看到 Include、Redist、Samples 这几个关键目录。Include 里面是 OpenNI.h 头文件Redist 里面是运行时库包括 OpenNI2.dll、OpenNI2.lib 以及一些设备配置文件。接下来把相机用 USB 线插到电脑上。打开设备管理器如果能看到一个 Orbbec 或者 Astra 相关的设备说明驱动已经自动装好了。Windows 10/11 一般会自动识别不需要手动安装驱动。如果设备管理器里显示的是带问号的未知设备就要去官网下载对应的 USB 驱动包手动安装。开发环境我用的是 Visual Studio 2019 和 2022。新建一个空 C 项目后需要做几步配置项目属性里把 Include 目录加到“VC 目录”的包含目录把 Redist 目录加到库目录链接器输入里加上 OpenNI2.lib。还要注意把运行平台切到 x64因为默认的 Win32 平台会让你链接一个根本不存在的 32 位库。程序编译出来之后记得把 Redist 里的 OpenNI2.dll 拷贝到 exe 同一目录下否则运行时马上报找不到 DLL。2.2 Linux x64 环境配置细节Linux 下稍微麻烦一点但也就多两步依赖安装和 udev 规则。以 Ubuntu 20.04 为例先执行sudo apt update sudo apt install -y libusb-1.0-0-dev udev然后解压厂商提供的 Linux x64 版 OpenNI2 包。包里面通常会带一个orbbec-usb.rules或者类似名字的 udev 规则文件。把它复制到系统目录sudo cp orbbec-usb.rules /etc/udev/rules.d/ sudo udevadm control --reload sudo udevadm trigger这个步骤非常关键如果漏掉程序在打开设备时大概率会报权限错误。把当前用户加入plugdev或dialout组也会有帮助sudo usermod -aG plugdev $USER改完用户组要重新登录一下才能生效。程序编译时使用 g 指定头文件和库路径例如g -o depth_viewer main.cpp \ -I./OpenNI2/Include \ -L./OpenNI2/Redist -lOpenNI2 \ -lpthread -lrt运行时需要让系统找到动态库一条命令搞定export LD_LIBRARY_PATH$LD_LIBRARY_PATH:./OpenNI2/Redist ./depth_viewer注意有些 SD 卡或网络文件系统不支持动态库加载最好把 Redist 目录放到本地磁盘再运行。2.3 Linux arm64 平台配置要点arm64 平台主要是嵌入式设备和开发板比如 RK3399、瑞芯微方案或者 NVIDIA Jetson 系列。奥比中光官网通常同样提供 Linux arm64 的 OpenNI2 包架构标识一般是 aarch64。拿到包之后头文件目录和 x64 版本完全一样只有 Redist 里的库文件是 arm64 架构的。我测试的板子是 RK3399系统是 Ubuntu 18.04 arm64。配置流程和 x64 几乎一致装 libusb、放 udev 规则、编译时指定 aarch64 的库路径。编译命令注意加-stdc11有些旧板子的默认 g 版本比较低不加会报一些奇怪错误。如果你在官网找不到 arm64 包两个办法一是找厂商 FAE 要他们一般有内部编译版本二是自己下源码交叉编译但这个成本高不推荐。我实测用官方 arm64 包在 RK3399 上跑 640x48030 深度流非常稳定CPU 占用也不高大约 20% 左右。3. 核心代码从初始化到拿到一帧深度图3.1 OpenNI2 API调用流程OpenNI2 的核心 API 调用流程可以总结成五步初始化、打开设备、创建流、启动流、循环读帧。代码结构很清晰我直接给出一个可用的最小示例里面加了详细注释#include OpenNI.h #include iostream using namespace openni; int main() { // 1. 初始化OpenNI2运行时 Status rc OpenNI::initialize(); if (rc ! STATUS_OK) { std::cerr 初始化失败: OpenNI::getExtendedError() std::endl; return -1; } // 2. 打开默认设备 Device device; rc device.open(ANY_DEVICE); if (rc ! STATUS_OK) { std::cerr 打开设备失败: OpenNI::getExtendedError() std::endl; OpenNI::shutdown(); return -1; } // 3. 创建深度流 VideoStream depthStream; rc depthStream.create(device, SENSOR_DEPTH); if (rc ! STATUS_OK) { std::cerr 创建深度流失败: OpenNI::getExtendedError() std::endl; device.close(); OpenNI::shutdown(); return -1; } // 4. 启动深度流 rc depthStream.start(); if (rc ! STATUS_OK) { std::cerr 启动深度流失败: OpenNI::getExtendedError() std::endl; depthStream.destroy(); device.close(); OpenNI::shutdown(); return -1; } // 5. 循环读帧 VideoFrameRef frame; while (true) { rc depthStream.readFrame(frame); if (rc ! STATUS_OK) { std::cerr 读取深度帧失败: OpenNI::getExtendedError() std::endl; continue; } int w frame.getWidth(); int h frame.getHeight(); const DepthPixel* pDepth (const DepthPixel*)frame.getData(); if (pDepth) { int cx w / 2; int cy h / 2; DepthPixel centerDist pDepth[cy * w cx]; std::cout 中心点距离: centerDist 毫米 std::endl; } if (std::cin.get() q) break; } // 6. 清理资源 depthStream.stop(); depthStream.destroy(); device.close(); OpenNI::shutdown(); return 0; }这段代码就是最核心的骨架。拿到VideoFrameRef之后getData()返回的是深度像素数组每个元素是DepthPixel类型本质上就是uint16_t单位是毫米。如果某个像素值是 0表示这个点无效可能原因是距离太近、太远或者物体表面反光导致结构光解算失败。3.2 设置分辨率和像素格式有时候你需要按指定分辨率采集比如 320x240 或者 1280x960。OpenNI2 允许在启动流之前设置VideoModeVideoMode vm; vm.setResolution(640, 480); vm.setFps(30); vm.setPixelFormat(PIXEL_FORMAT_DEPTH_1_MM); depthStream.setVideoMode(vm);但这里有个重要前提不是所有分辨率都支持。Astra Pro 和 Astra Pro SM 支持的深度模式不完全一样不同 SDK 版本也可能不同。最稳妥的办法是直接把设备支持的所有模式打印出来再选一个合适的const SensorInfo* info depthStream.getSensorInfo(); const ArrayVideoMode modes info-getSupportedVideoModes(); for (int i 0; i modes.getSize(); i) { std::cout modes[i].getResolutionX() x modes[i].getResolutionY() modes[i].getFps() fps, pixelFormat modes[i].getPixelFormat() std::endl; }我实测 Astra Pro 在 OpenNI2 下通常支持 320x24030 和 640x48030 两种深度模式。PIXEL_FORMAT_DEPTH_1_MM是默认像素格式每个深度值直接代表毫米。有些 SDK 版本还支持PIXEL_FORMAT_DEPTH_100_UM这种情况下存储的值要除以 10 才是毫米千万别混淆。3.3 深度图转可视化与数据读取如果你想把深度图显示出来直接用 16 位灰度值是看不到东西的因为深度范围集中在某个区间直接显示会一片漆黑。最常见的做法是先缩放再转 8 位#include opencv2/opencv.hpp // 假设已经拿到VideoFrameRef frame int w frame.getWidth(); int h frame.getHeight(); const DepthPixel* pDepth (const DepthPixel*)frame.getData(); cv::Mat depth16(h, w, CV_16UC1, (void*)pDepth); // 方法一把0~8000mm映射到0~255 cv::Mat depth8u; depth16.convertTo(depth8u, CV_8U, 255.0 / 8000.0); cv::imshow(Depth, depth8u); // 方法二先截断最大距离再归一化 cv::Mat depthTruncated; cv::threshold(depth16, depthTruncated, 8000, 8000, cv::THRESH_TRUNC); depthTruncated.convertTo(depth8u, CV_8U, 255.0 / 8000.0);方法二我用的更多因为结构光深度相机在远距离时噪声很大直接把 8000mm 以上的像素都截断成 8000图像看起来更干净。DepthPixel数组的访问方式就是普通二维数组pDepth[y * width x]取出来的值就是该点到相机平面的距离单位毫米。这里要说清楚深度值表示的是沿光轴方向的“平面距离”不是欧氏距离计算点云的时候需要结合相机内参做逆投影。4. 完整示例工程与跨平台编译4.1 工程目录与CMake写法为了让你能直接抄作业我给出一个完整的工程目录结构。这个结构我实际在三个平台上都验证过改动最小depth_viewer/ ├── CMakeLists.txt ├── main.cpp ├── third_party/ │ └── OpenNI2/ │ ├── Include/ │ │ └── OpenNI.h │ └── Redist/ │ ├── libOpenNI2.so # Linux │ └── OpenNI2.dll/.lib # WindowsCMakeLists.txt 可以这样写cmake_minimum_required(VERSION 3.10) project(depth_viewer) set(CMAKE_CXX_STANDARD 11) find_package(OpenCV REQUIRED) set(OpenNI2_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/OpenNI2/Include) set(OpenNI2_REDIST_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/OpenNI2/Redist) include_directories(${OpenNI2_INCLUDE_DIR}) add_executable(depth_viewer main.cpp) target_link_libraries(depth_viewer PRIVATE ${OpenCV_LIBS}) if(WIN32) target_link_libraries(depth_viewer PRIVATE ${OpenNI2_REDIST_DIR}/OpenNI2.lib) else() target_link_libraries(depth_viewer PRIVATE ${OpenNI2_REDIST_DIR}/libOpenNI2.so) endif()在 Windows 上编译完成后记得把OpenNI2.dll拷贝到 exe 所在目录。在 Linux 上不太需要拷贝因为 CMake 写的是绝对路径运行时直接用LD_LIBRARY_PATH指向 Redist 目录即可。如果可执行文件要部署到其他机器则要把libOpenNI2.so一起带上并放到系统库路径或者程序旁的lib目录。4.2 三个平台编译实战记录Windows 下直接在 Visual Studio 打开 CMake 工程选择 x64 配置生成解决方案。我遇到最多的坑是平台选成了 x86然后链接报错找不到 OpenNI2.lib。这个没什么好技巧就是项目属性里把活动解决方案平台改成 x64。Linux x64 下执行mkdir build cd build cmake .. make -j4然后运行export LD_LIBRARY_PATH$LD_LIBRARY_PATH:../third_party/OpenNI2/Redist ./depth_viewerarm64 板子上的编译命令基本相同只不过 CMake 需要指定工具链或者直接在板子上原生编译。我的建议是直接在板子上原生编译交叉编译容易出现动态库路径、头文件路径对不上的问题。Jetson 这类设备上如果 JetPack 里带了 OpenCVCMake 会自动找到省事很多。4.3 运行结果与参数检查程序跑起来后控制台会一直在打印中心点距离。把相机对准人距离大概在 1 到 1.5 米时中心点数值会稳定在一个小范围内波动说明深度流正常。如果数值始终是 0先别急有几种典型情况物体距离相机太近低于最小工作距离物体表面是强反光材质或者红外镜头被遮挡。你也可以在代码里加一个模式打印函数把所有支持的 VideoMode 打出来确认当前设备到底支持哪些分辨率和帧率。我遇到过一种情况Astra Pro SM 在某个固件版本下320x240 只支持 25fps 而不支持 30fps如果强制 setFps(30) 会返回错误。最好的办法就是先枚举再选择不要一拍脑门写死。5. 踩坑记录与问题排查速查表5.1 Linux下无法打开设备权限问题这个是我遇到过的最多的问题没有之一。症状是程序启动后在device.open()这一步返回STATUS_ERROR错误信息可能是DeviceOpen failed或者Access denied。原因几乎都是 udev 规则没生效或者用户不在设备组里。排查步骤lsusb | grep -i orbbec如果这条命令能看到设备说明 USB 层面正常。再看设备节点权限ls -l /dev/bus/usb/001/*如果设备对应的文件权限是root root那你普通用户访问不了。解决方法是把 udev 规则文件放好重新插拔相机然后执行sudo udevadm control --reload。如果还是不行直接把用户加入plugdev组并重启会话。实在着急时临时用sudo ./depth_viewer运行也可以但正式部署必须把权限配好。5.2 深度图全黑或者全是零深度图全黑很多初学者第一反应是“相机坏了”。实际上绝大多数情况是显示处理不对。原始深度数据是 16 位你用 8 位图像直接显示如果距离都在 2000mm 以上那么在 0~255 的灰度区间里确实接近黑色。解决办法是用convertTo(depth8u, CV_8U, 255.0 / 8000.0)或者其他归一化手段处理后再显示。另外要检查采集到的深度最大值和最小值可以在代码里加一行double minVal, maxVal; cv::minMaxLoc(depth16, minVal, maxVal); std::cout 深度范围: minVal ~ maxVal std::endl;如果最大值只有几十毫米说明相机确实没解算出有效深度这时候再考虑硬件问题比如太近、强光干扰或者镜头脏了。5.3 编译时找不到头文件或链接失败编译报错找不到 OpenNI.h基本就是 Include 路径没指对。检查 CMake 里的OpenNI2_INCLUDE_DIR是否真的指向包含OpenNI.h的目录而不是 OpenNI2 的根目录。链接失败报cannot find -lOpenNI2在 Linux 上检查 Redist 目录下是不是真的有libOpenNI2.so文件并用file命令确认架构file libOpenNI2.so输出里应该能看到x86-64或者aarch64。如果在 x64 机器上拿到 arm64 包编译能过但运行会报cannot open shared object file或者直接段错误这个特别容易在下载 SDK 时搞错架构。5.4 常见问题速查表现象可能原因解决办法Linux下设备打开失败提示Access deniedudev规则未生效或用户无权限安装orbbec-usb.rules重载udev加入plugdev组Windows下设备管理器出现未知设备USB驱动未正确安装到官网下载驱动手动安装深度图全黑显示时未对16位值做归一化用convertTo或minMaxLoc处理后再显示深度值几乎全是0目标太近/太远、反光、遮挡调整距离避免强红外干扰清洁镜头同时开深度流和彩色流掉帧USB带宽不足降低分辨率或帧率避免使用USB HUB链接失败cannot find -lOpenNI2库路径配置错误检查Redist目录路径确认库文件存在程序启动崩溃SDK版本与固件不匹配尝试更新或更换OpenNI2版本设置视频模式返回错误设备不支持该分辨率和帧率组合先枚举getSupportedVideoModes再设置每次遇到问题我习惯先做两件事第一把 OpenNI2 的日志打开。在 exe 同目录放一个OpenNI2.ini文件LogLevel2 LogToConsole1 LogToFile1这样终端和文件里都会输出详细日志很多初始化问题一眼就能看出来。第二用厂商自带的示例程序验证硬件比如 SimpleRead 或 SampleViewer。如果官方示例都跑不通那问题基本在环境如果官方示例正常问题大概率在你自己代码里。最后分享一点个人体会OpenNI2 这套东西虽然有些年头了但在 Astra 系列相机上依然是一条稳定可靠的技术路线特别适合那些需要长期维护、多平台部署的视觉项目。你不需要把 SDK 里的所有接口都搞懂把初始化、读帧、清理这三个环节吃透再学会枚举设备支持的模式已经能应对绝大多数需求。调 A 平台踩过的坑到 B 平台大概率还会再踩一次所以建议把这些经验整理进项目的 README别低估笔记的价值。
返回列表