feat: v0.1.0更新

This commit is contained in:
2026-01-14 18:07:26 +08:00
commit efd8a7cc20
55 changed files with 6200 additions and 0 deletions

1138
src/gui/MainWindow.cpp Normal file

File diff suppressed because it is too large Load Diff

159
src/gui/MainWindow.h Normal file
View File

@@ -0,0 +1,159 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTimer>
#include <QDateTime>
#include <memory>
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
class ConfigManager;
class NetworkManager;
class DeviceScanner;
class GVSPParser;
class PointCloudProcessor;
class PointCloudWidget;
struct DeviceInfo;
class QListWidget;
class QListWidgetItem;
class QPushButton;
class QSlider;
class QSpinBox;
class QLabel;
class QImage;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
// 相机控制槽函数
void onStartClicked();
void onStopClicked();
void onOnceClicked();
void onExposureChanged(int value);
void onCaptureClicked();
void onBrowseSavePathClicked();
// 网络配置槽函数
void onConnectClicked();
void onIpAddressChanged(const QString &ip);
void onControlPortChanged(int port);
void onDataPortChanged(int port);
void onNetworkConnected();
void onNetworkDisconnected();
void onDataReceived(const QByteArray &data);
// GVSP数据处理槽函数
void onImageReceived(const QImage &image, uint32_t blockId);
void onDepthDataReceived(const QByteArray &depthData, uint32_t blockId);
void onPointCloudReady(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud, uint32_t blockId);
// 设备扫描槽函数
void onRefreshClicked();
void onDeviceFound(const DeviceInfo &device);
void onScanProgress(int current, int total);
void onScanFinished(int devicesFound);
void onDeviceSelected(QListWidgetItem *item);
// 定时器更新
void updateUI();
// 日志槽函数
void onClearLogClicked();
void onSaveLogClicked();
protected:
// 重写事件处理函数以监听系统主题变化
void changeEvent(QEvent *event) override;
private:
void setupUI();
void setupConnections();
void loadSettings();
void saveSettings();
// 拍照辅助函数
bool saveDepthImage(const QString &dir, const QString &baseName, const QString &format);
bool savePointCloud(const QString &dir, const QString &baseName, const QString &format);
void performBackgroundSave(const QString &saveDir, const QString &baseName,
const QString &depthFormat, const QString &pointCloudFormat,
const QImage &image, pcl::PointCloud<pcl::PointXYZ>::Ptr cloud);
// 日志辅助函数
void addLog(const QString &message, const QString &level = "INFO");
void updateStatistics();
private:
// 核心管理器
std::unique_ptr<ConfigManager> m_configManager;
std::unique_ptr<NetworkManager> m_networkManager;
std::unique_ptr<DeviceScanner> m_deviceScanner;
std::unique_ptr<PointCloudProcessor> m_pointCloudProcessor;
// UI更新定时器
QTimer *m_updateTimer;
// 状态变量
bool m_isConnected;
bool m_autoSaveOnNextFrame;
// 当前帧数据(用于拍照)
QImage m_currentImage;
pcl::PointCloud<pcl::PointXYZ>::Ptr m_currentPointCloud;
uint32_t m_currentFrameId;
// UI控件指针
class QWidget *m_centralWidget;
class QSplitter *m_mainSplitter;
class QWidget *m_controlPanel;
class QLabel *m_imageDisplay;
PointCloudWidget *m_pointCloudWidget;
// 按钮控件
QPushButton *m_refreshBtn;
QPushButton *m_connectBtn;
QPushButton *m_startBtn;
QPushButton *m_stopBtn;
QPushButton *m_onceBtn;
QPushButton *m_captureBtn;
// 输入控件
QSlider *m_exposureSlider;
QSpinBox *m_exposureSpinBox;
QSpinBox *m_ctrlPortSpinBox;
QSpinBox *m_dataPortSpinBox;
// 拍照参数控件
class QLineEdit *m_savePathEdit;
QPushButton *m_browseSavePathBtn;
class QComboBox *m_depthFormatCombo;
class QComboBox *m_pointCloudFormatCombo;
// 显示控件
QLabel *m_statusLabel;
QListWidget *m_deviceList;
// 统计信息控件
QLabel *m_fpsLabel;
QLabel *m_resolutionLabel;
QLabel *m_queueLabel;
// 日志显示控件
class QTextEdit *m_logDisplay;
QPushButton *m_clearLogBtn;
QPushButton *m_saveLogBtn;
// 统计数据
QDateTime m_lastFrameTime;
int m_frameCount;
int m_totalFrameCount;
double m_currentFps;
};
#endif // MAINWINDOW_H

View File

