应用程序实用工具——使用 Orbbec Astra 3D 摄像机 OpenCV v4.8.0

上一个教程使用 Kinect 和其他兼容 OpenNI 的深度传感器

下一个教程使用 Creative Senz3D 和其他兼容 Intel RealSense SDK 的深度传感器

简介

本教程专门介绍奥尔贝克 3D 摄像机 Astra 系列 (https://orbbec3d.com/index/Product/info.html?cate=38&id=36)。除了普通的彩色传感器外,该相机还配有深度传感器。深度传感器可通过 cv::VideoCapture 类的开源 OpenNI API 读取。视频流通过普通摄像头接口提供。

安装说明

要将 Astra 摄像机的深度传感器与 OpenCV 结合使用,需要执行以下步骤:

  1. 下载最新版本的 Orbbec OpenNI SDK(从此处 https://orbbec3d.com/index/download.html)。解压压缩包,根据操作系统选择构建版本,然后按照自述文件中提供的步骤进行安装。
  2. 例如,如果使用的是 64 位 GNU/Linux,运行
$ cd Linux/OpenNI-Linux-x64-2.3.0.63/
$ sudo ./install.sh

安装完成后,请确保重新插入设备,以使 udev 规则生效。现在,摄像头应该可以作为普通摄像头设备使用了。请注意,当前用户应属于 video 组,才能访问摄像头。此外,请确保将 OpenNIDevEnvironment 文件源化:

$ source OpenNIDevEnvironment

要验证源代码命令是否有效,以及能否找到 OpenNI 库和头文件,请运行以下命令,您应该会在终端看到类似的内容:

$ echo $OPENNI2_INCLUDE
/home/user/OpenNI_2.3.0.63/Linux/OpenNI-Linux-x64-2.3.0.63/Include
$ echo $OPENNI2_REDIST
/home/user/OpenNI_2.3.0.63/Linux/OpenNI-Linux-x64-2.3.0.63/Redist

如果上述两个变量为空,则需要重新创建 OpenNIDevEnvironment 的源代码。

注意事项 Orbbec OpenNI SDK 2.3.0.86 及更新版本不再提供 install.sh。您可以使用以下脚本初始化环境:

# Check if user is root/running with sudo
if [ `whoami` != root ]; then
 echo Please run this script with sudo
 exit
fi
ORIG_PATH=`pwd`
cd `dirname $0`
SCRIPT_PATH=`pwd`
cd $ORIG_PATH
if [ "`uname -s`" != "Darwin" ]; then
 # Install UDEV rules for USB device
 cp ${SCRIPT_PATH}/orbbec-usb.rules /etc/udev/rules.d/558-orbbec-usb.rules
 echo "usb rules file install at /etc/udev/rules.d/558-orbbec-usb.rules"
fi
OUT_FILE="$SCRIPT_PATH/OpenNIDevEnvironment"
echo "export OPENNI2_INCLUDE=$SCRIPT_PATH/../sdk/Include" > $OUT_FILE
echo "export OPENNI2_REDIST=$SCRIPT_PATH/../sdk/libs" >> $OUT_FILE
chmod a+r $OUT_FILE
echo "exit"
  1. 现在,你可以通过在 CMake 中设置 WITH_OPENNI2 标志,在配置 OpenCV 时启用 OpenNI 支持。您可能还想启用 BUILD_EXAMPLES 标志,以获得与 Astra 摄像头配合使用的代码示例。在包含 OpenCV 源代码的目录中运行以下命令以启用 OpenNI 支持:
$ mkdir build
cd build
$ cmake -DWITH_OPENNI2=ON .

如果找到 OpenNI 库,OpenCV 将在构建时支持 OpenNI2。您可以在 CMake 日志中查看 OpenNI2 支持的状态:

-- Video I/O:
-- DC1394: YES (2.2.6)
-- FFMPEG: YES
-- avcodec: YES (58.91.100)
-- avformat: YES (58.45.100)
-- avutil: YES (56.51.100)
-- swscale: YES (5.7.100)
-- avresample: NO
-- GStreamer: YES (1.18.1)
-- OpenNI2: YES (2.3.0)
-- v4l/v4l2: YES (linux/videodev2.h)
  1. 构建 OpenCV:
$ make

代码

Astra Pro 相机有两个传感器:深度传感器和颜色传感器。深度传感器可通过 cv::VideoCapture 类的 OpenNI 接口读取。OpenNI API 无法提供视频流,只能通过常规的摄像头接口提供。因此,要同时获取深度和颜色帧,需要创建两个 cv::VideoCapture 对象:

  // 打开深度流
  VideoCapture depthStream(CAP_OPENNI2_ASTRA)// 打开颜色流
  VideoCapture colorStream(0, CAP_V4L2)

第一个对象将使用 OpenNI2 API 获取深度数据。第二个对象使用 Video4Linux2 接口访问颜色传感器。请注意,上面的示例假定 Astra 摄像机是系统中的第一台摄像机。如果连接了多台摄像机,可能需要明确设置适当的摄像机编号。

在使用创建的 VideoCapture 对象之前,您可能需要通过设置对象属性来设置流参数。最重要的参数是帧宽、帧高和帧频。在本示例中,我们将把两个数据流的宽度和高度都配置为 VGA 分辨率,这是两个传感器的最大分辨率,而且我们希望两个数据流的参数都相同,以方便颜色到深度数据的注册:

  // 设置色彩流和深度流参数
  colorStream.set(CAP_PROP_FRAME_WIDTH, 640);
  colorStream.set(CAP_PROP_FRAME_HEIGHT, 480);
  depthStream.set(CAP_PROP_FRAME_WIDTH, 640);
  depthStream.set(CAP_PROP_FRAME_HEIGHT, 480);
  depthStream.set(CAP_PROP_OPENNI2_MIRROR, 0)

要设置和检索传感器数据生成器的某些属性,请分别使用 cv::VideoCapture::setcv::VideoCapture::get 方法,例如 :

  // 打印深度流参数
  cout << "Depth stream: "
  << depthStream.get(CAP_PROP_FRAME_WIDTH) << "x" << depthStream.get(CAP_PROP_FRAME_HEIGHT)
  << " @" << depthStream.get(CAP_PROP_FPS) << " fps" << endl;

深度生成器支持以下通过 OpenNI 接口提供的摄像机属性:

  • cv::CAP_PROP_FRAME_WIDTH - 以像素为单位的帧宽。
  • cv::CAP_PROP_FRAME_HEIGHT - 以像素为单位的帧高度。
  • cv::CAP_PROP_FPS - 以 FPS 为单位的帧速率。
  • cv::CAP_PROP_OPENNI_REGISTRATION - 通过改变深度生成器的视点(如果该标志为 “开”)或将该视点设置为正常视点(如果该标志为 “关”),将深度图重映射到图像映射的标志。配准过程生成的图像是像素对齐的,这意味着图像中的每个像素都与深度图中的一个像素对齐。
  • cv::CAP_PROP_OPENNI2_MIRROR - 启用或禁用此数据流镜像的标志。设置为 0 表示禁用镜像

以下属性仅用于获取:

注意事项
OpenCV 的 VideoCapture 提供同步API,因此必须在新线程中抓取帧,以避免在读取另一个流时阻塞一个流。VideoCapture并非线程安全类,因此需要小心避免任何可能的死锁或数据竞争。

由于需要同时读取两个视频源,因此有必要创建两个线程来避免阻塞。示例实现在一个新线程中从每个传感器获取帧,并将它们连同时间戳一起存储在一个列表中:

  // 创建两个列表来存储帧
  std::list<Frame> depthFrames, colorFrames;
  const std::size_t maxFrames = 64// 同步对象
  std::mutex mtx;
  std::condition_variable dataReady;
  std::atomic<bool> isFinish;
 
  isFinish = false// 启动深度读取线程
  std::thread depthReader([&])
  {
  while (!isFinish)
  {
  // 抓取并解码新帧
  if (depthStream.grab())
  {
  Frame f;
  f.timestamp = cv::getTickCount();
  depthStream.retrieve(f.frame, CAP_OPENNI_DEPTH_MAP)if (f.frame.empty())
  {
  cerr << "ERROR: 从深度流解码帧失败" << endl;
  break}
 
  {
  std::lock_guard<std::mutex> lk(mtx)if(depthFrames.size() >= maxFrames)
  depthFrames.pop_front();
  depthFrames.push_back(f)}
  dataReady.notify_one()}
  }
  });
 
  // 启动读取颜色的线程
  std::thread colorReader([&])
  {
  while (!isFinish)
  {
  // 抓取并解码新帧
  if (colorStream.grab())
  {
  Frame f;
  f.timestamp = cv::getTickCount();
  colorStream.retrieve(f.frame)if(f.frame.empty())
  {
  cerr << "ERROR: 从颜色流解码帧失败" << endl;
  break}
 
  {
  std::lock_guard<std::mutex> lk(mtx)if(colorFrames.size() >= maxFrames)
  colorFrames.pop_front();
  colorFrames.push_back(f)}
  dataReady.notify_one()}
  }
  });

