基于 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();
}
相关推荐
用户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