C++ 使用相邻像素叉积从深度图像计算曲面法线
正如标题所说,我想通过使用相邻像素的叉积来计算给定深度图像的曲面法线。我希望使用Opencv来实现这一点,并避免使用PCL。然而,我并不真正理解这个过程,因为我在这方面的知识非常有限。因此,如果有人能提供一些提示,我将不胜感激。这里要提到的是,除了深度图像和相应的rgb图像之外,我没有任何其他信息,因此没有C++ 使用相邻像素叉积从深度图像计算曲面法线,c++,opencv,depth,normals,cross-product,C++,Opencv,Depth,Normals,Cross Product,正如标题所说,我想通过使用相邻像素的叉积来计算给定深度图像的曲面法线。我希望使用Opencv来实现这一点,并避免使用PCL。然而,我并不真正理解这个过程,因为我在这方面的知识非常有限。因此,如果有人能提供一些提示,我将不胜感激。这里要提到的是,除了深度图像和相应的rgb图像之外,我没有任何其他信息,因此没有K相机矩阵信息 因此,假设我们有以下深度图像: 我想找到对应点的法向量,对应深度值,如下图所示: 如何使用相邻像素的叉积来实现这一点?我不介意法线是否不是很精确 谢谢 更新: 好的,我试
K
相机矩阵信息
因此,假设我们有以下深度图像:
我想找到对应点的法向量,对应深度值,如下图所示:
如何使用相邻像素的叉积来实现这一点?我不介意法线是否不是很精确
谢谢
更新: 好的,我试着按照@timday的答案,将他的代码移植到Opencv。使用以下代码:
Mat depth = <my_depth_image> of type CV_32FC1
Mat normals(depth.size(), CV_32FC3);
for(int x = 0; x < depth.rows; ++x)
{
for(int y = 0; y < depth.cols; ++y)
{
float dzdx = (depth.at<float>(x+1, y) - depth.at<float>(x-1, y)) / 2.0;
float dzdy = (depth.at<float>(x, y+1) - depth.at<float>(x, y-1)) / 2.0;
Vec3f d(-dzdx, -dzdy, 1.0f);
Vec3f n = normalize(d);
normals.at<Vec3f>(x, y) = n;
}
}
imshow("depth", depth / 255);
imshow("normals", normals);
Mat depth=类型CV_32FC1
垫法线(深度大小(),CV_32FC3);
对于(int x=0;x
我得到了正确的以下结果(我必须用float
替换double
,并将Vecd
替换为Vecf
,但我不知道为什么会有任何不同):
您实际上不需要为此使用叉积,但请参见下文
假设你的距离像是一个函数z(x,y)
曲面的法线方向为(-dz/dx,-dz/dy,1)。(式中,dz/dx表示微分:z与x的变化率)。然后法线按常规标准化为单位长度
顺便说一下,如果你想知道(-dz/dx,-dz/dy,1)是从哪里来的。。。如果在平行于x轴和y轴的平面上取两个正交切线向量,它们是(1,0,dzdx)和(0,1,dzdy)。法线垂直于切线,因此应为(1,0,dzdx)X(0,1,dzdy)-其中“X”为叉积-即(-dzdx,-dzdy,1)。这里有一个由叉积导出的法线,但是当您可以直接使用法线的结果表达式时,几乎不需要在代码中如此显式地计算它
计算(x,y)处单位长度法线的伪代码如下
dzdx=(z(x+1,y)-z(x-1,y))/2.0;
dzdy=(z(x,y+1)-z(x,y-1))/2.0;
direction=(-dzdx,-dzdy,1.0)
magnitude=sqrt(direction.x**2 + direction.y**2 + direction.z**2)
normal=direction/magnitude
根据您尝试执行的操作,用一些大数字替换NaN值可能更有意义
使用这种方法,从您的范围图像中,我可以得到:
(然后我使用计算的法线方向进行一些简单的着色;注意距离图像的量化导致的“steppy”外观;理想情况下,对于真实距离数据,您的精度将高于8位)
对不起,不是OpenCV或C++代码,而是为了完整性:下面是完整的生成该图像的代码(GLSL嵌入Qt QML文件中,可以用Qt5的QMLVIEW运行)。上面的伪代码可以在片段着色器的
main()
函数中找到:
import QtQuick 2.2
Image {
source: 'range.png' // The provided image
ShaderEffect {
anchors.fill: parent
blending: false
property real dx: 1.0/parent.width
property real dy: 1.0/parent.height
property variant src: parent
vertexShader: "
uniform highp mat4 qt_Matrix;
attribute highp vec4 qt_Vertex;
attribute highp vec2 qt_MultiTexCoord0;
varying highp vec2 coord;
void main() {
coord=qt_MultiTexCoord0;
gl_Position=qt_Matrix*qt_Vertex;
}"
fragmentShader: "
uniform highp float dx;
uniform highp float dy;
varying highp vec2 coord;
uniform sampler2D src;
void main() {
highp float dzdx=( texture2D(src,coord+vec2(dx,0.0)).x - texture2D(src,coord+vec2(-dx,0.0)).x )/(2.0*dx);
highp float dzdy=( texture2D(src,coord+vec2(0.0,dy)).x - texture2D(src,coord+vec2(0.0,-dy)).x )/(2.0*dy);
highp vec3 d=vec3(-dzdx,-dzdy,1.0);
highp vec3 n=normalize(d);
highp vec3 lightDirection=vec3(1.0,-2.0,3.0);
highp float shading=0.5+0.5*dot(n,normalize(lightDirection));
gl_FragColor=vec4(shading,shading,shading,1.0);
}"
}
}
我认为代码(矩阵计算)是正确的:
def normalization(data):
mo_chang =np.sqrt(np.multiply(data[:,:,0],data[:,:,0])+np.multiply(data[:,:,1],data[:,:,1])+np.multiply(data[:,:,2],data[:,:,2]))
mo_chang = np.dstack((mo_chang,mo_chang,mo_chang))
return data/mo_chang
x,y=np.meshgrid(np.arange(0,width),np.arange(0,height))
x=x.reshape([-1])
y=y.reshape([-1])
xyz=np.vstack((x,y,np.ones_like(x)))
pts_3d=np.dot(np.linalg.inv(K),xyz*img1_depth.reshape([-1]))
pts_3d_world=pts_3d.reshape((3,height,width))
f= pts_3d_world[:,1:height-1,2:width]-pts_3d_world[:,1:height-1,1:width-1]
t= pts_3d_world[:,2:height,1:width-1]-pts_3d_world[:,1:height-1,1:width-1]
normal_map=np.cross(f,l,axisa=0,axisb=0)
normal_map=normalization(normal_map)
normal_map=normal_map*0.5+0.5
alpha = np.full((height-2,width-2,1), (1.), dtype="float32")
normal_map=np.concatenate((normal_map,alpha),axis=2)
normal\u-map=normal\u-map*0.5+0.5
将法线值映射到(0,1)
欢迎使用通讯。感谢您的回答,我现在正尝试将您的代码移植到opencv。但是,既然我需要做叉积,那么我如何使用叉积来做同样的事情。在你的例子中,你使用的是4邻域叉积,对吗?如果我想使用8-邻域,那么我应该对对角线像素应用相同的过程,对吗?@theodore:一种方法是,叉积的“输入”是两个切线向量,叉积生成垂直法线。上面,我有效地使用了4个邻居作为两个切线向量的端点。不清楚如何将其扩展到8个相邻点。。。然而,我知道有更先进的方法,将样条曲线拟合到邻域中的更多点,然后使用样条曲线曲面的法线(例如)。谢谢,我明白了。实际上我脑子里的想法是这样的:
dzdx=(z(x+1,y)-z(x-1,y))/2.0;dzdy=(z(x,y+1)-z(x,y-1))/2.0;方向=(-dxdz,-dydz,1.0)幅值=sqrt(方向x**2+方向y**2+方向z**2)dzdx1=(z(x+1,y+1)-z(x-1,y-1))/2.0;dzdy1=(z(x-1,y+1)-z(x+1,y-1))/2.0;方向1=(-dxdz1,-dydz1,1.0)震级1=sqrt(方向1.x**2+方向1.y**2+方向1.z**2)法线=(方向/震级)+(方向1/震级1)
,但我不知道这有多正确。@先生,因为如果例如dxdz是1.0(而dydz是0.0),然后是45度的坡度,你想要形成一个直角等腰三角形(因此z=1)。法线方向沿三角形的斜边,并且您希望三角形具有单位长度,因此后续的归一化步骤取决于OpenCV在转储向量图像时所做的操作。看起来XYZ没有映射到RGB,如果不进行一些缩放,正/负分量范围可能无法很好地映射到0-255像素值。这就是为什么我的代码还包括一个简单的着色模型,用于从法线生成灰度图像。Hi@timday我不认为这是Opencv问题,因为如果我将法线从Matlab脚本加载到Opencv并imshow()