【opencv】示例-videocapture_openni.cpp 深度数据获取和处理的示例

时间:2024-04-16 16:34:59

2d520a22982137dccb3510682f626946.png

该代码是一个与使用OpenCV进行深度传感器捕获和处理相关的程序的一部分。主要功能包括处理Kinect或XtionPRO等深度传感器数据,解析命令行参数,打开视频捕获设备,以及在GUI上显示深度图,彩色图像,和红外图像等。代码中使用了OpenCV库函数进行视频捕获和图像处理。

// 包含OpenCV库中处理视频的头文件
#include "opencv2/videoio/videoio.hpp"
// 包含OpenCV库中高级GUI的头文件
#include "opencv2/highgui.hpp"
// 包含OpenCV库中图像处理的头文件
#include "opencv2/imgproc.hpp"


// 包含标准输入输出流库
#include <iostream>


// 使用OpenCV命名空间中的所有名称
using namespace cv;
// 使用标准命名空间中的所有名称
using namespace std;


// 帮助函数,用于输出程序的帮助信息
static void help()
{
    cout << "\nThis program demonstrates usage of depth sensors (Kinect, XtionPRO,...).\n"
                    "The user gets some of the supported output images.\n"
        "\nAll supported output map types:\n"
        // 下面列出了各种支持的传感器输出类型以及它们的格式说明
        "1.) Data given from depth generator\n"
        // 深度图,以毫米为单位
        "   CAP_OPENNI_DEPTH_MAP            - depth values in mm (CV_16UC1)\n"
        // 点云数据,以米为单位
        "   CAP_OPENNI_POINT_CLOUD_MAP      - XYZ in meters (CV_32FC3)\n"
        // 视差图,以像素为单位
        "   CAP_OPENNI_DISPARITY_MAP        - disparity in pixels (CV_8UC1)\n"
        // 视差图的浮点格式
        "   CAP_OPENNI_DISPARITY_MAP_32F    - disparity in pixels (CV_32FC1)\n"
        // 有效深度掩码,标志有效的像素点
        "   CAP_OPENNI_VALID_DEPTH_MASK     - mask of valid pixels (not occluded, not shaded etc.) (CV_8UC1)\n"
        "2.) Data given from RGB image generator\n"
        // RGB彩色图像
        "   CAP_OPENNI_BGR_IMAGE            - color image (CV_8UC3)\n"
        // 灰度图像
        "   CAP_OPENNI_GRAY_IMAGE           - gray image (CV_8UC1)\n"
        "2.) Data given from IR image generator\n"
        // 红外图像
        "   CAP_OPENNI_IR_IMAGE             - gray image (CV_16UC1)\n"
     << endl;
}


// 定义一个函数,来为视差图像上色
static void colorizeDisparity( const Mat& gray, Mat& rgb, double maxDisp=-1.f)
{
    // 确保传入的灰度图像不为空,并且数据类型为CV_8UC1
    CV_Assert( !gray.empty() );
    CV_Assert( gray.type() == CV_8UC1 );


    // 如果maxDisp小于等于0,则重新计算maxDisp的最大值
    if( maxDisp <= 0 )
    {
        maxDisp = 0;
        minMaxLoc( gray, 0, &maxDisp );
    }


    // 创建大小和输入灰度图像相同,类型为CV_8UC3的彩色图像,并初始化为全0
    rgb.create( gray.size(), CV_8UC3 );
    rgb = Scalar::all(0);
    // 如果最大视差小于1,则直接返回
    if( maxDisp < 1 )
        return;


    // 创建临时矩阵,将灰度图像转换为彩色图像,并进行彩色映射
    Mat tmp;
    convertScaleAbs(gray, tmp, 255.f / maxDisp);
    applyColorMap(tmp, rgb, COLORMAP_JET);
}


// 定义一个函数,获取最大视差值
static float getMaxDisparity( VideoCapture& capture )
{
    // 定义最小检测深度为400毫米
    const int minDistance = 400; // mm
    // 获取深度生成器的基线距离值
    float b = (float)capture.get( CAP_OPENNI_DEPTH_GENERATOR_BASELINE ); // mm
    // 获取深度生成器的焦距值
    float F = (float)capture.get( CAP_OPENNI_DEPTH_GENERATOR_FOCAL_LENGTH ); // pixels
    // 计算并返回最大视差
    return b * F / minDistance;
}


