首页 > 其他分享 >Orbbec SDK使用摄像头读取并展示图像(以深度图为例)

Orbbec SDK使用摄像头读取并展示图像(以深度图为例)

时间:2022-12-29 21:57:08浏览次数:72  
标签:深度图 mat 为例 frame Orbbec OB include cv SDK

在上一篇文章《Linux环境下奥比中光摄像头开发环境搭建(基于Orbbec SDK)》中,我们讲解了如何在Linux下配置奥比中光摄像头的使用环境,下载了Orbbec SDK,配置其编译环境,并编译、测试了附带的深度图像读取例程。本文讲解如何自行编写CMake编译C++程序,借助Orbbec Astra Pro Plus摄像头读取并展示深度图。

工程目录

在上一篇文章《Linux环境下奥比中光摄像头开发环境搭建(基于Orbbec SDK)》,我们已经安装了OpenCV 4,并讲解了Orbbec的SDK的目录结构。当我们自行构建一个工程的时候,我们只需要把SDK文件夹放在我们的目录中,不需要例程。在这篇文章中,目录结构如下所示:

DepthViewerDemo
├── 3rdparty            // 第三方库的目录。本文仅用到Orbbec SDK(OpenCV已经被安装到系统路径中)
│   └── SDK             // SDK安装包下的SDK目录
│       ├── include     // SDK头文件目录
│       │   └── libobsensor
│       │       ├── h
│       │       │   ├── Context.h
│       │       │   ├── ...
│       │       │   └── Version.h
│       │       ├── hpp
│       │       │   ├── Context.hpp
│       │       │   ├── ...
│       │       │   └── Version.hpp
│       │       ├── ObSensor.h
│       │       └── ObSensor.hpp
│       └── lib        // SDK的链接库文件目录
│           ├── libOrbbecSDK.so
│           ├── libOrbbecSDK.so.1.4
│           └── libOrbbecSDK.so.1.4.3
├── CMakeLists.txt     // CMake脚本
├── include            // 自己代码的头文件目录
└── src                // 自己代码的源文件目录
    └── main.cpp       // 主程序脚本

CMake编译脚本

通过在CMakeLists.txt文件中,写入如下脚本,便可以编译这个项目:

cmake_minimum_required(VERSION 3.13)

# Project basic information
project(DepthViewer LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED 11)
set(THIRD_PARTY_DEPENDENCY_DIR ${CMAKE_SOURCE_DIR}/3rdparty)