@@ -0,0 +1,9 @@
void MainWindow::onDataReceived(const QByteArray &data)
{
static int packetCount = 0;
packetCount++;
if (packetCount % 100 == 0) {
qDebug() << "Received" << packetCount << "packets, latest size:" << data.size() << "bytes";
}
}

View File

@@ -0,0 +1,259 @@
#include "gui/PointCloudGLWidget.h"
#include <QDebug>
#include <cmath>
#include <cfloat>
PointCloudGLWidget::PointCloudGLWidget(QWidget *parent)
: QOpenGLWidget(parent)
, m_program(nullptr)
, m_vertexBuffer(nullptr)
, m_vao(nullptr)
, m_pointCount(0)
, m_orthoSize(2000.0f) // 正交投影视野大小
, m_rotationX(0.0f) // 从正面看0度
, m_rotationY(0.0f) // 从正面看0度
, m_translation(0.0f, 0.0f, 0.0f)
, m_leftButtonPressed(false)
, m_rightButtonPressed(false)
{
setMinimumSize(400, 400);
}
PointCloudGLWidget::~PointCloudGLWidget()
{
makeCurrent();
if (m_vao) {
m_vao->destroy();
delete m_vao;
}
if (m_vertexBuffer) {
m_vertexBuffer->destroy();
delete m_vertexBuffer;
}
if (m_program) {
delete m_program;
}
doneCurrent();
}
void PointCloudGLWidget::initializeGL()
{
initializeOpenGLFunctions();
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glEnable(GL_DEPTH_TEST);
glEnable(GL_PROGRAM_POINT_SIZE);
setupShaders();
// 创建VAO和VBO
m_vao = new QOpenGLVertexArrayObject(this);
m_vao->create();
m_vertexBuffer = new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
m_vertexBuffer->create();
qDebug() << "[PointCloudGLWidget] OpenGL initialized";
}
void PointCloudGLWidget::setupShaders()
{
m_program = new QOpenGLShaderProgram(this);
// 顶点着色器
const char *vertexShaderSource = R"(
#version 330 core
layout(location = 0) in vec3 position;
uniform mat4 mvp;
void main() {
gl_Position = mvp * vec4(position, 1.0);
gl_PointSize = 2.0;
}
)";
// 片段着色器
const char *fragmentShaderSource = R"(
#version 330 core
out vec4 fragColor;
void main() {
fragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
)";
if (!m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource)) {
qDebug() << "[PointCloudGLWidget] Vertex shader error:" << m_program->log();
}
if (!m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource)) {
qDebug() << "[PointCloudGLWidget] Fragment shader error:" << m_program->log();
}
if (!m_program->link()) {
qDebug() << "[PointCloudGLWidget] Shader link error:" << m_program->log();
} else {
qDebug() << "[PointCloudGLWidget] Shaders compiled and linked successfully";
}
}
void PointCloudGLWidget::resizeGL(int w, int h)
{
m_projection.setToIdentity();
// 使用正交投影代替透视投影,避免"喷射状"效果
float aspect = float(w) / float(h);
m_projection.ortho(-m_orthoSize * aspect, m_orthoSize * aspect,
-m_orthoSize, m_orthoSize,
-50000.0f, 50000.0f); // 近平面和远平面
}
void PointCloudGLWidget::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
if (m_pointCount == 0 || !m_program) {
return;
}
// 每帧重新计算正交投影矩阵确保使用最新的m_orthoSize
m_projection.setToIdentity();
float aspect = float(width()) / float(height());
m_projection.ortho(-m_orthoSize * aspect, m_orthoSize * aspect,
-m_orthoSize, m_orthoSize,
-50000.0f, 50000.0f);
// 设置view矩阵
m_view.setToIdentity();
m_view.rotate(m_rotationX, 1.0f, 0.0f, 0.0f);
m_view.rotate(m_rotationY, 0.0f, 1.0f, 0.0f);
m_view.translate(m_translation);
// 设置model矩阵
m_model.setToIdentity();
// 计算MVP矩阵
QMatrix4x4 mvp = m_projection * m_view * m_model;
// 绑定shader和设置uniform
m_program->bind();
m_program->setUniformValue("mvp", mvp);
// 绑定VAO和绘制
m_vao->bind();
glDrawArrays(GL_POINTS, 0, m_pointCount);
m_vao->release();
m_program->release();
}
void PointCloudGLWidget::mousePressEvent(QMouseEvent *event)
{
m_lastMousePos = event->pos();
if (event->button() == Qt::LeftButton) {
m_leftButtonPressed = true;
} else if (event->button() == Qt::RightButton) {
m_rightButtonPressed = true;
}
}
void PointCloudGLWidget::mouseMoveEvent(QMouseEvent *event)
{
QPoint delta = event->pos() - m_lastMousePos;
m_lastMousePos = event->pos();
if (m_leftButtonPressed) {
// 左键:旋转
m_rotationX += delta.y() * 0.5f;
m_rotationY += delta.x() * 0.5f;
update();
} else if (m_rightButtonPressed) {
// 右键:平移(根据正交投影视野大小调整平移速度)
float scale = m_orthoSize * 0.002f;
m_translation.setX(m_translation.x() + delta.x() * scale);
m_translation.setY(m_translation.y() - delta.y() * scale);
update();
}
}
void PointCloudGLWidget::mouseReleaseEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
m_leftButtonPressed = false;
} else if (event->button() == Qt::RightButton) {
m_rightButtonPressed = false;
}
}
void PointCloudGLWidget::wheelEvent(QWheelEvent *event)
{
// 滚轮:缩放(调整正交投影视野大小)
float delta = event->angleDelta().y() / 120.0f;
m_orthoSize *= (1.0f - delta * 0.1f);
m_orthoSize = qMax(100.0f, qMin(m_orthoSize, 10000.0f)); // 范围100-10000
update(); // 触发重绘paintGL会使用新的m_orthoSize
}
void PointCloudGLWidget::updatePointCloud(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud)
{
if (!cloud) {
return;
}
// 过滤全零点并转换为顶点数组
m_vertices.clear();
float minX = FLT_MAX, maxX = -FLT_MAX;
float minY = FLT_MAX, maxY = -FLT_MAX;
float minZ = FLT_MAX, maxZ = -FLT_MAX;
for (const auto& point : cloud->points) {
if (point.z > 0.01f) {
m_vertices.push_back(point.x);
m_vertices.push_back(point.y);
m_vertices.push_back(point.z);
// 统计坐标范围
if (point.x < minX) minX = point.x;
if (point.x > maxX) maxX = point.x;
if (point.y < minY) minY = point.y;
if (point.y > maxY) maxY = point.y;
if (point.z < minZ) minZ = point.z;
if (point.z > maxZ) maxZ = point.z;
}
}
m_pointCount = m_vertices.size() / 3;
// 添加调试日志
static int updateCount = 0;
if (updateCount < 3 || updateCount % 100 == 0) {
qDebug() << "[PointCloudGLWidget] Update" << updateCount << "- Points:" << m_pointCount
<< "Total cloud size:" << cloud->size();
qDebug() << " X range:" << minX << "to" << maxX;
qDebug() << " Y range:" << minY << "to" << maxY;
qDebug() << " Z range:" << minZ << "to" << maxZ;
}
updateCount++;
updateBuffers();
update();
}
void PointCloudGLWidget::updateBuffers()
{
if (m_vertices.empty() || !m_vao || !m_vertexBuffer) {
return;
}
makeCurrent();
m_vao->bind();
m_vertexBuffer->bind();
m_vertexBuffer->allocate(m_vertices.data(), m_vertices.size() * sizeof(float));
// 设置顶点属性指针
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), nullptr);
m_vertexBuffer->release();
m_vao->release();
doneCurrent();
}

