使用 PCL 和 Qt 实现点云可视化与交互

下面我将介绍如何结合点云库(PCL)和Qt框架(特别是QML)来实现点云的可视化与交互功能,包括高亮选择等效果。

1. 基本架构设计

首先需要建立一个结合PCL和Qt的基本架构:

cpp 复制代码
// PCLQtViewer.h
#pragma once

#include <QObject>
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>

class PCLQtViewer : public QObject
{
    Q_OBJECT
    
public:
    explicit PCLQtViewer(QObject* parent = nullptr);
    
    // 加载点云
    Q_INVOKABLE void loadPointCloud(const QString& filePath);
    
    // 获取点云数据供QML使用
    Q_INVOKABLE QVariantList getPointCloudData() const;
    
    // 高亮选择的点
    Q_INVOKABLE void highlightPoints(const QVariantList& indices);
    
signals:
    void pointCloudLoaded();
    void selectionChanged();
    
private:
    pcl::PointCloud<pcl::PointXYZRGB>::Ptr m_cloud;
    std::vector<int> m_selectedIndices;
};

2. PCL与Qt的集成实现

cpp 复制代码
// PCLQtViewer.cpp
#include "PCLQtViewer.h"
#include <pcl/io/pcd_io.h>
#include <pcl/common/colors.h>

PCLQtViewer::PCLQtViewer(QObject* parent) 
    : QObject(parent), m_cloud(new pcl::PointCloud<pcl::PointXYZRGB>())
{
}

void PCLQtViewer::loadPointCloud(const QString& filePath)
{
    if (pcl::io::loadPCDFile<pcl::PointXYZRGB>(filePath.toStdString(), *m_cloud) == -1) {
        qWarning() << "Failed to load PCD file";
        return;
    }
    emit pointCloudLoaded();
}

QVariantList PCLQtViewer::getPointCloudData() const
{
    QVariantList points;
    for (const auto& point : *m_cloud) {
        QVariantMap pt;
        pt["x"] = point.x;
        pt["y"] = point.y;
        pt["z"] = point.z;
        pt["r"] = point.r;
        pt["g"] = point.g;
        pt["b"] = point.b;
        pt["selected"] = std::find(m_selectedIndices.begin(), 
                                 m_selectedIndices.end(), 
                                 &point - &m_cloud->points[0]) != m_selectedIndices.end();
        points.append(pt);
    }
    return points;
}

void PCLQtViewer::highlightPoints(const QVariantList& indices)
{
    m_selectedIndices.clear();
    for (const auto& idx : indices) {
        m_selectedIndices.push_back(idx.toInt());
    }
    emit selectionChanged();
}

3. QML点云可视化界面

qml

cpp 复制代码
// main.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import Qt3D.Core 2.15
import Qt3D.Render 2.15
import Qt3D.Input 2.15
import Qt3D.Extras 2.15

ApplicationWindow {
    id: window
    width: 1280
    height: 720
    visible: true
    
    PCLQtViewer {
        id: pclViewer
        onPointCloudLoaded: pointCloudModel.reload()
        onSelectionChanged: pointCloudModel.reload()
    }
    
    // 3D视图
    Qt3DWindow {
        id: view3d
        anchors.fill: parent
        
        Camera {
            id: camera
            projectionType: CameraLens.PerspectiveProjection
            fieldOfView: 45
            aspectRatio: view3d.width / view3d.height
            nearPlane: 0.1
            farPlane: 1000.0
            position: Qt.vector3d(0.0, 0.0, 10.0)
            upVector: Qt.vector3d(0.0, 1.0, 0.0)
            viewCenter: Qt.vector3d(0.0, 0.0, 0.0)
        }
        
        // 点云实体
        Entity {
            id: pointCloudEntity
            
            components: [
                Transform {
                    id: cloudTransform
                    scale: 1.0
                },
                GeometryRenderer {
                    id: pointGeometry
                    primitiveType: GeometryRenderer.Points
                    geometry: Geometry {
                        boundingVolumePositionAttribute: position
                        
                        Attribute {
                            id: position
                            attributeType: Attribute.VertexAttribute
                            vertexBaseType: Attribute.Float
                            vertexSize: 3
                            byteOffset: 0
                            byteStride: 6 * 4
                            count: pointCloudModel.count
                            buffer: pointBuffer
                        }
                        
                        Attribute {
                            id: color
                            attributeType: Attribute.ColorAttribute
                            vertexBaseType: Attribute.Float
                            vertexSize: 3
                            byteOffset: 3 * 4
                            byteStride: 6 * 4
                            count: pointCloudModel.count
                            buffer: pointBuffer
                        }
                        
                        Buffer {
                            id: pointBuffer
                            data: pointCloudModel.geometryData
                        }
                    }
                },
                Material {
                    effect: Effect {
                        techniques: Technique {
                            renderPasses: RenderPass {
                                shaderProgram: ShaderProgram {
                                    vertexShaderCode: "
                                        #version 330
                                        in vec3 vertexPosition;
                                        in vec3 vertexColor;
                                        out vec3 color;
                                        uniform mat4 mvp;
                                        void main() {
                                            color = vertexColor;
                                            gl_Position = mvp * vec4(vertexPosition, 1.0);
                                            gl_PointSize = 3.0;
                                        }"
                                    fragmentShaderCode: "
                                        #version 330
                                        in vec3 color;
                                        out vec4 fragColor;
                                        void main() {
                                            fragColor = vec4(color, 1.0);
                                        }"
                                }
                            }
                        }
                    }
                }
            ]
        }
        
        OrbitCameraController {
            camera: camera
        }
    }
    
    // 点云数据模型
    ListModel {
        id: pointCloudModel
        
        function reload() {
            clear();
            var points = pclViewer.getPointCloudData();
            for (var i = 0; i < points.length; i++) {
                append(points[i]);
            }
        }
        
        property var geometryData: {
            var data = new Float32Array(count * 6);
            for (var i = 0; i < count; i++) {
                var pt = get(i);
                data[i * 6] = pt.x;
                data[i * 6 + 1] = pt.y;
                data[i * 6 + 2] = pt.z;
                
                // 选中的点显示为红色,否则使用原始颜色
                if (pt.selected) {
                    data[i * 6 + 3] = 1.0;
                    data[i * 6 + 4] = 0.0;
                    data[i * 6 + 5] = 0.0;
                } else {
                    data[i * 6 + 3] = pt.r / 255.0;
                    data[i * 6 + 4] = pt.g / 255.0;
                    data[i * 6 + 5] = pt.b / 255.0;
                }
            }
            return data;
        }
    }
    
    // 控制面板
    Rectangle {
        width: 300
        height: parent.height
        color: "#eee"
        
        Column {
            spacing: 10
            padding: 10
            
            Button {
                text: "加载点云"
                onClicked: fileDialog.open()
            }
            
            ListView {
                id: selectionListView
                width: parent.width
                height: 200
                model: ListModel {}
                
                delegate: ItemDelegate {
                    width: parent.width
                    text: "点 " + index
                    highlighted: pointCloudModel.get(index).selected
                    
                    onClicked: {
                        pclViewer.highlightPoints([index]);
                    }
                }
            }
        }
    }
    
    FileDialog {
        id: fileDialog
        title: "选择点云文件"
        folder: shortcuts.home
        nameFilters: ["PCD文件 (*.pcd)"]
        onAccepted: pclViewer.loadPointCloud(fileDialog.fileUrl)
    }
}

