我正在使用Google Tango平板电脑获取点云数据和RGB摄像头图像,想要创建房间的3D扫描。为此,我需要将2D图像像素映射到点云点上。我会用很多点云和相应的图像来完成这个过程。因此,我需要编写一个代码脚本,它有两个输入:1.点云和2.从相同方向拍摄的相同位置的图像,脚本应输出彩色点云。我该如何处理,哪些平台最简单易用?
我正在使用Google Tango平板电脑获取点云数据和RGB摄像头图像,想要创建房间的3D扫描。为此,我需要将2D图像像素映射到点云点上。我会用很多点云和相应的图像来完成这个过程。因此,我需要编写一个代码脚本,它有两个输入:1.点云和2.从相同方向拍摄的相同位置的图像,脚本应输出彩色点云。我该如何处理,哪些平台最简单易用?
这里是将一个三维点v
映射到相机图像的二维像素空间的数学公式(假设v
已经包含了外部相机位置和方向,详见下方注释*):
// Project to tangent space.
vec2 imageCoords = v.xy/v.z;
// Apply radial distortion.
float r2 = dot(imageCoords, imageCoords);
float r4 = r2*r2;
float r6 = r2*r4;
imageCoords *= 1.0 + k1*r2 + k2*r4 + k3*r6;
// Map to pixel space.
vec3 pixelCoords = cameraTransform*vec3(imageCoords, 1);
这里的cameraTransform
是一个3x3矩阵:
[ fx 0 cx ]
[ 0 fy cy ]
[ 0 0 1 ]
TangoCameraIntrinsics
中获取fx
、fy
、cx
、cy
、k1
、k2
和k3
,用于计算pixelCoords
。虽然pixelCoords
声明为vec3
,但实际上是二维齐次坐标系。第三个坐标始终为1,因此可以在实际应用中忽略它。cameraTransform
上(如任何自上而下与自下而上扫描行寻址)。TangoXYZij
提供的点已经包含深度相机外部转换。从技术上讲,由于当前的开发者平板电脑在深度和彩色图像采集之间共享相同的硬件,你将无法获取完全匹配的彩色图像,除非设备和场景都是静止的。幸运的是,在实际应用中,大多数应用程序可能可以假定相机姿态和场景变化不足以在一帧时间内显着影响颜色查找。这个回答并非原创,只是为Unity用户提供了一个便利,由@rhashimoto提供正确的答案。我的贡献(希望如此)是提供了一段代码,将常规的16次乘法和12次加法(给定Unity只支持4x4矩阵)通过删除所有零结果减少到2次乘法和2次加法。我测试了近百万个点,每次都检查我的计算结果是否与基本矩阵计算相符 - 其定义为两个结果之间的绝对差小于机器epsilon - 我对此感到非常满意,知道@rhashimoto可能会出现并揭开其中的漏洞 :-)
如果您想来回切换,记住这是C#,因此USEMATRIXMATH定义必须出现在文件开头。
考虑到现在只有一个Tango设备,并且我假设所有设备上的内部参数都是恒定的,我只需将它们作为常量放入代码中,例如:
fx = 1042.73999023438
fy = 1042.96997070313
cx = 637.273986816406
cy = 352.928985595703
k1 = 0.228532999753952
k2 = -0.663019001483917
k3 = 0.642908990383148
是的,它们可以作为常量输入,这会使代码更易读,并且C#可能足够聪明以优化它 - 然而,我在Agner Fogg的东西中度过了太多时间,所以总是有点偏执。
底部的注释掉的代码用于测试差异,如果您想要测试结果,则必须取消注释其他内容并注释返回。
再次感谢@rhashimoto,这比我原来写的好多了。
我坚持他的逻辑,记住这些是像素坐标,而不是UV坐标 - 他正确地指出,您可以预乘变换以获得归一化的UV值,但由于他已经教过我这个知识,所以在我进行太多调整之前,我将坚持他之前提出的数学方法。 :-)
static public Vector2 PictureUV(Vector3 tangoDepthPoint)
{
Vector2 imageCoords = new Vector2(tangoDepthPoint.x / tangoDepthPoint.z, tangoDepthPoint.y / tangoDepthPoint.z);
float r2 = Vector2.Dot(imageCoords, imageCoords);
float r4 = r2*r2;
float r6 = r2*r4;
imageCoords *= 1.0f + 0.228532999753952f*r2 + -0.663019001483917f*r4 + 0.642908990383148f*r6;
Vector3 ic3 = new Vector3(imageCoords.x,imageCoords.y,1);
#if USEMATRIXMATH
Matrix4x4 cameraTransform = new Matrix4x4();
cameraTransform.SetRow(0,new Vector4(1042.73999023438f,0,637.273986816406f,0));
cameraTransform.SetRow(1, new Vector4(0, 1042.96997070313f, 352.928985595703f, 0));
cameraTransform.SetRow(2, new Vector4(0, 0, 1, 0));
cameraTransform.SetRow(3, new Vector4(0, 0, 0, 1));
Vector3 pixelCoords = cameraTransform * ic3;
return new Vector2(pixelCoords.x, pixelCoords.y);
#else
//float v1 = 1042.73999023438f * imageCoords.x + 637.273986816406f;
//float v2 = 1042.96997070313f * imageCoords.y + 352.928985595703f;
//float v3 = 1;
return new Vector2(1042.73999023438f * imageCoords.x + 637.273986816406f,1042.96997070313f * imageCoords.y + 352.928985595703);
#endif
//float dx = Math.Abs(v1 - pixelCoords.x);
//float dy = Math.Abs(v2 - pixelCoords.y);
//float dz = Math.Abs(v3 - pixelCoords.z);
//if (dx > float.Epsilon || dy > float.Epsilon || dz > float.Epsilon)
// UnityEngine.Debug.Log("Well, that didn't work");
//return new Vector2(v1, v2);
}
最后提醒一下,注意他所提供的代码是GLSL格式的。如果你只是用它来制作漂亮的图片,那么可以使用它。但是如果你需要进行额外的处理,就需要使用这个代码。