View File

@@ -0,0 +1,56 @@
#include "gui/PointCloudWidget.h"
#include "gui/PointCloudGLWidget.h"
#include <QVBoxLayout>
#include <QDebug>
PointCloudWidget::PointCloudWidget(QWidget *parent)
: QWidget(parent)
, m_statusLabel(nullptr)
, m_glWidget(nullptr)
{
// 创建布局
QVBoxLayout *layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(5);
// 创建状态标签
m_statusLabel = new QLabel("点云显示 (等待数据...)", this);
m_statusLabel->setAlignment(Qt::AlignCenter);
m_statusLabel->setStyleSheet("QLabel { background-color: #1C1C1C; color: white; font-size: 12px; padding: 5px; }");
m_statusLabel->setMaximumHeight(30);
layout->addWidget(m_statusLabel);
// 创建OpenGL点云显示widget
m_glWidget = new PointCloudGLWidget(this);
layout->addWidget(m_glWidget);
setLayout(layout);
qDebug() << "[PointCloudWidget] Embedded OpenGL widget created";
}
PointCloudWidget::~PointCloudWidget()
{
// OpenGL widget会自动清理
}
void PointCloudWidget::updatePointCloud(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud)
{
if (!cloud || !m_glWidget) {
return;
}
// 统计有效点数
int validPoints = 0;
for (const auto& point : cloud->points) {
if (point.z > 0.01f) {
validPoints++;
}
}
// 更新状态标签
m_statusLabel->setText(QString("点云显示 | 有效点数: %1").arg(validPoints));
// 更新OpenGL显示
m_glWidget->updatePointCloud(cloud);
}