Bootstrap

【Azure Kinect】基于Visual Studio实现RGB、Depth、Ir图像采集及本地保存

目录

 第一个Azure Kinect应用程序:查找Azure Kinect DK设备

基于opencv实现实时RGB、Depth、Ir图像采集及本地保存


  • 第一个Azure Kinect应用程序:查找Azure Kinect DK设备

安装Visual Studio并创建一个项目Kinect,然后右键项目找到“管理NuGet程序包”选项:

在浏览里面搜索“Microsoft.Azure.Kinect.Sensor”,安装一下:

添加头文件k4a.h,默认路径为“C:\Program Files\Azure Kinect SDK v1.4.1\sdk\include\k4a\k4a.h":

在源文件中,创建一个test.cpp:

将项目的属性改为x64:

添加头文件目录("C:\Program Files\Azure Kinect SDK v1.4.1\sdk\include"):

添加库文件目录("C:\Program Files\Azure Kinect SDK v1.4.1\sdk\windows-desktop\amd64\release\lib"):

添加库文件附加依赖项("C:\Program Files\Azure Kinect SDK v1.4.1\sdk\windows-desktop\amd64\release\lib\k4a.lib"):

添加完记得点右下角的应用,然后点击确定。

在test.cpp中运行以下代码:

#pragma comment(lib, "k4a.lib")
#include <k4a/k4a.h>

#include <stdio.h>
#include <stdlib.h>

int main()
{
    uint32_t count = k4a_device_get_installed_count();
    if (count == 0)
    {
        printf("No k4a devices attached!\n");
        return 1;
    }

    // Open the first plugged in Kinect device
    k4a_device_t device = NULL;
    if (K4A_FAILED(k4a_device_open(K4A_DEVICE_DEFAULT, &device)))
    {
        printf("Failed to open k4a device!\n");
        return 1;
    }

    // Get the size of the serial number
    size_t serial_size = 0;
    k4a_device_get_serialnum(device, NULL, &serial_size);

    // Allocate memory for the serial, then acquire it
    char* serial = (char*)(malloc(serial_size));
    k4a_device_get_serialnum(device, serial, &serial_size);
    printf("Opened device: %s\n", serial);
    free(serial);

    // Configure a stream of 4096x3072 BRGA color data at 15 frames per second
    k4a_device_configuration_t config = K4A_DEVICE_CONFIG_INIT_DISABLE_ALL;
    config.camera_fps = K4A_FRAMES_PER_SECOND_15;
    config.color_format = K4A_IMAGE_FORMAT_COLOR_BGRA32;
    config.color_resolution = K4A_COLOR_RESOLUTION_3072P;

    // Start the camera with the given configuration
    if (K4A_FAILED(k4a_device_start_cameras(device, &config)))
    {
        printf("Failed to start cameras!\n");
        k4a_device_close(device);
        return 1;
    }

    // Camera capture and application specific code would go here

    // Shut down the camera when finished with application logic
    k4a_device_stop_cameras(device);
    k4a_device_close(device);

    return 0;
}

正常运行结果如下:

  • 基于opencv实现实时RGB、Depth、Ir图像采集及本地保存

opencv官网链接:Releases - OpenCV

解压:

配置系统环境变量:

将D:\opencv3.4.1\build\x64\vc14\bin文件夹下的opencv_ffmpeg341_64.dll、opencv_world341.dll、opencv_world341d.dll三个文件复制到C:\Windows\SysWOW64和C:\Windows\System32两个文件夹中:

在Kinect项目中,菜单栏->视图->其他窗口->属性管理器

在属性管理器中选择debugx64,右键选择属性:

将“D:\opencv3.4.1\build\include”、“D:\opencv3.4.1\build\include\opencv”和“D:\opencv3.4.1\build\include\opencv2”三条路径添加到包含目录中:

将“D:\opencv3.4.1\build\x64\vc14\lib”路径添加到库目录中:

将opencv_world341d.lib名字粘贴到debug和项目两者的链接器输入中的附加依赖项中:

测试,运行后不报错且正确显示图片即证明VS上的opencv配置全部成功:

#include<iostream>
#include<opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
using namespace cv;

int main()
{
	//读入一张图片(选择自己图片的绝对路径)
	Mat img = imread("C:/Users/Pictures/Saved Pictures/green spring.jpg");
	// 创建一个名为‘test’的窗口
	namedWindow("test");
	imshow("test", img);
	waitKey(0);
	return 0;
}

在kinect项目中,创建saveimage.cpp,完整代码如下:

#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING

#include <iostream>
#include <fstream>
#include <chrono>
#include <thread>
#include <iomanip>
#include <k4a/k4a.hpp>
#include <opencv2/opencv.hpp>
#include <experimental/filesystem>


