我有深度图像,我使用3D CAD数据生成。该深度图像也可以从诸如Kinect或立体相机的深度成像传感器获取。所以基本上它是在成像视图中可见的点的深度图。换句话说,它是来自特定视图的对象的分段点云。
我想确定(估计也会)每个点的曲面法线,然后找到该点的切线平面。
我该怎么做?我做了一些研究并找到了一些技术,但对它们并不了解(我无法实现)。更重要的是我如何在Matlab或OpenCV中做到这一点?我无法使用surfnorm
命令执行此操作。 AFAIK需要单面,我的深度图像中有部分表面。
这是一个示例深度图像。
[编辑]
我想要做的是,在每个点获得表面法线后,我将在这些点处创建切线平面。然后使用这些切平面通过将相邻点的距离之和与切平面相乘来确定该点是否来自平坦区域。
[编辑]
答案 0 :(得分:8)
因此,您的问题中有一些未定义的内容,但我会尽力概述答案。
您想要做的基本思路是采用图像的渐变,然后对渐变应用变换以获得法线向量。在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;
答案 1 :(得分:5)
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 and 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];
}
答案 2 :(得分:1)
我将描述我认为你必须在概念上做什么,并提供指向opencv的相关部分的链接,
确定pointcloud中给定(3d)点的正常值:
创建点云的kd-tree或(balltree?)表示,以便您可以有效地计算k个最近邻居。您对k的选择应取决于数据的密度。 http://docs.opencv.org/trunk/modules/flann/doc/flann_fast_approximate_nearest_neighbor_search http://physics.nyu.edu/grierlab/manuals/opencv/classcv_1_1KDTree.html
在查询给定点p的k-最近邻居之后,使用它们找到最合适的平面。您可以使用PCA执行此操作。设置maxComponents = 2。 http://physics.nyu.edu/grierlab/manuals/opencv/classcv_1_1PCA.html https://github.com/Itseez/opencv/blob/master/samples/cpp/pca.cpp
第2步应该返回两个特征向量来定义你感兴趣的平面。这两个向量的叉积应该是(估计)你想要的法向量。您可以在opencv中找到有关如何计算此信息的信息(Mat :: cross)http://docs.opencv.org/modules/core/doc/basic_structures.html
答案 3 :(得分:0)