一、系统架构设计
激光雷达 → 串口/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();
}