×

Kinect学习(5)——深度图与彩色图配准(学习版1)

96
暗夜望月
2017.03.06 20:50* 字数 1485

目前找到了几个Kinect配准程序,都跑通了,先学习一下他们的想法,代码都添加了注释。
我的环境:Win 10 + Kinect 2.0+VS 2013 + OpenCV 2.4.13

1、Kinect深度图与彩色图的坐标校准

作者:Michael__Shi
作者环境:Windows 8.1 + Kinect 2.0 + VS 2012 + OpenCV2.4.8

先用规则的长方体来测一下在深度图和彩色图中的同一边长的比例,为了测量更加准确,就把深度图同一时间的深度图和彩色图给截图下来,然后通过matlab来实现坐标显示,来得到同一位置长方体的同一边长,通过求几组数据的平均得到深度图和彩色图的比例,这样,在使用OpenCV的resize函数便将深度图和彩色图的比例调整为1:1了。最后通过将同一点在深度图和彩色图中的坐标关系来将两种图的坐标关系对应好,这样便实现了坐标的对应,通过opencv的ROI来将两幅图重合的部分裁剪下来,并且在同一幅画面中显示。

核心代码:
1)深度图处理

    // 之前已经把深度数据转换为8位RGB像素,下面把深度像素转存到8位4通道的Mat内  
    Mat DepthImage(nHeight, nWidth, CV_8UC4, m_pDepthRGBX);  
    Mat show = DepthImage.clone();  

    // 用resize函数把宽、高分辨率都扩大到原来的1.437倍,存储到showImageDepth内  
    resize(DepthImage, show, Size(cDepthWidth*1.437 , cDepthHeight*1.437));  
    showImageDepth = show.clone();  

    // 显示的仍然是原始分辨率           
    imshow("DepthImage", show);

2)彩色图处理和配准

    // 之前已经获取了彩色图的8位RGB像素,下面把彩色图像素转存到8位4通道Mat内
    Mat ColorImage(nHeight, nWidth, CV_8UC4, pBuffer);

    // 用resize函数把宽、高分辨率都缩小为原来的1/2 
    Mat showImage = ColorImage.clone();
    resize(ColorImage, showImage, Size(nWidth / 2, nHeight / 2));

    // 矩形Rect,函数原型是 Rect_(_Tp _x, _Tp _y, _Tp _width, _Tp _height); 
    // 前两个参数表示矩形左上角的坐标,后两个参数表示矩形的宽和高 
    Rect rect(145, 0, 702, 538);

    // 偏移量为(33,-145),即彩色(i,j)对应深度(i+33,j-145)
    // opencv坐标对应关系是(x,y)对应(列号col,行号row)
    // 对应坐标的深度与彩色像素按照 0.6:0.4 比例相加混合到新的Mat showImage内
    int x = 33, y = -145;
    for (int i = 0; i <540; i++)
        for (int j = 145; j < 960 - 114; j++)
            showImage.at<Vec4b>(i, j) = showImageDepth.at<Vec4b>(i + x, j + y)*0.6 + showImage.at<Vec4b>(i, j)*0.4;

    // 用矩形指定showImage的感兴趣区域(不感兴趣区域应该是配准效果不好的区域)
    Mat image_roi = showImage(rect);
    // 显示感兴趣区域,也是配准后的结果 
    imshow("Image_Roi", image_roi);

总结起来就是,先测量了深度图和彩色图上同一边长的比例,测量结果是1.437x2=2.874;然后在Opencv里按这个比例把两幅图调整成同样大小,深度图尺寸放大1.437倍,彩色图尺寸缩小一半;最后考虑到边缘的配准效果不好,因此只保留和显示中间配准好的矩形区域。

这个方法相当于只做了尺寸变换,还真是简单粗暴啊。。。另外2.874的比例应该是图内物体长度的比例,而不是图片的比例吧

2、通过Kinect v2.0+ VS2012+openCV2.4.9将彩色帧映射到深度空间

作者:xx__hu
这段程序是基于MapColorFrameToDepthSpace实现配准的。