// 定义一个函数,用于打印命令行参数的帮助信息
static void printCommandLineParams()
{
    cout << "-cd=       Colorized disparity? (0 or 1; 1 by default) Ignored if disparity map is not selected to show." << endl;
    cout << "-fmd=      Fixed max disparity? (0 or 1; 0 by default) Ignored if disparity map is not colorized (-cd 0)." << endl;
    cout << "-mode=     image mode: resolution and fps, supported three values:  0 - CAP_OPENNI_VGA_30HZ, 1 - CAP_OPENNI_SXGA_15HZ," << endl;
    cout << "          2 - CAP_OPENNI_SXGA_30HZ (0 by default). Ignored if rgb image or gray image are not selected to show." << endl;
    cout << "-m=        Mask to set which output images are need. It is a string of size 6. Each element of this is '0' or '1' and" << endl;
    cout << "          determine: is depth map, disparity map, valid pixels mask, rgb image, gray image need or not (correspondently), ir image" << endl ;
    cout << "          By default -m=010100 i.e. disparity map and rgb image will be shown." << endl ;
    cout << "-r=        Filename of .oni video file. The data will grabbed from it." << endl ;
}
// 打印命令行参数信息
static void printCommandLineParams2()
{
    cout << "-cd=       是否对视差进行彩色着色?(0 或 1;默认为 1)如果未选择视差图像,则忽略。" << endl;
    cout << "-fmd=      是否使用固定的最大视差值?(0 或 1;默认为 0)如果未对视差图进行彩色着色(-cd 0),则忽略。" << endl;
    cout << "-mode=     图像模式:分辨率和帧率,支持三个值:0 - CAP_OPENNI_VGA_30HZ,1 - CAP_OPENNI_SXGA_15HZ," << endl;
    cout << "          2 - CAP_OPENNI_SXGA_30HZ(默认为 0)。如果未选择 RGB 图像或灰度图像进行显示,则忽略。" << endl;
    cout << "-m=        设置需要的输出图像的掩码。它是一个长度为 6 的字符串。每个元素为 '0' 或 '1'," << endl;
    cout << "          分别表示是否需要深度图、视差图、有效像素掩码、彩色图像、灰度图像、红外图像(对应)。" << endl;
    cout << "          默认为 -m=010100,即显示视差图和彩色图像。" << endl;
    cout << "-r=        .oni 视频文件的文件名。数据将从该文件中获取。" << endl;
}
// 定义一个函数用于解析命令行参数,并根据参数设定程序的行为
static void parseCommandLine( int argc, char* argv[], bool& isColorizeDisp, bool& isFixedMaxDisp, int& imageMode, bool retrievedImageFlags[],
                       string& filename, bool& isFileReading )
{
    // 清理filename以准备新的输入
    filename.clear();
    // 创建命令行解析器对象,并使用定义的参数格式初始化
    cv::CommandLineParser parser(argc, argv, "{h help||}{cd|1|}{fmd|0|}{mode|-1|}{m|010100|}{r||}");
    // 如果命令行中有帮助标志,则打印帮助信息后退出程序
    if (parser.has("h"))
    {
        help();
        printCommandLineParams();
        exit(0);
    }
    // 根据命令行参数设置是否为视差图上色等变量的值
    isColorizeDisp = (parser.get<int>("cd") != 0);
    isFixedMaxDisp = (parser.get<int>("fmd") != 0);
    imageMode = parser.get<int>("mode");
    // 获取"-m"标志的参数值,用于确定需要哪些输出图像
    int flags = parser.get<int>("m");
    // 判断是否有"-r"标志,并根据这个标志设置是否从文件中读取数据以及文件名称
    isFileReading = parser.has("r");
    if (isFileReading)
        filename = parser.get<string>("r");
    // 检查解析的参数是否有错误,如果有,则打印错误信息并退出程序
    if (!parser.check())
    {
        parser.printErrors();
        help();
        exit(-1);
    }
    // 判断"-m"标志的参数值是否正确,如果没有选中任何输出图像,则退出程序
    if (flags % 1000000 == 0)
    {
        cout << "No one output image is selected." << endl;
        exit(0);
    }
    // 解析"-m"标志的参数值,设置哪些图像将被程序获取和显示
    for (int i = 0; i < 6; i++)
    {
        retrievedImageFlags[5 - i] = (flags % 10 != 0);
        flags /= 10;
    }
}
/*
 * To work with Kinect or XtionPRO the user must install OpenNI library and PrimeSensorModule for OpenNI and
 * configure OpenCV with WITH_OPENNI flag is ON (using CMake).
 */
