基于 QT5.7.0 的八线激光雷达点云聚类实现

一、系统架构设计

复制代码
激光雷达 → 串口/UDP → 数据解析 → 点云存储 → 聚类算法 → 可视化显示
                  ↓           ↓         ↓         ↓
               QT串口     QList<Point>  DBSCAN    QCustomPlot/OpenGL

二、项目结构

复制代码
LidarCluster/
├── LidarCluster.pro       # QT项目文件
├── main.cpp              # 主程序入口
├── MainWindow.cpp/h      # 主窗口
├── LidarParser.cpp/h     # 雷达数据解析
├── ClusterAlgo.cpp/h     # 聚类算法
├── PointCloud.cpp/h      # 点云数据结构
└── PointCloudView.cpp/h  # 点云显示

三、点云数据结构

1、点定义

cpp 复制代码
// Point.h
#ifndef POINT_H
#define POINT_H

#include <QVector3D>
#include <QColor>

// 激光点结构
struct LidarPoint {
    float x;           // 米
    float y;           // 米
    float z;           // 米
    float distance;    // 距离
    float intensity;   // 反射强度
    int   laser_id;    // 激光线号 0-7
    int   cluster_id;  // 聚类ID (-1:噪声, 0+:聚类编号)
    QColor color;      // 显示颜色
    
    LidarPoint() : x(0), y(0), z(0), distance(0), 
                   intensity(0), laser_id(0), cluster_id(-1) {}
};

// 点云结构
class PointCloud {
public:
    PointCloud();
    void clear();
    void addPoint(const LidarPoint& point);
    int size() const;
    const LidarPoint& at(int index) const;
    QVector3D getMinBounds() const;
    QVector3D getMaxBounds() const;
    
private:
    QVector<LidarPoint> points;
};

#endif

四、激光雷达数据解析(八线)

cpp 复制代码
// LidarParser.h
#ifndef LIDARPARSER_H
#define LIDARPARSER_H

#include <QObject>
#include <QSerialPort>
#include "Point.h"

// 常见八线雷达协议
class LidarParser : public QObject {
    Q_OBJECT
    
public:
    explicit LidarParser(QObject *parent = nullptr);
    ~LidarParser();
    
    bool openSerial(const QString& port, int baud);
    void closeSerial();
    
signals:
    void newPointCloud(const PointCloud& cloud);
    void parseError(const QString& error);
    
private slots:
    void onSerialData();
    
private:
    // 解析雷达数据包
    bool parseDataPacket(const QByteArray& data);
    
    // 不同雷达型号的解析
    bool parseRoboSense(const QByteArray& data);
    bool parseVelodyneVLP8(const QByteArray& data);
    bool parseHesaiPandar8(const QByteArray& data);
    
    QSerialPort* serial;
    QByteArray buffer;
    PointCloud current_cloud;
    
    // 雷达参数
    float angle_resolution;  // 角度分辨率
    int points_per_packet;   // 每包点数
};
cpp 复制代码
// LidarParser.cpp
#include "LidarParser.h"
#include <QDebug>

LidarParser::LidarParser(QObject *parent) : QObject(parent) {
    serial = new QSerialPort(this);
    angle_resolution = 0.4f;  // 默认0.4度
    points_per_packet = 100;
}

bool LidarParser::openSerial(const QString& port, int baud) {
    serial->setPortName(port);
    serial->setBaudRate(baud);
    serial->setDataBits(QSerialPort::Data8);
    serial->setParity(QSerialPort::NoParity);
    serial->setStopBits(QSerialPort::OneStop);
    
    if (!serial->open(QIODevice::ReadWrite)) {
        emit parseError("无法打开串口: " + port);
        return false;
    }
    
    connect(serial, &QSerialPort::readyRead, 
            this, &LidarParser::onSerialData);
    
    return true;
}

