orbbec_astra.cpp获取和处理来自深度和颜色摄像头流的视觉数据
代码编写了一个多线程的视觉应用程序,其中利用opencv库进行视频流处理。主要功能如下:
开启两个视频流,一个用于深度图像,一个用于彩色图像。
设置视频流参数(如分辨率和帧速率)。
并行地在两个线程中分别读取并存储深度和彩色视频流的帧。
将深度帧和彩色帧进行配对,并显示这些帧。
如果用户按下esc键,程序会终止处理并关闭。
程序中还使用了互斥锁和条件变量来同步两个视频流的帧,确保它们被适时地处理和显示。
这段代码在两个单独的线程中处理深度和彩色视频。然后,它以同步的方式配对深度和彩色帧,并在窗口中显示它们。当用户按下 esc 键时,程序会停止。
#include <opencv2/videoio/videoio.hpp> // 包含opencv视频输入输出头文件
#include <opencv2/highgui.hpp> // 包含opencv的gui(图形用户界面)头文件
#include <opencv2/imgproc.hpp> // 包含opencv图像处理头文件
#include <list> // 包含c++标准库列表容器头文件
#include <iostream> // 包含输入输出流头文件
// 检查是否定义了多线程支持
#if !defined(have_threads)
int main()
{
std::cout << "this sample is built without threading support. sample code is disabled." << std::endl; // 如果没有开启多线程支持,则输出信息
return 0;
}
#else
#include <thread> // 包含c++11标准线程头文件
#include <mutex> // 包含互斥锁头文件
#include <condition_variable> // 包含条件变量头文件
#include <atomic> // 包含原子操作头文件
using namespace cv; // 使用命名空间cv,用于opencv相关功能
using std::cout; // 使用标准输出流cout
using std::cerr; // 使用标准错误流cerr
using std::endl; // 使用换行符endl
// 存储帧及其时间戳的结构体
struct frame
{
int64 timestamp; // 时间戳
mat frame; // 帧数据
};
int main()
{
//! [open streams]
// 打开深度摄像头流
videocapture depthstream(cap_openni2_astra);
// 打开彩色摄像头流
videocapture colorstream(0, cap_v4l2);
//! [open streams]
// 检查彩色流是否已打开
if (!colorstream.isopened())
{
cerr << "error: unable to open color stream" << endl;
return 1;
}
// 检查深度流是否已打开
if (!depthstream.isopened())
{
cerr << "error: unable to open depth stream" << endl;
return 1;
}
//! [setup streams]
// 设置彩色和深度流参数
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);
//! [setup streams]
// 打印彩色流参数
cout << "color stream: "
<< colorstream.get(cap_prop_frame_width) << "x" << colorstream.get(cap_prop_frame_height)
<< " @" << colorstream.get(cap_prop_fps) << " fps" << endl;
//! [get properties]
// 打印深度流参数
cout << "depth stream: "
<< depthstream.get(cap_prop_frame_width) << "x" << depthstream.get(cap_prop_frame_height)
<< " @" << depthstream.get(cap_prop_fps) << " fps" << endl;
//! [get properties]
//! [read streams]
// 创建两个列表存储帧数据
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: 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(); // 通知条件变量
}
}
});
// 开启读取彩色流的线程
std::thread colorreader([&]
{
while (!isfinish) // 当没有结束时
{
// 抓取并解码新帧
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;
}
{//实例化的时候就会立即尝试获取互斥锁,如果没有获取到,则会阻塞到获取锁为止;并且一旦lock_guard对象被创建,你就不能手动来释放锁或者再次获取锁。
std::lock_guard<std::mutex> lk(mtx); // 加锁
if (colorframes.size() >= maxframes) // 如果超过最大帧数
colorframes.pop_front(); // 删除列表前端帧
colorframes.push_back(f); // 加入新帧到列表
}
dataready.notify_one(); // 通知条件变量
}
}
});
//! [read streams]
//! [pair 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(); // 加锁
// 从列表中取出一个深度帧
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;//当程序执行到continue这个语句时,它将立即结束本轮循环中剩余的操作,并跳过循环体中continue之后的代码,直接开始下一轮的循环。
}
else if (colort + maxtdiff < deptht) // 如果彩色帧时间戳小于深度帧时间戳
{
colorframes.pop_front(); // 删除彩色帧
continue;//当程序执行到continue这个语句时,它将立即结束本轮循环中剩余的操作,并跳过循环体中continue之后的代码,直接开始下一轮的循环。
}
depthframes.pop_front(); // 删除深度帧
colorframes.pop_front(); // 删除彩色帧
lk.unlock(); // 解锁
//! [show frames]
// 显示深度帧
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); // 显示彩色帧图
//! [show frames]
// 通过按esc键退出
int key = waitkey(1);
if (key == 27) // 如果是esc键
{
isfinish = true; // 设置结束标志为真
break;
}
}
}
//! [pair frames]
dataready.notify_one(); // 通知条件变量
depthreader.join(); // 等待深度读取线程结束
colorreader.join(); // 等待彩色读取线程结束
return 0;
}
#endif
运行报错:
[ info:0@0.052] global videoio_registry.cpp:244 cv::`anonymous-namespace'::videobackendregistry::videobackendregistry videoio: enabled backends(9, sorted by priority): ffmpeg(1000); gstreamer(990); intel_mfx(980); msmf(970); dshow(960); cv_images(950); cv_mjpeg(940); ueye(930); obsensor(920)
error: unable to open color stream
/** @brief sets a property in the videocapture.
@param propid property identifier from cv::videocaptureproperties (eg. cv::cap_prop_pos_msec, cv::cap_prop_pos_frames, ...)
or one from @ref videoio_flags_others
@param value value of the property.
@return `true` if the property is supported by backend used by the videocapture instance.
@note even if it returns `true` this doesn't ensure that the property
value has been accepted by the capture device. see note in videocapture::get()
*/
cv_wrap virtual bool set(int propid, double value);
depthstream.retrieve(f.frame, cap_openni_depth_map);
std::lock_guard<std::mutex> lk(mtx); 与 std::unique_lock<std::mutex> lk(mtx);
`std::lock_guard<std::mutex> lk(mtx);`和`std::unique_lock<std::mutex> lk(mtx);`分别是什么?它们有什么区别?
const int64 maxtdiff = int64(1000000000 / (2 * colorstream.get(cap_prop_fps)));
depthframe.frame.convertto(d8, cv_8u, 255.0 / 2500);
applycolormap(d8, dcolor, colormap_ocean);
使用orbbec astra 3d摄像头
======================================================
简介
这篇教程是关于orbbec astra系列3d摄像头的使用 (https://orbbec3d.com/index/product/info.html?cate=38&id=36)。
这些摄像头除了常见的彩色传感器外,还有一个深度传感器。可以使用开源的openni api,
通过@ref cv::videocapture 类来读取深度传感器的数据。视频流是通过常规的摄像头接口提供的。
安装说明
为了能够使用astra摄像头的深度传感器和opencv,你需要执行以下步骤:
-# 下载orbbec openni sdk的最新版本(从这里https://orbbec3d.com/index/download.html下载)。
解压缩文件,根据你的操作系统选择build,并按照readme文件中提供的步骤进行安装。
-# 例如,如果你使用64位的gnu/linux,执行:
$ cd linux/openni-linux-x64-2.3.0.63/
$ sudo ./install.sh
安装完成后,确保重新插拔你的设备以使udev规则生效。摄像头此时应该能作为通用摄像设备工作。请注意,
你当前的用户需要属于video
组,才能访问摄像头。同时,请确保已经加载了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
如果上面的两个变量是空的,那么你需要重新加载opennidevenvironment
。
@note 从orbbec openni sdk版本2.3.0.86开始,不再提供install.sh
。
你可以使用下面的脚本来初始化环境:
# 检查用户是否是root/用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
# 为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"
-# 现在你可以配置opencv,通过设置cmake中的with_openni2
标志来启用openni支持。
为了得到一个与你的astra摄像头一起工作的代码样本,你可能还想启用build_examples
标志。
在包含opencv源代码的目录中运行以下命令来启用openni支持:
$ mkdir build
$ cd build
$ cmake -dwith_openni2=on ..
如果找到了openni库,opencv就会被构建成支持openni2的版本。你可以在cmake日志中看到openni2支持的状态:
-- 视频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摄像头有两个传感器 -- 一个深度传感器和一个彩色传感器。深度传感器可以使用openni接口
通过@ref cv::videocapture 类读取。视频流不通过openni api提供,而是通过常规摄像头接口提供。
因此,为了获取深度和彩色帧,需要创建两个@ref cv::videocapture对象:
@snippetlineno samples/cpp/tutorial_code/videoio/orbbec_astra/orbbec_astra.cpp 打开视频流
第一个对象将使用openni2 api来检索深度数据。第二个对象使用video4linux2接口访问彩色传感器。
请注意,上面的例子假设astra摄像头是系统中的第一个摄像头。如果你连接了多个摄像头,你可能需要显式设置正确的摄像头编号。
在使用创建的videocapture对象之前,你可能想通过设置对象的属性来设置流参数。
最重要的参数是帧宽度、帧高度和帧率。对于这个例子,我们将配置两个流的宽度和高度为vga分辨率,
这是两个传感器可用的最大分辨率,我们希望两个流的参数都是一样的,以便于颜色数据到深度数据的注册更加容易:
@snippetlineno samples/cpp/tutorial_code/videoio/orbbec_astra/orbbec_astra.cpp 设置视频流
要设置和获取传感器数据生成器的一些属性,请分别使用@ref cv::videocapture::set 和
@ref cv::videocapture::get方法,例如:
@snippetlineno samples/cpp/tutorial_code/videoio/orbbec_astra/orbbec_astra.cpp 获取属性
以下是通过openni接口可用的摄像头属性,适用于深度生成器:
@ref cv::cap_prop_frame_width -- 像素中的帧宽度。
@ref cv::cap_prop_frame_height -- 像素中的帧高度。
@ref cv::cap_prop_fps -- 帧率,单位fps。
@ref cv::cap_prop_openni_registration -- 标志,注册了深度图到图像的映射(如果标志是“开”的话),
或者将这个视点设置为它的正常视点(如果标志是“关”的话)。注册的结果图像是像素对齐的,
也就是说图像中的每个像素都跟深度图像中的像素对齐。@ref cv::cap_prop_openni2_mirror -- 标志,用于为这个流启用或禁用镜像功能。设置为0以禁用镜像。
下面的属性仅用于获取:
@ref cv::cap_prop_openni_frame_max_depth -- 摄像头最大支持的深度,单位mm。
@ref cv::cap_prop_openni_baseline -- 基线值,单位mm。
设置好videocapture对象后,你可以开始从它们中读取帧。
@note
opencv的videocapture提供了同步api,因此你必须在新的线程中抓取帧,以避免一个流被阻塞,
而另一个流正在被读取。videocapture不是一个线程安全的类,所以你需要小心避免任何可能的死锁
或数据竞争。
由于需要同时从两个视频源读取数据,因此必须创建两个线程以避免阻塞。下面的示例实现了在新线程中
从每个传感器获取帧,并将它们连同其时间戳存储在列表中:
@snippetlineno samples/cpp/tutorial_code/videoio/orbbec_astra/orbbec_astra.cpp 读取视频流
videocapture可以检索以下数据:
-# 由深度生成器提供的数据:
- @ref cv::cap_openni_depth_map - 深度值,单位mm(cv_16uc1)
- @ref cv::cap_openni_point_cloud_map - xyz,单位m(cv_32fc3)
- @ref cv::cap_openni_disparity_map - 视差值,单位像素(cv_8uc1)
- @ref cv::cap_openni_disparity_map_32f - 视差值,单位像素(cv_32fc1)
- @ref cv::cap_openni_valid_depth_mask - 有效像素的掩码(非被遮挡,非被阴影覆盖等)(cv_8uc1)
-# 由彩色传感器提供的数据是普通的bgr图像(cv_8uc3)。
当新数据可用时,每个读取线程使用条件变量通知主线程。
帧被存储在有序列表中 —— 列表中的第一个帧是最早捕获的,最后一个帧是最晚捕获的。
由于深度和彩色帧是从独立源读取的,即使位两个视频流设置了相同的帧率,它们也可能不同步。
可以对流进行后同步处理,将深度和彩色帧配对。下面的示例代码演示了这个过程:
@snippetlineno samples/cpp/tutorial_code/videoio/orbbec_astra/orbbec_astra.cpp 配对帧
在上面的代码片段中,执行将被阻塞,直到两个帧列表中都有一些帧为止。
当有新帧时,将检查它们的时间戳 - 如果它们的差异超过帧周期的一半,则其中一个帧将被丢弃。
如果时间戳足够接近,那么两个帧就被配对了。现在,我们有了两个帧:一个包含彩色信息,另一个包含深度信息。
在上面的示例中,检索到的帧简单地显示在cv::imshow函数中,但你可以在这里插入任何其他处理代码。
在最顶部的样本图像中,你可以看到表示相同场景的彩色帧和深度帧。
从彩色帧看起来很难区分植物叶子和墙上画的叶子,
但深度数据可以轻松做到。
发表评论