OpenCV:旋转/平移向量到OpenGL模型视图矩阵

6
我正在尝试使用OpenCV进行基本的增强现实。我的方法是使用findChessboardCorners从相机图像中获取一组点。然后,我在z = 0平面上创建一个3D四边形,并使用solvePnP获取成像点和平面点之间的单应性矩阵。从那里开始,我认为我应该能够设置一个模型视图矩阵,这将使我能够在图像上方以正确的姿态渲染一个立方体。 solvePnP文档说它输出一个旋转向量,“(与[平移向量]一起)将点从模型坐标系带到相机坐标系。”我认为这与我想要的相反;因为我的四边形在z = 0平面上,所以我需要一个模型视图矩阵,将该四边形变换为适当的3D平面。

我曾尝试通过相反顺序执行相反旋转和平移来计算正确的模型视图矩阵,但似乎不起作用。虽然渲染的对象(一个立方体)随着相机图像移动并且在平移方面大致正确,但旋转根本不起作用;它在多个轴上旋转,而应该只在一个轴上旋转,并且有时是错误的方向。这是我目前正在做的:

std::vector<Point2f> corners;
bool found = findChessboardCorners(*_imageBuffer, cv::Size(5,4), corners,
                                      CV_CALIB_CB_FILTER_QUADS |
                                      CV_CALIB_CB_FAST_CHECK);
if(found)
{
  drawChessboardCorners(*_imageBuffer, cv::Size(6, 5), corners, found);

  std::vector<double> distortionCoefficients(5);  // camera distortion
  distortionCoefficients[0] = 0.070969;
  distortionCoefficients[1] = 0.777647;
  distortionCoefficients[2] = -0.009131;
  distortionCoefficients[3] = -0.013867;
  distortionCoefficients[4] = -5.141519;

  // Since the image was resized, we need to scale the found corner points
  float sw = _width / SMALL_WIDTH;
  float sh = _height / SMALL_HEIGHT;
  std::vector<Point2f> board_verts;
  board_verts.push_back(Point2f(corners[0].x * sw, corners[0].y * sh));
  board_verts.push_back(Point2f(corners[15].x * sw, corners[15].y * sh));
  board_verts.push_back(Point2f(corners[19].x * sw, corners[19].y * sh));
  board_verts.push_back(Point2f(corners[4].x * sw, corners[4].y * sh));
  Mat boardMat(board_verts);

  std::vector<Point3f> square_verts;
  square_verts.push_back(Point3f(-1, 1, 0));                              
  square_verts.push_back(Point3f(-1, -1, 0));
  square_verts.push_back(Point3f(1, -1, 0));
  square_verts.push_back(Point3f(1, 1, 0));
  Mat squareMat(square_verts);

  // Transform the camera's intrinsic parameters into an OpenGL camera matrix
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();

  // Camera parameters
  double f_x = 786.42938232; // Focal length in x axis
  double f_y = 786.42938232; // Focal length in y axis (usually the same?)
  double c_x = 217.01358032; // Camera primary point x
  double c_y = 311.25384521; // Camera primary point y


  cv::Mat cameraMatrix(3,3,CV_32FC1);
  cameraMatrix.at<float>(0,0) = f_x;
  cameraMatrix.at<float>(0,1) = 0.0;
  cameraMatrix.at<float>(0,2) = c_x;
  cameraMatrix.at<float>(1,0) = 0.0;
  cameraMatrix.at<float>(1,1) = f_y;
  cameraMatrix.at<float>(1,2) = c_y;
  cameraMatrix.at<float>(2,0) = 0.0;
  cameraMatrix.at<float>(2,1) = 0.0;
  cameraMatrix.at<float>(2,2) = 1.0;

  Mat rvec(3, 1, CV_32F), tvec(3, 1, CV_32F);
  solvePnP(squareMat, boardMat, cameraMatrix, distortionCoefficients, 
               rvec, tvec);

  _rv[0] = rvec.at<double>(0, 0);
  _rv[1] = rvec.at<double>(1, 0);
  _rv[2] = rvec.at<double>(2, 0);
  _tv[0] = tvec.at<double>(0, 0);
  _tv[1] = tvec.at<double>(1, 0);
  _tv[2] = tvec.at<double>(2, 0);
}

然后在绘图代码中...
GLKMatrix4 modelViewMatrix = GLKMatrix4MakeTranslation(0.0f, 0.0f, 0.0f);
modelViewMatrix = GLKMatrix4Translate(modelViewMatrix, -tv[1], -tv[0], -tv[2]);
modelViewMatrix = GLKMatrix4Rotate(modelViewMatrix, -rv[0], 1.0f, 0.0f, 0.0f);
modelViewMatrix = GLKMatrix4Rotate(modelViewMatrix, -rv[1], 0.0f, 1.0f, 0.0f);
modelViewMatrix = GLKMatrix4Rotate(modelViewMatrix, -rv[2], 0.0f, 0.0f, 1.0f);

我正在渲染的顶点创建一个以原点为中心的单位长度立方体(即每条边从-0.5到0.5)。我知道OpenGL的平移函数执行的是“相反顺序”的变换,所以上述操作应该先绕z、y和x轴旋转立方体,然后再进行平移。然而,似乎它先进行了平移,然后再旋转,所以也许苹果的GLKMatrix4工作方式不同? 这个问题看起来与我的非常相似,特别是coder9的答案似乎更或多或少是我需要的。然而,我尝试了一下并将结果与我的方法进行了比较,两种情况下我得到的矩阵都是相同的。我觉得那个答案是正确的,但我可能错过了一些关键细节。

1
尝试自己学习如何做这件事。看到了关于坐标系差异的主题帖子。https://dev59.com/SGox5IYBdhLWcg3wnlpI - Koobz
2个回答

2
你必须确保轴朝向正确方向,特别是在OpenGL和OpenCV中,y和z轴朝向不同,以确保x-y-z基底是直接的。你可以找到一些信息和代码(使用iPad相机)在这篇博客文章中。(博客链接)
- 编辑 - 啊,好吧。不幸的是,我用这些资源来反过来做(opengl ---> opencv)以测试某些算法。我的主要问题是OpenGL和OpenCV之间的图像行顺序是相反的(也许这有帮助)。
当模拟摄像头时,我遇到了与这里广义投影矩阵论文中找到的相同投影矩阵。在博客文章的评论中引用的这篇论文还显示了计算机视觉和OpenGL投影之间的某些联系。

谢谢提供链接。不幸的是,我之前已经看过那篇文章了。我尝试几乎完全照搬他的代码,但结果基本相同。我认为那篇文章也缺少关键细节,因为它没有展示如何计算单应性,这可能很重要。我也知道y/z轴的差异,但我已经多次检查我的代码,据我所知我已经处理好了。 - Mitch Lindgren

0

我不是IOS程序员,所以我的回答可能会误导您!如果问题不在应用旋转和平移的顺序上,那么建议使用更简单、更常用的坐标系。

角落向量中的点具有图像左上角的原点(0,0),y轴朝向图像底部。通常从数学上来说,我们习惯于将坐标系的原点放在中心,y轴朝向图像顶部。从您推入board_verts的坐标来看,我猜您也犯了同样的错误。如果是这种情况,可以通过以下方式轻松地转换角落的位置:

for (i=0;i<corners.size();i++) {
  corners[i].x -= width/2;
  corners[i].y = -corners[i].y + height/2;
}

然后您调用solvePnP()。调试这并不难,只需打印出四个角的位置和估计的R和T,看看它们是否合理。然后您可以继续进行OpenGL步骤。请告诉我进展如何。


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