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>
相关推荐
墨神谕2 小时前
Java中,为什么要将.java文件编译成,class文件,而不是直接将.java编译成机器码
java·开发语言
和小潘一起学AI3 小时前
CentOS 7安装Anaconda
开发语言·python
努力努力再努力dyx3 小时前
【无标题】
开发语言·python
傻小胖3 小时前
Object.defineProperty() 完整指南
开发语言·前端·javascript
xyx-3v3 小时前
qt创建新工程
开发语言·c++·qt
小陈工3 小时前
Python Web开发入门(十六):前后端分离架构设计——从“各自为政”到“高效协同”
开发语言·前端·数据库·人工智能·python
前进的李工3 小时前
MySQL用户管理与权限控制指南(含底层架构说明)
开发语言·数据库·sql·mysql·架构
少司府3 小时前
C++基础入门:类和对象(中)
c语言·开发语言·c++·类和对象·运算符重载·默认成员函数
橘子编程4 小时前
操作系统原理:从入门到精通全解析
java·linux·开发语言·windows·计算机网络·面试