上一个教程: 使用 Kinect 和其他兼容 OpenNI 的深度传感器
下一个教程: 使用奥比中光 3D 深度摄像头 (UVC)
简介
本教程专门介绍奥比中光 Astra 系列 3D 深度摄像头 (https://www.orbbec.com/products/structured-light-camera/astra-series/)。这些摄像头除了常见的彩色传感器外,还配备了深度传感器。深度传感器可以使用开源的 OpenNI API 通过 cv::VideoCapture 类进行读取。视频流通过常规摄像头接口提供。
安装说明
为了在 OpenCV 中使用 Astra 摄像头的深度传感器,您应该执行以下步骤:
- 下载最新版本的奥比中光 OpenNI SDK(从此处 https://www.orbbec.com/developers/openni-sdk/)。解压存档,根据您的操作系统选择构建版本,并按照 Readme 文件中提供的安装步骤进行操作。
例如,如果您使用 64 位 GNU/Linux,请运行:
$ cd Linux/OpenNI-Linux-x64-2.3.0.63/
$ sudo ./install.sh
安装完成后,请务必重新插拔您的设备,以便 udev 规则生效。摄像头现在应该可以作为通用摄像头设备工作。请注意,您当前的用户应属于 video 用户组,才能访问摄像头。另外,请务必 source OpenNIDevEnvironment 文件:
$ source OpenNIDevEnvironment
要验证 source 命令是否有效以及 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
如果上述两个变量为空,则需要再次 source OpenNIDevEnvironment。
- 注意
- 奥比中光 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"
- 最新尝试的版本
2.3.0.86_202210111154_4c8f5aa4_beta6 在现代 Linux 上无法正常工作,即使按照说明重建 libusb 后也如此。已知可用的最新版本是 2.3.0.63(在 Ubuntu 18.04 amd64 上测试)。该版本并未在官方下载页面提供,而是由奥比中光技术支持人员在奥比中光社区论坛 此处 发布。
- 现在,您可以通过在 CMake 中设置
WITH_OPENNI2 标志来配置启用 OpenNI 支持的 OpenCV。您可能还希望启用 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)
- 构建 OpenCV
代码
Astra Pro 摄像头有两个传感器——深度传感器和彩色传感器。深度传感器可以使用 OpenNI 接口通过 cv::VideoCapture 类读取。视频流不通过 OpenNI API 提供,仅通过常规摄像头接口提供。因此,要获取深度和彩色帧,应创建两个 cv::VideoCapture 对象:
第一个对象将使用 OpenNI2 API 获取深度数据。第二个对象使用 Video4Linux2 接口访问彩色传感器。请注意,上述示例假设 Astra 摄像头是系统中的第一个摄像头。如果您连接了多个摄像头,可能需要明确设置正确的摄像头编号。
在使用创建的 VideoCapture 对象之前,您可能希望通过设置对象的属性来配置流参数。最重要的参数是帧宽、帧高和帧率。对于本示例,我们将两个流的宽度和高度配置为 VGA 分辨率(这是两个传感器可用的最大分辨率),并且我们希望两个流参数相同,以便更轻松地进行彩色到深度数据注册。
60
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 方法,例如:
74
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 不是线程安全的类,因此您需要小心,以避免任何可能的死锁或数据竞争。
由于有两个视频源需要同时读取,因此有必要创建两个线程以避免阻塞。以下示例实现从新线程中的每个传感器获取帧,并将其与时间戳一起存储在列表中:
81
82 std::list<Frame> depthFrames, colorFrames;
83 const std::size_t maxFrames = 64;
84
85
86 std::mutex mtx;
87 std::condition_variable dataReady;
88 std::atomic<bool> isFinish;
89
90 isFinish = false;
91
92
93 std::thread depthReader([&]
94 {
95 while (!isFinish)
96 {
97
98 if (depthStream.grab())
99 {
100 Frame f;
102 depthStream.retrieve(f.frame, CAP_OPENNI_DEPTH_MAP);
103 if (f.frame.empty())
104 {
105 cerr << "ERROR: Failed to decode frame from depth stream" << endl;
106 break;
107 }
108
109 {
110 std::lock_guard<std::mutex> lk(mtx);
111 if (depthFrames.size() >= maxFrames)
112 depthFrames.pop_front();
113 depthFrames.push_back(f);
114 }
115 dataReady.notify_one();
116 }
117 }
118 });
119
120
121 std::thread colorReader([&]
122 {
123 while (!isFinish)
124 {
125
126 if (colorStream.grab())
127 {
128 Frame f;
130 colorStream.retrieve(f.frame);
131 if (f.frame.empty())
132 {
133 cerr << "ERROR: Failed to decode frame from color stream" << endl;
134 break;
135 }
136
137 {
138 std::lock_guard<std::mutex> lk(mtx);
139 if (colorFrames.size() >= maxFrames)
140 colorFrames.pop_front();
141 colorFrames.push_back(f);
142 }
143 dataReady.notify_one();
144 }
145 }
146 });
VideoCapture 可以检索以下数据
- 深度生成器提供的数据:
- 彩色传感器提供的数据是常规的 BGR 图像 (CV_8UC3)。
当新数据可用时,每个读取线程使用条件变量通知主线程。帧存储在有序列表中——列表中的第一帧是捕获最早的,最后一帧是捕获最新的。由于深度和彩色帧是从独立源读取的,即使两个流都设置为相同的帧率,它们也可能不同步。可以对流应用后同步过程,将深度和彩色帧组合成对。以下示例代码演示了此过程:
150
151 while (!isFinish)
152 {
153 std::unique_lock<std::mutex> lk(mtx);
154 while (!isFinish && (depthFrames.empty() || colorFrames.empty()))
155 dataReady.wait(lk);
156
157 while (!depthFrames.empty() && !colorFrames.empty())
158 {
159 if (!lk.owns_lock())
160 lk.lock();
161
162
163 Frame depthFrame = depthFrames.front();
164 int64 depthT = depthFrame.timestamp;
165
166
167 Frame colorFrame = colorFrames.front();
168 int64 colorT = colorFrame.timestamp;
169
170
171 const int64 maxTdiff =
int64(1000000000 / (2 * colorStream.get(CAP_PROP_FPS)));
172 if (depthT + maxTdiff < colorT)
173 {
174 depthFrames.pop_front();
175 continue;
176 }
177 else if (colorT + maxTdiff < depthT)
178 {
179 colorFrames.pop_front();
180 continue;
181 }
182 depthFrames.pop_front();
183 colorFrames.pop_front();
184 lk.unlock();
185
187
189 depthFrame.frame.convertTo(d8,
CV_8U, 255.0 / 2500);
191 imshow(
"Depth (colored)", dColor);
192
193
194 imshow(
"Color", colorFrame.frame);
196
197
199 if (key == 27)
200 {
201 isFinish = true;
202 break;
203 }
204 }
205 }
在上面的代码片段中,执行会一直阻塞,直到两个帧列表都有帧。当有新帧可用时,会检查它们的时间戳——如果它们相差超过半个帧周期,则丢弃其中一帧。如果时间戳足够接近,则将两帧配对。现在,我们有了两帧:一帧包含彩色信息,另一帧包含深度信息。在上面的示例中,获取的帧只是通过 cv::imshow 函数显示,但您可以在此处插入任何其他处理代码。
在下面的示例图像中,您可以看到表示相同场景的彩色帧和深度帧。从彩色帧看,很难区分植物叶子和墙上画的叶子,但深度数据使其变得容易。
完整的实现可以在 samples/cpp/tutorial_code/videoio 目录下的 openni_orbbec_astra.cpp 文件中找到。