首页 智能穿戴

VTK 正射投影:从多个 2D 坐标反算 3D 空间坐标的实用技巧

分类:智能穿戴
字数: (6751)
阅读: (6990)
内容摘要:VTK 正射投影:从多个 2D 坐标反算 3D 空间坐标的实用技巧,

在医疗图像处理、三维建模等领域,我们经常需要在 VTK (Visualization Toolkit) 中利用相机正射投影将二维图像与三维模型进行对应。一个常见的需求是:已知多个 2D 坐标(例如鼠标点击位置),如何反算出对应的 3D 空间坐标? 这篇文章将深入探讨这一问题的解决方案,并提供实战经验。

问题场景重现:2D 坐标到 3D 坐标的转换

假设我们有一个 VTK 渲染窗口,使用正射投影相机,用户通过鼠标在窗口中点击了若干个点,得到了这些点的 2D 坐标(x, y)。我们需要将这些 2D 坐标转换成 3D 空间中的坐标(x, y, z),以便进行后续的三维分析,例如测量距离、绘制 3D 标注等。

VTK 正射投影:从多个 2D 坐标反算 3D 空间坐标的实用技巧

底层原理深度剖析:正射投影与逆变换

正射投影是一种平行投影,其特点是投影线与投影面垂直。在 VTK 中,vtkCamera 类提供了控制相机视口、投影方式等参数的方法。要将 2D 坐标转换成 3D 坐标,本质上需要进行一次逆变换,即从屏幕坐标系转换到世界坐标系。

VTK 正射投影:从多个 2D 坐标反算 3D 空间坐标的实用技巧

这个过程涉及到以下几个关键步骤:

VTK 正射投影:从多个 2D 坐标反算 3D 空间坐标的实用技巧
  1. 屏幕坐标系到视口坐标系的转换:将 2D 屏幕坐标转换为视口坐标,需要考虑窗口大小、像素坐标原点等因素。
  2. 视口坐标系到裁剪坐标系的转换:视口坐标是一个归一化的坐标范围,通常在 [-1, 1] 之间。我们需要将视口坐标转换为裁剪坐标,这一步涉及到投影矩阵的逆矩阵。
  3. 裁剪坐标系到世界坐标系的转换:最后,将裁剪坐标转换为世界坐标,需要考虑相机的位置、方向、缩放等参数。这一步涉及到模型视图矩阵的逆矩阵。

具体代码解决方案:VTK 实现 2D 到 3D 坐标转换

以下是一个示例代码,展示了如何使用 VTK 实现 2D 坐标到 3D 坐标的转换。

VTK 正射投影:从多个 2D 坐标反算 3D 空间坐标的实用技巧
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkRenderer.h>
#include <vtkCamera.h>
#include <vtkSphereSource.h>
#include <vtkPolyDataMapper.h>
#include <vtkActor.h>
#include <vtkWorldPointPicker.h>
#include <vtkProperty.h>

int main()
{
  // 创建渲染器、渲染窗口和交互器
  vtkRenderer *renderer = vtkRenderer::New();
  vtkRenderWindow *renderWindow = vtkRenderWindow::New();
  renderWindow->AddRenderer(renderer);
  vtkRenderWindowInteractor *interactor = vtkRenderWindowInteractor::New();
  interactor->SetRenderWindow(renderWindow);

  // 创建一个球体
  vtkSphereSource *sphereSource = vtkSphereSource::New();
  sphereSource->SetCenter(0.0, 0.0, 0.0);
  sphereSource->SetRadius(1.0);
  sphereSource->Update();

  vtkPolyDataMapper *mapper = vtkPolyDataMapper::New();
  mapper->SetInputConnection(sphereSource->GetOutputPort());

  vtkActor *actor = vtkActor::New();
  actor->SetMapper(mapper);
  renderer->AddActor(actor);

  // 设置相机为正射投影
  vtkCamera *camera = renderer->GetActiveCamera();
  camera->SetParallelProjection(true);
  // 设置相机位置和焦点
  camera->SetPosition(0, 0, 5);
  camera->SetFocalPoint(0, 0, 0);
  renderer->ResetCamera();

  // 创建 WorldPointPicker 用于拾取 3D 坐标
  vtkWorldPointPicker *picker = vtkWorldPointPicker::New();
  interactor->SetPicker(picker);

  // 定义一个鼠标点击事件的回调函数
  interactor->AddObserver(vtkCommand::LeftButtonPressEvent, [](vtkObject* caller, unsigned long eventId, void* clientData, void* callData) {
    vtkRenderWindowInteractor *iren = static_cast<vtkRenderWindowInteractor*>(caller);
    vtkWorldPointPicker *picker = static_cast<vtkWorldPointPicker*>(iren->GetPicker());
    int x = iren->GetEventPosition()[0];
    int y = iren->GetEventPosition()[1];

    // 使用 Pick3DPoint 方法获取 3D 坐标
    picker->Pick(x, iren->GetRenderWindow()->GetYSize() - y - 1, 0, iren->GetRenderer()); // 注意 Y 坐标需要翻转
    double *pos = picker->GetPickPosition();

    std::cout << "Clicked at 2D: (" << x << ", " << y << ")\n";
    std::cout << "Picked 3D: (" << pos[0] << ", " << pos[1] << ", " << pos[2] << ")\n";

    // 可视化拾取到的 3D 点 (可选)
    vtkSphereSource *pickedSphere = vtkSphereSource::New();
    pickedSphere->SetCenter(pos[0], pos[1], pos[2]);
    pickedSphere->SetRadius(0.1);
    pickedSphere->Update();

    vtkPolyDataMapper *pickedMapper = vtkPolyDataMapper::New();
    pickedMapper->SetInputConnection(pickedSphere->GetOutputPort());

    vtkActor *pickedActor = vtkActor::New();
    pickedActor->SetMapper(pickedMapper);
    pickedActor->GetProperty()->SetColor(1, 0, 0); // 红色
    static_cast<vtkRenderer*>(clientData)->AddActor(pickedActor);

    pickedSphere->Delete();
    pickedMapper->Delete();
    pickedActor->Delete();
    iren->GetRenderWindow()->Render();
  }, renderer);


  // 初始化交互器并开始渲染
  renderer->SetBackground(0.1, 0.2, 0.4);
  renderWindow->SetSize(600, 600);
  renderWindow->Render();
  interactor->Initialize();
  interactor->Start();

  // 清理资源
  sphereSource->Delete();
  mapper->Delete();
  actor->Delete();
  camera->Delete();
  renderer->Delete();
  renderWindow->Delete();
  interactor->Delete();
  picker->Delete();

  return 0;
}