namespace fs = std::experimental::filesystem;

int main() {
    // 初始化 Azure Kinect 设备
    k4a::device device;
    try {
        device = k4a::device::open(K4A_DEVICE_DEFAULT);
    }
    catch (const std::exception& e) {
        std::cerr << "打开 Kinect 设备失败: " << e.what() << std::endl;
        return -1;
    }

    k4a_device_configuration_t config = K4A_DEVICE_CONFIG_INIT_DISABLE_ALL;
    config.camera_fps = K4A_FRAMES_PER_SECOND_30;
    config.color_format = K4A_IMAGE_FORMAT_COLOR_BGRA32;
    /*
        K4A_COLOR_RESOLUTION_OFF = 0,  Color camera will be turned off with this setting
        K4A_COLOR_RESOLUTION_720P,     1280 * 720  16:9
        K4A_COLOR_RESOLUTION_1080P,    1920 * 1080 16:9
        K4A_COLOR_RESOLUTION_1440P,    2560 * 1440 16:9
        K4A_COLOR_RESOLUTION_1536P,    2048 * 1536 4:3
        K4A_COLOR_RESOLUTION_2160P,    3840 * 2160 16:9
        K4A_COLOR_RESOLUTION_3072P,    4096 * 3072 4:3
    */
    config.color_resolution = K4A_COLOR_RESOLUTION_720P;
    /*
    Azure Kinect 深度相机支持不同的工作模式,主要区别在视场(FOV)大小和图像分辨率方面。这四种模式分别是:

    NFOV_2X2BINNED (Narrow Field of View, 2x2 Binned):视场角较小,适合近距离捕获,景深范围较窄。
    原始分辨率为 640x576,但输出时进行了 2x2 的 binning,即将相邻的 2x2 像素求平均合并为一个像素,最终输出分辨率为 320x288。
    帧率可达 30 FPS。

    NFOV_UNBINNED (Narrow Field of View, Unbinned):视场角与 NFOV_2X2BINNED 相同。
    输出原始分辨率 640x576,没有进行 binning。
    帧率同样可达 30 FPS,但每帧处理时间更长。

    WFOV_2X2BINNED (Wide Field of View, 2x2 Binned):视场角更大,适合捕获更广阔的场景,但景深范围相对 NFOV 更有限。
    原始分辨率为 1024x1024,经过 2x2 binning 后输出分辨率为 512x512。
    帧率可达 30 FPS。

    WFOV_UNBINNED (Wide Field of View, Unbinned):视场角与 WFOV_2X2BINNED 相同。
    输出原始分辨率 1024x1024,没有进行 binning。
    由于数据量大,帧率只有 15 FPS。

    总的来说:NFOV 模式适合近距离精细捕获,WFOV 模式适合大范围低精度捕获。
    Binning 通过降低分辨率来提高帧率和降低噪声,但细节会有损失。Unbinned 模式分辨率高,细节丰富,但帧率较低,噪声更多。
    */
    config.depth_mode = K4A_DEPTH_MODE_NFOV_UNBINNED;
    config.synchronized_images_only = true;

    try {
        device.start_cameras(&config);
    }
    catch (const std::exception& e) {
        std::cerr << "启动相机失败: " << e.what() << std::endl;
        device.close();
        return -1;
    }

    std::cout << "相机启动成功。" << std::endl;

    // 保存路径
    std::string colorSavePath = "D:\\Azure Kinect Data\\rgb\\";
    std::string colorTxtFilename = colorSavePath + "color_data.txt";

    std::string depthSavePath = "D:\\Azure Kinect Data\\depth\\";
    std::string depthTxtFilename = depthSavePath + "depth_data.txt";

    std::string irSavePath = "D:\\Azure Kinect Data\\ir\\";
    std::string irTxtFilename = irSavePath + "ir_data.txt";

    // 如果文件夹不存在,则创建文件夹
    if (!fs::exists(colorSavePath)) {
        fs::create_directories(colorSavePath);
    }
    if (!fs::exists(depthSavePath)) {
        fs::create_directories(depthSavePath);
    }
    if (!fs::exists(irSavePath)) {
        fs::create_directories(irSavePath);
    }

    std::ofstream colorTxtFile(colorTxtFilename);
    std::ofstream depthTxtFile(depthTxtFilename);
    std::ofstream irTxtFile(irTxtFilename);

    if (!colorTxtFile.is_open() || !depthTxtFile.is_open() || !irTxtFile.is_open()) {
        std::cerr << "打开数据文件失败。" << std::endl;
        device.close();
        return -1;
    }

    bool isRecording = false;
    bool stopRecording = false;
    int frameCount = 0;
    std::chrono::high_resolution_clock::time_point startTime;

    // 处理用户输入的线程
    std::thread inputThread([&]() {
        while (!stopRecording) {
            std::string command;
            std::cout << "输入 'start' 开始录制,输入 'end' 停止录制: ";
            std::getline(std::cin, command);
            if (command == "start") {
                startTime = std::chrono::high_resolution_clock::now();
                isRecording = true;
                std::cout << "开始录制..." << std::endl;
            }
            else if (command == "end") {
                isRecording = false;
                stopRecording = true;
                std::cout << "停止录制。" << std::endl;
            }
            else {
                std::cout << "无效的命令。请输入 'start' 或 'end'。" << std::endl;
            }
        }
        });

    // 捕获图像的主循环
    k4a::capture capture;
    while (!stopRecording) {
        if (isRecording && device.get_capture(&capture)) {
            auto currentTime = std::chrono::high_resolution_clock::now();
            auto elapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - startTime);
            double relativeTime = elapsedTime.count() / 1000.0;

            // 捕获彩色图像
            k4a::image colorImg = capture.get_color_image();
            if (colorImg) {
                uint8_t* colorBuffer = colorImg.get_buffer();
                int colorWidth = colorImg.get_width_pixels();
                int colorHeight = colorImg.get_height_pixels();
                cv::Mat colorImage(colorHeight, colorWidth, CV_8UC4, colorBuffer, colorImg.get_stride_bytes());
                cv::cvtColor(colorImage, colorImage, cv::COLOR_BGRA2BGR);
                std::string colorFilename = "color_frame_" + std::to_string(frameCount) + ".png";
                std::string colorFilePath = colorSavePath + colorFilename;
                cv::imwrite(colorFilePath, colorImage);

                // 保存彩色图像数据
                colorTxtFile << "Color Image: " << colorFilename << ", Time: " << std::fixed << std::setprecision(3) << relativeTime << "s" << std::endl;
            }

            // 捕获深度图像
            k4a::image depthImg = capture.get_depth_image();
            if (depthImg) {
                int depthWidth = depthImg.get_width_pixels();
                int depthHeight = depthImg.get_height_pixels();

                // 将深度图像对齐到彩色图像
                k4a::calibration calibration = device.get_calibration(config.depth_mode, config.color_resolution);
                k4a::transformation transform(calibration);
                k4a::image alignedDepthImg = transform.depth_image_to_color_camera(depthImg);
                uint16_t* alignedDepthBuffer = reinterpret_cast<uint16_t*>(alignedDepthImg.get_buffer());
                cv::Mat alignedDepthImage(depthHeight, depthWidth, CV_16U, alignedDepthBuffer, alignedDepthImg.get_stride_bytes());

                // 将对齐后的深度图像转换为8位深度图像
                cv::Mat alignedDepthImage8U;
                alignedDepthImage.convertTo(alignedDepthImage8U, CV_8U, 1.0 ); // 将16位深度图像缩放到8位

                // 保存对齐后的8位深度图像
                std::string depthFilename = "depth_frame_" + std::to_string(frameCount) + ".png";
                std::string depthFilePath = depthSavePath + depthFilename;
                cv::imwrite(depthFilePath, alignedDepthImage8U);

                // 保存深度图像数据
                depthTxtFile << "Depth Image: " << depthFilename << ", Time: " << std::fixed << std::setprecision(3) << relativeTime << "s" << std::endl;
            }

            // 捕获IR图像
            k4a::image irImg = capture.get_ir_image();
            if (irImg) {
                int irWidth = irImg.get_width_pixels();
                int irHeight = irImg.get_height_pixels();

                uint16_t* irBuffer = reinterpret_cast<uint16_t*>(irImg.get_buffer());
                cv::Mat irImage(irHeight, irWidth, CV_16U, irBuffer, irImg.get_stride_bytes());

                // 将16位IR图像转换为8位灰度图像
                cv::Mat irImage8U;
                irImage.convertTo(irImage8U, CV_8U, 1.0 );

                // 保存8位IR图像
                std::string irFilename = "ir_frame_" + std::to_string(frameCount) + ".png";
                std::string irFilePath = irSavePath + irFilename;
                cv::imwrite(irFilePath, irImage8U);

                // 保存IR图像数据
                irTxtFile << "IR Image: " << irFilename << ", Time: " << std::fixed << std::setprecision(3) << relativeTime << "s" << std::endl;
            }

            frameCount++;
        }
    }

    // 等待输入线程完成
    inputThread.join();

    // 关闭数据文件
    colorTxtFile.close();
    depthTxtFile.close();
    irTxtFile.close();

    // 停止相机并关闭设备
    device.stop_cameras();
    device.close();

    return 0;
}

运行结果如下:

 

;