返回:OpenCV系列文章目录(持续更新中......)
上一篇:OpenCV使用 Kinect 和其他兼容 OpenNI 的深度传感器(75)
下一篇 :OpenCV系列文章目录(持续更新中......)
介绍
本教程专门介绍 Astra 系列 Orbbec 3D 相机 (Products - ORBBEC - 3D Vision for a 3D World)。除了常见的颜色传感器外,相机还具有深度传感器。可以使用带有cv::VideoCapture类的开源OpenNI API读取深度传感器。视频流通过常规摄像头接口提供。
安装说明
要将 Astra 相机的深度传感器与 OpenCV 一起使用,您应该执行以下步骤:
- 下载最新版本的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
脚本初始化环境:
#使用 sudo 检查用户是否是 root/运行
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"
现在,您可以通过在CMake中设置标志来配置启用OpenNI支持的OpenCV。您可能还希望启用该WITH_OPENNI2
标志以获取与 Astra 相机配合使用的代码示例。在包含OpenCV源代码的目录中BUILD_EXAMPLES
运行以下命令以启用OpenNI支持:
$ mkdir build
$ cd build
$ cmake -DWITH_OPENNI2=ON ..
如果找到 OpenNI 库,则将使用 OpenNI2 支持构建 OpenCV。您可以在 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:
$ make
代码:
Astra Pro 相机有两个传感器——深度传感器和颜色传感器。可以使用带有cv::VideoCapture类的OpenNI接口读取深度传感器。视频流无法通过OpenNI API获得,只能通过常规的相机接口提供。因此,要同时获取深度和颜色帧,应创建两个 cv::VideoCapture 对象:
// Open depth stream
VideoCapture depthStream(CAP_OPENNI2_ASTRA);
// Open color stream
VideoCapture colorStream(0, CAP_V4L2);
第一个对象将使用 OpenNI2 API 检索深度数据。第二个使用 Video4Linux2 接口访问颜色传感器。请注意,上面的示例假定 Astra 相机是系统中的第一台相机。如果您连接了多个摄像头,则可能需要显式设置正确的摄像头编号。
在使用创建的 VideoCapture 对象之前,您可能需要通过设置对象的属性来设置流参数。最重要的参数是帧宽、帧高和 fps。在此示例中,我们将两个流的宽度和高度配置为 VGA 分辨率,这是两个传感器可用的最大分辨率,并且我们希望两个流参数相同,以便更轻松地进行颜色到深度的数据配准:
// Set color and depth stream parameters
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::set 和 cv::VideoCapture::get 方法,例如:
// Print depth stream parameters
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 以禁用镜像
接下来的属性仅供获取:
- cv::CAP_PROP_OPENNI_FRAME_MAX_DEPTH – 相机支持的最大深度(以毫米为单位)。
- cv::CAP_PROP_OPENNI_BASELINE – 基线值,单位为毫米。
设置 VideoCapture 对象后,您可以开始从中读取帧。
注意
OpenCV 的 VideoCapture 提供同步 API,因此您必须在新线程中抓取帧,以避免在读取另一个流时阻塞另一个流。VideoCapture 不是一个线程安全类,因此您需要小心避免任何可能的死锁或数据争用。
由于应同时读取两个视频源,因此有必要创建两个线程以避免阻塞。示例实现,用于从新线程中的每个传感器获取帧,并将它们及其时间戳存储在列表中:
// Create two lists to store frames
std::list<Frame> depthFrames, colorFrames;
const std::size_t maxFrames = 64;
// Synchronization objects
std::mutex mtx;
std::condition_variable dataReady;
std::atomic<bool> isFinish;
isFinish = false;
// Start depth reading thread
std::thread depthReader([&]
{
while (!isFinish)
{
// Grab and decode new frame
if (depthStream.grab())
{
Frame f;
f.timestamp = cv::getTickCount();
depthStream.retrieve(f.frame, CAP_OPENNI_DEPTH_MAP);
if (f.frame.empty())
{
cerr << "ERROR: Failed to decode frame from depth stream" << endl;
break;
}
{
std::lock_guard<std::mutex> lk(mtx);
if (depthFrames.size() >= maxFrames)
depthFrames.pop_front();
depthFrames.push_back(f);
}
dataReady.notify_one();
}
}
});
// Start color reading thread
std::thread colorReader([&]
{
while (!isFinish)
{
// Grab and decode new frame
if (colorStream.grab())
{
Frame f;
f.timestamp = cv::getTickCount();
colorStream.retrieve(f.frame);
if (f.frame.empty())
{
cerr << "ERROR: Failed to decode frame from color stream" << endl;
break;
}
{
std::lock_guard<std::mutex> lk(mtx);
if (colorFrames.size() >= maxFrames)
colorFrames.pop_front();
colorFrames.push_back(f);
}
dataReady.notify_one();
}
}
});
VideoCapture 可以检索以下数据:
- 深度生成器给出的数据:
- cv::CAP_OPENNI_DEPTH_MAP - 深度值,单位为 mm (CV_16UC1)
- cv::CAP_OPENNI_POINT_CLOUD_MAP - XYZ,单位为米 (CV_32FC3)
- cv::CAP_OPENNI_DISPARITY_MAP - 像素视差 (CV_8UC1)
- cv::CAP_OPENNI_DISPARITY_MAP_32F - 像素视差 (CV_32FC1)
- cv::CAP_OPENNI_VALID_DEPTH_MASK - 有效像素的遮罩(未遮挡、未着色等)(CV_8UC1)
- 颜色传感器给出的数据是常规的 BGR 图像 (CV_8UC3)。
当新数据可用时,每个读取线程都使用条件变量通知主线程。帧存储在有序列表中 – 列表中的第一帧是最早捕获的帧,最后一帧是最新捕获的帧。由于深度和颜色帧是从独立来源读取的,因此即使将两个视频流设置为相同的帧速率,两个视频流也可能不同步。可以将后同步过程应用于流,以将深度和颜色帧组合成对。下面的示例代码演示了此过程:
// Pair depth and color frames
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();
// Get a frame from the list
Frame depthFrame = depthFrames.front();
int64 depthT = depthFrame.timestamp;
// Get a frame from the list
Frame colorFrame = colorFrames.front();
int64 colorT = colorFrame.timestamp;
// Half of frame period is a maximum time diff between frames
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();
// Show depth frame
Mat d8, dColor;
depthFrame.frame.convertTo(d8, CV_8U, 255.0 / 2500);
applyColorMap(d8, dColor, COLORMAP_OCEAN);
imshow("Depth (colored)", dColor);
// Show color frame
imshow("Color", colorFrame.frame);
// Exit on Esc key press
int key = waitKey(1);
if (key == 27) // ESC
{
isFinish = true;
break;
}
}
}
在上面的代码片段中,执行被阻止,直到两个帧列表中都有一些帧。当有新帧时,会检查它们的时间戳——如果它们相差超过帧周期的一半,则其中一个帧将被丢弃。如果时间戳足够接近,则两个帧配对。现在,我们有两个框架:一个包含颜色信息,另一个包含深度信息。在上面的示例中,检索到的帧仅使用 cv::imshow 函数显示,但您可以在此处插入任何其他处理代码。
在下面的示例图像中,您可以看到表示同一场景的色框和深度框。从色框上看,很难区分植物叶子和画在墙上的叶子,但深度数据很容易。
完整的实现可以在samples/cpp/tutorial_code/videoio
目录中的orbbec_astra.cpp中找到。