void LidarParser::onSerialData() {
    buffer.append(serial->readAll());
    
    // 寻找数据包起始
    while (buffer.size() > 4) {
        // 检查起始标志(示例:0xEE 0xFF)
        if (static_cast<quint8>(buffer[0]) == 0xEE && 
            static_cast<quint8>(buffer[1]) == 0xFF) {
            
            // 检查包长度
            int packet_size = buffer[2] + (buffer[3] << 8);
            if (buffer.size() >= packet_size) {
                QByteArray packet = buffer.left(packet_size);
                buffer.remove(0, packet_size);
                
                if (parseDataPacket(packet)) {
                    emit newPointCloud(current_cloud);
                    current_cloud.clear();
                }
            } else {
                break;  // 数据不够,等待下次
            }
        } else {
            buffer.remove(0, 1);  // 移除无效字节
        }
    }
}

bool LidarParser::parseVelodyneVLP8(const QByteArray& data) {
    // VLP-8 数据包格式解析
    const quint8* raw = reinterpret_cast<const quint8*>(data.data());
    
    // 跳过包头
    int offset = 42;  // VLP-8 包头长度
    
    for (int block = 0; block < 12; block++) {  // 12个数据块
        quint16 flag = raw[offset] + (raw[offset+1] << 8);
        offset += 2;
        
        if (flag != 0xEEFF) break;  // 数据块标志
        
        quint16 azimuth = raw[offset] + (raw[offset+1] << 8);
        offset += 2;
        
        float angle = azimuth * 0.01f;  // 0.01度分辨率
        
        // 8个激光点
        for (int laser = 0; laser < 8; laser++) {
            quint16 distance = raw[offset] + (raw[offset+1] << 8);
            quint8 intensity = raw[offset+2];
            offset += 3;
            
            if (distance == 0) continue;  // 无效点
            
            LidarPoint point;
            point.laser_id = laser;
            point.distance = distance * 0.002f;  // 2mm分辨率
            point.intensity = intensity;
            
            // 计算3D坐标
            float vertical_angle = 0;  // 根据激光线号查表
            float vertical_angles[8] = { -15, 1, -13, 3, -11, 5, -9, 7 };
            
            float rad_azimuth = qDegreesToRadians(angle);
            float rad_elevation = qDegreesToRadians(vertical_angles[laser]);
            
            point.x = point.distance * cos(rad_elevation) * sin(rad_azimuth);
            point.y = point.distance * cos(rad_elevation) * cos(rad_azimuth);
            point.z = point.distance * sin(rad_elevation);
            
            current_cloud.addPoint(point);
        }
    }
    
    return true;
}

五、聚类算法实现

1、DBSCAN算法(推荐)

cpp 复制代码
// ClusterAlgo.h
#ifndef CLUSTERALGO_H
#define CLUSTERALGO_H

#include "Point.h"
#include <QVector>
#include <QSet>

class ClusterAlgo {
public:
    ClusterAlgo();
    
    // DBSCAN聚类
    int dbscan(PointCloud& cloud, float eps, int min_pts);
    
    // 欧式距离聚类
    int euclideanCluster(PointCloud& cloud, float distance_threshold);
    
    // 设置参数
    void setEps(float eps) { this->eps = eps; }
    void setMinPts(int min_pts) { this->min_pts = min_pts; }
    
    // 获取统计
    int getClusterCount() const { return cluster_count; }
    QVector<int> getClusterSizes() const { return cluster_sizes; }
    
private:
    // KD树加速(简单实现)
    QVector<int> regionQuery(const PointCloud& cloud, int point_idx, float eps);
    
    // 扩展聚类
    void expandCluster(PointCloud& cloud, int point_idx, 
                      QVector<int>& neighbors, int cluster_id);
    
    float eps;          // 邻域半径
    int min_pts;        // 最小点数
    int cluster_count;  // 聚类数量
    QVector<int> cluster_sizes;  // 各聚类大小
};
cpp 复制代码
// ClusterAlgo.cpp
#include "ClusterAlgo.h"
#include <cmath>
#include <QQueue>

ClusterAlgo::ClusterAlgo() : eps(0.2f), min_pts(5), cluster_count(0) {}

// 计算两点距离
static float distance3D(const LidarPoint& p1, const LidarPoint& p2) {
    float dx = p1.x - p2.x;
    float dy = p1.y - p2.y;
    float dz = p1.z - p2.z;
    return sqrt(dx*dx + dy*dy + dz*dz);
}

