上一个教程: 使用 Kinect 和其他兼容 OpenNI 的深度传感器
下一个教程: 使用 Creative Senz3D 和其他兼容英特尔 RealSense SDK 的深度传感器
简介
本教程主要介绍 Orbbec 3D 摄像头 Astra 系列(https://orbbec3d.com/index/Product/info.html?cate=38&id=36)。除了常见的颜色传感器,这些摄像头还配有深度传感器。可以使用开源 OpenNI API 与 cv::VideoCapture 类读取深度传感器。视频流通过常规摄像头界面提供。
安装说明
要使用 OpenCV 中的 Astra 摄像头深度传感器,您需要执行以下步骤
- 下载最新版本的 Orbbec OpenNI SDK(此处 https://orbbec3d.com/index/download.html)。解压缩存档,根据您的操作系统选择版本,并按照自述文件中提供的安装步骤进行操作。
例如,如果您使用 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
。可以使用以下脚本初始化环境# 检查用户是否为 root/使用 sudo 运行
if [ `whoami` != root ]; then
echo 请使用 sudo 运行此脚本
exit
fi
ORIG_PATH=`pwd`
cd `dirname $0`
SCRIPT_PATH=`pwd`
cd $ORIG_PATH
if [ "`uname -s`" != "Darwin" ]; then
# 为 USB 设备安装 UDEV 规则
cp ${SCRIPT_PATH}/orbbec-usb.rules /etc/udev/rules.d/558-orbbec-usb.rules
echo "USB 规则文件安装于 /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"
- 现在,您可以通过在 CMake 中设置
WITH_OPENNI2
标志启用 OpenNI 支持来配置 OpenCV。您可能还希望启用BUILD_EXAMPLES
标志,以便获得一个代码示例,用于配合您的 Astra 相机工作。在包含 OpenCV 源代码的目录中运行以下命令,以启用 OpenNI 支持$ mkdir build
$ cd build
$ cmake -DWITH_OPENNI2=ON ..
如果找到 OpenNI 库,OpenCV 将使用 OpenNI2 支持进行构建。您可以在 CMake 日志中查看 OpenNI2 支持的状态-- 视频输入/输出
-- DC1394: 是 (2.2.6)
-- FFMPEG: 是
-- avcodec: 是 (58.91.100)
-- avformat: 是 (58.45.100)
-- avutil: 是 (56.51.100)
-- swscale: 是 (5.7.100)
-- avresample: 否
-- GStreamer: 是 (1.18.1)
-- OpenNI2: 是 (2.3.0)
-- v4l/v4l2: 是 (linux/videodev2.h)
- 构建 OpenCV
代码
Astra Pro 相机有两个传感器——一个深度传感器和一个颜色传感器。深度传感器可以使用 OpenNI 接口与cv::VideoCapture类一起读取。视频流无法通过 OpenNI API 获得,只能通过常规相机接口提供。因此,若要同时获取深度帧和颜色帧,应创建两个cv::VideoCapture对象
40 VideoCapture depthStream(CAP_OPENNI2_ASTRA);
42 VideoCapture colorStream(0, CAP_V4L2);
第一个对象将使用 OpenNI2 API 来检索深度数据。第二个对象使用 Video4Linux2 接口来访问颜色传感器。注意,上面的示例假定 Astra 相机是系统中的第一台相机。如果您连接了多台相机,则可能需要明确设置正确的相机编号。
在使用已创建的 VideoCapture 对象之前,您可能希望通过设置对象的属性来设置流参数。最重要的参数是帧宽度、帧高度和 fps。对于此示例,我们将两个流的宽度和高度配置为 VGA 分辨率(这是两个传感器的最大可用分辨率),并且我们希望两个流参数相同,以便更轻松地进行颜色到深度数据注册
61 colorStream.set(CAP_PROP_FRAME_WIDTH, 640);
62 colorStream.set(CAP_PROP_FRAME_HEIGHT, 480);
63 depthStream.set(CAP_PROP_FRAME_WIDTH, 640);
64 depthStream.set(CAP_PROP_FRAME_HEIGHT, 480);
65 depthStream.set(CAP_PROP_OPENNI2_MIRROR, 0);
对于设置和检索传感器数据生成器的某些属性,使用 cv::VideoCapture::set 和 cv::VideoCapture::get 方法,例如
75 cout <<
"Depth stream: "
76 << depthStream.get(CAP_PROP_FRAME_WIDTH) <<
"x" << depthStream.get(CAP_PROP_FRAME_HEIGHT)
77 <<
" @" << depthStream.get(CAP_PROP_FPS) <<
" fps" << endl;
深度生成器支持通过 OpenNI 接口提供的以下相机属性
在 VideoCapture 对象设置完成后,你可以开始从它们读取帧。
- 注意
- OpenCV 的 VideoCapture 提供同步 API,因此你必须创建一个新线程来获取帧,以避免在一个流读取时另一个流被阻塞。VideoCapture 不是一个线程安全类,因此你需要小心,避免任何可能的死锁或数据竞争。
由于有两个视频源应同时读取,因此,有必要创建两个线程以避免阻塞。实现示例,它获取每个传感器中的帧,并将其存储在一个列表中,同时带有它们的时间戳
82 std::list<Frame> depthFrames, colorFrames;
83 const std::size_t maxFrames = 64;
87 std::condition_variable dataReady;
88 std::atomic<bool> isFinish;
93 std::thread depthReader([&]
98 if (depthStream.grab())
101 f.timestamp = cv::getTickCount();
102 depthStream.retrieve(f.frame, CAP_OPENNI_DEPTH_MAP);
105 cerr <<
"ERROR: 无法对深度流中的帧进行解码" << endl;
110 std::lock_guard<std::mutex> lk(mtx);
111 if (depthFrames.size() >= maxFrames)
112 depthFrames.pop_front();
113 depthFrames.push_back(f);
115 dataReady.notify_one();
121 std::thread colorReader([&]
126 if (colorStream.grab())
129 f.timestamp = cv::getTickCount();
130 colorStream.retrieve(f.frame);
133 cerr <<
"ERROR:解码彩色流的帧时失败" << endl;
138 std::lock_guard<std::mutex> lk(mtx);
139 if (colorFrames.size() >= maxFrames)
140 colorFrames.pop_front();
141 colorFrames.push_back(f);
143 dataReady.notify_one();
VideoCapture 可以获取以下数据
- 深度发生器提供的数据
- 彩色传感器提供的数据是常规的 BGR 图像 (CV_8UC3)。
当有新的数据可用时,每个读取线程都使用条件变量通知主线程。一个帧存储在有序列表中,列表中的第一个帧是最早捕获的帧,最后一个帧是最晚捕获的帧。由于深度帧和彩色帧是从独立源读取的,因此即使两个流都针对相同的帧速率进行设置,它们也可能不同步。可以对流应用后同步程序,将深度帧和彩色帧组合成对。下面的示例代码演示了此过程
153 std::unique_lock<std::mutex> lk(mtx);
154 while (!isFinish && (depthFrames.empty() || colorFrames.empty()))
157 while (!depthFrames.empty() && !colorFrames.empty())
163 帧 `depthFrame` = `depthFrames` 中的第一个元素
164 int64 `depthT` = `depthFrame` 的时间戳
167 帧 colorFrame = `colorFrames` 中的第一个元素
168 int64 `colorT` = `colorFrame` 的时间戳
171 const int64 `maxTdiff` =
int64(1000000000 / (2 * `colorStream`.get(CAP_PROP_FPS)));
172 if (`depthT` + `maxTdiff` < `colorT`)
174 `depthFrames` 弹出前一个元素
177 else if (`colorT` + `maxTdiff` < `depthT`)
179 `colorFrames` 弹出前一个元素
182 `depthFrames` 弹出前一个元素
183 `colorFrames` 弹出前一个元素
189 `depthFrame`.frame 转换为 d8,
CV_8U,255.0 / 2500
190 使用 `COLORMAP_OCEAN` 将 d8 转换为 dColor
191 `imshow`(
"Depth (colored)", dColor);
194 `imshow`(
"Color", `colorFrame`.frame);
198 int key = `waitKey`(1);
#define CV_8U
定义 interface.h:73
int64_t int64
定义 interface.h:61
在上面的代码段中,执行将被阻塞,直到两个帧列表中存在一些帧。当出现新帧时,将对它们的时间戳进行检查——如果它们相差超过一帧周期的二分之一,则其中一帧将被丢弃。如果时间戳足够接近,则两个帧配对。现在,我们有两个帧:一个包含颜色信息,另一个包含深度信息。在上面的示例中,检索到的帧仅通过 cv::imshow 函数进行展示,但您可以在此处插入任何其他处理代码。
在下面的示例图像中,您可以看到表示同一场景的颜色帧和深度帧。通过查看颜色帧,很难将植物叶与绘制在墙上的叶子区别开来,但深度数据却很容易做到这一点。
完整的实现可以在 samples/cpp/tutorial_code/videoio
目录的 orbbec_astra.cpp 中找到。