如何将使用EPnP计算的相机位姿变换应用于VTK相机?

11

为了我的增强现实项目,我有一个使用VTK相机查看的三维模型和一个使用真实相机查看的该模型的实际对象。

我使用EPnP算法估计了真实相机的外部矩阵(此相机事先已经经过校准,因此我知道内部参数),通过提供来自VTK的三维点以及来自真实相机图像的对应二维点和真实相机的内部参数来进行工作。

之后,我获得了一个旋转和平移矩阵,其元素为->R1、R2、R3......R9和t1、t2和t3。

因此,我的真实相机的外部矩阵看起来像这样(我们将其称为extrinsicReal)

R1 R2 R3 T1
R4 R5 R6 T2
R7 R8 R9 T3
 0  0  0  1

接下来,我使用以下代码估算了VTK相机的外参矩阵:

vtkSmartPointer<vtkMatrix4x4> extrinsicVTK = vtkSmartPointer<vtkMatrix4x4>::New();
extrinsicVTK->DeepCopy(renderer->GetActiveCamera()->GetViewTransformMatrix());

要将VTK相机的3D模型与实际相机融合,需要将VTK相机设置到与真实相机位置相同的位置,并且VTK相机的焦距应该与真实相机的焦距相同。另一个重要的步骤是将真实相机的外参矩阵应用于VTK相机。我应该如何操作?

我的做法是取外参矩阵的逆矩阵并将其与VTK相机的外参矩阵相乘得到一个新的4*4矩阵(我们称之为newMatrix)。我使用这个矩阵对VTK相机进行变换。

vtkSmartPointer<vtkMatrix4x4> newMatrix = vtkSmartPointer<vtkMatrix4x4>::New();
vtkMatrix4x4::Multiply4x4(extrinsicRealInvert,extrinsicVTK,newMatrix);

vtkSmartPointer<vtkTransform> transform = vtkSmartPointer<vtkTransform>::New();
transform->SetMatrix(NewM); 
transform->Update();

renderer->GetActiveCamera()->ApplyTransform(transform);

我不确定这是否是正确的方法,但是我检查了真实相机位置(在进行EPnP之后获得)和应用上述变换后的VTK相机位置,它们完全相同。此外,真实相机的方向和VTK相机的投影方向也相同。

问题在于,即使对于VTK和真实相机,上述参数都匹配,3D VTK模型似乎仍然不能完美地与真实相机视频对齐。有人可以逐步指导我调试该问题吗?


你的问题对我帮助很大,非常感谢你在问题中分享了你的代码。 - smoothumut
1个回答

14

当应用这些参数到vtk相机时,情况变得复杂起来。以下是我如何做的(只列出了重要的代码段,完整的代码会太长且对你也没有用处)。其他需要考虑的点:

  1. 我将内窥镜图像作为背景纹理呈现在我的vtkRenderWindow中。
  2. 我使用了VTK、ITK(vnl)、OpenCV函数的混合,但它们应该是可互换的(例如cvRound也可以被vtkMath::Round()等替换)。

首先,我使用来自我的vtkRenderer的活动相机:

d->m_Renderer->GetActiveCamera()

下一步是通过应用您的变换来持续更新活动相机。根据您的渲染窗口是否可调整大小,您必须初始化或持续更新两个进一步的参数:1.视角,2.窗口中心(非常重要,vtk完全没有记录。但最终您必须在此应用您通过校准找到的主点,否则您的表面将呈现出偏移。花了我3个月才找到这两行解决方案)。
视角的计算方法如下:
  double focalLengthY = _CameraIntrinsics->GetFocalLengthY();
  if( _WindowSize.height != _ImageSize.height )
  {
    double factor = static_cast<double>(_WindowSize.height)/static_cast<double>(_ImageSize.height);
    focalLengthY = _CameraIntrinsics->GetFocalLengthY() * factor;
  }

  _ViewAngle = 2 * atan( ( _WindowSize.height / 2 ) / focalLengthY ) * 180 / vnl_math::pi;

应用视角:

d->m_Renderer->GetActiveCamera()->SetViewAngle(viewAngle);

窗口中心的计算:

  double px = 0;
  double width = 0;

  double py = 0;
  double height = 0;

  if( _ImageSize.width != _WindowSize.width || _ImageSize.height != _WindowSize.height )
  {
    double factor = static_cast<double>(_WindowSize.height)/static_cast<double>(_ImageSize.height);

    px = factor * _CameraIntrinsics->GetPrincipalPointX();
    width = _WindowSize.width;
    int expectedWindowSize = cvRound(factor * static_cast<double>(_ImageSize.width));
    if( expectedWindowSize != _WindowSize.width )
    {
      int diffX = (_WindowSize.width - expectedWindowSize) / 2;
      px = px + diffX;
    }

    py = factor * _CameraIntrinsics->GetPrincipalPointY();
    height = _WindowSize.height;
  }
  else
  {
    px = _CameraIntrinsics->GetPrincipalPointX();
    width = _ImageSize.width;

    py = _CameraIntrinsics->GetPrincipalPointY();
    height = _ImageSize.height;
  }

  double cx = width - px;
  double cy = py;

  _WindowCenter.x = cx / ( ( width-1)/2 ) - 1 ;
  _WindowCenter.y = cy / ( ( height-1)/2 ) - 1;

