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

发布于:2025-04-22 ⋅ 阅读:(17) ⋅ 点赞:(0)

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

1. 基本架构设计

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

// 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的集成实现

// 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

// 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

// 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

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

3D框选功能

qml

// 在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

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

6. 性能优化建议

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

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

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

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

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

7. 项目配置(CMake)

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/交互能力,可以构建出功能丰富、交互友好的点云处理应用程序。


网站公告

今日签到

点亮在社区的每一天
去签到