Matlab 如何估计/确定深度图像点处的曲面法线和切线平面?

Matlab 如何估计/确定深度图像点处的曲面法线和切线平面?,matlab,opencv,graphics,3d,computer-vision,Matlab,Opencv,Graphics,3d,Computer Vision,我有一个深度图像,是我用3D CAD数据生成的。该深度图像也可以从深度成像传感器(如Microsoft Kinect或任何其他立体相机)获取。所以基本上它是成像视图中可见点的深度图。换句话说,它是从某个视图中分割出的对象点云 我想确定(估计也可以)每个点的曲面法线,然后找到该点的切面 我该怎么做?我做了一些研究,发现了一些技术,但没有很好地理解它们(我无法实现它)。更重要的是,如何在Matlab或OpenCV中实现这一点?我无法使用surfnorm命令执行此操作。好吧,它需要一个曲面,我的深度图

我有一个深度图像,是我用3D CAD数据生成的。该深度图像也可以从深度成像传感器(如Microsoft Kinect或任何其他立体相机)获取。所以基本上它是成像视图中可见点的深度图。换句话说,它是从某个视图中分割出的对象点云

我想确定(估计也可以)每个点的曲面法线,然后找到该点的切面

我该怎么做?我做了一些研究,发现了一些技术,但没有很好地理解它们(我无法实现它)。更重要的是,如何在Matlab或OpenCV中实现这一点?我无法使用
surfnorm
命令执行此操作。好吧,它需要一个曲面,我的深度图像中有部分曲面

这是一个深度图像示例

[编辑]


我想做的是,在得到每个点的曲面法线后,我将在这些点上创建相切平面。然后使用这些切面,通过计算相邻点到切面的距离之和,来确定该点是否来自平坦区域。

因此,在您的问题中有几点未定义,但我将尽我所能概括出答案

你要做的基本思想是取图像的梯度,然后对梯度进行变换,得到法向量。在matlab中获取梯度很容易:

[m, g] = imgradient(d);
给出图像在每个点处的梯度(相对于水平方向,以度为单位)的大小(
m
)和方向(
g
)。例如,如果我们为您的图像显示渐变的大小,它如下所示:

现在,更难的部分是,把梯度的信息转化为法向量。为了正确地做到这一点,我们需要知道如何从图像坐标转换到世界坐标。对于像您这样的CAD生成的图像,此信息包含在用于生成图像的投影变换中。对于从Kinect获得的真实图像,您必须查找图像捕获设备的规格

我们需要的关键信息是:在现实世界坐标中,每个像素的宽度是多少?对于非正交投影(如真实世界的图像捕获设备所使用的投影),我们可以通过假设每个像素代表与真实世界固定角度内的光来近似这一点。如果我们知道这个角度(称之为
p
并用弧度测量),那么一个像素所覆盖的实际距离就是
sin(p)。*d
,或者大约
p.*d
,其中
d
是每个像素处图像的深度

现在如果我们有了这些信息,我们可以构造法向量的3个分量:

width = p .* d;
gradx = m .* cos(g) * width;
grady = m .* sin(g) * width;

normx = - gradx;
normy = - grady;
normz = 1;

len = sqrt(normx .^ 2 + normy .^ 2 + normz .^ 2);
x = normx ./ len;
y = normy ./ len;
z = normz ./ len;

mattnewport的建议是可以在像素着色器中完成。在每个像素着色器中,计算两个向量A和B,向量的叉积将为您提供法线。计算这两个向量的方法如下所示:

float2 du //values sent to the shader based on depth image's width and height
float2 dv //normally du = float2(1/width, 0) and dv = float2(0, 1/height)
float D = sample(depthtex, uv)
float D1 = sample(depthtex, uv + du)
float D2 = sample(depthtex, uv + dv)
float3 A = float3(du*width_of_image, 0, D1-D)
float3 B = float3(0, dv*height_of_image, D2-D)
float3 normal = AXB
return normal
当深度值中存在不连续时,这将中断

要计算像素着色器中的曲面是否平坦,可以使用二阶偏导数。计算二阶导数的方法是计算有限差分,然后在上面求差分,就像这样:

float D = sample(depthtex, uv)
float D1 = sample(depthtex, uv + du)
float D3 = sample(depthtex, uv - du)

float dx1 = (D1 - D)/du
float dx2 = (D - D3)/du
float dxx = (dx2 - dx1)/du
同样,您必须计算
dyy、dxy和dyx
。如果
dxx=dyy=dxy=dyx=0,则表面是平坦的。

通常,您会选择du和dv为深度图像的1/宽度和1/高度

所有这些事情都发生在GPU上,这使得一切都非常快。但是如果你不关心这个问题,你也可以在CPU中运行这个方法。唯一的问题是您需要替换像
sample
这样的函数,并实现您自己的版本。它将深度图像和u、v值作为输入,并在采样点返回深度值

编辑:

这里有一个假设的采样函数,它在CPU上进行最近邻采样

float Sample(const Texture& texture, vector_2d uv){
    return texture.data[(int)(uv.x * texture.width + 0.5)][(int)(uv.y * texture.height + 0.5];
}

我将从概念上描述我认为您必须做的事情,并提供指向opencv相关部分的链接

要确定点云中给定(3d)点的法线,请执行以下操作:

  • 创建点云的kd树或(balltree?)表示形式,以便高效地计算k个最近邻。k的选择应取决于数据的密度。

  • 查询给定点p的k近邻后,使用它们来查找最佳拟合平面。您可以使用PCA来执行此操作。设置maxComponents=2。

  • 第2步应该返回两个定义您感兴趣的平面的特征向量。这两个向量的叉积应该是所需法向量的(估计值)。您可以在opencv(Mat::cross)中找到如何计算此值的信息

  • 您可以参考“有效组合位置和法线以获得精确三维几何体”中的(5)、(6)和(8):


    如何定义深度不连续点处的表面法线?哦,你是指在这张图中,杯子内部可见的边缘或底部等点?如果有不连续,那么曲面法线对我来说并不重要。所以我不需要定义它。你有没有试过只取相邻像素的有限差分?结果可能会很嘈杂,需要一些平滑,另外你需要做一些事情来处理不连续性,但它应该给你足够好的照明。你需要法线做什么?我不知道什么是有限差分,所以我没有试过。我会调查的。我不打算对闪电使用法线,在得到法线后,我将计算该点的切线曲面。然后使用该曲面确定该点是否平坦