Kinect实现简单的三维重建

Kinect想必大家已经很熟悉了,最近基于Kinect的创意应用更是呈井喷状态啊!看到很多国外大牛用Kinect做三维重建,其中最著名的要数来自微软研究院的Kinect Fusion了,可以看看下面这个视频,或者。

可惜Kinect Fusion是不开源的,不过PCL实现了一个差不多的开源版本,。有兴趣同时电脑配置高的朋友可以研究一下。

最近比较闲,有一点手痒,想自己做一个三维重建,不过肯定不会像Kinect Fusion那么强大,只是自己练练手、玩玩而已。代码在最后有下载。

1. 获取Kinect深度图:

首先我使用微软官方的Kinect SDK来控制Kinect,三维绘图我选用了OpenFrameworks。OpenFrameworks(以后简称OF)是一个开源的公共基础库,将很多常用的库统一到了一起,比如OpenGL,OpenCV,Boost等等,而且有大量的第三方扩展库,使用非常方便。具体可见。

在一切开始之前,我们需要对OpenGL和三维场景做一些设置:

void testApp::setup(){//Do some environment settings.ofSetVerticalSync(true);ofSetWindowShape(640,480);ofBackground(0,0,0);//Turn on depth test for OpenGL.glEnable(GL_DEPTH_TEST);glDepthFunc(GL_LEQUAL);glShadeModel(GL_SMOOTH);//Put a camera in the scene.m_camera.setDistance(3);m_camera.setNearClip(0.1f);//Turn on the light.m_light.enable();//Allocate memory to store point cloud and normals.m_cloud_map.Resize(DEPTH_IMAGE_WIDTH,DEPTH_IMAGE_HEIGHT);m_normal_map.Resize(DEPTH_IMAGE_WIDTH,DEPTH_IMAGE_HEIGHT);//Initialize Kinect.InitNui();}OF是使用OpenGL进行绘图的,所以可以直接使用OpenGL中的函数(以gl开头),为了方便,OF还自己封装了一些常用函数(以of开头)。在上面代码的最后有一个InitNui()函数,在那里面我们会对Kinect进行初始化:void testApp::InitNui(){m_init_succeeded = false;m_nui = NULL;int count = 0;HRESULT hr;hr = NuiGetSensorCount(&count);if (count <= 0){cout<<"No kinect sensor was found!!"<<endl;goto Final;}hr = NuiCreateSensorByIndex(0,&m_nui);if (FAILED(hr)){cout<<"Create Kinect Device Failed!!"<<endl;goto Final;}//We only just need depth data.hr = m_nui->NuiInitialize(NUI_INITIALIZE_FLAG_USES_DEPTH);if (FAILED(hr)){cout<<"Initialize Kinect Failed!!"<<endl;goto Final;}//Resolution of 320×240 is good enough to reconstruct a 3D model.hr = m_nui->NuiImageStreamOpen(NUI_IMAGE_TYPE_DEPTH,NUI_IMAGE_RESOLUTION_320x240,0,2,NULL,&m_depth_stream);if (FAILED(hr)){cout<<"Open Streams Failed!!"<<endl;goto Final;}m_init_succeeded = true;Final:if (FAILED(hr)){if (m_nui != NULL){m_nui->NuiShutdown();m_nui->Release();m_nui = NULL;}}}接下来我们需要将每一帧的深度信息保存到我们自己的buffer中,专门写一个函数来做这件事情:bool testApp::UpdateDepthFrame(){if (!m_init_succeeded)return false;HRESULT hr;NUI_IMAGE_FRAME image_frame = {0};NUI_LOCKED_RECT locked_rect = {0};hr = m_nui->NuiImageStreamGetNextFrame(m_depth_stream,0,&image_frame);//If there's no new frame, we will return immediately.if (SUCCEEDED(hr)){hr = image_frame.pFrameTexture->LockRect(0,&locked_rect,NULL,0);if (SUCCEEDED(hr)){//Copy depth data to our own buffer.memcpy(m_depth_buffer,locked_rect.pBits,locked_rect.size);image_frame.pFrameTexture->UnlockRect(0);}//Release frame.m_nui->NuiImageStreamReleaseFrame(m_depth_stream,&image_frame);}if (SUCCEEDED(hr))return true;return false;}通过上面几步,我们已经可以拿到一幅深度图了。在OF中,每一帧更新时,update()函数都会被调用,我们可以把所有需要适时更新的代码都写在里面:void testApp::update(){//Get a new depth frame from Kinect.m_new_depth = UpdateDepthFrame();if (m_new_depth){Mat depth_frame = Mat(DEPTH_IMAGE_HEIGHT,DEPTH_IMAGE_WIDTH,CV_16UC1,m_depth_buffer); <span style="white-space:pre"></span>imshow("Depth Frame", depth_frame); }}现在编译并运行程序,我们可以看到通过OpenCV画出来的深度图:

但你会发现,这样的深度图具有很多小孔和噪点,边缘也不平滑。因此要对图像进行滤波,为了使边缘不被模糊掉,这里最好使用中值滤波。修改一下上面的update()函数,使用5×5的窗口进行中值滤波:

void testApp::update(){//Get a new depth frame from Kinect.m_new_depth = UpdateDepthFrame();if (m_new_depth){Mat smoothed_depth = Mat(DEPTH_IMAGE_HEIGHT,DEPTH_IMAGE_WIDTH,CV_16UC1,m_depth_buffer);medianBlur(smoothed_depth,smoothed_depth,5);imshow("Depth Frame", smoothed_depth);}}再次运行程序,得到的深度图就变成下面这样了,感觉好了很多!!

2. 通过深度图得到点云:

为了得到点云,我专门写了一个类来完成这一操作。这个类不仅会根据深度图计算点云,还会将得到的点云以矩阵的形式存放起来,矩阵中每一个元素代表一个点,同时对应深度图中具有相同行列坐标的像素。而计算点云的方法,Kinect SDK自身有提供,即NuiTransformDepthImageToSkeleton()函数,具体用法可看官方文档。下面是这个类中生成点云的代码:

void PointCloudMap::Create(Mat& depth_image,USHORT max_depth,float scale){USHORT* depth_line = (USHORT*)depth_image.data;UINT stride = depth_image.step1();//m_points is the place where we store the whole point cloud.ofVec3f* points_line = m_points;Vector4 vec;for (DWORD y = 0; y < m_height; y++){for (DWORD x = 0; x < m_width; x++){ofVec3f point(0);USHORT real_depth = (depth_line[x] >> 3);if (real_depth >= 800 && real_depth < max_depth){//For each pixel in the depth image, we calculate its space coordinates.vec = NuiTransformDepthImageToSkeleton(x,y,depth_line[x]);//Save the point with a scale.point.x = vec.x*scale;point.y = vec.y*scale;point.z = -vec.z*scale;}points_line[x] = point;}depth_line += stride;points_line += m_width;}}拿到点云后,我们可以考虑对点云进行三角化了。一提到三角化,很多人脑海中的第一印象是复杂、计算量大等等,我个人也是这样。但是,Kinect返回的点云是结构化的,并不是无序点云,也就是说每一个点在空间中与其他点的相互关系我们是知道的,因此可以用一些简单的方法来实现三角化,虽然这样的三角化结果不是最优的,但是简单快速,60fps毫无压力。首先,我们的点云是存放在一个矩阵中的,而且矩阵的大小与深度图完全一样(行x列),因此我们将点云视为一幅图,每一个像素存放的是点的空间坐标。我们可以像遍历一般图像的像素一样遍历点云图,从而得到空间中某一点的所有相邻点。然后,我们使用OpenGL的连线功能,每画一个点就与它之前的两个点连成一个三角面。如下图,点旁边的序号是画点的顺序:

也许叔本华是对的,人与人的距离太远会寂寞到寒冷,

Kinect实现简单的三维重建

相关文章:

你感兴趣的文章:

标签云: