博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
KinectFusion: Real-time 3D Reconstruction and Interaction Using a Moving Depth Camera
阅读量:4042 次
发布时间:2019-05-24

本文共 9437 字,大约阅读时间需要 31 分钟。

先说下他的工作原理

把kinect每帧能得到一张深度图, 然后这张深度图呢一个像素对应camera view 中的一个点,如果知道camera的变换矩阵, 这些点就可以变成全局坐标. 我们把kinnect照出的点想成一个手电筒打出一堆点, 以第一帧的手电筒的位置和方向做为世界坐标系, 然后处理第二帧时, 得到一堆点,这堆点是在第二个手电筒下的坐标值, 这时我们把第二个手电筒放在第一个手电筒时, 这些点是没有对齐的, 这时我们用ICP, 让这些点对齐,对齐的同时, 第二个手电筒也动, 则当收敛时,第二个手电筒的位置和方向就确认了。这样后面每一帧手电筒的位置都可以通过前一帧来得到。

注意 ICP不是找最近, 而是找前后两帧Image view的对应点, 然后转到世界坐标系中来算camera pose.

然后就是voxel里面tsdf值的问题, 体素是事先建好的, 从第一帧开始,对于每个体素来说,先算体素中心点(注意中心点是世界坐标)到手电筒(camera)距离d1,然后该中心点通过手电筒的变换矩阵的逆转到camera view中, 然后用perspective project投影,将坐标标准化, 最后用这个标准化坐标去索引第一帧的深度图(注意不是整数的坐标会插值得到深度), 得到该中心点的深度, 也就是手电筒到中心点的方向上, 手电筒到surface的距离d2,这样d1-d2就是surface到该体素中心的距离, 后面也照这个方向去算, 只不过算出来的值会与以前的值进行权值平均, 权值怎么算, 怎么平均请看paper.

最后就是通过raycast从体素中提取点的问题了,对于最新一帧手电筒得到深度图, 每个像素,执行[u,0]和[u,1]的反投影,得到对应在camera view的坐标, 结合camera的变换矩阵, 得到对应的世界坐标的值, 然后得到对应的体素, 用他们的体素坐标来算,然后沿着射线的方向,来依次搜索tsdf为0的点,如果搜索到提取出其世界坐标, 具体怎么提取看paper, 然后算该点(网格坐标下)的tsdf的梯度, 作为该点的法向量。 注意论文中global mesh vertex是其他已知的mesh, 先按照camera的方向渲染出一个mesh map, 如果没有找到0截面就用mesh map对应的值, 或者secondary ray.

1) Depth map conversion

vi(u)=Di(u)K1[u,1] v i ( u ) = D i ( u ) K − 1 [ u , 1 ] 可以找出深度图中每 个像素对应的camera view中的坐标

2) Camera tracking

这里论文中的算法写得不太清楚
直接对照着源码来看吧
pcl/gpu/kinfu/src/cuda/estimate_combined.cu

__device__ __forceinline__ bool      search (int x, int y, float3& n, float3& d, float3& s) const      {        float3 ncurr;        ncurr.x = nmap_curr.ptr (y)[x];        if (isnan (ncurr.x))          return (false);        float3 vcurr;//当前camera view中的坐标        vcurr.x = vmap_curr.ptr (y       )[x];        vcurr.y = vmap_curr.ptr (y + rows)[x];        vcurr.z = vmap_curr.ptr (y + 2 * rows)[x];        float3 vcurr_g = Rcurr * vcurr + tcurr;//用上一步的R和t作为这帧的R,t的估计,然后得到这帧中点的全局坐标        float3 vcurr_cp = Rprev_inv * (vcurr_g - tprev);         // prev camera coo space 通过这个公式将该点称回到上一帧的camera view的坐标系中        int2 ukr;         //projection        ukr.x = __float2int_rn (vcurr_cp.x * intr.fx / vcurr_cp.z + intr.cx);      //4        ukr.y = __float2int_rn (vcurr_cp.y * intr.fy / vcurr_cp.z + intr.cy);                      //4        //获取该点在上一帧中image space的坐标        if (ukr.x < 0 || ukr.y < 0 || ukr.x >= cols || ukr.y >= rows || vcurr_cp.z < 0)//如果在上一帧中的视图中,继续;否则结束           return (false);        float3 nprev_g;//全局法向量通过前一帧的全局nmap来找的        nprev_g.x = nmap_g_prev.ptr (ukr.y)[ukr.x];        if (isnan (nprev_g.x))          return (false);        float3 vprev_g;//全局点坐标通过前一帧的全局vmap来找的        vprev_g.x = vmap_g_prev.ptr (ukr.y       )[ukr.x];        vprev_g.y = vmap_g_prev.ptr (ukr.y + rows)[ukr.x];        vprev_g.z = vmap_g_prev.ptr (ukr.y + 2 * rows)[ukr.x];        float dist = norm (vprev_g - vcurr_g); //前一帧点的全局坐标和当前帧点的全局坐标差        if (dist > distThres)          return (false);        ncurr.y = nmap_curr.ptr (y + rows)[x];        ncurr.z = nmap_curr.ptr (y + 2 * rows)[x];        float3 ncurr_g = Rcurr * ncurr;//当前帧的全局法向量        nprev_g.y = nmap_g_prev.ptr (ukr.y + rows)[ukr.x];        nprev_g.z = nmap_g_prev.ptr (ukr.y + 2 * rows)[ukr.x];        float sine = norm (cross (ncurr_g, nprev_g));        if (sine >= angleThres)//如果当前帧的全局法向量和前一帧的全局法向量超过一定anlge则是不匹配          return (false);        n = nprev_g;        d = vprev_g;        s = vcurr_g;        return (true);      }

注意这里要先找correspondence, 再求Optimal transforamtion

论文的算法太confusing了, 直接跳过

3) volumetric representation