核心代码:

    //Color Frame 获取彩色图,为了避免尺寸太大显示不便,用resize把彩色图的宽、高都缩小为原来的一半,存在CV_8UC4的colorMat内
    ... ...
    resize(colorBufferMat, colorMat, Size(), 0.5, 0.5);

    //Depth Frame 获取彩色图,convertTo函数的作用是转换数据类型不同的Mat,这里的意义是把深度数据转化为像素值
    ... ...
    depthBufferMat.convertTo(depthMat, CV_8U, -255.0f / 8000.0f, 255.0f);

    // Mapping ( Depth to Color ) 配准
    if (SUCCEEDED(hResult))
    {
        // 深度空间坐标点集
        vector<DepthSpacePoint> depthSpacePoints(colorWidth *colorHeight);

        // 实际的配准语句,这里就用了MapColorFrameToDepthSpace函数,函数说明在下面
        hResult = pCoordinateMapper->MapColorFrameToDepthSpace(depthWidth * depthHeight,
            reinterpret_cast<UINT16*>(depthBufferMat.data), colorWidth *colorHeight,
                &depthSpacePoints[0]);

        if (SUCCEEDED(hResult))
        {
            CoordinateMapperMat = Scalar(0, 0, 0, 0); // 定义为Mat(colorHeight, colorWidth, CV_8UC4)
            // 遍历彩色图所有的点
            for (int y = 0; y < colorHeight; y++)
            {
                for (int x = 0; x < colorWidth; x++)
                {
                    // 按照彩色图像素坐标生成深度坐标点集的索引index,根据索引得到对应深度点
                    unsigned int index = y * colorWidth + x;
                    DepthSpacePoint p = depthSpacePoints[index];
                    // 如果对应深度点坐标没有超限,就把对应彩色点赋给新Mat CoordinateMapperMat(?)
                    if (p.X != -std::numeric_limits<float>::infinity() && p.Y != -std::numeric_limits<float>::infinity())
                    {
                        int depthX = static_cast<int>(p.X + 0.2f);
                        int depthY = static_cast<int>(p.Y + 0.2f);

                        if ((depthX >= 0) && (depthX < depthWidth) && (depthY >= 0) && (depthY < depthHeight))
                        {
                            CoordinateMapperMat.at<Vec4b>(y, x) = colorBufferMat.at<Vec4b>(y, x);                               
                        }
                    }
                }
            }
        }
    }
    imshow("depth", depthMat);
    imshow("color", colorMat);
    // CoordinateMapperMat就是要显示的配准结果
    imshow("CoordinateMapper", CoordinateMapperMat);
ICoordinateMapper::MapColorFrameToDepthSpace Method

根据官网上MapColorFrameToDepthSpace的函数说明:
Uses the depth frame data to map the entire frame from color space to depth space.
使用深度帧数据,从彩色空间向深度空间映射整个帧。

public:
HRESULT MapColorFrameToDepthSpace(
         UINT depthDataPointCount,
         const UINT16 *depthFrameData,
         UINT depthPointCount,
         DepthSpacePoint *depthSpacePoints
)
  • depthDataPointCount:深度数据点的数量,就是深度图的宽x高
  • depthFrameData:深度帧数据
  • depthPointCount:深度点的数量,实际上是指要匹配的深度点的数量,也就是彩色点的数量,彩色图的宽x高
  • depthSpacePoints:要存放深度空间点坐标的集合

函数运行后,depthSpacePoints内按彩色图像素的顺序存放对应的深度点坐标。之后按彩色图像素逐行扫描的顺序遍历坐标集合,如果配准正确(坐标有效),就把该点的彩色像素值赋给一个和彩色图同样大小类型的Mat,并显示出来。所以,所谓的配准结果其实是残缺的彩色图的样子,正常显示的地方表示配准成功,没有像素的地方(黑色)表示配准失败。

实际运行结果还可以,残缺区域主要来源于深度图的残缺。但帧率很慢,大约也就1fps左右吧。另外,我觉得应该按上一个例子那样把深度图和彩色图叠加起来显示,这样才能更直观的看出配准结果。

另外,还有一些Kinect配准的例子和教程,还没有实践和详细研究,记录如下:

其他:

Kinect—深度/彩色配准
Web note ad 1