// DBSCAN主算法
int ClusterAlgo::dbscan(PointCloud& cloud, float eps, int min_pts) {
    this->eps = eps;
    this->min_pts = min_pts;
    this->cluster_count = 0;
    this->cluster_sizes.clear();
    
    int point_count = cloud.size();
    QVector<bool> visited(point_count, false);
    QVector<bool> noise(point_count, false);
    QVector<int> cluster_ids(point_count, -1);
    
    for (int i = 0; i < point_count; i++) {
        if (visited[i]) continue;
        
        visited[i] = true;
        
        // 查找邻域
        QVector<int> neighbors = regionQuery(cloud, i, eps);
        
        if (neighbors.size() < min_pts) {
            noise[i] = true;  // 标记为噪声
        } else {
            // 创建新聚类
            cluster_count++;
            cluster_ids[i] = cluster_count;
            
            // 扩展聚类
            expandCluster(cloud, i, neighbors, cluster_count);
        }
    }
    
    // 更新点云聚类ID
    for (int i = 0; i < point_count; i++) {
        cloud.at(i).cluster_id = cluster_ids[i];
    }
    
    return cluster_count;
}

// 查找邻域点
QVector<int> ClusterAlgo::regionQuery(const PointCloud& cloud, 
                                      int point_idx, float eps) {
    QVector<int> neighbors;
    const LidarPoint& center = cloud.at(point_idx);
    
    for (int i = 0; i < cloud.size(); i++) {
        if (i == point_idx) continue;
        
        float dist = distance3D(center, cloud.at(i));
        if (dist <= eps) {
            neighbors.append(i);
        }
    }
    
    return neighbors;
}

// 扩展聚类
void ClusterAlgo::expandCluster(PointCloud& cloud, int point_idx,
                               QVector<int>& neighbors, int cluster_id) {
    QVector<bool> visited(cloud.size(), false);
    visited[point_idx] = true;
    
    QQueue<int> queue;
    for (int neighbor_idx : neighbors) {
        queue.enqueue(neighbor_idx);
    }
    
    int cluster_size = 1;
    
    while (!queue.isEmpty()) {
        int current_idx = queue.dequeue();
        
        if (visited[current_idx]) continue;
        visited[current_idx] = true;
        
        // 标记为当前聚类
        cloud.at(current_idx).cluster_id = cluster_id;
        cluster_size++;
        
        // 查找当前点的邻域
        QVector<int> current_neighbors = regionQuery(cloud, current_idx, eps);
        
        if (current_neighbors.size() >= min_pts) {
            for (int neighbor_idx : current_neighbors) {
                if (!visited[neighbor_idx]) {
                    queue.enqueue(neighbor_idx);
                }
            }
        }
    }
    
    cluster_sizes.append(cluster_size);
}

2、欧式距离聚类(更快)

cpp 复制代码
// 欧式距离聚类
int ClusterAlgo::euclideanCluster(PointCloud& cloud, float distance_threshold) {
    int point_count = cloud.size();
    QVector<bool> processed(point_count, false);
    QVector<int> cluster_ids(point_count, -1);
    cluster_count = 0;
    cluster_sizes.clear();
    
    for (int i = 0; i < point_count; i++) {
        if (processed[i]) continue;
        
        // 创建新聚类
        cluster_count++;
        int cluster_size = 0;
        
        QQueue<int> queue;
        queue.enqueue(i);
        processed[i] = true;
        
        while (!queue.isEmpty()) {
            int current_idx = queue.dequeue();
            cluster_ids[current_idx] = cluster_count;
            cluster_size++;
            
            const LidarPoint& current_point = cloud.at(current_idx);
            
            // 查找邻近点
            for (int j = 0; j < point_count; j++) {
                if (processed[j] || j == current_idx) continue;
                
                float dist = distance3D(current_point, cloud.at(j));
                if (dist <= distance_threshold) {
                    queue.enqueue(j);
                    processed[j] = true;
                }
            }
        }
        
        cluster_sizes.append(cluster_size);
        
        // 忽略太小的聚类
        if (cluster_size < min_pts) {
            for (int j = 0; j < point_count; j++) {
                if (cluster_ids[j] == cluster_count) {
                    cluster_ids[j] = -1;  // 标记为噪声
                }
            }
            cluster_count--;
            cluster_sizes.removeLast();
        }
    }
    
    // 更新点云聚类ID
    for (int i = 0; i < point_count; i++) {
        cloud.at(i).cluster_id = cluster_ids[i];
    }
    
    return cluster_count;
}