设置窗口中心:

 d->m_Renderer->GetActiveCamera()->SetWindowCenter(_WindowCenter.x, _WindowCenter.y);

将外部矩阵应用于相机:
// create a scaling matrix (THE CLASS TRANSFORM IS A WRAPPER FOR A 4x4 Matrix, methods should be self-documenting)
d->m_ScaledTransform = Transform::New();
d->m_ScaleMat.set_identity();
d->m_ScaleMat(1,1) = -d->m_ScaleMat(1,1);
d->m_ScaleMat(2,2) = -d->m_ScaleMat(2,2);

// scale the matrix appropriately (m_VnlMat is a VNL 4x4 Matrix)
d->m_VnlMat = d->m_CameraExtrinsicMatrix->GetMatrix();
d->m_VnlMat = d->m_ScaleMat * d->m_VnlMat;
d->m_ScaledTransform->SetMatrix( d->m_VnlMat );

d->m_VnlRotation = d->m_ScaledTransform->GetVnlRotationMatrix();
d->m_VnlRotation.normalize_rows();
d->m_VnlInverseRotation = vnl_matrix_inverse<mitk::ScalarType>( d->m_VnlRotation );

// rotate translation vector by inverse rotation P = P'
d->m_VnlTranslation = d->m_ScaledTransform->GetVnlTranslation();
d->m_VnlTranslation = d->m_VnlInverseRotation * d->m_VnlTranslation;
d->m_VnlTranslation *= -1;  // save -P'

// from here proceed as normal
// focalPoint = P-viewPlaneNormal, viewPlaneNormal is rotation[2]
d->m_ViewPlaneNormal[0] = d->m_VnlRotation(2,0);
d->m_ViewPlaneNormal[1] = d->m_VnlRotation(2,1);
d->m_ViewPlaneNormal[2] = d->m_VnlRotation(2,2);

d->m_vtkCamera->SetPosition(d->m_VnlTranslation[0], d->m_VnlTranslation[1], d->m_VnlTranslation[2]);

d->m_vtkCamera->SetFocalPoint( d->m_VnlTranslation[0] - d->m_ViewPlaneNormal[0],
                               d->m_VnlTranslation[1] - d->m_ViewPlaneNormal[1],
                               d->m_VnlTranslation[2] - d->m_ViewPlaneNormal[2] );
d->m_vtkCamera->SetViewUp( d->m_VnlRotation(1,0), d->m_VnlRotation(1,1), d->m_VnlRotation(1,2) );

最后进行剪辑范围重置:

d->m_Renderer->ResetCameraClippingRange();

希望这可以帮到你。我没有时间解释更多细节,特别是最后的代码(将外参应用于相机)有一些涉及坐标系方向的影响。但对我来说,那个方法可行。
祝好, Michael

3
只是想说这个翻译非常准确。VTK的文档十分糟糕,所以在过去的4个月里一直碰壁后能够发现像这样的东西真的太好了。我甚至从未考虑过SetWindowCenter函数!尽管我们仍然存在一个明显由于窗口大小引起的偏移(错误随着缩放而改变),但现在我们采用了你的一些技巧之后,其他所有的事情都很好处理了。我理解你对于VTK的文档感同身受。弄清楚其中的内容真的太困难了。有人需要纠正这一点。 - SwarthyMantooth
我不理解应用外部部分。缩放矩阵带有两个负号的作用是什么?如果R和t是来自OpenCV外部矩阵的值,则相机位置应为-Rinv * t,视图向上应为Rinv [:,1],焦点应为camera_pos - Rinv [:,2]。当我应用此时,我没有得到正确的模型叠加。即使我应用您的转换,假设m_CameraExtrinsicMatrix具有OpenCV矩阵的格式,我也无法获得正确的渲染。 - krips89
在Python中,按照@Michael的出色答案所指出的“外部”部分相同的方法,可以得到更简洁的版本:(1) camera.SetPosition(*eye) (2) camera.SetFocalPoint(*center) (3) camera.SetViewUp(*up),其中eye = (-R'T), center = eye + R'(0,0,1)', up = -R[0][1] - squid
谢谢您提供这个很好的例子,有人可以解释一下变量"d"是什么吗? - Kev1n91

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