相关资料

kinect相机官方文档:https://learn.microsoft.com/en-us/previous-versions/azure/kin...
sdk文档:https://microsoft.github.io/Azure-Kinect-Sensor-SDK/master/in...

硬件属性

组成

相机拥有depth camera、color camera(RGB camera)、IR emitters、gyroscope、accelerometer和microphone多个传感器。本次学习只使用了其中的深度图相机和颜色相机。
image.png

深度相机和颜色相机的视场

深度相机视场有两个模式, 分别是norrow field of view和wide field of view,如下图所示。
image.png
下图是2000mm处拍摄的不同模式的图像范围的对比,所以如果进行depth image和color image的坐标转换可能有些坐标无法转换。
image.png

多相机同步

相机通过耳机线同步数据,其有两个3.5mm耳机孔:sync in和sync out

kinect有两种同步相机的拓扑结构:

  • Daisy-chain configuration
    相机连成链状,最多支持8台子设备。
    image.png
  • Star configuration
    主相机输出线分成两股,也只最多支持2台子设备。
    image.png

相机坐标系

2D坐标系

kinect输出的2d图像x方向构成了图像的width,y方向构成了图像的height,像素单元在内存中按先x后y的顺序存放,不同图像的像素构成不同。
image.png

3D坐标系

image.png

SDK简介

简单介绍下常用的方法。

static

// 获取当前设备个数
static uint32_t k4a::device::get_installed_count()

class device

// 根据特定配置打开相机,打开后相机会开始拍照,通过stop_cameras关闭
void start_cameras(const k4a_device_configuration_t *configuration)
// 获取当前设备序列号
std::string get_serialnum () const
// 捕获图像,其中cap为输出参数,可通过cap获取捕获到的图像
bool k4a::device::get_capture(capture *cap, std::chrono::milliseconds timeout)    
// 获取相机标定参数
k4a::calibration k4a::device::get_calibration(k4a_depth_mode_t depth_mode,
                                              k4a_color_resolution_t color_resolution) const

class capture

// 获取捕获到的各种图像
k4a::image k4a::capture::get_color_image ()    const
k4a::image k4a::capture::get_depth_image ()    const
k4a::image k4a::capture::get_ir_image () const

class image

// 获取图像宽/高
int k4a::image::get_width_pixels () const
int k4a::image::get_height_pixels () const
// 获取步长字节,这里的步长指的是在x方向像素的字节长度
int k4a::image::get_stride_bytes ()    const
// 获取图像流/长度
const uint8_t* k4a::image::get_buffer () const
size_t k4a::image::get_size () const

不同图像字节流像素结构

!注意3d和2d的像素按线性顺序对应
图像类型像素大小像素组成
RGBA图像4*1字节,类型为uint8依次为Blue, Green, Red, Alpha
点云图像3*2字节,类型为int16依次为x坐标,y坐标,z坐标

class calibration

calbration类提供了一些方法进行depth camera和rgb camera之间的坐标系转换

// 将源相机2d坐标系的坐标转化为目标相机2d坐标系坐标, target_point2d为输出参数
bool k4a::calibration::convert_2d_to_2d(const k4a_float2_t &source_point2d,
                                        float source_depth,
                                        k4a_calibration_type_t source_camera,
                                        k4a_calibration_type_t target_camera,
                                        k4a_float2_t *target_point2d) const
// ..其它方法类似

class transformation

该类提供了将不同坐标系下的图像进行转化的方法,输入输出都是图像,默认通过GPU加速

// 将color image的像素点通过坐标转换到depth camera坐标系上
k4a::image k4a::transformation::color_image_to_depth_camera(const image &depth_image, const image &color_image)  const
// 将depth image的像素点通过坐标转换到color camera坐标系上
k4a::image k4a::transformation::depth_image_to_color_camera(const image &depth_image) const

// 将深度图像转化为点云图像,其中camera参数要指明当前深度图所在的坐标系是color坐标系还是depth坐标系
k4a::image k4a::transformation::depth_image_to_point_cloud(const image &depth_image, k4a_calibration_type_t camera) const

下图展示了depth camera和rgb camera在2d和3d坐标系下的转换方法
image.png

编写代码,生成点云图

struct Image {
    k4a::image color;
    k4a::image depth;
    k4a::image depth_in_color;
};

struct Voxel {
    int16_t x, y, z;
    uint8_t r, g, b, a;
};

class Camera {
public:
    Camera();
    ~Camera();

    Image get_image();
    std::vector<Voxel> generate_point_cloud(Image &image) const;
    static void save_ply_file(const std::string& filename, const std::vector<Voxel>& points);

private:
    k4a::device device_;
    k4a::calibration calibration_{};
    k4a::transformation transformation_;
};
#include <fstream>
#include "Camera.h"

Camera::Camera() {
    uint32_t num = k4a::device::get_installed_count();
    if (num <= 0) {
        std::cout << "no device" << std::endl;
        return;
    }
    device_ = k4a::device::open(0);

    k4a_device_configuration_t config = K4A_DEVICE_CONFIG_INIT_DISABLE_ALL;
    config.camera_fps = K4A_FRAMES_PER_SECOND_15;
    config.depth_mode = K4A_DEPTH_MODE_WFOV_UNBINNED;
    config.color_format = K4A_IMAGE_FORMAT_COLOR_BGRA32;
    config.color_resolution = K4A_COLOR_RESOLUTION_3072P;
    config.synchronized_images_only = true;

    device_.start_cameras(&config);
    calibration_ = device_.get_calibration(config.depth_mode, config.color_resolution);
    transformation_ = k4a::transformation(calibration_);
}

Camera::~Camera() {
    device_.stop_cameras();
    device_.close();
}

Image Camera::get_image() {
    k4a::capture cap;
    bool result = device_.get_capture(&cap);
    if (!result) {
        throw std::runtime_error("get capture error");
    }

    Image image;
    image.color = cap.get_color_image();
    image.depth = cap.get_depth_image();
    image.depth_in_color = transformation_.depth_image_to_color_camera(image.depth);
    return image;
}

std::vector<Voxel> Camera::generate_point_cloud(Image &image) const {
    k4a::image point_cloud = transformation_.depth_image_to_point_cloud(image.depth_in_color,
                                                                        K4A_CALIBRATION_TYPE_COLOR);
    int width = point_cloud.get_width_pixels();
    int height = point_cloud.get_height_pixels();
    long long num = width * height;

    auto buff = (int16_t *) point_cloud.get_buffer();
    auto color_points = (uint8_t *) image.color.get_buffer();
    std::vector<Voxel> points(num);
    for (int i = 0; i < num; i++) {
        points[i].x = buff[i * 3];
        points[i].y = buff[i * 3 + 1];
        points[i].z = buff[i * 3 + 2];
        points[i].b = color_points[i * 4];
        points[i].g = color_points[i * 4 + 1];
        points[i].r = color_points[i * 4 + 2];
    }
    return points;
}

void Camera::save_ply_file(const std::string& filename, const std::vector<Voxel>& points) {
    // 打开文件
    std::ofstream plyFile(filename);
    if (!plyFile.is_open()) {
        std::cerr << "无法打开文件 " << filename << std::endl;
        return;
    }

    // 写入 PLY 文件头
    plyFile << "ply\n";
    plyFile << "format ascii 1.0\n";
    plyFile << "element vertex " << points.size() << "\n";
    plyFile << "property float x\n";
    plyFile << "property float y\n";
    plyFile << "property float z\n";
    plyFile << "property uchar red\n";
    plyFile << "property uchar green\n";
    plyFile << "property uchar blue\n";
    plyFile << "end_header\n";

    // 写入点数据
    for (const auto& point : points) {
        plyFile << (float)point.x << " " << (float)point.y << " " << (float)point.z << " "
                << static_cast<int>(point.r) << " "
                << static_cast<int>(point.g) << " "
                << static_cast<int>(point.b) << "\n";
    }

    // 关闭文件
    plyFile.close();
    std::cout << "PLY file have saved in: " << filename << std::endl;
}

int main() {
    Camera camera;
    Image image = camera.get_image();
    vector<Voxel> point_cloud = camera.generate_point_cloud(image);
    Camera::save_ply_file("result.ply", point_cloud);
    return 0;
}

berrydreams
1 声望1 粉丝

Talk is cheap


引用和评论

0 条评论