图漾深度摄像头基本使用方法

版权声明:学习记录~ https://blog.csdn.net/robinhjwy/article/details/80273174

搞到一款图漾的3d相机(型号为FM810-C)有好久了,一直也只是编译了sample代码跑起来看看样子,并没有着手看SDK以及开发的事。近几日对照SDK以及其中的SimpleView_FetchFrame例子,捋了捋头绪。
先看一下官方文档给出的开发流程:

  1. 初始化 API。
  2. 操作设备。
    (a) 获取设备列表。
    初次获取设备信息时可以通过 TYGetDeviceList() 查询已连接的设备。在已知设备 ID 的情况下,
    可以不调用该函数查询。
    (b) 打开设备。
    对于 USB 设备可以通过 TYOpenDevice() 打开设备;对于网络设备可以通过 TYOpenDevice-
    WithIP() 打开设备。
  3. 操作组件。
    (a) 查询组件状态。
    使用 TYGetComponentIDs() 可查询所有组件状态;使用 TYGetEnabledComponents() 查询使能
    状态的组件。
    (b) 配置组件。
    每个设备打开后都必须配置组件,
    默认情况下只有虚拟组件 TY_COMPONENT_DEVICE
    是使能状态。多个设备可以通过位或方式同时使能。代码示例如下:

    cd source
    int32_t componentIDs = TY_COMPONENT_DEPTH_CAM | TY_COMPONENT_RGB_CAM ;
    TYEnableComponents ( hDevice , componentIDs ) ;
  4. 操作参数。
    (a) 查询指定参数的信息。通过填充结构体 TY_FEATURE_INFO 来获取指定组件的指定参数的信息。
    如果该组件不包含所指定的参数,则 TY_FEATURE_INFO 中 isValid 值为 false。
    (b) 配置参数。
    配置参数的 API 按照参数的数据类型分类,如 TYGetIntRange,和 TYSetFloat 等。
  5. 给驱动程序分配 framebuffer。
    (a) 查询当前配置下每个 framebuffer 的大小。
    (b) 分配 framebuffer 并压入驱动内的缓冲队列。
    驱动内部维护一个缓冲队列(buffer queue),每帧数据传出时会将填充好的 buffer 作 dequeue
    操作,并完全传出给用户使用。用户需要保证新的一帧数据到来时驱动的缓冲队列 buffer queue
    不为空,否则该帧数据将被丢弃。
  6. 注册回调函数(主动获取模式下不调用)。
  7. 启动采集。
  8. 主动获取帧数据(主动获取模式下不调用)。
  9. 停止采集。
  10. 关闭设备。
  11. 释放 API。

看起来流程还算清晰,但是只有这个感觉还是一头雾水。于是对应sample中的SimpleView_FetchFrame源码,慢慢的理了理头绪。先贴一个最最最简单核心的骨架流程:
1、初始化设备 TYInitLib();
2、打开设备 TYOpenDevice();
3、使能组件 TYEnableComponents();
4、将帧缓存压入队列 TYEnqueueBuffer();
5、开始拍摄 TYStartCapture();
6、获取帧数据 TYFetchFrame();
7、解析帧数据 parseFrame();
8、显示图像 cv::imshow();
9、停止拍摄 TYStopCapture();
10、关闭设备 TYCloseDevice();
11、注销API TYDeinitLib();

流程看起来跟SDK上说的没有什么差别啊,是的,并没有太多的差别,但是后面贴的代码是核心流程中最最最简化的核心流程,用于抽出框架,其他的功能在此框架上添加(比如SimpleView_FetchFrame例子中的一些信息状态输出,运行状态检测,等等)。

贴上对应的代码:


#include "../common/Utils.hpp"

int main(int argc, char* argv[])
{
    //初始化
    TYInitLib();

    // 打开设备
    // 用设备ID打开设备,同时构建此设备的设备管理器。
    TY_DEV_HANDLE hDevice;
    TYOpenDevice("207000000866", &hDevice);

    // 使能组件
    // 按位或上组件结构体中的值,即可打开设备。
    TYEnableComponents(hDevice, TY_COMPONENT_RGB_CAM | TY_COMPONENT_DEPTH_CAM | TY_COMPONENT_IR_CAM_LEFT | TY_COMPONENT_IR_CAM_RIGHT);

    // 取得帧缓存大小。
    // 说一下这一步的目的,由于打开不同组件(打开哪些组件),以及不同组件参数设定(分辨率)的原因,每一帧数据的大小是不固定的,
    // 所以要用此函数计算一下当前设备以及当前设定下,一帧数据出来,需要多大的缓存buffer。
    // 因为后面需要压入buffer队列进行数据读取,所以要保证buffer的大小能否装下一帧数据。
    int32_t frameSize;
    TYGetFrameBufferSize(hDevice, &frameSize);

    // 驱动内部维护一个queue,用户往队列中enqueue空的buffer,
    // 设备用每一帧的framedata去填充这个空的buffer,填充后,将此buffer弹出队列(dequeue),返回给用户使用。
    // 这几个buffer是循环使用的,弹出被用户取出数据后,又会回到队列尾部。理论上将,只需要两个buffer即可循环使用。
    int frameBuffer_number = 3;
    char* frameBuffer[frameBuffer_number];
    for (int i = 0; i < frameBuffer_number; ++i)
    {
        frameBuffer[i] = new char[frameSize];
        TYEnqueueBuffer(hDevice, frameBuffer[i], frameSize);
    }

    //开始拍摄
    TYStartCapture(hDevice);

    //取景后不断有帧输出,需要循环取得帧数据,并进行解析输出。
    TY_FRAME_DATA frame;
    cv::Mat depth, irl, irr, color;
    while(1)
    {
        // 取得帧
        TYFetchFrame(hDevice, &frame, -1);

        // 这句为整个程序的核心,解析得到的帧,解析为cv::Mat类型的图像,跟OpenCV对接后即可进行后续的处理。 defined in Utils.hpp line36
        parseFrame(frame, &depth, &irl, &irr, &color, 0);

        //图像显示
        cv::imshow("Depth", depth);
        cv::imshow("LeftIR", irl);
        cv::imshow("RightIR", irr);
        cv::imshow("Color", color);

        //检测
        if (cv::waitKey(1) == 'q') { break; }

        // 这里就是申请的buffer循环使用的根本,每次buffer被dequeue用户取得帧后,在这个帧处理函数中,最后又把当前的buffer压入队列中,循环使用,
        // 由于队列特性,队首出列,队尾入列,所以会有循环使用的样子。
        TYEnqueueBuffer(hDevice, frame.userBuffer, frame.bufferSize);
    }

    //停止拍摄。
    TYStopCapture(hDevice);
    //关闭设备。
    TYCloseDevice(hDevice);
    //反初始化,注销API。
    TYDeinitLib();
    //delete释放内存。
    for (int j = 0; j < frameBuffer_number; ++j)
    {
        delete frameBuffer[j];
    }

    return 0;
}

先一句句来说:
第一句 :#include "../common/Utils.hpp"
这是一个工具头文件:

#ifndef SAMPLE_COMMON_UTILS_HPP_
#define SAMPLE_COMMON_UTILS_HPP_

#include <opencv2/opencv.hpp>
#include "TY_API.h"

static inline const char* colorFormatName(TY_PIXEL_FORMAT fmt){...}

static inline const TY_IMAGE_DATA* TYImageInFrame(const TY_FRAME_DATA& frame, const TY_COMPONENT_ID comp){...}

static inline int parseFrame(const TY_FRAME_DATA& frame, cv::Mat* pDepth, cv::Mat* pLeftIR, cv::Mat* pRightIR, cv::Mat* pColor, cv::Mat* pPoints){...}

#endif

函数的实现略掉,仅看结构。很简单,包含了opencv头文件和TY自己的API头文件。这是最低配了,因为显示图像要用到opencv,API自然也不必说。然后就是三个静态内联函数,主要看第三个:parseFrame() 。这个函数也是第7步中用于解析帧数据的函数,是本程序的核心函数,也就是通过它,将从设备取出的frame数据解析成opencv下cv::Mat格式的各个图像,用于后续的处理。

紧接着是流程中的第一步,初始化。也就是main函数中的TYInitLib(); 没什么好说的,就一个无参函数。

这两句是流程中第二步的打开设备

TY_DEV_HANDLE hDevice;
TYOpenDevice("207000000866", &hDevice);

首先看一下TYOpenDevice("207000000866", &hDevice); 函数的原型:

/// @brief Open device by device ID.
/// @param  [in]  deviceID      Device ID string, can be get from TY_DEVICE_BASE_INFO.
/// @param  [out] deviceHandle  Handle of opened device.
/// @retval TY_STATUS_OK        Succeed.
/// @retval TY_STATUS_NOT_INITED        TYInitLib not called.
/// @retval TY_STATUS_NULL_POINTER      deviceID or deviceHandle is NULL.
/// @retval TY_STATUS_INVALID_PARAMETER Device not found.
/// @retval TY_STATUS_BUSY              Device has been opened.
/// @retval TY_STATUS_DEVICE_ERROR      Open device failed.
TY_CAPI TYOpenDevice              (const char* deviceID, TY_DEV_HANDLE* deviceHandle);

