目前找到了几个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深度图与摄像头RGB的标定与配准 作者:aipiano
搭配外置RGB相机的Kinect一代的标定+配准过程,计算了深度坐标系和彩色坐标系的变换矩阵,看起来很有可操作性。 -
kinect 2.0 SDK学习笔记(二)--坐标映射 和 kinect 2.0 SDK学习笔记(四)--深度图与彩色图对齐 作者:jiaojialulu
前者解释了如何通过windows SDK将获得到的原始数据进行坐标空间的映射,还有一个使用MapDepthFrameToColorSpace
函数的例子(注意是Depth To Color,上边的是Color To Depth);后者讲了不用SDK函数的话怎么做配准,包括标定、转换公式的计算和代码实现,和本列表的第一个例子是一种类型。 -
Kinect数据提取与坐标变换 作者:熊白白
基于Kinect一代,综合Developer Toolkit中C#范例 Color Basics,Depth Basics,Coordinate Mapping Basics 部分,配准使用了SDK函数MapColorFrameToDepthFrame
和MapDepthFrameToColorFrame
。 -
Kinect For Windows V2开发日志七:照片合成与背景消除 作者:Beginner_YH
里面有部分程序是深度图和彩色图的像素对齐配准,也是使用了SDK的MapColorFrameToDepthSpace
函数。 -
Kinect学习笔记六CoordinateMapping上 作者:云中留梦
博客的六七八都是关于映射的,虽然博主貌似更关注骨骼信息和它们的映射,而且是C#的,但是有一些各种空间的和相互映射的介绍。 -
kinect深度图和彩图对准的源代码, 附上数据一组 作者:qinhl060343 秦洪亮
还没看懂。。。另外存数据的网盘已经崩了。。。
其他:
- 图像配准简介 各种类型的配准方法介绍
- 深度图像与彩色图像的配准方法研究 2012年华南理工大学的硕士论文,使用Harris-Laplace角点检测算法和SIFT特征点检测进行特征匹配
- 深度图像配准(Registration)原理