Qt5.15+VTK9.3.0实现点云点选功能

之前实现了,直接使用pcl的PCLVisualizer进行的点选,这是pcl对vtk进行了封装,能够实现点选功能。

但是现在有个问题就是,我把上面的代码嵌入qt中,就实现不了点选功能,我尝试了好久,还是一直卡住了。

分析原因:

  1. PCLVisualizer 内部管理了自己的交互器
  2. 在 Qt VTK 集成环境中,交互器被重新配置
  3. 回调函数的调用链被破坏

可能由于笔者笨拙,就是实现不了。

所以改用 VTK 原生的 vtkPointPicker,直接与底层的渲染器和交互器交互,不依赖 PCLVisualizer 的封装。

此外,再加上我的版本是VTK9.3.0,网上的更多的是低版本的,内容不太一样。

点选功能实现详解

1. 核心组件

在 VTK 中实现点选功能主要依赖以下几个组件:

复制代码
┌─────────────────────────────────────────────────────────────┐
│                    VTK 点选机制                               │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  vtkRenderWindowInteractor     ← 鼠标事件的处理者           │
│           ↓                                                 │
│  vtkPointPicker                ← 负责在屏幕上点击位置拾取点   │
│           ↓                                                 │
│  vtkRenderer                   ← 渲染器,包含所有可拾取对象   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

2. 实现步骤

第一步:创建点选器 (setupPointPicking)
cpp 复制代码
void pointpicking::setupPointPicking() {
    // 1. 创建 vtkPointPicker 实例
    pointPicker = vtkSmartPointer<vtkPointPicker>::New();
    
    // 2. 获取交互器
    vtkRenderWindowInteractor* interactor = ui.openGLWidget->interactor();
    
    // 3. 将点选器设置到交互器
    interactor->SetPicker(pointPicker);
    
    // 4. 添加鼠标左键事件观察者
    //    当左键按下时,调用 onPointPicked 回调
    interactor->AddObserver(vtkCommand::LeftButtonPressEvent, 
                           this, 
                           &pointpicking::onPointPicked);
}

关键点解释

  • vtkPointPicker 是专门用于拾取点的选择器
  • AddObserver 是 VTK 的观察者模式,监听鼠标事件
  • vtkCommand::LeftButtonPressEvent 是左键按下的事件类型
第二步:处理点击事件 (onPointPicked)
cpp 复制代码
void pointpicking::onPointPicked(vtkObject* caller, 
                                 unsigned long eventId, 
                                 void* callData) {
    // 1. 获取触发事件的交互器
    vtkRenderWindowInteractor* interactor = 
        reinterpret_cast<vtkRenderWindowInteractor*>(caller);
    
    // 2. 获取鼠标点击的屏幕坐标
    int* pos = interactor->GetEventPosition();
    
    // 3. 执行拾取操作
    //    在 (pos[0], pos[1]) 位置,拾取 renderer 中的对象
    pointPicker->Pick(pos[0], pos[1], 0, renderer);
    
    // 4. 获取拾取结果
    if (pointPicker->GetPointId() != -1) {
        // 拾取成功,获取世界坐标
        double* pickedPos = pointPicker->GetPickPosition();
        
        // 显示结果
        QString info = QString("Picked point - Coords: (%1, %2, %3)")
                           .arg(pickedPos[0])
                           .arg(pickedPos[1])
                           .arg(pickedPos[2]);
        logMessage(info);
    }
}

关键点解释

  • GetEventPosition() 返回鼠标在窗口中的像素坐标 (x, y)
  • Pick(x, y, 0, renderer) 在指定位置拾取对象,第三个参数是 z 平面(设为 0)
  • GetPointId() 返回拾取到的点 ID,-1 表示没有拾取到任何点
  • GetPickPosition() 返回拾取点的世界坐标 (x, y, z)

3. 工作流程图

复制代码
用户点击屏幕
     ↓
VTK 捕获鼠标事件 (LeftButtonPressEvent)
     ↓
触发 onPointPicked 回调
     ↓
获取鼠标屏幕坐标 (pos[0], pos[1])
     ↓
调用 pointPicker->Pick() 执行拾取
     ↓
遍历 renderer 中所有 actor
     ↓
找到点击位置最近的几何图元
     ↓
返回拾取结果 (PointId 和 坐标)
     ↓
在日志面板显示结果

运行结果

完整代码

pointpicking.h

cpp 复制代码
#pragma once

#include <QtWidgets/QMainWindow>
#include "ui_pointpicking.h"
#include <pcl/io/pcd_io.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/visualization/point_picking_event.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkCommand.h>
#include <vtkCellPicker.h>
#include <vtkPointPicker.h>
#include <vtkActor.h>
#include <vtkRenderer.h>
#include <QVTKRenderWidget.h>
#include <QVTKOpenGLNativeWidget.h>

class pointpicking : public QMainWindow
{
    Q_OBJECT

public:
    pointpicking(QWidget* parent = nullptr);
    ~pointpicking();

private:
    Ui::pointpicking ui;
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud;
    boost::shared_ptr<pcl::visualization::PCLVisualizer> view;
    vtkSmartPointer<vtkRenderer> renderer;
    vtkSmartPointer<vtkPointPicker> pointPicker;

    void initialVtkWidget();
    void logMessage(const QString& message);
    void setupPointPicking();
    void onPointPicked(vtkObject* caller, unsigned long eventId, void* callData);

private slots:
    void onOpen();

};

pointpicking.cpp