可以看出,需要输出字符串类型的deviceID,和一个设备管理器指针类型的deviceHandle。
deviceHandle好弄,上一句定义一下就好了。而且他是一个输出承接参数,将deviceID对应的这台设备绑定这里定义的deviceHandle。
那deviceID这里是为了简化,获取后将获取程序去掉,直接用ID进行设备打开(因为我只有一台设备,ID也不会变,所以获取后即可直接用,好处是代码简化,坏处是代码不具有兼容性)。
那到底是如何获取设备ID的呢?其实是利用TYGetDeviceList()函数

//定义设备基本信息指针,用于承接后续得到的多个设备的信息,因为有可能连接多个设备。
TY_DEVICE_BASE_INFO* pBaseInfo;
//取得设备列表,结果存放在pBaseInfo数组中。
TYGetDeviceList(pBaseInfo, 100, &n)//这里即为打开第1个设备。利用第一个设备对应的ID。如果有很多设备的话,往后罗列即可。
TYOpenDevice(pBaseInfo[0].id, &hDevice)

看一下TY_DEVICE_BASE_INFO 结构体的定义:

typedef struct TY_DEVICE_BASE_INFO
{
    TY_INTERFACE        devInterface;       ///< interface, see TY_INTERFACE_LIST
    char                id[32];
    char                vendorName[32];     
    char                modelName[32];
    TY_VERSION_INFO     hardwareVersion;
    TY_VERSION_INFO     firmwareVersion;
    TY_DEVICE_NET_INFO  netInfo;
    TY_STATUS           status;
    char                reserved[248];
}TY_DEVICE_BASE_INFO;

发现可以输出很多设备的基本信息,不光包括ID,还有供应商信息、型号、硬件版本、固件版本、网络信息、状态等。反正就是用它能得到关于设备的很多信息就是了。然后用.ID 得到ID,传入设备打开函数。

继续看,到第三步,使能组件:

TYEnableComponents(hDevice, TY_COMPONENT_RGB_CAM | TY_COMPONENT_DEPTH_CAM | TY_COMPONENT_IR_CAM_LEFT | TY_COMPONENT_IR_CAM_RIGHT);

先看一下函数原型:

/// @brief Enable components.
/// @param  [in]  hDevice       Device handle.
/// @param  [in]  componentIDs  Components to be enabled.
/// @retval TY_STATUS_OK        Succeed.
/// @retval TY_STATUS_INVALID_HANDLE    Invalid device handle.
/// @retval TY_STATUS_INVALID_COMPONENT Some components specified by componentIDs are invalid.
/// @retval TY_STATUS_BUSY      Device is capturing.
TY_CAPI TYEnableComponents        (TY_DEV_HANDLE hDevice, int32_t componentIDs);

它需要两个输入参数,第一个就是上方绑定好的设备管理器hDevice 。第二个是组件ID,注意看,它的类型是int32_t 也就是说是一个数字!也就是说我用这个数字,来控制到底使能哪些组件!背后应该是有将各个组件对应成数字,然后用于使能,OK,找到了这个(TY_API.h line 154):

typedef enum TY_DEVICE_COMPONENT_LIST
{
    TY_COMPONENT_DEVICE         = 0x80000000, ///< Abstract component stands for whole device, always enabled
    TY_COMPONENT_DEPTH_CAM      = 0x00010000, ///< Depth camera
    TY_COMPONENT_POINT3D_CAM    = 0x00020000, ///< Point3D camera
    TY_COMPONENT_IR_CAM_LEFT    = 0x00040000, ///< Left IR camera
    TY_COMPONENT_IR_CAM_RIGHT   = 0x00080000, ///< Right IR camera
    TY_COMPONENT_RGB_CAM_LEFT   = 0x00100000, ///< Left RGB camera
    TY_COMPONENT_RGB_CAM_RIGHT  = 0x00200000, ///< Right RGB camera
    TY_COMPONENT_LASER          = 0x00400000, ///< Laser
    TY_COMPONENT_IMU            = 0x00800000, ///< Inertial Measurement Unit
    TY_COMPONENT_BRIGHT_HISTO   = 0x01000000, ///< virtual component for brightness histogram of ir 

    TY_COMPONENT_RGB_CAM        = TY_COMPONENT_RGB_CAM_LEFT /// Some device has only one RGB camera, map it to left
}TY_DEVICE_COMPONENT_LIST;
typedef int32_t TY_COMPONENT_ID;

这里发现了,组件ID就是一个数字!不过不是随便一个数字,而是在TY_DEVICE_COMPONENT_LIST 枚举中定义的这些。总体的思路应该是每个组件被对应成了一个16进制的数字也就是枚举中体现的这些。需要使能它们或者其他操作时,本质上是直接输入数字即可,但是肯定不会直接输入数字,因为输入枚举值更能直观的对应是哪个组件。另外还有就是代码中的按位或运算| 这在嵌入式中用法应该是同时打开各个位。
看一下0x00010000 -0x00080000 在内存中的存储形式(int32_t为32位,0x代表十六进制,转换为2进制后,高位补0即可):

0000 0000 0000 0001 0000 0000 0000 0000
0000 0000 0000 0010 0000 0000 0000 0000
0000 0000 0000 0100 0000 0000 0000 0000
0000 0000 0000 1000 0000 0000 0000 0000

可以非常明晰的发现各个组件其实就是对应一个32位bit串中的一个位置,在处理的时候,设备按位去读取值,每一位对应一个组件,若为1,则使能,若为0则不使能。而按位与| 操作能够一次性的将各个位上的1合并到一个数字上去,比如上面的四个做| 操作会得到:0000 0000 0000 1111 0000 0000 0000 0000 这样,将此数字传入函数,会一次性使能4个组件。所以程序中也将4个组件| 操作,同时使能。分别是彩色摄像头,深度摄像头,左右两个红外摄像头。

到了第五步,将帧缓存压入队列

    int32_t frameSize;
    TYGetFrameBufferSize(hDevice, &frameSize);

    int frameBuffer_number = 100;
    char* frameBuffer[frameBuffer_number];
    for (int i = 0; i < frameBuffer_number; ++i)
    {
        frameBuffer[i] = new char[frameSize];
        TYEnqueueBuffer(hDevice, frameBuffer[i], frameSize);
    }

这里一开始有点糊涂,先仔细的看一下文档:

5 给驱动程序分配 framebuffer。
(a) 查询当前配置下每个 framebuffer 的大小。
(b) 分配 framebuffer 并压入驱动内的缓冲队列。
驱动内部维护一个缓冲队列(buffer queue),每帧数据传出时会将填充好的 buffer 作 dequeue
操作,并完全传出给用户使用。用户需要保证新的一帧数据到来时驱动的缓冲队列 buffer queue
不为空,否则该帧数据将被丢弃。

打个比喻说一下相机将帧传送到外部的流程:
缓冲队列:食堂打饭窗口,
buffer:餐盘,
帧:鸡腿。
打饭大妈固定时间往窗口上扔一个鸡腿(相机固定时间采集一帧数据输出),假如底下有盘子(队列里面有buffer,不为空),接住了鸡腿(buffer被帧数据填充),那么将这个盘子端走去吃(将填充好的buffer弹出列,供用户使用)。
那由于大妈是固定时间扔一个鸡腿,所以为了不损失鸡腿(不丢帧),我们需要注意三个问题:
1、大妈开始扔鸡腿前(开始拍摄前),我必须先放上至少一个盘子(先将队列压入至少一个buffer)。
2、在装了鸡腿后(填充了buffer)走了(将buffer弹出队列)时,下一刻的鸡腿没地方放了!这个鸡腿要被扔地上了(要丢帧)!所以,最开始我们准备至少2个盘子先放在台子上(取景前先压入两个缓冲buffer),这样端走一个鸡腿去吃的时候(弹出队列去处理的时候),后面的不至于扔了。
3、另外你吃完鸡腿后(用户处理完后),空的盘子还是要放回台子上循环接鸡腿(还是要继续将buffer压入队列)。

另外还有一个问题就是要先确定大妈扔的是鸡腿有多大,是火鸡腿还是小鸡腿,好准备盘子大小(获取buffer大小,以免装不下frame)。

综上,所以我们看到的结果就是,在TYStartCapture(hDevice); 之前,压入了两个buffer。且在处理完当前帧后,又将此buffer压入队列,以供循环使用。其实这里可以压入更多,以防丢帧,只要能保证处理的时间小于下一帧到来时所需的时间即可,但也不能无限多,会吃内存。

然后第五步开始拍摄:TYStartCapture(hDevice); 没什么说的,需要传入设备管理器。

第678步是一组循环操作,
获取帧:TYFetchFrame(hDevice, &frame, -1);
解析帧:parseFrame(frame, &depth, &irl, &irr, &color, 0);
显示图像:cv::imshow()
其中解析帧数据的时候,就用到了Utils.hpp中定义的函数。
每次循环操作结束后,一定记得将buffer再压入队列,不然盘子就用空了。。。。

后面的几步就没什么说的了,流程化操作。

    //停止拍摄。
    TYStopCapture(hDevice);
    //关闭设备。
    TYCloseDevice(hDevice);
    //反初始化,注销API。
    TYDeinitLib();

最后记得释放申请的内存,以防泄露:

    for (int j = 0; j < frameBuffer_number; ++j)
    {
        delete frameBuffer[j];
    }

猜你喜欢

转载自blog.csdn.net/robinhjwy/article/details/80273174