六、点云可视化(QCustomPlot + OpenGL)

1、2D显示(QCustomPlot)

cpp 复制代码
// PointCloudView.h
#ifndef POINTCLOUDVIEW_H
#define POINTCLOUDVIEW_H

#include <QWidget>
#include <QCustomPlot/qcustomplot.h>
#include "Point.h"

class PointCloudView : public QCustomPlot {
    Q_OBJECT
    
public:
    explicit PointCloudView(QWidget *parent = nullptr);
    void updatePointCloud(const PointCloud& cloud);
    void clearPoints();
    
    // 视图控制
    void zoomIn();
    void zoomOut();
    void fitView();
    
private:
    void setupGraph();
    QColor getClusterColor(int cluster_id);
    
    QCPColorMap* color_map;
    QCPColorScale* color_scale;
};
cpp 复制代码
// PointCloudView.cpp
#include "PointCloudView.h"
#include <QDebug>

PointCloudView::PointCloudView(QWidget *parent) : QCustomPlot(parent) {
    // 设置交互
    setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectPlottables);
    
    // 初始化颜色表
    QVector<QColor> color_table = {
        QColor(255, 0, 0),     // 红
        QColor(0, 255, 0),     // 绿
        QColor(0, 0, 255),     // 蓝
        QColor(255, 255, 0),   // 黄
        QColor(255, 0, 255),   // 品红
        QColor(0, 255, 255),   // 青
        QColor(255, 128, 0),   // 橙
        QColor(128, 0, 255),   // 紫
        QColor(128, 128, 0),   // 橄榄
        QColor(0, 128, 128)    // 深青
    };
}

void PointCloudView::updatePointCloud(const PointCloud& cloud) {
    clearGraphs();
    
    // 按聚类分组显示
    QMap<int, QVector<QCPGraphData>> cluster_points;
    
    for (int i = 0; i < cloud.size(); i++) {
        const LidarPoint& point = cloud.at(i);
        
        if (point.cluster_id >= 0) {
            cluster_points[point.cluster_id].append(
                QCPGraphData(point.x, point.y));
        } else {
            // 噪声点
            cluster_points[-1].append(
                QCPGraphData(point.x, point.y));
        }
    }
    
    // 绘制每个聚类
    QMapIterator<int, QVector<QCPGraphData>> it(cluster_points);
    while (it.hasNext()) {
        it.next();
        
        QCPGraph* graph = addGraph();
        graph->setLineStyle(QCPGraph::lsNone);
        graph->setScatterStyle(QCPScatterStyle(
            QCPScatterStyle::ssCircle, 4));
        
        // 设置颜色
        if (it.key() == -1) {
            graph->setPen(QPen(QColor(128, 128, 128, 50)));  // 灰色噪声点
        } else {
            graph->setPen(QPen(getClusterColor(it.key() % 10)));
        }
        
        // 设置数据
        graph->data()->set(it.value());
    }
    
    replot();
    fitView();
}

QColor PointCloudView::getClusterColor(int cluster_id) {
    static QVector<QColor> colors = {
        QColor(255, 0, 0),     // 红
        QColor(0, 255, 0),     // 绿
        QColor(0, 0, 255),     // 蓝
        QColor(255, 255, 0),   // 黄
        QColor(255, 0, 255),   // 品红
        QColor(0, 255, 255),   // 青
        QColor(255, 128, 0),   // 橙
        QColor(128, 0, 255),   // 紫
        QColor(128, 128, 0),   // 橄榄
        QColor(0, 128, 128)    // 深青
    };
    return colors[cluster_id % colors.size()];
}

2、3D显示(QOpenGLWidget)

cpp 复制代码
// GLPointCloudView.h
#ifndef GLPOINTCLOUDVIEW_H
#define GLPOINTCLOUDVIEW_H

#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QOpenGLShaderProgram>
#include <QMatrix4x4>
#include "Point.h"

class GLPointCloudView : public QOpenGLWidget, protected QOpenGLFunctions {
    Q_OBJECT
    
public:
    explicit GLPointCloudView(QWidget *parent = nullptr);
    ~GLPointCloudView();
    
    void updatePointCloud(const PointCloud& cloud);
    
protected:
    void initializeGL() override;
    void resizeGL(int w, int h) override;
    void paintGL() override;
    
    void mousePressEvent(QMouseEvent *event) override;
    void mouseMoveEvent(QMouseEvent *event) override;
    void wheelEvent(QWheelEvent *event) override;
    
private:
    QOpenGLShaderProgram* shader_program;
    GLuint vbo;
    int point_count;
    
    QMatrix4x4 projection;
    QMatrix4x4 view;
    QMatrix4x4 model;
    
    QPoint last_mouse_pos;
    float zoom_level;
    float rotate_x, rotate_y;
};

七、主窗口界面

cpp 复制代码
// MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QTimer>
#include "LidarParser.h"
#include "ClusterAlgo.h"
#include "PointCloudView.h"

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow {
    Q_OBJECT
    
public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
    
private slots:
    void onConnectClicked();
    void onDisconnectClicked();
    void onClusteringClicked();
    void onNewPointCloud(const PointCloud& cloud);
    
    void updateStatus();
    void saveResults();
    
private:
    Ui::MainWindow *ui;
    
    LidarParser* lidar_parser;
    ClusterAlgo* cluster_algo;
    PointCloud current_cloud;
    QTimer status_timer;
    
    // 参数
    float cluster_eps;
    int cluster_min_pts;
};
cpp 复制代码
// MainWindow.cpp
#include "MainWindow.h"
#include "ui_MainWindow.h"
#include <QFileDialog>
#include <QMessageBox>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow) {
    
    ui->setupUi(this);
    
    // 初始化组件
    lidar_parser = new LidarParser(this);
    cluster_algo = new ClusterAlgo();
    
    // 连接信号槽
    connect(lidar_parser, &LidarParser::newPointCloud,
            this, &MainWindow::onNewPointCloud);
    
    connect(ui->btnConnect, &QPushButton::clicked,
            this, &MainWindow::onConnectClicked);
    connect(ui->btnDisconnect, &QPushButton::clicked,
            this, &MainWindow::onDisconnectClicked);
    connect(ui->btnCluster, &QPushButton::clicked,
            this, &MainWindow::onClusteringClicked);
    connect(ui->btnSave, &QPushButton::clicked,
            this, &MainWindow::saveResults);
    
    // 参数设置
    cluster_eps = ui->spinEps->value();
    cluster_min_pts = ui->spinMinPts->value();
    
    // 状态定时器
    status_timer.setInterval(1000);
    connect(&status_timer, &QTimer::timeout,
            this, &MainWindow::updateStatus);
    status_timer.start();
}

void MainWindow::onConnectClicked() {
    QString port = ui->comboPort->currentText();
    int baud = ui->comboBaud->currentText().toInt();
    
    if (lidar_parser->openSerial(port, baud)) {
        ui->statusBar->showMessage("已连接雷达: " + port);
    }
}

void MainWindow::onNewPointCloud(const PointCloud& cloud) {
    current_cloud = cloud;
    ui->pointCloudView->updatePointCloud(cloud);
    
    // 更新点数量显示
    ui->labelPointCount->setText(
        QString("点数: %1").arg(cloud.size()));
}

void MainWindow::onClusteringClicked() {
    if (current_cloud.size() == 0) {
        QMessageBox::warning(this, "警告", "没有点云数据");
        return;
    }
    
    // 获取参数
    cluster_eps = ui->spinEps->value();
    cluster_min_pts = ui->spinMinPts->value();
    
    // 执行聚类
    cluster_algo->setEps(cluster_eps);
    cluster_algo->setMinPts(cluster_min_pts);
    
    int cluster_count = cluster_algo->dbscan(current_cloud, 
                                            cluster_eps, cluster_min_pts);
    
    // 更新显示
    ui->pointCloudView->updatePointCloud(current_cloud);
    
    // 显示结果
    ui->labelClusterCount->setText(
        QString("聚类数: %1").arg(cluster_count));
    
    QVector<int> sizes = cluster_algo->getClusterSizes();
    QString size_text = "各聚类大小: ";
    for (int i = 0; i < sizes.size(); i++) {
        size_text += QString::number(sizes[i]);
        if (i < sizes.size() - 1) size_text += ", ";
    }
    ui->labelClusterSizes->setText(size_text);
}

八、QT项目文件

pro 复制代码
# LidarCluster.pro
QT       += core gui serialport
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = LidarCluster
TEMPLATE = app

# C++11支持
CONFIG += c++11

# 源文件
SOURCES += \
    main.cpp \
    MainWindow.cpp \
    LidarParser.cpp \
    ClusterAlgo.cpp \
    PointCloud.cpp \
    PointCloudView.cpp

HEADERS += \
    MainWindow.h \
    LidarParser.h \
    ClusterAlgo.h \
    Point.h \
    PointCloud.h \
    PointCloudView.h

FORMS += \
    MainWindow.ui

# 包含路径
INCLUDEPATH += $$PWD

# 链接库
LIBS += -lm

# 发布配置
CONFIG(release, debug|release) {
    DESTDIR = release
    OBJECTS_DIR = release/.obj
    MOC_DIR = release/.moc
    RCC_DIR = release/.qrc
    UI_DIR = release/.ui
}

# 调试配置
CONFIG(debug, debug|release) {
    DESTDIR = debug
    OBJECTS_DIR = debug/.obj
    MOC_DIR = debug/.moc
    RCC_DIR = debug/.qrc
    UI_DIR = debug/.ui
}

参考代码 使用QT5.7.0.实现八线激光雷达点云数据的聚类 www.youwenfan.com/contentcsu/69993.html

九、UI界面设计