# Set output options
set (CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set (CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)

# Executable
add_executable(${PROJECT_NAME}
    "src/main.cpp"
)

target_include_directories(${PROJECT_NAME}
    PRIVATE
        "include"
)

# Find OpenCV
# set(OpenCV_DIR "3rdparty")
find_package(OpenCV 4 REQUIRED)

# Configurate Orbbec SDK
set(OrbbecSDK_ROOT_DIR ${THIRD_PARTY_DEPENDENCY_DIR}/SDK)
set(OrbbecSDK_INCLUDE_DIR ${OrbbecSDK_ROOT_DIR}/include)
set(OrbbecSDK_LIBRARY_DIRS ${OrbbecSDK_ROOT_DIR}/lib)

# Add dependences
target_include_directories(${PROJECT_NAME} SYSTEM
    PRIVATE
        ${OpenCV_INCLUDE_DIR}
        ${OrbbecSDK_INCLUDE_DIR}
)

target_link_directories(${PROJECT_NAME}
    PRIVATE
        ${OpenCV_LIBRARY_DIRS}
        ${OrbbecSDK_LIBRARY_DIRS}
)

# Add all libraries
target_link_libraries(${PROJECT_NAME}
    PRIVATE
        OrbbecSDK 
        ${OpenCV_LIBS}
        $<${UNIX}:pthread>
)

将摄像头的帧转换为OpenCV的矩阵

Orbbec SDK并没有为我们提供图像处理的功能,仅提供了流的功能帮助我们提取相机读取出来的每一帧图像。因此,我们需要将流中提取到的每一帧图像,转换为OpenCV的图像矩阵cv::Mat,才能进行图像的更进一步处理、展示。Orbbec SDK为我们提供了ob::Pipeline对象,帮助我们读取摄像头采集到的视频流的帧集合ob::FrameSet。在帧集合中,我们可以提取出其深度帧、红外线帧等对象。之后,我们就可以将其转为OpenCV的矩阵cv::Mat

ob::Frame的继承关系如下所示:

               Frame
                 |
          +------+----------+---------+------------+
          |      |          |         |            |
    VideoFrame PointFrame AccelFrame GyroFrame FrameSet
          |
    +-----+----+---------+
    |          |         |
ColorFrame DepthFrame IRFrame

当我们在循环中不断读取深度帧,将其转换为OpenCV的矩阵并不断刷新地展示,便可以在屏幕中看到动态的深度图像。彩色摄像头图像和红外摄像头图像也可以通过类似的方法获取。代码如下所示:

#include <cstdlib>
#include <iostream>
#include <libobsensor/hpp/Device.hpp>
#include <libobsensor/hpp/Error.hpp>
#include <libobsensor/hpp/Pipeline.hpp>
#include <libobsensor/hpp/StreamProfile.hpp>
#include <memory>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>

int main(void) {
    ob::Pipeline pipe;
    try {
        // 获取深度相机的所有流配置,包括流的分辨率,帧率,以及帧的格式
        auto profiles = pipe.getStreamProfileList(OB_SENSOR_DEPTH);
        std::shared_ptr<ob::VideoStreamProfile> depth_profile = nullptr;
        try {
            // 根据指定的格式查找对应的Profile,优先查找Y16格式
            depth_profile = profiles->getVideoStreamProfile(640, 0, OB_FORMAT_Y16, 30);
        } catch (const ob::Error &) {
            // 没找到Y16格式后不匹配格式查找对应的Profile进行开流
            depth_profile = profiles->getVideoStreamProfile(640, 0, OB_FORMAT_UNKNOWN, 30);
        }

        // 通过创建Config来配置Pipeline要启用或者禁用哪些流,这里将启用深度流
        std::shared_ptr<ob::Config> config = std::make_shared<ob::Config>();
        config->enableStream(depth_profile);

        // 启动在Config中配置的流,如果不传参数,将启动默认配置启动流
        pipe.start(config);

        // 设置镜像模式,先判断设备是否有可读可写的权限,再进行设置
        const auto &device = pipe.getDevice();
        if (device->isPropertySupported(OB_PROP_DEPTH_MIRROR_BOOL, OB_PERMISSION_WRITE)) {
            device->setBoolProperty(OB_PROP_DEPTH_MIRROR_BOOL, true);
        }

        while (cv::waitKey(1) != 27) {  // ESC的ASCII码是27,按ESC键可以终止程序
            // 以阻塞的方式等待一帧数据,该帧是一个复合帧,配置里启用的所有流的帧数据都会包含在frameSet内,
            // 并设置帧的等待超时时间为100ms
            auto frame_set = pipe.waitForFrames(100);
            if (frame_set == nullptr) {
                continue;
            }
            cv::Mat img = frame2mat(frame_set->depthFrame());
            cv::imshow("Depth Frame", img);
        }

        pipe.stop();
    } catch (const ob::Error &e) {
        pipe.stop();
        std::cerr << "Function:" << e.getName() << "\nargs:" << e.getArgs()
                  << "\nmessage:" << e.getMessage() << "\ntype:" << e.getExceptionType()
                  << std::endl;
        exit(EXIT_FAILURE);
    } catch (const std::exception &e) {
        pipe.stop();
        std::cerr << e.what() << std::endl;
        exit(EXIT_FAILURE);
    } catch (...) {
        pipe.stop();
        std::cerr << "Unexpected Error!" << std::endl;
        exit(EXIT_FAILURE);
    }
    return EXIT_SUCCESS;
}

将Orbbec SDK帧转为OpenCV的矩阵

在上面的代码中,用到了frame2mat这个函数,将将Orbbec SDK的视频帧转为OpenCV的矩阵。这个函数是用户定义而非SDK中自带的。参考Orbbec SDK的例程,这个函数的实现如下:

#include <libobsensor/hpp/Frame.hpp>
#include <opencv2/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>

cv::Mat frame2mat(const std::shared_ptr<ob::Frame> &raw_frame) {
    const int data_size = static_cast<int>(raw_frame->dataSize());
    if (raw_frame == nullptr || data_size < 1024) {
        return {};
    }

    const auto frame = raw_frame->as<ob::VideoFrame>();
    const OBFrameType frame_type = frame->type();
    const OBFormat frame_format = frame->format();
    const int frame_height = static_cast<int>(frame->height());
    const int frame_width = static_cast<int>(frame->width());
    void *const frame_data = frame->data();

    cv::Mat result_mat;
    if (frame_type == OB_FRAME_COLOR) {
        // Color image
        if (frame_format == OB_FORMAT_MJPG) {
            const cv::Mat raw_mat(1, data_size, CV_8UC1, frame_data);
            result_mat = cv::imdecode(raw_mat, 1);
        } else if (frame_format == OB_FORMAT_NV21) {
            const cv::Mat raw_mat(frame_height * 3 / 2, frame_width, CV_8UC1, frame_data);
            cv::cvtColor(raw_mat, result_mat, cv::COLOR_YUV2BGR_NV21);
        } else if (frame_format == OB_FORMAT_YUYV || OB_FORMAT_YUY2) {
            const cv::Mat raw_mat(frame_height, frame_width, CV_8UC2, frame_data);
            cv::cvtColor(raw_mat, result_mat, cv::COLOR_YUV2BGR_YUY2);
        } else if (frame_format == OB_FORMAT_RGB888) {
            const cv::Mat raw_mat(frame_height, frame_width, CV_8UC3, frame_data);
            cv::cvtColor(raw_mat, result_mat, cv::COLOR_RGB2BGR);
        } else if (frame_format == OB_FORMAT_UYVY) {
            const cv::Mat raw_mat(frame_height, frame_width, CV_8UC2, frame_data);
            cv::cvtColor(raw_mat, result_mat, cv::COLOR_YUV2BGR_UYVY);
        }
    } else if (frame_format == OB_FORMAT_Y16 || frame_format == OB_FORMAT_YUYV ||
               frame_format == OB_FORMAT_YUY2) {
        // IR or depth image
        const cv::Mat raw_mat(frame_height, frame_width, CV_16UC1, frame_data);
        const double scale = 1 / pow(2, frame->pixelAvailableBitSize() -
                                            (frame_type == OB_FRAME_DEPTH ? 10 : 8));
        cv::convertScaleAbs(raw_mat, result_mat, scale);
    } else if (frame_type == OB_FRAME_IR) {
        // IR image
        if (frame_format == OB_FORMAT_Y8) {
            result_mat = cv::Mat(frame_height, frame_width, CV_8UC1, frame_data);
        } else if (frame_format == OB_FORMAT_MJPG) {
            const cv::Mat raw_mat(1, data_size, CV_8UC1, frame_data);
            result_mat = cv::imdecode(raw_mat, 1);
        }
    }
    return result_mat;
}

环境说明

  1. 操作系统:Ubuntu 22.04 x86_64
  2. 摄像头:Orbbec Astra Pro Plus

参考文献

https://developer.orbbec.com.cn/technical_library.html?id=42

标签:深度图,mat,为例,frame,Orbbec,OB,include,cv,SDK
From: https://www.cnblogs.com/fang-d/p/OrbbecSDK_Image_Reading.html

相关文章