4. 主程序集成

cpp

cpp 复制代码
// main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "PCLQtViewer.h"

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QGuiApplication app(argc, argv);
    
    qmlRegisterType<PCLQtViewer>("PCL", 1, 0, "PCLQtViewer");
    
    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;
    
    return app.exec();
}

5. 高级功能扩展

点云聚类高亮

cpp

cpp 复制代码
// 在PCLQtViewer类中添加
Q_INVOKABLE void highlightCluster(int clusterId)
{
    // 假设已经执行过聚类算法,结果存储在m_clusterIndices中
    m_selectedIndices = m_clusterIndices[clusterId];
    emit selectionChanged();
}

3D框选功能

qml

cpp 复制代码
// 在Qt3DWindow中添加
Entity {
    components: [
        ObjectPicker {
            id: picker
            dragEnabled: true
            onClicked: {
                var worldPos = pick.worldPosition;
                // 转换坐标并选择附近的点
                pclViewer.selectPointsInSphere(worldPos.x, worldPos.y, worldPos.z, 0.5);
            }
            onPressed: {
                // 开始框选
                selectionRect.visible = true;
                selectionRect.x = pick.x;
                selectionRect.y = pick.y;
            }
            onReleased: {
                // 结束框选
                selectionRect.visible = false;
                var selected = selectPointsInRectangle(selectionRect);
                pclViewer.highlightPoints(selected);
            }
        }
    ]
}

Rectangle {
    id: selectionRect
    visible: false
    color: "#8033B5E5"
    border.color: "#0033B5E5"
}

点云着色模式切换

qml

cpp 复制代码
ComboBox {
    model: ["原始颜色", "高度着色", "曲率着色", "聚类着色"]
    onCurrentIndexChanged: pclViewer.setColorMode(currentIndex)
}

6. 性能优化建议

  1. 点云分块加载:对于大型点云,实现分块加载机制

  2. LOD(细节层次):根据视点距离动态调整显示细节

  3. 后台处理:将PCL处理任务放在后台线程

  4. GPU加速:使用计算着色器处理点云数据

  5. 空间索引:使用八叉树等结构加速空间查询

7. 项目配置(CMake)

cpp 复制代码
cmake_minimum_required(VERSION 3.5)

project(PCL_Qt_Viewer)

find_package(Qt5 COMPONENTS Quick QuickControls2 3DCore 3DRender 3DInput 3DExtras REQUIRED)
find_package(PCL 1.8 REQUIRED)

set(CMAKE_AUTOMOC ON)
set(CMAKE_CXX_STANDARD 14)

add_executable(${PROJECT_NAME}
    main.cpp
    PCLQtViewer.cpp
    PCLQtViewer.h
)

target_link_libraries(${PROJECT_NAME}
    Qt5::Quick
    Qt5::QuickControls2
    Qt5::3DCore
    Qt5::3DRender
    Qt5::3DInput
    Qt5::3DExtras
    ${PCL_LIBRARIES}
)

这种集成方式充分利用了PCL强大的点云处理能力和Qt/QML出色的UI/交互能力,可以构建出功能丰富、交互友好的点云处理应用程序。

相关推荐
用户805533698032 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner2 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz7 天前
QML Hello World 入门示例
qt
xcyxiner10 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner11 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner11 天前
DicomViewer (添加模型类)3
qt
xcyxiner12 天前
DicomViewer (目录调整) 2
qt
xcyxiner12 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
桥田智能14 天前
桥田智能 QT-650S:面向白车身焊装的 800kg 重载快换解决方案
开发语言·qt·系统架构
森G14 天前
75、服务器源码解析---------云视频服务项目
linux·服务器·网络·c++·qt