xml 复制代码
<!-- MainWindow.ui -->
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>1200</width>
    <height>800</height>
   </rect>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QHBoxLayout" name="horizontalLayout">
    <item>
     <widget class="QWidget" name="widgetLeft" native="true">
      <layout class="QVBoxLayout" name="verticalLayout">
       <item>
        <widget class="QGroupBox" name="groupBox_3">
         <property name="title">
          <string>雷达连接</string>
         </property>
         <layout class="QHBoxLayout" name="horizontalLayout_2">
          <item>
           <widget class="QLabel" name="label">
            <property name="text">
             <string>串口:</string>
            </property>
           </widget>
          </item>
          <item>
           <widget class="QComboBox" name="comboPort"/>
          </item>
          <item>
           <widget class="QLabel" name="label_2">
            <property name="text">
             <string>波特率:</string>
            </property>
           </widget>
          </item>
          <item>
           <widget class="QComboBox" name="comboBaud"/>
          </item>
          <item>
           <widget class="QPushButton" name="btnConnect">
            <property name="text">
             <string>连接</string>
            </property>
           </widget>
          </item>
          <item>
           <widget class="QPushButton" name="btnDisconnect">
            <property name="text">
             <string>断开</string>
            </property>
           </widget>
          </item>
         </layout>
        </widget>
       </item>
       <item>
        <widget class="QGroupBox" name="groupBox">
         <property name="title">
          <string>聚类参数</string>
         </property>
         <layout class="QGridLayout" name="gridLayout">
          <item row="0" column="0">
           <widget class="QLabel" name="label_3">
            <property name="text">
             <string>邻域半径 (m):</string>
            </property>
           </widget>
          </item>
          <item row="0" column="1">
           <widget class="QDoubleSpinBox" name="spinEps">
            <property name="decimals" stdset="0">
             <number>2</number>
            </property>
            <property name="maximum" stdset="0">
             <double>10.000000000000000</double>
            </property>
            <property name="value" stdset="0">
             <double>0.200000000000000</double>
            </property>
           </widget>
          </item>
          <item row="1" column="0">
           <widget class="QLabel" name="label_4">
            <property name="text">
             <string>最小点数:</string>
            </property>
           </widget>
          </item>
          <item row="1" column="1">
           <widget class="QSpinBox" name="spinMinPts">
            <property name="minimum" stdset="0">
             <number>1</number>
            </property>
            <property name="maximum" stdset="0">
             <number>1000</number>
            </property>
            <property name="value" stdset="0">
             <number>5</number>
            </property>
           </widget>
          </item>
          <item row="2" column="0" colspan="2">
           <widget class="QPushButton" name="btnCluster">
            <property name="text">
             <string>执行聚类</string>
            </property>
           </widget>
          </item>
         </layout>
        </widget>
       </item>
       <item>
        <widget class="QGroupBox" name="groupBox_2">
         <property name="title">
          <string>状态信息</string>
         </property>
         <layout class="QVBoxLayout" name="verticalLayout_2">
          <item>
           <widget class="QLabel" name="labelPointCount">
            <property name="text">
             <string>点数: 0</string>
            </property>
           </widget>
          </item>
          <item>
           <widget class="QLabel" name="labelClusterCount">
            <property name="text">
             <string>聚类数: 0</string>
            </property>
           </widget>
          </item>
          <item>
           <widget class="QLabel" name="labelClusterSizes">
            <property name="text">
             <string>各聚类大小:</string>
            </property>
           </widget>
          </item>
         </layout>
        </widget>
       </item>
       <item>
        <spacer name="verticalSpacer">
         <property name="orientation">
          <enum>Qt::Vertical</enum>
         </property>
         <property name="sizeHint" stdset="0">
          <size>
           <width>20</width>
           <height>40</height>
          </size>
         </property>
        </spacer>
       </item>
       <item>
        <widget class="QPushButton" name="btnSave">
         <property name="text">
          <string>保存结果</string>
         </property>
        </widget>
       </item>
      </layout>
     </widget>
    </item>
    <item>
     <widget class="PointCloudView" name="pointCloudView" native="true"/>
    </item>
   </layout>
  </widget>
  <widget class="QStatusBar" name="statusBar"/>
 </widget>
 <customwidgets>
  <customwidget>
   <class>PointCloudView</class>
   <extends>QWidget</extends>
   <header>PointCloudView.h</header>
  </customwidget>
 </customwidgets>
 <resources/>
 <connections/>
</ui>

十、主程序入口

cpp 复制代码
// main.cpp
#include "MainWindow.h"
#include <QApplication>
#include <QStyleFactory>

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    
    // 设置全局样式
    QApplication::setStyle(QStyleFactory::create("Fusion"));
    
    // 设置中文字体
    QFont font("Microsoft YaHei", 9);
    a.setFont(font);
    
    MainWindow w;
    w.show();
    
    return a.exec();
}
相关推荐
yoyo_zzm1 小时前
汇编到PHP:五大编程语言核心特性全解析
开发语言·汇编·php
.ZGR.2 小时前
线程池相关知识及并发统计案例实现
java·开发语言
流年如夢2 小时前
初入C++
开发语言·c++
zzzsde2 小时前
【Linux】线程同步和互斥(1):线程互斥与加锁实现
linux·运维·服务器·开发语言·算法
yoyo_zzm2 小时前
编程语言大比拼:C++到PHP全解析
开发语言·c++·php
努力努力再努力wz2 小时前
【C++高阶数据结构系列】:时间轮定时器详解:原理分析与代码实现,带你从零手撕时间轮!(附时间轮的实现源码)
c语言·开发语言·数据结构·c++·qt·算法·ui
Chen_harmony2 小时前
十九、数据在内存中的存储
c语言·开发语言
basketball6162 小时前
C 的 malloc/free 与 C++ 的 new/delete 一些区别
c语言·开发语言·c++
iiiiyu2 小时前
⾯向对象和集合编程题
java·大数据·开发语言·数据结构·编程语言