当前位置: 代码网 > it编程>编程语言>Java > 【opencv】教程代码 —videoio(1)使用Orbbec Astra 3D摄像头获取和处理深度和颜色摄像头流的视觉数据...

【opencv】教程代码 —videoio(1)使用Orbbec Astra 3D摄像头获取和处理深度和颜色摄像头流的视觉数据...

2024年07月28日 Java 我要评论
orbbec_astra.cpp获取和处理来自深度和颜色摄像头流的视觉数据代码编写了一个多线程的视觉应用程序,其中利用OpenCV库进行视频流处理。主要功能如下:开启两个视频流,一个用于深度图像,一个用于彩色图像。设置视频流参数(如分辨率和帧速率)。并行地在两个线程中分别读取并存储深度和彩色视频流的帧。将深度帧和彩色帧进行配对,并显示这些帧。如果用户按下Esc键,程序会终止处理并关闭。程序中还使用...

24e817165c09c430b46fadd1ffc817b1.png

orbbec_astra.cpp获取和处理来自深度和颜色摄像头流的视觉数据

代码编写了一个多线程的视觉应用程序,其中利用opencv库进行视频流处理。主要功能如下:

  • 开启两个视频流,一个用于深度图像,一个用于彩色图像。

  • 设置视频流参数(如分辨率和帧速率)。

  • 并行地在两个线程中分别读取并存储深度和彩色视频流的帧。

  • 将深度帧和彩色帧进行配对,并显示这些帧。

  • 如果用户按下esc键,程序会终止处理并关闭。

程序中还使用了互斥锁和条件变量来同步两个视频流的帧,确保它们被适时地处理和显示。

这段代码在两个单独的线程中处理深度和彩色视频。然后,它以同步的方式配对深度和彩色帧,并在窗口中显示它们。当用户按下 esc 键时,程序会停止。

82520e20c16362277e5bf7f5097f60c8.png

#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

2f300f8b8585d63a78a965c21163f974.png

/** @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);

8b200debadd8e7ad82440f6c9dd91144.png

depthstream.retrieve(f.frame, cap_openni_depth_map);

a8eff8cecee397a5df3d0cac58389ddc.png

std::lock_guard<std::mutex> lk(mtx); 与  std::unique_lock<std::mutex> lk(mtx);

41f3cdb2a907863a8c5900685d48d306.png

`std::lock_guard<std::mutex> lk(mtx);`和`std::unique_lock<std::mutex> lk(mtx);`分别是什么?它们有什么区别?

7cf518f15563c1e516d9186ef7fbac06.png

const int64 maxtdiff = int64(1000000000 / (2 * colorstream.get(cap_prop_fps)));

b71fd80bbc9bea5ddf78654e36a1fb33.png

depthframe.frame.convertto(d8, cv_8u, 255.0 / 2500);

6487af956265c81913ff5023f9422499.png

applycolormap(d8, dcolor, colormap_ocean);

85cbd53718c5d63bc2f6023075cac1de.png

 使用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函数中,但你可以在这里插入任何其他处理代码。

在最顶部的样本图像中,你可以看到表示相同场景的彩色帧和深度帧。
从彩色帧看起来很难区分植物叶子和墙上画的叶子,
但深度数据可以轻松做到。

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com