VideoCapture 可以获取以下数据:

  1. 深度生成器提供的数据:
  2. 颜色传感器提供的数据是普通的 BGR 图像 (CV_8UC3)。
    当有新数据时,每个读取线程会使用条件变量通知主线程。帧被存储在有序列表中–列表中的第一帧是最早捕获的帧,最后一帧是最新捕获的帧。由于深度和彩色帧是从独立的信号源读取的,因此即使两个视频流都设置为相同的帧频,也可能会出现不同步的情况。可以对视频流采用后同步程序,将深度和色彩帧合成一对。下面的示例代码演示了这一过程:
  // 将深度和色彩帧配对
  while (!isFinish)
  {
  std::unique_lock<std::mutex> lk(mtx)while (!isFinish && (depthFrames.empty() || colorFrames.empty()))
  dataReady.wait(lk)while (!depthFrames.empty() && !colorFrames.empty())
  {
  if(!lk.owns_lock())
  lk.lock()// 从列表中获取一个帧
  Frame depthFrame = depthFrames.front();
  int64 depthT = depthFrame.timestamp;
 
  // 从列表中获取一个帧
  Frame colorFrame = colorFrames.front();
  int64 colorT = colorFrame.timestamp;
 
  // 帧周期的一半是帧之间的最大时间差
  const int64 maxTdiff = int64(1000000000 / (2 * colorStream.get(CAP_PROP_FPS)))if(depthT + maxTdiff < colorT)
  {
  depthFrames.pop_front()continue}
  else if(colorT + maxTdiff < depthT)
  {
  colorFrames.pop_front()continue}
  depthFrames.pop_front();
  colorFrames.pop_front();
  lk.unlock()// 显示深度框
  Mat d8, dColor;
  depthFrame.frame.convertTo(d8, CV_8U, 255.0 / 2500)applyColorMap(d8, dColor, COLORMAP_OCEAN)imshow("Depth (colored)", dColor)// 显示颜色框
  imshow("Color", colorFrame.frame)// 按 Esc 键退出
  int key = waitKey(1)if (key == 27) // ESC
  {
  isFinish = truebreak}
  }
  }

