feat: v0.1.0更新
This commit is contained in:
1138
src/gui/MainWindow.cpp
Normal file
1138
src/gui/MainWindow.cpp
Normal file
File diff suppressed because it is too large
Load Diff
159
src/gui/MainWindow.h
Normal file
159
src/gui/MainWindow.h
Normal 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
|
||||
9
src/gui/MainWindow_data_handler.cpp
Normal file
9
src/gui/MainWindow_data_handler.cpp
Normal 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";
|
||||
}
|
||||
}
|
||||
259
src/gui/PointCloudGLWidget.cpp
Normal file
259
src/gui/PointCloudGLWidget.cpp
Normal 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();
|
||||
}
|
||||
56
src/gui/PointCloudWidget.cpp
Normal file
56
src/gui/PointCloudWidget.cpp
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user