看下listing 2的算法

1 对于每个体素点(方块中间那个点)

2 对每层的体素
3 先转成世界坐标
4 转成camera view坐标
5 转成Image space 坐标
6 如果在image space中
7 tivg ‖ t i − v g ‖ 是表示体素的global position 到摄像机的距离, Di(p) D i ( p ) 表示摄像机到surface的距离, |tivgDi(p) | t i − v g ‖ − D i ( p ) 表示体素到surface的距离
后面就是将体素到surface的距离截取到[-1,1]范围内,然后一直更新前后所算得的距离

4) raycasting for rendering and tracking 看算法

__device__ __forceinline__ void      operator () () const      {        int x = threadIdx.x + blockIdx.x * CTA_SIZE_X;        int y = threadIdx.y + blockIdx.y * CTA_SIZE_Y;        if (x >= cols || y >= rows)          return;        vmap.ptr (y)[x] = numeric_limits
::quiet_NaN (); nmap.ptr (y)[x] = numeric_limits
::quiet_NaN (); float3 ray_start = tcurr;//摄像机的全局位置 float3 ray_next = Rcurr * get_ray_next (x, y) + tcurr;//摄像机远平面对应像素x,y的点的全局坐标 float3 ray_dir = normalized (ray_next - ray_start); //ensure that it isn't a degenerate case ray_dir.x = (ray_dir.x == 0.f) ? 1e-15 : ray_dir.x; ray_dir.y = (ray_dir.y == 0.f) ? 1e-15 : ray_dir.y; ray_dir.z = (ray_dir.z == 0.f) ? 1e-15 : ray_dir.z; // computer time when entry and exit volume float time_start_volume = getMinTime (volume_size, ray_start, ray_dir); float time_exit_volume = getMaxTime (volume_size, ray_start, ray_dir); const float min_dist = 0.f; //in meters time_start_volume = fmax (time_start_volume, min_dist); if (time_start_volume >= time_exit_volume) return; float time_curr = time_start_volume; int3 g = getVoxel (ray_start + ray_dir * time_curr); g.x = max (0, min (g.x, VOLUME_X - 1)); g.y = max (0, min (g.y, VOLUME_Y - 1)); g.z = max (0, min (g.z, VOLUME_Z - 1)); float tsdf = readTsdf (g.x, g.y, g.z); //infinite loop guard const float max_time = 3 * (volume_size.x + volume_size.y + volume_size.z); for (; time_curr < max_time; time_curr += time_step) { float tsdf_prev = tsdf; int3 g = getVoxel ( ray_start + ray_dir * (time_curr + time_step) ); if (!checkInds (g)) break; tsdf = readTsdf (g.x, g.y, g.z); if (tsdf_prev < 0.f && tsdf > 0.f) break; if (tsdf_prev > 0.f && tsdf < 0.f) //zero crossing { float Ftdt = interpolateTrilineary (ray_start, ray_dir, time_curr + time_step); if (isnan (Ftdt)) break; float Ft = interpolateTrilineary (ray_start, ray_dir, time_curr);//current time时该 if (isnan (Ft)) break; //float Ts = time_curr - time_step * Ft/(Ftdt - Ft); float Ts = time_curr - time_step * Ft / (Ftdt - Ft);//减少时间回去到tsdf为0的点 float3 vetex_found = ray_start + ray_dir * Ts;//tsdf为0的点 vmap.ptr (y )[x] = vetex_found.x; vmap.ptr (y + rows)[x] = vetex_found.y; vmap.ptr (y + 2 * rows)[x] = vetex_found.z; int3 g = getVoxel ( ray_start + ray_dir * time_curr ); if (g.x > 1 && g.y > 1 && g.z > 1 && g.x < VOLUME_X - 2 && g.y < VOLUME_Y - 2 && g.z < VOLUME_Z - 2) { float3 t; float3 n; //开始求法向量 t = vetex_found; t.x += cell_size.x; float Fx1 = interpolateTrilineary (t); t = vetex_found; t.x -= cell_size.x; float Fx2 = interpolateTrilineary (t); n.x = (Fx1 - Fx2); t = vetex_found; t.y += cell_size.y; float Fy1 = interpolateTrilineary (t); t = vetex_found; t.y -= cell_size.y; float Fy2 = interpolateTrilineary (t); n.y = (Fy1 - Fy2); t = vetex_found; t.z += cell_size.z; float Fz1 = interpolateTrilineary (t); t = vetex_found; t.z -= cell_size.z; float Fz2 = interpolateTrilineary (t); n.z = (Fz1 - Fz2); n = normalized (n); nmap.ptr (y )[x] = n.x; nmap.ptr (y + rows)[x] = n.y; nmap.ptr (y + 2 * rows)[x] = n.z; } break; } } /* for(;;) */ } }; __global__ void rayCastKernel (const RayCaster rc) { rc (); } }}
__device__ __forceinline__ float      interpolateTrilineary (const float3& point) const      {        int3 g = getVoxel (point);        if (g.x <= 0 || g.x >= VOLUME_X - 1)//为了保证顺利进行插值, 把边界的体素去掉,           return numeric_limits
::quiet_NaN (); if (g.y <= 0 || g.y >= VOLUME_Y - 1) return numeric_limits
::quiet_NaN (); if (g.z <= 0 || g.z >= VOLUME_Z - 1) return numeric_limits
::quiet_NaN (); float vx = (g.x + 0.5f) * cell_size.x; float vy = (g.y + 0.5f) * cell_size.y; float vz = (g.z + 0.5f) * cell_size.z; //算落在体素的哪个卦限, 根据卦限算出由voxel center组成的cube的最左下角的voxel center所在的voxel的索引 g.x = (point.x < vx) ? (g.x - 1) : g.x; g.y = (point.y < vy) ? (g.y - 1) : g.y; g.z = (point.z < vz) ? (g.z - 1) : g.z; //在voxel center组成的cube中, 由当前位置减去该cube的左下角的点(体素center点)求出每维上的权值 float a = (point.x - (g.x + 0.5f) * cell_size.x) / cell_size.x; float b = (point.y - (g.y + 0.5f) * cell_size.y) / cell_size.y; float c = (point.z - (g.z + 0.5f) * cell_size.z) / cell_size.z; 这里是算该点到由八个voxel中心点围成的cube在每个维度上的权值 float res = readTsdf (g.x + 0, g.y + 0, g.z + 0) * (1 - a) * (1 - b) * (1 - c) + readTsdf (g.x + 0, g.y + 0, g.z + 1) * (1 - a) * (1 - b) * c + readTsdf (g.x + 0, g.y + 1, g.z + 0) * (1 - a) * b * (1 - c) + readTsdf (g.x + 0, g.y + 1, g.z + 1) * (1 - a) * b * c + readTsdf (g.x + 1, g.y + 0, g.z + 0) * a * (1 - b) * (1 - c) + readTsdf (g.x + 1, g.y + 0, g.z + 1) * a * (1 - b) * c + readTsdf (g.x + 1, g.y + 1, g.z + 0) * a * b * (1 - c) + readTsdf (g.x + 1, g.y + 1, g.z + 1) * a * b * c; return res; }

转载地址:http://itxdi.baihongyu.com/

你可能感兴趣的文章
flex中设置Label标签文字的自动换行
查看>>
Flex 中的元数据标签
查看>>
flex4 中创建自定义弹出窗口
查看>>
01Java基础语法-13. if分支语句的灵活使用
查看>>
01Java基础语法-15.for循环结构
查看>>
01Java基础语法-16. while循环结构
查看>>
01Java基础语法-17. do..while循环结构
查看>>
01Java基础语法-18. 各种循环语句的区别和应用场景
查看>>
01Java基础语法-19. 循环跳转控制语句
查看>>
Django框架全面讲解 -- Form
查看>>
socket,accept函数解析
查看>>
今日互联网关注(写在清明节后):每天都有值得关注的大变化
查看>>
”舍得“大法:把自己的优点当缺点倒出去
查看>>
[今日关注]鼓吹“互联网泡沫,到底为了什么”
查看>>
[互联网学习]如何提高网站的GooglePR值
查看>>
[关注大学生]求职不可不知——怎样的大学生不受欢迎
查看>>
[关注大学生]读“贫困大学生的自白”
查看>>
[互联网关注]李开复教大学生回答如何学好编程
查看>>
[关注大学生]李开复给中国计算机系大学生的7点建议
查看>>
[关注大学生]大学毕业生择业:是当"鸡头"还是"凤尾"?
查看>>