在上面的代码片段中,执行被阻塞,直到两个帧列表中都有一些帧。当出现新的帧时,将检查它们的时间戳–如果它们的时间戳相差超过帧周期的一半,那么其中一个帧将被丢弃。如果时间戳足够接近,则将两个帧配对。现在,我们有两个帧:一个包含颜色信息,另一个包含深度信息。在上面的示例中,我们使用 cv::imshow 函数简单地显示了检索到的帧,但您也可以在此插入任何其他处理代码。

在下面的示例图像中,您可以看到代表同一场景的颜色帧和深度帧。从彩色帧中很难分辨出植物的叶子和画在墙上的叶子,而深度数据则让分辨变得容易。

在这里插入图片描述

颜色帧

在这里插入图片描述

深度帧

完整的实现可以在 samples/cpp/tutorial_code/videoio 目录下的 orbbec_astra.cpp 中找到。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
你可以使用以下示例代码来开始使用奥比中光(OrbbecAstra深度摄像头: ```cpp #include <astra/astra.hpp> #include <iostream> class SampleFrameListener : public astra::FrameListener { public: void on_frame_ready(astra::StreamReader& reader, astra::Frame& frame) override { const astra::DepthFrame depthFrame = frame.get<astra::DepthFrame>(); if (!depthFrame.is_valid()) { std::cout << "Invalid depth frame!" << std::endl; return; } const int width = depthFrame.width(); const int height = depthFrame.height(); const int frameIndex = depthFrame.frame_index(); std::cout << "Depth frame " << frameIndex << " ready: " << width << "x" << height << std::endl; const astra::Vector3f* depthData = depthFrame.data(); const int numPixels = width * height; for (int i = 0; i < numPixels; ++i) { const float depth = depthData[i].z; // 处理深度数据,例如将其可视化或进行其他计算 } } }; int main(int argc, char** argv) { astra::initialize(); astra::StreamSet streamSet; astra::StreamReader reader = streamSet.create_reader(); SampleFrameListener listener; reader.stream<astra::DepthStream>().start(); reader.add_listener(listener); while (true) { astra_update(); // 执行其他任务,或者在这里添加退出循环的条件 } astra::terminate(); return 0; } ``` 这个示例代码演示了如何使用Astra SDK来读取并处理深度帧数据。你可以在`on_frame_ready`函数中处理深度数据,例如将其可视化或进行其他计算。 请确保已经正确安装Astra SDK,并将其相关库文件和头文件链接到你的项目中。此外,你还需要为你的程序提供适当的权限来访问Astra摄像头设备。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值