基于Kinect Azure的多相机数据采集(一)
Kinect Azure相机是微软近几年推出的一款RGBD相机。相比于Kinect一代和二代,Kinect Azure相机采集的图像可达更高的分辨率,且在硬件方面设置了同步接口,更方便于多相机的同步采集。
具体的功能以及SDK所提供的函数接口可参考以下官方提供的文件:
https://docs.microsoft.com/zh-cn/azure/kinect-dk/
https://microsoft.github.io/Azure-Kinect-Sensor-SDK/master/
本系列主要是记录下近期用Kinect Azure做双相机数据采集的过程。以下所有全是个人理解,欢迎各位博主交流指正。
双相机采集主要包括相机间的同步、数据采集、点云配准以及点云融合四个方面。同时双相机采集也是多相机采集的基础,对多个相机两两完成上述双相机采集步骤,并将每个相机获取的数据整合到同一个相机的相机坐标系下即可完成多相机的数据采集。
一、相机采集
双相机数据采集的根本是单相机的采集,单相机采集必须包括以下步骤:
1、获取设备 k4a_device_t
2、打开设备 k4a_device_open
3、配置属性 k4a_device_configuration_t
4、启动相机 k4a_device_start_cameras
5、捕获数据 k4a_device_get_capture
如有需要,可以加上获取标定参数这一步,用k4a_device_get_calibration
函数获取,用于存放相机的参数,包括深度传感器和彩色传感器的内参,以及深度传感器到彩色传感器的外参。下面为单相机捕获数据的实现流程:
// Open Kinect device
k4a_device_t device = NULL;
if (K4A_FAILED(k4a_device_open(0, &device)))
{
printf("Failed to open k4a device!\n");
return 0;
}
// Configure of devices
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;
config.color_resolution = K4A_COLOR_RESOLUTION_1080P;
config.depth_mode = K4A_DEPTH_MODE_NFOV_UNBINNED;
config.synchronized_images_only = true;
// Start the camera
if (K4A_RESULT_SUCCEEDED != k4a_device_start_cameras(device, &config))
{
printf("Failed to start cameras!\n");
k4a_device_close(device);
return 1;
}
//get calibration
k4a_calibration_t sensor_calibration;
k4a_device_get_calibration(device, config.depth_mode, config.color_resolution, &sensor_calibration);
while (1)
{
k4a_capture_t sensor_capture;
k4a_device_get_capture(device, &sensor_capture, K4A_WAIT_INFINITE);
k4a_image_t rgbImage = k4a_capture_get_color_image(sensor_capture);
k4a_image_t depthImage = k4a_capture_get_depth_image(sensor_capture);
k4a_image_t irImage = k4a_capture_get_ir_image(sensor_capture);
k4a_capture_release(sensor_capture);
}
代码注释:
1、相机的synchronized_images_only
属性要设置成true,并且要把depthengine_2_0.dll
文件放到工程的运行目录下,以保证深度传感器和彩色传感器都能正常使用,否则只会有一个传感器能正常使用。
2、相机的depth_mode
属性为深度传感器的模式,主要用于深度图像的有四种,分别为NFOV、WFOV和2X2BINNED、UNBINNED的四种结合。NFOV为正六边形镜头,适用于窄视角;WFOV为圆形镜头,适用于宽视角。UNBINNED是将采集图像的每个像素作为一个用于分析的数据,2X2BINNED是将采集得到的图像每2x2的方格做平均后作为一个用于分析的数据,相当于2X2BINNED模式在x轴和y轴上对UNBINNED模式进行了下采样。
二、合成彩色点云数据
合成彩色点云数据步骤:
1、深度图像2D数据转为深度传感器下的3D点云数据
2、获得彩色传感器下的3D点云数据并投影到彩色图像
3、保存对应点的xyz坐标和rgb属性
4、遍历深度图像所有点,并排除数据异常点后得到的点集即为彩色点云数据
上图显示的是各传感器各数据之间的转换关系,通过深度和彩色传感器的内参以及两者之间的外参,可以确定深度图像点和彩色图像点的对应关系,从而将深度传感器下的点云与彩色图像点一一对应,形成具有位置信息和颜色属性的彩色点,去除坐标异常和数据异常点后,所有有效点组成的集合即为该相机获取的单帧彩色点云数据。
上文提到,Kinect Azure通过k4a_device_get_calibration
函数可以获取各传感器的内参以及它们之间的外参,并存放在k4a_calibration_t
数据类型中,方便使用者直接获取数据。在SDK提供的各函数接口中,会通过k4a_transformation_create
函数将k4a_calibration_t
数据类型转换成k4a_transformation_t
类型后进行使用。换句话说,直接使用SDK提供的函数时用k4a_transformation_t
类型,需要读取内外参具体数据时用k4a_calibration_t
类型。
那么具体到合成彩色点云数据也有两种实现方式,一种是调用函数接口去完成,另外一种是根据上图的坐标转换关系,获取每一个点对应的6个参数信息。根据需求选择合适的方法,目前我使用调用函数接口的方式,主要是为了先成功获取数据,之后为了采集的性能会改成另外一种方法,因为第二种方法可以使用CUDA并行化的实现对每个点的处理,处理速度会更快。这里先给出调用函数接口的代码及思路。
k4a_image_t transformation_color_image;
k4a_image_create(K4A_IMAGE_FORMAT_COLOR_BGRA32, depthimage_width, depthimage_height,depthimage_width * 4 * (int)sizeof(uint8_t), &transformation_color_image);
k4a_transformation_color_image_to_depth_camera(Transformation, depthImage, rgbImage, transformation_color_image);
k4a_image_t point_cloud;
k4a_image_create(K4A_IMAGE_FORMAT_CUSTOM, depthimage_width, depthimage_height, depthimage_width * 3 * (int)sizeof(uint16_t), &point_cloud);
k4a_transformation_depth_image_to_point_cloud(Transformation, depthImage, K4A_CALIBRATION_TYPE_DEPTH, point_cloud);
....
vector<color_point_t> points;
int width = k4a_image_get_width_pixels(point_cloud);
int height = k4a_image_get_height_pixels(point_cloud);
int16_t *point_cloud_image_data = (int16_t *)(void *)k4a_image_get_buffer(point_cloud);
uint8_t *color_image_data = k4a_image_get_buffer(color_image);
for (int i = 0; i < width * height; i++)
{
color_point_t point;
point.xyz[0] = point_cloud_image_data[3 * i + 0];
point.xyz[1] = point_cloud_image_data[3 * i + 1];
point.xyz[2] = point_cloud_image_data[3 * i + 2];
if (point.xyz[2] == 0 ||point.xyz[2]>maxValue*0.3)
continue;
point.bgr[0] = color_image_data[4 * i + 0];
point.bgr[1] = color_image_data[4 * i + 1];
point.bgr[2] = color_image_data[4 * i + 2];
uint8_t alpha = color_image_data[4 * i + 3];
if (point.bgr[0] == 0 && point.bgr[1] == 0 && point.bgr[2] == 0 && alpha == 0)
continue;
points.push_back(point);
}
上述代码主要分成三个部分:
1、生成与深度图对应的彩色图
2、生成与深度图对应的深度传感器下的点云数据
3、点云数据与彩色图的对应
通过k4a_transformation_color_image_to_depth_camera
函数可以获取到深度图像上每个像素对应的彩色值,并保存在同分辨率的transformation_color_image
图像中。通过k4a_transformation_depth_image_to_point_cloud
函数可以获取到深度图像上每个像素对应的点云坐标。最后遍历每个像素点,将对应的点云坐标和彩色数据写在构造的color_point_t
结构体中作为一个彩色点,排除数据异常点后,所有被写在vector向量中的点即为单相机获得的单帧点云数据。
三、双相机数据采集
双相机数据采集是单相机数据采集的延伸,也是多相机数据采集的基础。在进行多相机或者双相机的数据采集时,为了使得各相机所捕获的每一帧数据都是同一时刻的场景,因此后续需要对各个相机间进行同步操作(如何同步详见后续文章)。当有多个Kinect Azure设备同时连接时,需将它们分成master属性(主设备)和subordinate属性(从属设备)两种,其中主设备有且只有一个,剩下的全部为从属设备。在硬件上如何设定相机的主从属性可以参考微软官方提供的文档:
https://docs.microsoft.com/zh-cn/azure/kinect-dk/multi-camera-sync
双相机甚至多相机数据采集的思路和单相机数据采集的思路完全一致,在单相机数据采集的基础上循环处理每一个设备就可以完成多相机的数据采集。下面主要来说一下双(多)相机数据采集与单相机数据采集的不同。
1、在开启设备之前要先用k4a_device_get_installed_count
函数获取已连接的设备数量,为了后续操作的方便以及可以直观的检查是否所有设备都正常连接。
2、在开启设备时,一般将序号为0的设备对应到主设备,这样在开启从属设备时就可以按照序号直接进行for循环,举个例子:
for (int i = 1; i < device_count; i++)
{
k4a_device_t subordinate_device = NULL;
if (K4A_FAILED(k4a_device_open(i, &subordinate_device)))
{
printf("Failed to open subordinate device %d!\n", i);
return 0;
}
devices.push_back(subordinate_device);
}
上述代码中divice_count是返回的连接设备个数(至少为2),当把序号为0的设备对应到主设备时,所有的从属设备可以按序号递增的方式循环的开启。
3、在设置设备属性时需要增加设定一个主从属性:
config.wired_sync_mode = K4A_WIRED_SYNC_MODE_MASTER; //设置为主设备
config.wired_sync_mode = K4A_WIRED_SYNC_MODE_SUBORDINATE; //设置为从属设备
4、为了能够正确的同步各个设备,在用k4a_device_start_cameras
函数启动相机时,必须先启动所有的从属设备,最后在启动主设备。
根据上述方式可以成功的获取到各个设备下的彩色点云数据,为了在数据融合阶段只获得拍摄到的物体,需要在采集阶段加上背景剔除的过程。目前采用的背景剔除的思想比较简单,先获取到深度图像上深度的最大值,再排除z坐标大于阈值的所有点,阈值的选取和最大的深度值有关。
minMaxLoc(cv_depth_16U, NULL, &maxvalue, NULL, NULL);
可以通过minMaxLoc
函数可以获取深度图像的最大最小值以及位置。但在捕获过程中图像上可能会存在曝光点,影响深度图像最大值的正常获取,通过下面的方法进行改进。
uint16_t *begin = cv_depth_value.ptr<uint16_t>(0);
uint16_t *end = cv_depth_value.ptr<uint16_t>(depthimage_height - 1);
sort(begin, end);
double maxvalue = cv_depth_value.ptr<uint16_t>(depthimage_height - 1)[depthimage_width - 100];
该方法通过对深度图像值进行从小到大的排序,可以通过获取排序后某位置的深度值来获取正常范围内的深度最大值。
至此,有关数据采集的所有内容已经记录完成,接下来会记录如何进行设备同步以及如何获取各相机间的外参,以及最后的数据融合。由于也是刚接触这个设备,对其中的一些处理可能理解的有问题。最后再次欢迎各位博主交流指正。