// 主函数,定义程序入口
int main( int argc, char* argv[] )
{
     // 定义了一系列变量来存储从命令行解析后的参数
    bool isColorizeDisp, isFixedMaxDisp;   // 是否给视差图上色,是否固定最大视差值
    int imageMode;                         // 图像模式参数
    bool retrievedImageFlags[6];           // 提取图像的标志位数组
    string filename;                       // 视频文件名
    bool isVideoReading;                   // 标志是否从视频文件中读取数据
    // 解析命令行参数,并将解析结果存储在之前定义的变量中
    parseCommandLine( argc, argv, isColorizeDisp, isFixedMaxDisp, imageMode, retrievedImageFlags, filename, isVideoReading );
    
    cout << "Device opening ..." << endl;  // 通知用户设备正在打开
    VideoCapture capture;                   // 定义视频捕获对象
    // 根据是否是读取视频文件的标志位来确定打开视频文件还是打开相机设备
    if( isVideoReading )
        capture.open( filename );          // 打开视频文件
    else
    {
        capture.open( CAP_OPENNI2 );       // 尝试使用OpenNI2接口打开相机设备
        if( !capture.isOpened() )          // 如果使用OpenNI2接口打开失败
            capture.open( CAP_OPENNI );    // 尝试使用更旧版本的OpenNI接口打开相机设备
    }
    
    cout << "done." << endl;               // 设备打开完成
    
    // 检查视频捕获对象是否成功打开
    if( !capture.isOpened() )
    {
        cout << "Can not open a capture object." << endl; // 如果打开失败,输出错误信息
        return -1;                                        // 返回错误码-1
    }
    
    // 如果不是从视频文件中读取并且已指定图像模式,则设置相应的图像模式
    if( !isVideoReading && imageMode >= 0 )
    {
        bool modeRes = false;               // 用于存储设置图像模式的操作结果
        // 使用switch结构来根据图像模式参数设置对应的图像输出模式
        switch ( imageMode )
        {
            case 0:
                // 设置VGA分辨率下30HZ的刷新率
                modeRes = capture.set( CAP_OPENNI_IMAGE_GENERATOR_OUTPUT_MODE, CAP_OPENNI_VGA_30HZ );
                break;
            case 1:
                // 设置SXGA分辨率下15HZ的刷新率
                modeRes = capture.set( CAP_OPENNI_IMAGE_GENERATOR_OUTPUT_MODE, CAP_OPENNI_SXGA_15HZ );
                break;
            case 2:
                // 设置SXGA分辨率下30HZ的刷新率
                modeRes = capture.set( CAP_OPENNI_IMAGE_GENERATOR_OUTPUT_MODE, CAP_OPENNI_SXGA_30HZ );
                break;
            // 下面的模式仅被Xtion Pro Live支持
            case 3:
                // 设置QVGA分辨率下30HZ的刷新率
                modeRes = capture.set( CAP_OPENNI_IMAGE_GENERATOR_OUTPUT_MODE, CAP_OPENNI_QVGA_30HZ );
                break;
            case 4:
                // 设置QVGA分辨率下60HZ的刷新率
                modeRes = capture.set( CAP_OPENNI_IMAGE_GENERATOR_OUTPUT_MODE, CAP_OPENNI_QVGA_60HZ );
                break;
            default:
                // 报告不支持的图像模式属性错误
                CV_Error( Error::StsBadArg, "Unsupported image mode property.\n");
        }
        // 如果设备不支持当前设置的图像模式,使用默认模式
        if (!modeRes)
            cout << "\nThis image mode is not supported by the device, the default value (CV_CAP_OPENNI_SXGA_15HZ) will be used.\n" << endl;
    }
 
    // 根据需要开启深度、颜色和红外图像生成器
    if (retrievedImageFlags[0] || retrievedImageFlags[1] || retrievedImageFlags[2])
        capture.set(CAP_OPENNI_DEPTH_GENERATOR_PRESENT, true);  // 如果需要深度图像,开启深度生成器
    else
        capture.set(CAP_OPENNI_DEPTH_GENERATOR_PRESENT, false); // 否则关闭深度生成器
    if (retrievedImageFlags[3] || retrievedImageFlags[4])
        capture.set(CAP_OPENNI_IMAGE_GENERATOR_PRESENT, true);  // 如果需要彩色或灰度图像,开启图像生成器
    else
        capture.set(CAP_OPENNI_IMAGE_GENERATOR_PRESENT, false); // 否则关闭图像生成器
    if (retrievedImageFlags[5])
        capture.set(CAP_OPENNI_IR_GENERATOR_PRESENT, true);     // 如果需要红外图像,开启红外生成器
    else
        capture.set(CAP_OPENNI_IR_GENERATOR_PRESENT, false);    // 否则关闭红外生成器
    
    // 打印摄像头相关设置的信息
    if (capture.get(CAP_OPENNI_DEPTH_GENERATOR_PRESENT))  // 如果深度生成器已启用
    {
        // 打印深度生成器的输出模式相关信息
        cout << "\nDepth generator output mode:" << endl <<
            "FRAME_WIDTH      " << capture.get(CAP_PROP_FRAME_WIDTH) << endl <<  // 输出帧宽度
            "FRAME_HEIGHT     " << capture.get(CAP_PROP_FRAME_HEIGHT) << endl << // 输出帧高度
            "FRAME_MAX_DEPTH  " << capture.get(CAP_PROP_OPENNI_FRAME_MAX_DEPTH) << " mm" << endl <<  // 输出最大深度值,单位毫米
            "FPS              " << capture.get(CAP_PROP_FPS) << endl <<  // 输出帧率
            "REGISTRATION     " << capture.get(CAP_PROP_OPENNI_REGISTRATION) << endl;  // 输出是否进行深度到彩色图像的配准
    }
    else
    {
        cout << "\nDevice doesn't contain depth generator or it is not selected." << endl;  // 如果没有启用深度生成器,输出提示
    }
    
    if( capture.get( CAP_OPENNI_IMAGE_GENERATOR_PRESENT ) ) // 如果图像生成器已启用
    {
        // 打印图像生成器的输出模式相关信息
        cout <<
            "\nImage generator output mode:" << endl <<
            "FRAME_WIDTH   " << capture.get( CAP_OPENNI_IMAGE_GENERATOR+CAP_PROP_FRAME_WIDTH ) << endl <<  // 输出帧宽度
            "FRAME_HEIGHT  " << capture.get( CAP_OPENNI_IMAGE_GENERATOR+CAP_PROP_FRAME_HEIGHT ) << endl << // 输出帧高度
            "FPS           " << capture.get( CAP_OPENNI_IMAGE_GENERATOR+CAP_PROP_FPS ) << endl;  // 输出帧率
    }
    else
    {
        cout << "\nDevice doesn't contain image generator or it is not selected." << endl;  // 如果没有启用图像生成器,输出提示
    }
    
    if( capture.get(CAP_OPENNI_IR_GENERATOR_PRESENT) )  // 如果红外生成器已启用
    {
        // 打印红外生成器的输出模式相关信息
        cout <<
            "\nIR generator output mode:" << endl <<
            "FRAME_WIDTH   " << capture.get(CAP_OPENNI_IR_GENERATOR + CAP_PROP_FRAME_WIDTH) << endl <<  // 输出帧宽度
            "FRAME_HEIGHT  " << capture.get(CAP_OPENNI_IR_GENERATOR + CAP_PROP_FRAME_HEIGHT) << endl << // 输出帧高度
            "FPS           " << capture.get(CAP_OPENNI_IR_GENERATOR + CAP_PROP_FPS) << endl;  // 输出帧率
    }
    else
    {
        cout << "\nDevice doesn't contain IR generator or it is not selected." << endl;  // 如果没有启用红外生成器,输出提示
    }


     // 进入无限循环,不断捕获并显示图像
    for(;;)
    {
        // 声明多个Mat类型的变量,用于存储不同种类的图像数据
        Mat depthMap; // 存储深度图像
        Mat validDepthMap; // 存储有效深度掩膜
        Mat disparityMap; // 存储视差图
        Mat bgrImage; // 存储彩色图像
        Mat grayImage; // 存储灰度图像
        Mat irImage; // 存储红外图像
    
        // 尝试从捕获设备中抓取一帧数据
        if( !capture.grab() )
        {
            cout << "Can not grab images." << endl; // 如果抓取失败,输出错误信息
            return -1; // 返回-1,结束程序
        }
        else // 如果成功抓取到数据
        {
            // 判断是否抓取到深度图
            if( retrievedImageFlags[0] && capture.retrieve( depthMap, CAP_OPENNI_DEPTH_MAP ) )
            {
                const float scaleFactor = 0.05f; // 设置深度图的显示比例因子
                Mat show; // 用于显示的图像
                depthMap.convertTo( show, CV_8UC1, scaleFactor ); // 将深度图转换为8位无符号单通道图像用于显示
                imshow( "depth map", show ); // 显示深度图
            }
    
            // 判断是否抓取到视差图
            if( retrievedImageFlags[1] && capture.retrieve( disparityMap, CAP_OPENNI_DISPARITY_MAP ) )
            {
                if( isColorizeDisp ) // 如果需要给视差图上色
                {
                    Mat colorDisparityMap; // 存储上色后的视差图
                    // 上色视差图,并根据是否固定最大视差来决定颜色化的方式
                    colorizeDisparity( disparityMap, colorDisparityMap, isFixedMaxDisp ? getMaxDisparity(capture) : -1 );
                    Mat validColorDisparityMap; // 存储有效的上色视差图(非零部分)
                    // 只复制视差图中的非零部分
                    colorDisparityMap.copyTo( validColorDisparityMap, disparityMap != 0 );
                    imshow( "colorized disparity map", validColorDisparityMap ); // 显示上色后的视差图
                }
                else
                {
                    imshow( "original disparity map", disparityMap ); // 显示原始视差图
                }
            }
    
            // 判断是否抓取到有效深度掩膜,并进行显示
            if( retrievedImageFlags[2] && capture.retrieve( validDepthMap, CAP_OPENNI_VALID_DEPTH_MASK ) )
                imshow( "valid depth mask", validDepthMap );
    
            // 判断是否抓取到彩色图像,并进行显示
            if( retrievedImageFlags[3] && capture.retrieve( bgrImage, CAP_OPENNI_BGR_IMAGE ) )
                imshow( "rgb image", bgrImage );
    
            // 判断是否抓取到灰度图像,并进行显示
            if( retrievedImageFlags[4] && capture.retrieve( grayImage, CAP_OPENNI_GRAY_IMAGE ) )
                imshow( "gray image", grayImage );
    
            // 判断是否抓取到红外图像,并进行转换和显示
            if( retrievedImageFlags[5] && capture.retrieve( irImage, CAP_OPENNI_IR_IMAGE ) )
            {
                Mat ir8; // 存储用于显示的8位红外图像
                irImage.convertTo(ir8, CV_8U, 256.0 / 3500, 0.0); // 将红外图像转换为8位格式以显示更多细节
                imshow("IR image", ir8); // 显示红外图像
            }
        }
    
        // 等待30毫秒,检查是否有按键操作
        if( waitKey( 30 ) >= 0 )
            break; // 如果有按键按下,则退出循环
    }


    return 0; // 程序运行成功,返回0
}