代码解析:

  • 创建了 VTK 渲染管线,包括渲染器、渲染窗口和交互器。
  • 创建了一个球体作为示例几何体。
  • 设置相机为正射投影,并调整相机的位置和焦点。
  • 使用 vtkWorldPointPicker 类来拾取 3D 坐标。Pick3DPoint 函数利用渲染器的相机参数和鼠标点击位置,计算得到对应的 3D 坐标。
  • 鼠标点击事件的回调函数中,首先获取鼠标的 2D 坐标,然后调用 picker->Pick() 方法进行拾取。注意,VTK 的 Y 轴方向与屏幕坐标系相反,需要进行 Y 坐标的翻转。
  • 为了验证拾取的 3D 坐标是否正确,我们在拾取到的位置绘制了一个小的红色球体。

实战避坑经验总结

  • Y 轴翻转: VTK 的 Y 轴方向与屏幕坐标系相反,务必注意 Y 坐标的翻转,否则拾取到的 3D 坐标会不正确。
  • 深度值的理解: 在正射投影中,所有投影线的深度值都是相同的。因此,仅仅通过 2D 坐标是无法确定 3D 坐标的深度的。通常我们需要假设一个深度值,例如将深度值设为 0,或者使用其他方法来确定深度值。vtkWorldPointPicker 默认使用的深度值可能需要根据实际场景进行调整。
  • 相机参数的正确设置: 相机的位置、方向、缩放等参数对坐标转换的结果有很大影响。确保相机参数设置正确,才能得到正确的 3D 坐标。
  • 性能优化: 如果需要频繁进行 2D 到 3D 坐标的转换,可以考虑缓存相机参数,避免重复计算。另外,可以使用多线程来加速计算。
  • 坐标系转换的理解:深刻理解屏幕坐标系、视口坐标系、裁剪坐标系和世界坐标系之间的转换关系,是解决此类问题的关键。

希望本文能够帮助读者理解 VTK 相机正射投影中,通过多个 2D 坐标计算 3D 坐标的方法,并能在实际项目中灵活应用。同时,需要注意在使用 Nginx 这类反向代理服务器时,需要考虑在高并发场景下的性能优化,例如调整 worker 进程数、使用 epoll 模型、设置合理的缓存策略等。 如果使用了宝塔面板,也需要关注其资源占用情况,避免影响服务器的整体性能。 这些后端知识在构建高性能应用时至关重要。

VTK 正射投影:从多个 2D 坐标反算 3D 空间坐标的实用技巧

转载请注明出处: 代码一只喵

本文的链接地址: http://m.acea2.store/blog/072721.SHTML

本文最后 发布于2026-04-20 08:38:20,已经过了7天没有更新,若内容或图片 失效,请留言反馈

()
您可能对以下文章感兴趣
评论
  • 起床困难户 1 天前
    讲的真透彻,之前一直卡在 Y 轴翻转的问题上,这篇文章解决了我的疑惑!
  • 猫奴本奴 6 小时前
    文章深入浅出,收藏了! Nginx 的知识点也顺带复习了一遍。