cpp 复制代码
#include "pointpicking.h"
#include <QFileDialog>
#include <QDebug>
#include <QDateTime>

pointpicking::pointpicking(QWidget* parent) : QMainWindow(parent) {
    ui.setupUi(this);

    initialVtkWidget();

    connect(ui.actionopen, SIGNAL(triggered()), this, SLOT(onOpen()));

    logMessage("Application started");
    logMessage("Tip: Hold Shift + Left click to pick points");
}

pointpicking::~pointpicking() {}

void pointpicking::setupPointPicking() {
    if (!view || !ui.openGLWidget->interactor())
        return;

    pointPicker = vtkSmartPointer<vtkPointPicker>::New();

    vtkRenderWindowInteractor* interactor = ui.openGLWidget->interactor();

    interactor->SetPicker(pointPicker);

    interactor->AddObserver(vtkCommand::LeftButtonPressEvent, this, &pointpicking::onPointPicked);

    logMessage("Point picking enabled");
}

void pointpicking::onPointPicked(vtkObject* caller, unsigned long eventId, void* callData) {
    vtkRenderWindowInteractor* interactor = reinterpret_cast<vtkRenderWindowInteractor*>(caller);
    if (!interactor)
        return;

    int* pos = interactor->GetEventPosition();
    pointPicker->Pick(pos[0], pos[1], 0, renderer);

    if (pointPicker->GetPointId() != -1) {
        double* pickedPos = pointPicker->GetPickPosition();

        QString info = QString("Picked point - PointId: %1, Coords: (%2, %3, %4)")
                           .arg(pointPicker->GetPointId())
                           .arg(pickedPos[0], 0, 'f', 4)
                           .arg(pickedPos[1], 0, 'f', 4)
                           .arg(pickedPos[2], 0, 'f', 4);
        logMessage(info);
    }
}

void pointpicking::onOpen() {

    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(
        new pcl::PointCloud<pcl::PointXYZ>());
    QString fileName = QFileDialog::getOpenFileName(this, "Open PointCloud", ".",
        "Open PCD files(*.pcd)");
    if (fileName == "") return;

    logMessage("Loading: " + fileName);

    if (pcl::io::loadPCDFile(fileName.toStdString(), *cloud) == -1) {
        logMessage("Error: Failed to load PCD file");
        return;
    }

    QString info = QString("Loaded %1 points").arg(cloud->size());
    logMessage(info);

    this->cloud = cloud;

    view->removeAllPointClouds();
    view->addPointCloud(cloud, "cloud");
    view->resetCamera();
    ui.openGLWidget->update();

    setupPointPicking();

    logMessage("Point cloud displayed");
}

void pointpicking::initialVtkWidget() {
    renderer = vtkSmartPointer<vtkRenderer>::New();
    vtkSmartPointer<vtkGenericOpenGLRenderWindow> renderWindow =
        vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
    renderWindow->AddRenderer(renderer);
    view.reset(new pcl::visualization::PCLVisualizer(renderer, renderWindow,
        "viewer", false));
    view->setBackgroundColor(0.2, 0.2, 0.2);
    view->setupInteractor(ui.openGLWidget->interactor(),
        ui.openGLWidget->renderWindow());
    ui.openGLWidget->setRenderWindow(view->getRenderWindow());

    logMessage("VTK widget initialized");
}

void pointpicking::logMessage(const QString& message) {
    QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
    ui.logTextEdit->append(QString("[%1] %2").arg(timestamp, message));
}

main.cpp

cpp 复制代码
#include "pointpicking.h"
#include <QtWidgets/QApplication>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    pointpicking window;
    window.show();
    return app.exec();
}

pointpicking.ui

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>pointpicking</class>
 <widget class="QMainWindow" name="pointpicking">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>800</width>
    <height>600</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>pointpicking</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QVBoxLayout" name="verticalLayout">
    <item>
     <widget class="QSplitter" name="splitter">
      <property name="orientation">
       <enum>Qt::Vertical</enum>
      </property>
      <widget class="QVTKOpenGLNativeWidget" name="openGLWidget">
       <property name="minimumSize">
        <size>
         <width>0</width>
         <height>300</height>
        </size>
       </property>
      </widget>
      <widget class="QTextEdit" name="logTextEdit">
       <property name="maximumSize">
        <size>
         <width>16777215</width>
         <height>150</height>
        </size>
       </property>
       <property name="readOnly">
        <bool>true</bool>
       </property>
      </widget>
     </widget>
    </item>
   </layout>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>800</width>
     <height>25</height>
    </rect>
   </property>
   <widget class="QMenu" name="menufile">
    <property name="title">
     <string>file</string>
    </property>
    <addaction name="actionopen"/>
   </widget>
   <addaction name="menufile"/>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
  <action name="actionopen">
   <property name="text">
    <string>open</string>
   </property>
  </action>
 </widget>
 <customwidgets>
  <customwidget>
   <class>QVTKOpenGLNativeWidget</class>
   <extends>QOpenGLWidget</extends>
   <header>qvtkopenglnativewidget.h</header>
  </customwidget>
 </customwidgets>
 <resources/>
 <connections/>
</ui>
相关推荐
用户805533698031 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner1 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz6 天前
QML Hello World 入门示例
qt
xcyxiner9 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner10 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner10 天前
DicomViewer (添加模型类)3
qt
xcyxiner11 天前
DicomViewer (目录调整) 2
qt
xcyxiner11 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
LDR00613 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术13 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript