OpenGL 中的相机镜头畸变

6
我正在尝试为我的SLAM项目模拟镜头畸变效果。已经提供并加载了扫描的彩色3D点云到OpenGL中。我的目标是在给定的姿态下渲染2D场景,并在真实图像(来自鱼眼相机)和渲染图像之间执行一些视觉里程计。由于相机具有严重的镜头畸变,因此在渲染阶段也应该考虑它。
问题是我不知道在哪里放置镜头畸变。着色器?
我找到了一些开源代码将畸变放置在几何着色器中。但我猜这个畸变模型与计算机视觉社区中的镜头畸变模型不同。在计算机视觉社区中,镜头畸变通常发生在投影平面上。 这个与我的工作非常相似,但他们没有使用畸变模型。
有人有好主意吗?
我刚发现了另一种实现。他们的代码在片段着色器和几何着色器中都实现了扭曲效果。但是在我的情况下,片段着色器版本可以使用。因此,我猜以下代码会起作用:
# vertex shader
p'=T.model x T.view x p
p_f = FisheyeProjection(p') // custom fish eye projection

1
另一种常见的方法是将渲染结果渲染到纹理上,然后将该纹理映射到矩形上,再扭曲该矩形的形状。 - p10ben
2个回答

9
受 VR 社区启发,我通过顶点位移实现了畸变。对于高分辨率的图像,这种方法在计算上更加高效,但需要具有良好顶点密度的网格。在扭曲图像前,您可能需要应用细分。

以下是实现 OpenCV 有理形变模型的代码(请参见https://docs.opencv.org/4.0.1/d9/d0c/group__calib3d.html 获取公式):

#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normal_in;
layout (location = 2) in vec2 texture_coordinate_in;
uniform mat4 model_matrix;
uniform mat4 view_matrix;
uniform float dist_coeffs[8];
uniform mat4 projection_matrix;
uniform vec3 light_position;
out vec2 texture_coordinate;
out vec3 normal;
out vec3 light_direction;

// distort the real world vertices using the rational model
vec4 distort(vec4 view_pos)
{
  // normalize
  float z = view_pos[2];
  float z_inv = 1 / z;
  float x1 = view_pos[0] * z_inv;
  float y1 = view_pos[1] * z_inv;
  // precalculations
  float x1_2 = x1*x1;
  float y1_2 = y1*y1;
  float x1_y1 = x1*y1;
  float r2 = x1_2 + y1_2;
  float r4 = r2*r2;
  float r6 = r4*r2;
  // rational distortion factor
  float r_dist = (1 + dist_coeffs[0]*r2 +dist_coeffs[1]*r4 + dist_coeffs[4]*r6) 
    / (1 + dist_coeffs[5]*r2 + dist_coeffs[6]*r4 + dist_coeffs[7]*r6);
  // full (rational + tangential) distortion
  float x2 = x1*r_dist + 2*dist_coeffs[2]*x1_y1 + dist_coeffs[3]*(r2 + 2*x1_2);
  float y2 = y1*r_dist + 2*dist_coeffs[3]*x1_y1 + dist_coeffs[2]*(r2 + 2*y1_2);
  // denormalize for projection (which is a linear operation)
  return vec4(x2*z, y2*z, z, view_pos[3]);
}

void main()
{
  vec4 local_pos = vec4(position, 1.0);
  vec4 world_pos  =  model_matrix * local_pos;
  vec4 view_pos = view_matrix * world_pos;
  vec4 dist_pos = distort(view_pos);
  gl_Position = projection_matrix * dist_pos;
  // lighting on world coordinates not distorted ones
  normal = mat3(transpose(inverse(model_matrix))) * normal_in;
  light_direction = normalize(light_position - world_pos.xyz);
  texture_coordinate = texture_coordinate_in;
}

需要翻译的内容:

需要注意的是,失真是在z标准化坐标下计算的,但在distort的最后一行被非标准化为视图坐标。这允许使用像这篇文章中所述的投影矩阵:http://ksimek.github.io/2013/06/03/calibrated_cameras_in_opengl/

编辑:对于有兴趣查看上下文代码的人,我已经在一个小的中发布了代码,失真着色器在这个示例中被使用。


你好伙计,我在我的着色器中实现了这段代码,它运行得非常好,除了当我靠得太近物体时,有时会出现奇怪的伪影。就像一个顶点超出某个范围时,它会被错误投影。 - user6138759
你可能比视锥体允许的更接近于你的对象吗? - Tuebel
不,这不是一个台锥体问题,因为当我进行无失真的投影时,没有任何问题。 - user6138759
我知道OpenCV的projectPoints有其限制,有时当未畸变点的投影超出图像周围某个椭圆时,畸变会产生奇怪的结果。因此我认为这是同样的问题。 - user6138759

8
镜头失真通常会将直线弯曲。OpenGL在光栅化线条和三角形时,无论如何转换顶点,原始边缘都保持直线。
如果你的模型具有足够精细的镶嵌,则将失真纳入顶点变换是可行的。如果你只渲染点,则它也适用。
但是,如果您的目标是普遍适用性,您必须想办法处理直边基元。一种方法是使用几何着色器进一步细分传入的模型;或者您可以使用细分着色器。
另一种方法是渲染到立方体贴图中,然后使用着色器创建相应的镜头等效物。我实际上建议使用这种方法生成鱼眼图像。
失真本身通常由3至5阶多项式表示,将光学中心轴的未失真角度距离映射到失真角度距离。

嗨!谢谢你的建议。我正在使用基于Surfel的地图表示,其中每个点都有一个固定半径约为1厘米或更小的圆盘,这足够小了。所以,我想你提出的第一种解决方案在不对它们进行细分的情况下适用于我。第二个想法也听起来不错。我会检查一下cubemap。谢谢! - Chanoh Park
那只是一个关于着色器的一般介绍,我知道什么是着色器。我的意思是,你所说的“为立方体贴图创建等效的镜头”是什么意思?在我看来,你需要学会更详细地阐述你的意思,因为你太简略了,留下了很多细节,让人们猜测你的意思。 - HelloGoodbye
1
@HelloGoodbye 简化版:立方体贴图是由方向向量寻址的。因此问题在于想出一个函数,将屏幕空间位置映射到如果使用鱼眼镜头投影到这样一种镜头的背焦平面上的图像时会发送光线的方向。类似针孔相机的默认透视变换是纯线性的。所以你需要一些非线性的映射。最简单的方法是使用多项式将“平坦”的径向距离映射为扭曲的距离。 - datenwolf
1
@HelloGoodbye:你可以使用这个向量进行立方体贴图查找。通过适当选择多项式的系数,你可以创建许多种镜头畸变,包括枕形和桶形畸变。 - datenwolf
1
@HelloGoodbye:假设你有一个片段着色器uniform vec2 pv,它的视口中心在0,0处,四个角分别在(+/-1,+/-1)处,那么你可以通过将该向量的长度通过具有系数vec4 a的4次多项式来实现径向畸变,即通过评估float r = length(pv); r = a[0] + r*(a[1] + r*(a[2] +r*a[4]));。我们希望得到一个长度为1的方向向量;系数被选择为新的r < 1,因此要获得单位长度向量,第三个分量将是sqrt(1-r*r),即vec3 dir = vec3(normalize(pv)*r, sqrt(1-r*r)); - datenwolf
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接