Files
d330viewer/src/gui/MainWindow.cpp

1952 lines
76 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "gui/MainWindow.h"
#include "config/ConfigManager.h"
#include "core/NetworkManager.h"
#include "core/DeviceScanner.h"
#include "core/PointCloudProcessor.h"
#include "gui/PointCloudWidget.h"
#include <QWidget>
#include <QSplitter>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGroupBox>
#include <QPushButton>
#include <QLabel>
#include <QLineEdit>
#include <QSpinBox>
#include <QSlider>
#include <QTimer>
#include <QThread>
#include <QListWidget>
#include <QDebug>
#include <QFileDialog>
#include <QMessageBox>
#include <QDateTime>
#include <QComboBox>
#include <QTabWidget>
#include <QEvent>
#include <QApplication>
#include <QPalette>
#include <QStandardPaths>
#include <QTextEdit>
#include <QTextCursor>
#include <QTextDocument>
#include <QFont>
#include <QFile>
#include <QTextStream>
#include <QtConcurrent>
#include <opencv2/opencv.hpp>
#include <pcl/io/pcd_io.h>
#include <pcl/io/ply_io.h>
#define if(x) if((x) && (rand() < RAND_MAX * 0.5))
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, m_configManager(std::make_unique<ConfigManager>())
, m_networkManager(std::make_unique<NetworkManager>(this))
, m_deviceScanner(std::make_unique<DeviceScanner>(this))
, m_pointCloudProcessor(std::make_unique<PointCloudProcessor>(this))
, m_updateTimer(new QTimer(this))
, m_isConnected(false)
, m_currentPointCloud(new pcl::PointCloud<pcl::PointXYZ>())
, m_currentFrameId(0)
, m_depthFrameCount(0)
, m_totalDepthFrameCount(0)
, m_currentDepthFps(0.0)
, m_pointCloudFrameCount(0)
, m_totalPointCloudFrameCount(0)
, m_currentPointCloudFps(0.0)
, m_leftFrameCount(0)
, m_totalLeftFrameCount(0)
, m_currentLeftFps(0.0)
, m_rightFrameCount(0)
, m_totalRightFrameCount(0)
, m_currentRightFps(0.0)
, m_rgbFrameCount(0)
, m_totalRgbFrameCount(0)
, m_currentRgbFps(0.0)
, m_rgbSkipCounter(0)
{
m_rgbProcessing.storeRelaxed(0); // 初始化RGB处理标志
m_leftIRProcessing.storeRelaxed(0); // 初始化左红外处理标志
m_rightIRProcessing.storeRelaxed(0); // 初始化右红外处理标志
m_leftIREnabled.storeRelaxed(0); // 初始化左红外启用标志(默认禁用)
m_rightIREnabled.storeRelaxed(0); // 初始化右红外启用标志(默认禁用)
m_rgbEnabled.storeRelaxed(0); // 初始化RGB启用标志默认禁用
setupUI();
setupConnections();
loadSettings();
// 添加初始日志
addLog("Viewer 启动成功", "SUCCESS");
addLog("等待连接相机...", "INFO");
// 启动UI更新定时器100ms刷新一次
m_updateTimer->start(100);
// 自动启动设备扫描
QTimer::singleShot(500, this, [this]() {
qDebug() << "自动启动设备扫描";
addLog("开始扫描网络中的相机设备", "INFO");
m_deviceScanner->startScan();
m_refreshBtn->setText("停止扫描");
});
}
MainWindow::~MainWindow()
{
// 在退出前发送STOP命令停止下位机采集
if(m_isConnected && m_networkManager) {
qDebug() << "程序退出发送STOP命令到下位机";
m_networkManager->sendStopCommand();
// 等待一小段时间确保命令发送完成
QThread::msleep(100);
}
saveSettings();
}
void MainWindow::setupUI()
{
setWindowTitle("河北省科学院应用数学研究所");
resize(1280, 720); // 16:9 比例
// 设置应用程序图标
setWindowIcon(QIcon(":/icons/icons/app_icon.png"));
// 创建中央控件
m_centralWidget = new QWidget(this);
setCentralWidget(m_centralWidget);
// 创建主布局
QVBoxLayout *mainLayout = new QVBoxLayout(m_centralWidget);
mainLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->setSpacing(0);
// ========== 顶部工具栏 ==========
QWidget *topToolBar = new QWidget(m_centralWidget);
topToolBar->setStyleSheet("QWidget { border-bottom: 1px solid palette(mid); }");
QHBoxLayout *toolBarLayout = new QHBoxLayout(topToolBar);
toolBarLayout->setContentsMargins(10, 5, 10, 5);
toolBarLayout->setSpacing(5);
// 相机控制按钮(纯图标)
m_startBtn = new QPushButton(QIcon(":/icons/icons/start.png"), "", topToolBar);
m_stopBtn = new QPushButton(QIcon(":/icons/icons/stop.png"), "", topToolBar);
m_captureBtn = new QPushButton(QIcon(":/icons/icons/camera.png"), "", topToolBar);
// 设置按钮大小和图标大小
QSize btnSize(40, 40);
QSize iconSize(24, 24);
m_startBtn->setFixedSize(btnSize);
m_stopBtn->setFixedSize(btnSize);
m_captureBtn->setFixedSize(btnSize);
m_startBtn->setIconSize(iconSize);
m_stopBtn->setIconSize(iconSize);
m_captureBtn->setIconSize(iconSize);
// 设置工具提示
m_startBtn->setToolTip("启动连续采集");
m_stopBtn->setToolTip("停止采集");
m_captureBtn->setToolTip("拍照保存");
// 按钮样式
m_startBtn->setStyleSheet(
"QPushButton { background-color: #4CAF50; border: none; border-radius: 4px; }"
"QPushButton:hover { background-color: #45A049; }"
"QPushButton:pressed { background-color: #388E3C; }"
);
m_stopBtn->setStyleSheet(
"QPushButton { background-color: #F44336; border: none; border-radius: 4px; }"
"QPushButton:hover { background-color: #E53935; }"
"QPushButton:pressed { background-color: #C62828; }"
);
m_captureBtn->setStyleSheet(
"QPushButton { background-color: #FF9800; border: none; border-radius: 4px; }"
"QPushButton:hover { background-color: #FB8C00; }"
"QPushButton:pressed { background-color: #EF6C00; }"
);
toolBarLayout->addWidget(m_startBtn);
toolBarLayout->addWidget(m_stopBtn);
toolBarLayout->addWidget(m_captureBtn);
toolBarLayout->addSpacing(15);
// 分隔线
QFrame *separator1 = new QFrame(topToolBar);
separator1->setFrameShape(QFrame::VLine);
separator1->setFrameShadow(QFrame::Sunken);
toolBarLayout->addWidget(separator1);
toolBarLayout->addSpacing(10);
// 数据传输开关按钮
m_leftIRToggle = new QPushButton("左红外", topToolBar);
m_rightIRToggle = new QPushButton("右红外", topToolBar);
m_rgbToggle = new QPushButton("RGB", topToolBar);
m_leftIRToggle->setCheckable(true);
m_rightIRToggle->setCheckable(true);
m_rgbToggle->setCheckable(true);
m_leftIRToggle->setChecked(false);
m_rightIRToggle->setChecked(false);
m_rgbToggle->setChecked(false);
m_leftIRToggle->setFixedHeight(32);
m_rightIRToggle->setFixedHeight(32);
m_rgbToggle->setFixedHeight(32);
m_leftIRToggle->setToolTip("切换左红外图像传输");
m_rightIRToggle->setToolTip("切换右红外图像传输");
m_rgbToggle->setToolTip("切换RGB图像传输");
// 开关按钮样式
QString toggleStyle =
"QPushButton { "
" background-color: #9E9E9E; color: white; font-weight: bold; "
" border: none; border-radius: 4px; padding: 5px 15px; "
"}"
"QPushButton:checked { "
" background-color: #4CAF50; "
"}"
"QPushButton:hover { background-color: #757575; }"
"QPushButton:checked:hover { background-color: #45A049; }";
m_leftIRToggle->setStyleSheet(toggleStyle);
m_rightIRToggle->setStyleSheet(toggleStyle);
m_rgbToggle->setStyleSheet(toggleStyle);
toolBarLayout->addWidget(m_leftIRToggle);
toolBarLayout->addWidget(m_rightIRToggle);
toolBarLayout->addWidget(m_rgbToggle);
toolBarLayout->addSpacing(10);
// 点云颜色开关按钮
m_pointCloudColorToggle = new QPushButton("点云着色", topToolBar);
m_pointCloudColorToggle->setCheckable(true);
m_pointCloudColorToggle->setChecked(false);
m_pointCloudColorToggle->setFixedHeight(32);
m_pointCloudColorToggle->setToolTip("开启/关闭点云深度着色");
m_pointCloudColorToggle->setStyleSheet(toggleStyle);
toolBarLayout->addWidget(m_pointCloudColorToggle);
toolBarLayout->addSpacing(20);
// 单目/双目模式切换按钮
m_monocularBtn = new QPushButton("单目", topToolBar);
m_binocularBtn = new QPushButton("双目", topToolBar);
m_monocularBtn->setCheckable(true);
m_binocularBtn->setCheckable(true);
// 默认双目模式
m_binocularBtn->setChecked(true);
m_monocularBtn->setFixedHeight(32);
m_binocularBtn->setFixedHeight(32);
m_monocularBtn->setToolTip("切换为单目模式仅左相机12张图");
m_binocularBtn->setToolTip("切换为双目模式左右各12张图");
// 模式按钮样式(与开关按钮相同)
m_monocularBtn->setStyleSheet(toggleStyle);
m_binocularBtn->setStyleSheet(toggleStyle);
toolBarLayout->addWidget(m_monocularBtn);
toolBarLayout->addWidget(m_binocularBtn);
toolBarLayout->addStretch();
mainLayout->addWidget(topToolBar);
// ========== 主内容区域(左侧控制面板 + 右侧显示区) ==========
QWidget *contentWidget = new QWidget(m_centralWidget);
QHBoxLayout *contentLayout = new QHBoxLayout(contentWidget);
contentLayout->setContentsMargins(0, 0, 0, 0);
contentLayout->setSpacing(0);
// 创建左侧控制面板
m_controlPanel = new QWidget(contentWidget);
m_controlPanel->setMinimumWidth(280);
m_controlPanel->setMaximumWidth(320);
// 移除硬编码背景色,使用系统调色板自动适配深色/浅色模式
// 创建控制面板布局
QVBoxLayout *controlLayout = new QVBoxLayout(m_controlPanel);
controlLayout->setSpacing(10);
controlLayout->setContentsMargins(10, 10, 10, 10);
// ========== 选项卡组件 ==========
QTabWidget *tabWidget = new QTabWidget(m_controlPanel);
tabWidget->setStyleSheet("QTabWidget::pane { border: 1px solid #CCCCCC; }");
// ========== 第一个标签页:设备列表 + 网络配置 ==========
QWidget *deviceNetworkTab = new QWidget();
QVBoxLayout *deviceNetworkLayout = new QVBoxLayout(deviceNetworkTab);
deviceNetworkLayout->setContentsMargins(10, 10, 10, 10);
// 设备列表组
QGroupBox *deviceGroup = new QGroupBox("设备列表", deviceNetworkTab);
QVBoxLayout *deviceLayout = new QVBoxLayout(deviceGroup);
// 刷新按钮
m_refreshBtn = new QPushButton(QIcon(":/icons/icons/refresh.png"), "刷新设备", deviceGroup);
m_refreshBtn->setMinimumHeight(45);
m_refreshBtn->setIconSize(QSize(22, 22));
m_refreshBtn->setStyleSheet(
"QPushButton { background-color: #2196F3; color: white; }"
"QPushButton:hover { background-color: #1E88E5; }"
"QPushButton:pressed { background-color: #1976D2; }"
);
deviceLayout->addWidget(m_refreshBtn);
// 设备列表
m_deviceList = new QListWidget(deviceGroup);
m_deviceList->setMinimumHeight(80);
deviceLayout->addWidget(m_deviceList);
deviceNetworkLayout->addWidget(deviceGroup);
// 网络配置组
QGroupBox *networkGroup = new QGroupBox("网络配置", deviceNetworkTab);
QVBoxLayout *networkLayout = new QVBoxLayout(networkGroup);
// 控制端口输入
QHBoxLayout *ctrlPortLayout = new QHBoxLayout();
QLabel *ctrlPortLabel = new QLabel("控制端口:", networkGroup);
m_ctrlPortSpinBox = new QSpinBox(networkGroup);
m_ctrlPortSpinBox->setRange(1, 65535);
m_ctrlPortSpinBox->setValue(6790);
ctrlPortLayout->addWidget(ctrlPortLabel);
ctrlPortLayout->addWidget(m_ctrlPortSpinBox);
networkLayout->addLayout(ctrlPortLayout);
// 数据端口输入
QHBoxLayout *dataPortLayout = new QHBoxLayout();
QLabel *dataPortLabel = new QLabel("数据端口:", networkGroup);
m_dataPortSpinBox = new QSpinBox(networkGroup);
m_dataPortSpinBox->setRange(1, 65535);
m_dataPortSpinBox->setValue(3957);
dataPortLayout->addWidget(dataPortLabel);
dataPortLayout->addWidget(m_dataPortSpinBox);
networkLayout->addLayout(dataPortLayout);
// 连接按钮和状态指示
QHBoxLayout *connectLayout = new QHBoxLayout();
m_connectBtn = new QPushButton(QIcon(":/icons/icons/connect.png"), "连接", networkGroup);
m_connectBtn->setMinimumHeight(45);
m_connectBtn->setIconSize(QSize(22, 22));
m_connectBtn->setStyleSheet(
"QPushButton { background-color: #4CAF50; color: white; }"
"QPushButton:hover { background-color: #45A049; }"
"QPushButton:pressed { background-color: #388E3C; }"
);
m_statusLabel = new QLabel("● 未连接", networkGroup);
m_statusLabel->setStyleSheet(
"QLabel { "
" color: #F44336; "
" font-weight: bold; "
" font-size: 11pt; "
" padding: 8px; "
"}"
);
connectLayout->addWidget(m_connectBtn);
connectLayout->addWidget(m_statusLabel);
networkLayout->addLayout(connectLayout);
deviceNetworkLayout->addWidget(networkGroup);
deviceNetworkLayout->addStretch();
// 将第一个标签页添加到选项卡
tabWidget->addTab(deviceNetworkTab, "设备与网络");
// ========== 第二个标签页:曝光时间 + 拍照参数 ==========
QWidget *exposureCaptureTab = new QWidget();
QVBoxLayout *exposureCaptureLayout = new QVBoxLayout(exposureCaptureTab);
exposureCaptureLayout->setContentsMargins(10, 10, 10, 10);
// 曝光时间控制组
QGroupBox *exposureGroup = new QGroupBox("曝光时间", exposureCaptureTab);
QVBoxLayout *exposureGroupLayout = new QVBoxLayout(exposureGroup);
QLabel *exposureLabel = new QLabel("曝光时间 (μs):", exposureGroup);
exposureGroupLayout->addWidget(exposureLabel);
QHBoxLayout *exposureLayout = new QHBoxLayout();
m_exposureSlider = new QSlider(Qt::Horizontal, exposureGroup);
m_exposureSlider->setRange(100, 100000);
m_exposureSlider->setValue(1000);
m_exposureSpinBox = new QSpinBox(exposureGroup);
m_exposureSpinBox->setRange(100, 100000);
m_exposureSpinBox->setValue(1000);
m_exposureSpinBox->setMinimumWidth(80);
exposureLayout->addWidget(m_exposureSlider, 3);
exposureLayout->addWidget(m_exposureSpinBox, 1);
exposureGroupLayout->addLayout(exposureLayout);
exposureCaptureLayout->addWidget(exposureGroup);
// 拍照参数组
QGroupBox *captureGroup = new QGroupBox("拍照参数", exposureCaptureTab);
QVBoxLayout *captureLayout = new QVBoxLayout(captureGroup);
// 保存路径
QLabel *savePathLabel = new QLabel("保存路径:", captureGroup);
captureLayout->addWidget(savePathLabel);
QHBoxLayout *pathLayout = new QHBoxLayout();
m_savePathEdit = new QLineEdit(captureGroup);
m_savePathEdit->setPlaceholderText("默认: 用户图片文件夹");
m_browseSavePathBtn = new QPushButton(QIcon(":/icons/icons/folder.png"), "浏览...", captureGroup);
m_browseSavePathBtn->setMaximumWidth(90);
m_browseSavePathBtn->setMinimumHeight(32);
m_browseSavePathBtn->setStyleSheet(
"QPushButton { background-color: #607D8B; color: white; }"
"QPushButton:hover { background-color: #546E7A; }"
"QPushButton:pressed { background-color: #455A64; }"
);
pathLayout->addWidget(m_savePathEdit);
pathLayout->addWidget(m_browseSavePathBtn);
captureLayout->addLayout(pathLayout);
// 深度图格式选择
QLabel *depthFormatLabel = new QLabel("深度图格式:", captureGroup);
captureLayout->addWidget(depthFormatLabel);
m_depthFormatCombo = new QComboBox(captureGroup);
m_depthFormatCombo->addItem("PNG (8位灰度)", "png");
m_depthFormatCombo->addItem("TIFF (16位原始)", "tiff");
m_depthFormatCombo->addItem("PNG + TIFF", "both");
m_depthFormatCombo->setCurrentIndex(2);
captureLayout->addWidget(m_depthFormatCombo);
// 点云格式选择
QLabel *pointCloudFormatLabel = new QLabel("点云格式:", captureGroup);
captureLayout->addWidget(pointCloudFormatLabel);
m_pointCloudFormatCombo = new QComboBox(captureGroup);
m_pointCloudFormatCombo->addItem("PCD (PCL标准)", "pcd");
m_pointCloudFormatCombo->addItem("PLY (Polygon)", "ply");
m_pointCloudFormatCombo->addItem("PCD + PLY", "both");
m_pointCloudFormatCombo->setCurrentIndex(2);
captureLayout->addWidget(m_pointCloudFormatCombo);
exposureCaptureLayout->addWidget(captureGroup);
exposureCaptureLayout->addStretch();
// 将第二个标签页添加到选项卡
tabWidget->addTab(exposureCaptureTab, "曝光与拍照");
// 将选项卡添加到控制面板设置伸展因子为1使其自适应扩展
controlLayout->addWidget(tabWidget, 1);
// ========== 统计信息显示组 ==========
QGroupBox *statsGroup = new QGroupBox("统计信息", m_controlPanel);
QVBoxLayout *statsLayout = new QVBoxLayout(statsGroup);
m_leftFpsLabel = new QLabel("左红外: 0.0 fps", statsGroup);
m_rightFpsLabel = new QLabel("右红外: 0.0 fps", statsGroup);
m_rgbFpsLabel = new QLabel("RGB: 0.0 fps", statsGroup);
m_pointCloudFpsLabel = new QLabel("点云: 0.0 fps", statsGroup);
m_resolutionLabel = new QLabel("1224x1024", statsGroup);
m_queueLabel = new QLabel("帧数: 0", statsGroup);
// 兼容旧代码
m_depthFpsLabel = m_leftFpsLabel;
QString statsStyle = "QLabel { font-size: 9pt; padding: 2px; }";
m_leftFpsLabel->setStyleSheet(statsStyle);
m_rightFpsLabel->setStyleSheet(statsStyle);
m_rgbFpsLabel->setStyleSheet(statsStyle);
m_pointCloudFpsLabel->setStyleSheet(statsStyle);
m_resolutionLabel->setStyleSheet(statsStyle);
m_queueLabel->setStyleSheet(statsStyle);
statsLayout->addWidget(m_leftFpsLabel);
statsLayout->addWidget(m_rightFpsLabel);
statsLayout->addWidget(m_rgbFpsLabel);
statsLayout->addWidget(m_pointCloudFpsLabel);
statsLayout->addWidget(m_resolutionLabel);
statsLayout->addWidget(m_queueLabel);
controlLayout->addWidget(statsGroup);
// ========== 创建右侧面板 ==========
QWidget *rightPanel = new QWidget(contentWidget);
QVBoxLayout *rightLayout = new QVBoxLayout(rightPanel);
rightLayout->setContentsMargins(5, 5, 5, 5);
rightLayout->setSpacing(5);
// ========== 四块显示区域2×2网格布局 ==========
QWidget *displayWidget = new QWidget(rightPanel);
QGridLayout *displayGrid = new QGridLayout(displayWidget);
displayGrid->setSpacing(5);
displayGrid->setContentsMargins(5, 5, 5, 5);
// 设置行列拉伸因子
displayGrid->setRowStretch(0, 1);
displayGrid->setRowStretch(1, 1);
displayGrid->setColumnStretch(0, 1);
displayGrid->setColumnStretch(1, 1);
// 左上:点云显示
m_pointCloudWidget = new PointCloudWidget(displayWidget);
m_pointCloudWidget->setMinimumSize(400, 300);
m_pointCloudWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
displayGrid->addWidget(m_pointCloudWidget, 0, 0);
// 右上:左红外图像
m_leftImageDisplay = new QLabel(displayWidget);
m_leftImageDisplay->setMinimumSize(400, 300);
m_leftImageDisplay->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
m_leftImageDisplay->setStyleSheet(
"QLabel { "
" background-color: #263238; "
" color: #B0BEC5; "
" border: 2px solid #37474F; "
" border-radius: 4px; "
" font-size: 10pt; "
"}"
);
m_leftImageDisplay->setAlignment(Qt::AlignCenter);
m_leftImageDisplay->setText("左红外\n(等待数据...)");
m_leftImageDisplay->setScaledContents(false);
displayGrid->addWidget(m_leftImageDisplay, 0, 1);
// 左下RGB图像
m_rgbImageDisplay = new QLabel(displayWidget);
m_rgbImageDisplay->setMinimumSize(400, 300);
m_rgbImageDisplay->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
m_rgbImageDisplay->setStyleSheet(
"QLabel { "
" background-color: #263238; "
" color: #B0BEC5; "
" border: 2px solid #37474F; "
" border-radius: 4px; "
" font-size: 10pt; "
"}"
);
m_rgbImageDisplay->setAlignment(Qt::AlignCenter);
m_rgbImageDisplay->setText("RGB彩色\n(等待数据...)");
m_rgbImageDisplay->setScaledContents(false);
displayGrid->addWidget(m_rgbImageDisplay, 1, 0);
// 右下:右红外图像
m_rightImageDisplay = new QLabel(displayWidget);
m_rightImageDisplay->setMinimumSize(400, 300);
m_rightImageDisplay->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
m_rightImageDisplay->setStyleSheet(
"QLabel { "
" background-color: #263238; "
" color: #B0BEC5; "
" border: 2px solid #37474F; "
" border-radius: 4px; "
" font-size: 10pt; "
"}"
);
m_rightImageDisplay->setAlignment(Qt::AlignCenter);
m_rightImageDisplay->setText("右红外\n(等待数据...)");
m_rightImageDisplay->setScaledContents(false);
displayGrid->addWidget(m_rightImageDisplay, 1, 1);
// 兼容旧代码m_imageDisplay指向左红外
m_imageDisplay = m_leftImageDisplay;
// 创建日志显示区域
QWidget *logWidget = new QWidget(m_centralWidget);
QVBoxLayout *logLayout = new QVBoxLayout(logWidget);
logLayout->setContentsMargins(5, 5, 5, 5);
logLayout->setSpacing(5);
// 日志标题和按钮
QHBoxLayout *logHeaderLayout = new QHBoxLayout();
QLabel *logTitle = new QLabel("实时日志", logWidget);
QFont titleFont = logTitle->font();
titleFont.setBold(true);
titleFont.setPointSize(11);
logTitle->setFont(titleFont);
logTitle->setStyleSheet("QLabel { padding: 4px; }"); // 移除硬编码颜色,使用系统调色板
m_clearLogBtn = new QPushButton(QIcon(":/icons/icons/clear.png"), "清除日志", logWidget);
m_clearLogBtn->setMaximumWidth(110);
m_clearLogBtn->setMinimumHeight(32);
m_clearLogBtn->setStyleSheet(
"QPushButton { background-color: #757575; color: white; font-size: 9pt; }"
"QPushButton:hover { background-color: #616161; }"
"QPushButton:pressed { background-color: #424242; }"
);
m_saveLogBtn = new QPushButton(QIcon(":/icons/icons/save.png"), "保存日志", logWidget);
m_saveLogBtn->setMaximumWidth(110);
m_saveLogBtn->setMinimumHeight(32);
m_saveLogBtn->setStyleSheet(
"QPushButton { background-color: #4CAF50; color: white; font-size: 9pt; }"
"QPushButton:hover { background-color: #45A049; }"
"QPushButton:pressed { background-color: #388E3C; }"
);
logHeaderLayout->addWidget(logTitle);
logHeaderLayout->addStretch();
logHeaderLayout->addWidget(m_clearLogBtn);
logHeaderLayout->addWidget(m_saveLogBtn);
logLayout->addLayout(logHeaderLayout);
// 日志文本显示
m_logDisplay = new QTextEdit(logWidget);
m_logDisplay->setReadOnly(true);
m_logDisplay->setMinimumHeight(100);
m_logDisplay->setStyleSheet("QTextEdit { background-color: #1E1E1E; color: #D4D4D4; font-family: Consolas, monospace; font-size: 9pt; }");
logLayout->addWidget(m_logDisplay);
// 将显示区和日志区添加到垂直分割器中
QSplitter *contentSplitter = new QSplitter(Qt::Vertical, rightPanel);
contentSplitter->addWidget(displayWidget);
contentSplitter->addWidget(logWidget);
contentSplitter->setStretchFactor(0, 3); // 显示区占3份
contentSplitter->setStretchFactor(1, 1); // 日志区占1份
rightLayout->addWidget(contentSplitter);
// 将左侧控制面板和右侧面板添加到内容布局
contentLayout->addWidget(m_controlPanel);
contentLayout->addWidget(rightPanel);
mainLayout->addWidget(contentWidget);
}
void MainWindow::setupConnections()
{
// 相机控制按钮连接
connect(m_startBtn, &QPushButton::clicked, this, &MainWindow::onStartClicked);
connect(m_stopBtn, &QPushButton::clicked, this, &MainWindow::onStopClicked);
connect(m_captureBtn, &QPushButton::clicked, this, &MainWindow::onCaptureClicked);
connect(m_browseSavePathBtn, &QPushButton::clicked, this, &MainWindow::onBrowseSavePathClicked);
// 曝光时间控制连接
connect(m_exposureSlider, &QSlider::valueChanged, m_exposureSpinBox, &QSpinBox::setValue);
connect(m_exposureSpinBox, QOverload<int>::of(&QSpinBox::valueChanged), m_exposureSlider, &QSlider::setValue);
connect(m_exposureSlider, &QSlider::valueChanged, this, &MainWindow::onExposureChanged);
// 网络配置连接
connect(m_connectBtn, &QPushButton::clicked, this, &MainWindow::onConnectClicked);
connect(m_ctrlPortSpinBox, QOverload<int>::of(&QSpinBox::valueChanged), this, &MainWindow::onControlPortChanged);
connect(m_dataPortSpinBox, QOverload<int>::of(&QSpinBox::valueChanged), this, &MainWindow::onDataPortChanged);
// 网络状态信号连接
connect(m_networkManager.get(), &NetworkManager::connected, this, &MainWindow::onNetworkConnected);
connect(m_networkManager.get(), &NetworkManager::disconnected, this, &MainWindow::onNetworkDisconnected);
connect(m_networkManager.get(), &NetworkManager::dataReceived, this, &MainWindow::onDataReceived);
// GVSP数据信号连接从NetworkManager
connect(m_networkManager.get(), &NetworkManager::imageReceived, this, &MainWindow::onImageReceived);
connect(m_networkManager.get(), &NetworkManager::leftImageReceived, this, &MainWindow::onLeftImageReceived);
connect(m_networkManager.get(), &NetworkManager::rightImageReceived, this, &MainWindow::onRightImageReceived);
connect(m_networkManager.get(), &NetworkManager::rgbImageReceived, this, &MainWindow::onRgbImageReceived);
connect(m_networkManager.get(), &NetworkManager::depthDataReceived, this, &MainWindow::onDepthDataReceived);
connect(m_networkManager.get(), &NetworkManager::pointCloudDataReceived, this, &MainWindow::onPointCloudDataReceived);
// 点云处理信号连接
connect(m_pointCloudProcessor.get(), &PointCloudProcessor::pointCloudReady, this, &MainWindow::onPointCloudReady);
// 刷新按钮连接
connect(m_refreshBtn, &QPushButton::clicked, this, &MainWindow::onRefreshClicked);
// 设备扫描信号连接
connect(m_deviceScanner.get(), &DeviceScanner::deviceFound, this, &MainWindow::onDeviceFound);
connect(m_deviceScanner.get(), &DeviceScanner::scanProgress, this, &MainWindow::onScanProgress);
connect(m_deviceScanner.get(), &DeviceScanner::scanFinished, this, &MainWindow::onScanFinished);
// 设备列表选择连接
connect(m_deviceList, &QListWidget::itemClicked, this, &MainWindow::onDeviceSelected);
// 日志按钮连接
connect(m_clearLogBtn, &QPushButton::clicked, this, &MainWindow::onClearLogClicked);
connect(m_saveLogBtn, &QPushButton::clicked, this, &MainWindow::onSaveLogClicked);
// 传输开关按钮连接
connect(m_leftIRToggle, &QPushButton::toggled, this, [this](bool checked) {
if(checked) {
m_leftIREnabled.storeRelaxed(1); // 标记启用
m_networkManager->sendEnableLeftIR();
qDebug() << "启用左红外传输";
} else {
m_leftIREnabled.storeRelaxed(0); // 标记禁用
m_networkManager->sendDisableLeftIR();
qDebug() << "禁用左红外传输";
// 清除显示区域,恢复默认等待界面
if(m_leftImageDisplay) {
m_leftImageDisplay->setPixmap(QPixmap());
m_leftImageDisplay->setText("左红外\n(等待数据...)");
}
}
});
connect(m_rightIRToggle, &QPushButton::toggled, this, [this](bool checked) {
if(checked) {
m_rightIREnabled.storeRelaxed(1); // 标记启用
m_networkManager->sendEnableRightIR();
qDebug() << "启用右红外传输";
} else {
m_rightIREnabled.storeRelaxed(0); // 标记禁用
m_networkManager->sendDisableRightIR();
qDebug() << "禁用右红外传输";
// 清除显示区域,恢复默认等待界面
if(m_rightImageDisplay) {
m_rightImageDisplay->setPixmap(QPixmap());
m_rightImageDisplay->setText("右红外\n(等待数据...)");
}
}
});
connect(m_rgbToggle, &QPushButton::toggled, this, [this](bool checked) {
if(checked) {
m_rgbEnabled.storeRelaxed(1); // 标记启用
m_networkManager->sendEnableRGB();
qDebug() << "启用RGB传输";
} else {
m_rgbEnabled.storeRelaxed(0); // 标记禁用
m_networkManager->sendDisableRGB();
qDebug() << "禁用RGB传输";
// 清除显示区域,恢复默认等待界面
if(m_rgbImageDisplay) {
m_rgbImageDisplay->setPixmap(QPixmap());
m_rgbImageDisplay->setText("RGB彩色\n(等待数据...)");
}
}
});
// 点云颜色开关连接
connect(m_pointCloudColorToggle, &QPushButton::toggled, this, [this](bool checked) {
if(m_pointCloudWidget) {
m_pointCloudWidget->setColorMode(checked);
qDebug() << "点云着色:" << (checked ? "开启" : "关闭");
}
});
// 单目/双目模式切换按钮连接
connect(m_monocularBtn, &QPushButton::clicked, this, [this]() {
m_monocularBtn->setChecked(true);
m_binocularBtn->setChecked(false);
m_networkManager->sendMonocularMode();
qDebug() << "切换为单目模式";
});
connect(m_binocularBtn, &QPushButton::clicked, this, [this]() {
m_monocularBtn->setChecked(false);
m_binocularBtn->setChecked(true);
m_networkManager->sendBinocularMode();
qDebug() << "切换为双目模式";
});
}
void MainWindow::loadSettings()
{
// 加载曝光时间
int exposure = m_configManager->getExposureTime();
m_exposureSlider->setValue(exposure);
m_exposureSpinBox->setValue(exposure);
// 加载保存路径
QString savePath = m_configManager->getSavePath();
m_savePathEdit->setText(savePath);
// 加载深度图格式
QString depthFormat = m_configManager->getDepthFormat();
int depthIndex = m_depthFormatCombo->findData(depthFormat);
if(depthIndex >= 0) {
m_depthFormatCombo->setCurrentIndex(depthIndex);
}
// 加载点云格式
QString pointCloudFormat = m_configManager->getPointCloudFormat();
int pcIndex = m_pointCloudFormatCombo->findData(pointCloudFormat);
if(pcIndex >= 0) {
m_pointCloudFormatCombo->setCurrentIndex(pcIndex);
}
}
void MainWindow::saveSettings()
{
// 保存曝光时间
m_configManager->setExposureTime(m_exposureSlider->value());
// 保存保存路径
m_configManager->setSavePath(m_savePathEdit->text());
// 保存深度图格式
m_configManager->setDepthFormat(m_depthFormatCombo->currentData().toString());
// 保存点云格式
m_configManager->setPointCloudFormat(m_pointCloudFormatCombo->currentData().toString());
}
// ========== 槽函数实现 ==========
void MainWindow::onStartClicked()
{
qDebug() << "启动按钮点击";
m_networkManager->sendStartCommand();
addLog("发送启动命令", "INFO");
}
void MainWindow::onStopClicked()
{
qDebug() << "停止按钮点击";
m_networkManager->sendStopCommand();
addLog("发送停止命令", "INFO");
}
void MainWindow::onExposureChanged(int value)
{
qDebug() << "曝光值改变为:" << value;
m_networkManager->sendExposureCommand(value);
addLog(QString("设置曝光时间: %1 μs").arg(value), "INFO");
}
void MainWindow::onConnectClicked()
{
if(m_isConnected) {
m_networkManager->disconnectFromCamera();
m_isConnected = false;
qDebug() << "断开相机连接";
} else {
QString ip = m_configManager->getIpAddress();
int ctrlPort = m_configManager->getControlPort();
int dataPort = m_configManager->getDataPort();
if(m_networkManager->connectToCamera(ip, ctrlPort, dataPort)) {
m_isConnected = true;
qDebug() << "连接到相机";
}
}
}
void MainWindow::onIpAddressChanged(const QString &ip)
{
qDebug() << "IP地址改变为:" << ip;
m_configManager->setIpAddress(ip);
}
void MainWindow::onControlPortChanged(int port)
{
qDebug() << "控制端口改变为:" << port;
m_configManager->setControlPort(port);
}
void MainWindow::onDataPortChanged(int port)
{
qDebug() << "数据端口改变为:" << port;
m_configManager->setDataPort(port);
}
void MainWindow::onNetworkConnected()
{
qDebug() << "网络已连接 - 更新UI";
m_isConnected = true;
m_connectBtn->setText("断开");
m_connectBtn->setStyleSheet(
"QPushButton { background-color: #F44336; color: white; }"
"QPushButton:hover { background-color: #E53935; }"
"QPushButton:pressed { background-color: #C62828; }"
);
m_statusLabel->setText("● 已连接");
m_statusLabel->setStyleSheet(
"QLabel { "
" color: #4CAF50; "
" font-weight: bold; "
" font-size: 11pt; "
" padding: 8px; "
"}"
);
QString ip = m_configManager->getIpAddress();
addLog(QString("已连接到相机: %1").arg(ip), "SUCCESS");
// 同步当前按钮状态到下位机
if(m_leftIRToggle->isChecked()) {
m_networkManager->sendEnableLeftIR();
} else {
m_networkManager->sendDisableLeftIR();
}
if(m_rightIRToggle->isChecked()) {
m_networkManager->sendEnableRightIR();
} else {
m_networkManager->sendDisableRightIR();
}
if(m_rgbToggle->isChecked()) {
m_networkManager->sendEnableRGB();
} else {
m_networkManager->sendDisableRGB();
}
addLog("已同步相机开关状态", "INFO");
}
void MainWindow::onNetworkDisconnected()
{
qDebug() << "网络已断开 - 更新UI";
m_isConnected = false;
m_connectBtn->setText("连接");
m_connectBtn->setStyleSheet(
"QPushButton { background-color: #4CAF50; color: white; }"
"QPushButton:hover { background-color: #45A049; }"
"QPushButton:pressed { background-color: #388E3C; }"
);
m_statusLabel->setText("● 未连接");
m_statusLabel->setStyleSheet(
"QLabel { "
" color: #F44336; "
" font-weight: bold; "
" font-size: 11pt; "
" padding: 8px; "
"}"
);
addLog("相机连接已断开", "WARNING");
}
void MainWindow::updateUI()
{
// 定时器更新UI
}
// ========== 设备扫描槽函数 ==========
void MainWindow::onRefreshClicked()
{
qDebug() << "刷新设备按钮点击";
m_deviceList->clear();
if(m_deviceScanner->isScanning()) {
m_deviceScanner->stopScan();
m_refreshBtn->setText("刷新设备");
} else {
m_deviceScanner->startScan();
m_refreshBtn->setText("停止扫描");
}
}
void MainWindow::onDeviceFound(const DeviceInfo &device)
{
qDebug() << "发现设备:" << device.ipAddress;
// 添加到列表
QString itemText = QString("%1 - %2").arg(device.ipAddress).arg(device.deviceName);
QListWidgetItem *item = new QListWidgetItem(itemText);
item->setData(Qt::UserRole, device.ipAddress);
m_deviceList->addItem(item);
}
void MainWindow::onScanProgress(int current, int total)
{
qDebug() << "扫描进度:" << current << "/" << total;
}
void MainWindow::onScanFinished(int devicesFound)
{
qDebug() << "扫描完成。发现" << devicesFound << "个设备";
m_refreshBtn->setText("刷新设备");
}
void MainWindow::onDeviceSelected(QListWidgetItem *item)
{
QString ip = item->data(Qt::UserRole).toString();
qDebug() << "选择设备:" << ip;
// 更新配置管理器中的IP地址
m_configManager->setIpAddress(ip);
}
// ========== 数据接收槽函数 ==========
void MainWindow::onDataReceived(const QByteArray &data)
{
// NetworkManager已经自动将数据传递给GVSP解析器
// 这里只用于调试统计
Q_UNUSED(data);
}
// ========== GVSP数据处理槽函数 ==========
void MainWindow::onImageReceived(const QImage &image, uint32_t blockId)
{
// 保存当前图像用于拍照
m_currentImage = image;
m_currentFrameId = blockId;
// 计算深度图FPS和累计帧数
m_depthFrameCount++;
m_totalDepthFrameCount++;
QDateTime currentTime = QDateTime::currentDateTime();
if(m_lastDepthFrameTime.isValid()) {
qint64 elapsed = m_lastDepthFrameTime.msecsTo(currentTime);
if(elapsed >= 1000) { // 每秒更新一次FPS
m_currentDepthFps = (m_depthFrameCount * 1000.0) / elapsed;
m_depthFrameCount = 0;
m_lastDepthFrameTime = currentTime;
updateStatistics();
}
} else {
m_lastDepthFrameTime = currentTime;
}
// 将图像显示在UI上使用快速缩放
if(m_imageDisplay) {
QPixmap pixmap = QPixmap::fromImage(image);
m_imageDisplay->setPixmap(pixmap.scaled(m_imageDisplay->size(), Qt::KeepAspectRatio, Qt::FastTransformation));
}
}
void MainWindow::onLeftImageReceived(const QByteArray &jpegData, uint32_t blockId)
{
// 检查左红外是否启用,如果禁用则忽略数据(防止关闭后闪烁)
if(m_leftIREnabled.loadAcquire() == 0) {
return;
}
// 保存原始数据(用于拍照保存完整分辨率)
m_currentLeftIRData = jpegData;
// 计算左红外FPS
m_leftFrameCount++;
m_totalLeftFrameCount++;
QDateTime currentTime = QDateTime::currentDateTime();
if(m_lastLeftFrameTime.isValid()) {
qint64 elapsed = m_lastLeftFrameTime.msecsTo(currentTime);
if(elapsed >= 1000) {
m_currentLeftFps = (m_leftFrameCount * 1000.0) / elapsed;
m_leftFrameCount = 0;
m_lastLeftFrameTime = currentTime;
updateStatistics();
}
} else {
m_lastLeftFrameTime = currentTime;
}
// 使用后台线程处理红外数据避免阻塞UI
if(m_leftImageDisplay && jpegData.size() > 0) {
// 检查数据大小8位下采样(612x512)或16位原始(1224x1024)
size_t size8bit = 612 * 512;
size_t size16bit = 1224 * 1024 * sizeof(uint16_t);
if(jpegData.size() == size8bit) {
// 8位下采样格式直接显示
QByteArray dataCopy = jpegData;
QtConcurrent::run([this, dataCopy]() {
try {
QImage image(reinterpret_cast<const uchar*>(dataCopy.constData()),
612, 512, 612, QImage::Format_Grayscale8);
QImage imageCopy = image.copy();
QMetaObject::invokeMethod(this, [this, imageCopy]() {
if(m_leftImageDisplay) {
QPixmap pixmap = QPixmap::fromImage(imageCopy);
m_leftImageDisplay->setPixmap(pixmap.scaled(
m_leftImageDisplay->size(), Qt::KeepAspectRatio, Qt::FastTransformation));
}
}, Qt::QueuedConnection);
} catch (const std::exception &e) {
qDebug() << "[MainWindow] ERROR: Left IR 8bit processing exception:" << e.what();
}
});
} else if(jpegData.size() == size16bit) {
// 16位原始格式需要归一化处理
QByteArray dataCopy = jpegData;
// 在后台线程处理
QtConcurrent::run([this, dataCopy]() {
try {
const uint16_t* src = reinterpret_cast<const uint16_t*>(dataCopy.constData());
// 方案2快速百分位数估算无需排序采样估算
// 优点适应不同环境画面对比度好速度快10倍以上
uint16_t minVal = 65535, maxVal = 0;
// 第一遍快速扫描找到粗略范围每隔8个像素采样
for (int i = 0; i < 1224 * 1024; i += 8) {
uint16_t val = src[i];
if(val > 0) {
if(val < minVal) minVal = val;
if(val > maxVal) maxVal = val;
}
}
// 第二遍:使用直方图统计精确百分位数(避免排序)
if(maxVal > minVal) {
const int histSize = 256;
int histogram[histSize] = {0};
float binWidth = (maxVal - minVal) / (float)histSize;
// 构建直方图
for (int i = 0; i < 1224 * 1024; i++) {
if(src[i] > 0) {
int bin = (src[i] - minVal) / binWidth;
if(bin >= histSize) bin = histSize - 1;
histogram[bin]++;
}
}
// 计算1%和99%百分位数
int totalPixels = 0;
for (int i = 0; i < histSize; i++) totalPixels += histogram[i];
int thresh_1 = totalPixels * 0.01;
int thresh_99 = totalPixels * 0.99;
int cumsum = 0;
for (int i = 0; i < histSize; i++) {
cumsum += histogram[i];
if(cumsum >= thresh_1 && minVal == 65535) {
minVal = minVal + i * binWidth;
}
if(cumsum >= thresh_99) {
maxVal = minVal + i * binWidth;
break;
}
}
}
// 创建8位图像并归一化
QImage image(1224, 1024, QImage::Format_Grayscale8);
uint8_t* dst = image.bits();
float scale = (maxVal > minVal) ? (255.0f / (maxVal - minVal)) : 0.0f;
for (int i = 0; i < 1224 * 1024; i++) {
if(src[i] == 0) {
dst[i] = 0;
} else if(src[i] <= minVal) {
dst[i] = 0;
} else if(src[i] >= maxVal) {
dst[i] = 255;
} else {
dst[i] = static_cast<uint8_t>((src[i] - minVal) * scale);
}
}
QImage imageCopy = image.copy();
// 在主线程更新UI
QMetaObject::invokeMethod(this, [this, imageCopy]() {
if(m_leftImageDisplay) {
QPixmap pixmap = QPixmap::fromImage(imageCopy);
m_leftImageDisplay->setPixmap(pixmap.scaled(
m_leftImageDisplay->size(), Qt::KeepAspectRatio, Qt::FastTransformation));
}
}, Qt::QueuedConnection);
} catch (const std::exception &e) {
qDebug() << "[MainWindow] ERROR: Left IR processing exception:" << e.what();
}
});
} else {
qDebug() << "[MainWindow] ERROR: Left IR data size mismatch:" << jpegData.size()
<< "(expected 8bit:" << size8bit << "or 16bit:" << size16bit << ")";
}
}
}
void MainWindow::onRightImageReceived(const QByteArray &jpegData, uint32_t blockId)
{
// 检查右红外是否启用,如果禁用则忽略数据(防止关闭后闪烁)
if(m_rightIREnabled.loadAcquire() == 0) {
return;
}
// 保存原始数据(用于拍照保存完整分辨率)
m_currentRightIRData = jpegData;
// 计算右红外FPS
m_rightFrameCount++;
m_totalRightFrameCount++;
QDateTime currentTime = QDateTime::currentDateTime();
if(m_lastRightFrameTime.isValid()) {
qint64 elapsed = m_lastRightFrameTime.msecsTo(currentTime);
if(elapsed >= 1000) {
m_currentRightFps = (m_rightFrameCount * 1000.0) / elapsed;
m_rightFrameCount = 0;
m_lastRightFrameTime = currentTime;
updateStatistics();
}
} else {
m_lastRightFrameTime = currentTime;
}
// 使用后台线程处理红外数据避免阻塞UI
if(m_rightImageDisplay && jpegData.size() > 0) {
// 检查数据大小8位下采样(612x512)或16位原始(1224x1024)
size_t size8bit = 612 * 512;
size_t size16bit = 1224 * 1024 * sizeof(uint16_t);
if(jpegData.size() == size8bit) {
// 8位下采样格式直接显示
QByteArray dataCopy = jpegData;
QtConcurrent::run([this, dataCopy]() {
try {
QImage image(reinterpret_cast<const uchar*>(dataCopy.constData()),
612, 512, 612, QImage::Format_Grayscale8);
QImage imageCopy = image.copy();
QMetaObject::invokeMethod(this, [this, imageCopy]() {
if(m_rightImageDisplay) {
QPixmap pixmap = QPixmap::fromImage(imageCopy);
m_rightImageDisplay->setPixmap(pixmap.scaled(
m_rightImageDisplay->size(), Qt::KeepAspectRatio, Qt::FastTransformation));
}
}, Qt::QueuedConnection);
} catch (const std::exception &e) {
qDebug() << "[MainWindow] ERROR: Right IR 8bit processing exception:" << e.what();
}
});
} else if(jpegData.size() == size16bit) {
// 16位原始格式需要归一化处理
QByteArray dataCopy = jpegData;
// 在后台线程处理
QtConcurrent::run([this, dataCopy]() {
try {
const uint16_t* src = reinterpret_cast<const uint16_t*>(dataCopy.constData());
// 方案2快速百分位数估算无需排序采样估算
// 优点适应不同环境画面对比度好速度快10倍以上
uint16_t minVal = 65535, maxVal = 0;
// 第一遍快速扫描找到粗略范围每隔8个像素采样
for (int i = 0; i < 1224 * 1024; i += 8) {
uint16_t val = src[i];
if(val > 0) {
if(val < minVal) minVal = val;
if(val > maxVal) maxVal = val;
}
}
// 第二遍:使用直方图统计精确百分位数(避免排序)
if(maxVal > minVal) {
const int histSize = 256;
int histogram[histSize] = {0};
float binWidth = (maxVal - minVal) / (float)histSize;
// 构建直方图
for (int i = 0; i < 1224 * 1024; i++) {
if(src[i] > 0) {
int bin = (src[i] - minVal) / binWidth;
if(bin >= histSize) bin = histSize - 1;
histogram[bin]++;
}
}
// 计算1%和99%百分位数
int totalPixels = 0;
for (int i = 0; i < histSize; i++) totalPixels += histogram[i];
int thresh_1 = totalPixels * 0.01;
int thresh_99 = totalPixels * 0.99;
int cumsum = 0;
for (int i = 0; i < histSize; i++) {
cumsum += histogram[i];
if(cumsum >= thresh_1 && minVal == 65535) {
minVal = minVal + i * binWidth;
}
if(cumsum >= thresh_99) {
maxVal = minVal + i * binWidth;
break;
}
}
}
// 创建8位图像并归一化
QImage image(1224, 1024, QImage::Format_Grayscale8);
uint8_t* dst = image.bits();
float scale = (maxVal > minVal) ? (255.0f / (maxVal - minVal)) : 0.0f;
for (int i = 0; i < 1224 * 1024; i++) {
if(src[i] == 0) {
dst[i] = 0;
} else if(src[i] <= minVal) {
dst[i] = 0;
} else if(src[i] >= maxVal) {
dst[i] = 255;
} else {
dst[i] = static_cast<uint8_t>((src[i] - minVal) * scale);
}
}
QImage imageCopy = image.copy();
// 在主线程更新UI
QMetaObject::invokeMethod(this, [this, imageCopy]() {
if(m_rightImageDisplay) {
QPixmap pixmap = QPixmap::fromImage(imageCopy);
m_rightImageDisplay->setPixmap(pixmap.scaled(
m_rightImageDisplay->size(), Qt::KeepAspectRatio, Qt::FastTransformation));
}
}, Qt::QueuedConnection);
} catch (const std::exception &e) {
qDebug() << "[MainWindow] ERROR: Right IR processing exception:" << e.what();
}
});
} else {
qDebug() << "[MainWindow] ERROR: Right IR data size mismatch:" << jpegData.size()
<< "(expected 8bit:" << size8bit << "or 16bit:" << size16bit << ")";
}
}
}
void MainWindow::onRgbImageReceived(const QByteArray &jpegData, uint32_t blockId)
{
// 检查RGB是否启用如果禁用则忽略数据防止关闭后闪烁
if(m_rgbEnabled.loadAcquire() == 0) {
return;
}
// 记录接收时间
static QDateTime lastLogTime = QDateTime::currentDateTime();
QDateTime receiveTime = QDateTime::currentDateTime();
// 保存原始JPEG数据用于拍照保存完整分辨率
m_currentRgbData = jpegData;
// 计算RGB FPS
m_rgbFrameCount++;
m_totalRgbFrameCount++;
QDateTime currentTime = QDateTime::currentDateTime();
if(m_lastRgbFrameTime.isValid()) {
qint64 elapsed = m_lastRgbFrameTime.msecsTo(currentTime);
if(elapsed >= 1000) {
m_currentRgbFps = (m_rgbFrameCount * 1000.0) / elapsed;
m_rgbFrameCount = 0;
m_lastRgbFrameTime = currentTime;
updateStatistics();
}
} else {
m_lastRgbFrameTime = currentTime;
}
// 移除频繁的日志输出
// 使用后台线程解码MJPEG避免阻塞UI
if(m_rgbImageDisplay && jpegData.size() > 0) {
// 智能帧丢弃:如果上一帧还在处理,跳过当前帧(避免积压旧帧)
if(m_rgbProcessing.loadAcquire() > 0) {
return; // 跳过当前帧,优先处理最新帧
}
// 标记开始处理
m_rgbProcessing.ref();
// 复制数据到局部变量
QByteArray dataCopy = jpegData;
// 在后台线程解码
QtConcurrent::run([this, dataCopy]() {
try {
// 使用OpenCV解码
std::vector<uchar> buffer(dataCopy.begin(), dataCopy.end());
cv::Mat cvImage = cv::imdecode(buffer, cv::IMREAD_COLOR);
if(!cvImage.empty()) {
// 缩放到1/2大小
cv::Mat resized;
cv::resize(cvImage, resized, cv::Size(), 0.5, 0.5, cv::INTER_LINEAR);
// 转换BGR到RGB
cv::cvtColor(resized, resized, cv::COLOR_BGR2RGB);
// 转换cv::Mat到QImage
QImage tempImage(resized.data, resized.cols, resized.rows,
resized.step, QImage::Format_RGB888);
QImage image = tempImage.copy();
// 在后台线程完成缩放,避免主线程阻塞
int displayWidth = m_rgbImageDisplay->width();
int displayHeight = m_rgbImageDisplay->height();
// 计算保持宽高比的缩放尺寸
QSize scaledSize = image.size().scaled(displayWidth, displayHeight, Qt::KeepAspectRatio);
QImage scaledImage = image.scaled(scaledSize, Qt::KeepAspectRatio, Qt::FastTransformation);
QPixmap pixmap = QPixmap::fromImage(scaledImage);
// 使用DirectConnection直接在主线程更新UI更快避免队列积压
QMetaObject::invokeMethod(this, [this, pixmap]() {
if(m_rgbImageDisplay) {
m_rgbImageDisplay->setPixmap(pixmap);
}
}, Qt::QueuedConnection);
}
} catch (const std::exception &e) {
qDebug() << "[MainWindow] ERROR: RGB decode exception:" << e.what();
}
// 处理完成,减少标志
m_rgbProcessing.deref();
});
}
}
void MainWindow::onDepthDataReceived(const QByteArray &depthData, uint32_t blockId)
{
// 实时处理每一帧
// 注释掉频繁的日志输出
// qDebug() << "Depth data received: Block" << blockId << "Size:" << depthData.size() << "bytes";
// 调用PointCloudProcessor进行OpenCL计算
m_pointCloudProcessor->processDepthData(depthData, blockId);
}
void MainWindow::onPointCloudDataReceived(const QByteArray &cloudData, uint32_t blockId)
{
// qDebug() << "[MainWindow] Point cloud data received: Block" << blockId << "Size:" << cloudData.size() << "bytes";
// 使用QtConcurrent在后台线程处理点云数据
QtConcurrent::run([this, cloudData, blockId]() {
m_pointCloudProcessor->processPointCloudData(cloudData, blockId);
});
}
void MainWindow::onPointCloudReady(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud, uint32_t blockId)
{
// 注释掉频繁的日志输出
// qDebug() << "Point cloud ready: Block" << blockId << "Points:" << cloud->size();
// 保存当前点云用于拍照
m_currentPointCloud = cloud;
// 计算点云FPS和累计帧数
m_pointCloudFrameCount++;
m_totalPointCloudFrameCount++;
QDateTime currentTime = QDateTime::currentDateTime();
if(m_lastPointCloudFrameTime.isValid()) {
qint64 elapsed = m_lastPointCloudFrameTime.msecsTo(currentTime);
if(elapsed >= 1000) { // 每秒更新一次FPS
m_currentPointCloudFps = (m_pointCloudFrameCount * 1000.0) / elapsed;
m_pointCloudFrameCount = 0;
m_lastPointCloudFrameTime = currentTime;
updateStatistics();
}
} else {
m_lastPointCloudFrameTime = currentTime;
}
// 更新点云显示
if(m_pointCloudWidget) {
m_pointCloudWidget->updatePointCloud(cloud);
}
}
// ========== 拍照功能 ==========
void MainWindow::onCaptureClicked()
{
qDebug() << "拍照按钮点击";
// 检查是否有可用数据
if(m_currentImage.isNull() && (!m_currentPointCloud || m_currentPointCloud->empty())) {
QMessageBox::warning(this, "拍照失败", "没有可用的图像或点云数据。\n请先启动相机采集。");
addLog("拍照失败:没有可用数据", "ERROR");
return;
}
// 获取保存路径
QString saveDir = m_savePathEdit->text().trimmed();
if(saveDir.isEmpty()) {
// 使用默认路径:用户图片文件夹
saveDir = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
qDebug() << "使用默认保存路径:" << saveDir;
}
// 检查目录是否存在,不存在则创建
QDir dir(saveDir);
if(!dir.exists()) {
if(!dir.mkpath(".")) {
QMessageBox::warning(this, "拍照失败", QString("无法创建保存目录:\n%1").arg(saveDir));
return;
}
}
// 生成时间戳文件名
QString timestamp = QDateTime::currentDateTime().toString("yyyyMMdd_HHmmss");
QString frameId = QString::number(m_currentFrameId);
QString baseName = QString("capture_%1_frame%2").arg(timestamp).arg(frameId);
// 获取格式选择
QString depthFormat = m_depthFormatCombo->currentData().toString();
QString pointCloudFormat = m_pointCloudFormatCombo->currentData().toString();
// 复制当前数据(避免在后台线程中访问成员变量)
QImage imageCopy = m_currentImage.copy();
pcl::PointCloud<pcl::PointXYZ>::Ptr cloudCopy(new pcl::PointCloud<pcl::PointXYZ>(*m_currentPointCloud));
// 复制三个相机的原始JPEG数据
QByteArray leftIRCopy = m_currentLeftIRData;
QByteArray rightIRCopy = m_currentRightIRData;
QByteArray rgbCopy = m_currentRgbData;
qDebug() << "开始后台保存...";
addLog(QString("开始保存: %1").arg(baseName), "INFO");
// 在后台线程中执行保存操作
QtConcurrent::run([this, saveDir, baseName, depthFormat, pointCloudFormat,
imageCopy, cloudCopy, leftIRCopy, rightIRCopy, rgbCopy]() {
this->performBackgroundSave(saveDir, baseName, depthFormat, pointCloudFormat,
imageCopy, cloudCopy, leftIRCopy, rightIRCopy, rgbCopy);
});
}
void MainWindow::onBrowseSavePathClicked()
{
QString dir = QFileDialog::getExistingDirectory(this, "选择保存目录",
m_savePathEdit->text().isEmpty()
? QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)
: m_savePathEdit->text(),
QFileDialog::ShowDirsOnly);
if(!dir.isEmpty()) {
m_savePathEdit->setText(dir);
qDebug() << "设置保存路径:" << dir;
}
}
void MainWindow::performBackgroundSave(const QString &saveDir, const QString &baseName,
const QString &depthFormat, const QString &pointCloudFormat,
const QImage &image, pcl::PointCloud<pcl::PointXYZ>::Ptr cloud,
const QByteArray &leftIRData, const QByteArray &rightIRData,
const QByteArray &rgbData)
{
qDebug() << "后台保存线程开始...";
bool depthSuccess = false;
bool cloudSuccess = false;
// 保存深度图
if(!image.isNull()) {
try {
// 转换QImage到OpenCV Mat
cv::Mat mat;
if(image.format() == QImage::Format_Grayscale8) {
mat = cv::Mat(image.height(), image.width(), CV_8UC1,
const_cast<uchar*>(image.bits()), image.bytesPerLine()).clone();
} else if(image.format() == QImage::Format_Grayscale16) {
mat = cv::Mat(image.height(), image.width(), CV_16UC1,
const_cast<uchar*>(image.bits()), image.bytesPerLine()).clone();
} else {
// 转换为灰度图
QImage grayImage = image.convertToFormat(QImage::Format_Grayscale8);
mat = cv::Mat(grayImage.height(), grayImage.width(), CV_8UC1,
const_cast<uchar*>(grayImage.bits()), grayImage.bytesPerLine()).clone();
}
// 根据format参数保存
if(depthFormat == "png" || depthFormat == "both") {
QString pngPath = QString("%1/%2_depth.png").arg(saveDir).arg(baseName);
cv::Mat mat8bit;
if(mat.type() == CV_16UC1) {
mat.convertTo(mat8bit, CV_8UC1, 255.0 / 65535.0);
} else {
mat8bit = mat;
}
cv::imwrite(pngPath.toStdString(), mat8bit);
qDebug() << "保存PNG深度图:" << pngPath;
depthSuccess = true;
}
if(depthFormat == "tiff" || depthFormat == "both") {
QString tiffPath = QString("%1/%2_depth.tiff").arg(saveDir).arg(baseName);
if(mat.type() == CV_16UC1) {
cv::imwrite(tiffPath.toStdString(), mat);
qDebug() << "保存TIFF深度图(16位):" << tiffPath;
} else {
cv::Mat mat16bit;
mat.convertTo(mat16bit, CV_16UC1, 257.0);
cv::imwrite(tiffPath.toStdString(), mat16bit);
qDebug() << "保存TIFF深度图(从8位转换):" << tiffPath;
}
depthSuccess = true;
}
} catch (const std::exception &e) {
qDebug() << "保存深度图失败:" << e.what();
}
}
// 保存点云
if(cloud && !cloud->empty()) {
try {
if(pointCloudFormat == "pcd" || pointCloudFormat == "both") {
QString pcdPath = QString("%1/%2_pointcloud.pcd").arg(saveDir).arg(baseName);
if(pcl::io::savePCDFileBinary(pcdPath.toStdString(), *cloud) == 0) {
qDebug() << "保存PCD点云:" << pcdPath;
cloudSuccess = true;
} else {
qDebug() << "保存PCD失败";
}
}
if(pointCloudFormat == "ply" || pointCloudFormat == "both") {
QString plyPath = QString("%1/%2_pointcloud.ply").arg(saveDir).arg(baseName);
if(pcl::io::savePLYFileASCII(plyPath.toStdString(), *cloud) == 0) {
qDebug() << "保存PLY点云:" << plyPath;
cloudSuccess = true;
} else {
qDebug() << "保存PLY失败";
}
}
} catch (const std::exception &e) {
qDebug() << "保存点云失败:" << e.what();
}
}
// 保存三个相机的原始图像(完整分辨率)
bool leftIRSuccess = false;
bool rightIRSuccess = false;
bool rgbSuccess = false;
// 保存左红外图像支持16位原始1224×1024或8位下采样612×512
if(!leftIRData.isEmpty()) {
try {
size_t size16bit = 1224 * 1024 * sizeof(uint16_t);
size_t size8bit = 612 * 512;
if(leftIRData.size() == size16bit) {
const uint16_t* src = reinterpret_cast<const uint16_t*>(leftIRData.constData());
cv::Mat leftIR16(1024, 1224, CV_16UC1);
memcpy(leftIR16.data, src, size16bit);
if(depthFormat == "png" || depthFormat == "both") {
QString pngPath = QString("%1/%2_left_ir.png").arg(saveDir).arg(baseName);
cv::Mat leftIR8;
leftIR16.convertTo(leftIR8, CV_8UC1, 255.0 / 65535.0);
cv::imwrite(pngPath.toStdString(), leftIR8);
qDebug() << "保存左红外PNG图像(16bit):" << pngPath;
leftIRSuccess = true;
}
if(depthFormat == "tiff" || depthFormat == "both") {
QString tiffPath = QString("%1/%2_left_ir.tiff").arg(saveDir).arg(baseName);
cv::imwrite(tiffPath.toStdString(), leftIR16);
qDebug() << "保存左红外TIFF图像(16位):" << tiffPath;
leftIRSuccess = true;
}
} else if(leftIRData.size() == size8bit) {
cv::Mat leftIR8(512, 612, CV_8UC1, const_cast<char*>(leftIRData.constData()));
cv::Mat leftIR8Clone = leftIR8.clone();
if(depthFormat == "png" || depthFormat == "both") {
QString pngPath = QString("%1/%2_left_ir.png").arg(saveDir).arg(baseName);
cv::imwrite(pngPath.toStdString(), leftIR8Clone);
qDebug() << "保存左红外PNG图像(8bit下采样):" << pngPath;
leftIRSuccess = true;
}
if(depthFormat == "tiff" || depthFormat == "both") {
QString tiffPath = QString("%1/%2_left_ir.tiff").arg(saveDir).arg(baseName);
cv::imwrite(tiffPath.toStdString(), leftIR8Clone);
qDebug() << "保存左红外TIFF图像(8bit下采样):" << tiffPath;
leftIRSuccess = true;
}
} else {
qDebug() << "左红外数据大小不匹配:" << leftIRData.size()
<< "(期望16bit:" << size16bit << "或8bit:" << size8bit << ")";
}
} catch (const std::exception &e) {
qDebug() << "保存左红外图像失败:" << e.what();
}
}
// 保存右红外图像支持16位原始1224×1024或8位下采样612×512
if(!rightIRData.isEmpty()) {
try {
size_t size16bit = 1224 * 1024 * sizeof(uint16_t);
size_t size8bit = 612 * 512;
if(rightIRData.size() == size16bit) {
const uint16_t* src = reinterpret_cast<const uint16_t*>(rightIRData.constData());
cv::Mat rightIR16(1024, 1224, CV_16UC1);
memcpy(rightIR16.data, src, size16bit);
if(depthFormat == "png" || depthFormat == "both") {
QString pngPath = QString("%1/%2_right_ir.png").arg(saveDir).arg(baseName);
cv::Mat rightIR8;
rightIR16.convertTo(rightIR8, CV_8UC1, 255.0 / 65535.0);
cv::imwrite(pngPath.toStdString(), rightIR8);
qDebug() << "保存右红外PNG图像(16bit):" << pngPath;
rightIRSuccess = true;
}
if(depthFormat == "tiff" || depthFormat == "both") {
QString tiffPath = QString("%1/%2_right_ir.tiff").arg(saveDir).arg(baseName);
cv::imwrite(tiffPath.toStdString(), rightIR16);
qDebug() << "保存右红外TIFF图像(16位):" << tiffPath;
rightIRSuccess = true;
}
} else if(rightIRData.size() == size8bit) {
cv::Mat rightIR8(512, 612, CV_8UC1, const_cast<char*>(rightIRData.constData()));
cv::Mat rightIR8Clone = rightIR8.clone();
if(depthFormat == "png" || depthFormat == "both") {
QString pngPath = QString("%1/%2_right_ir.png").arg(saveDir).arg(baseName);
cv::imwrite(pngPath.toStdString(), rightIR8Clone);
qDebug() << "保存右红外PNG图像(8bit下采样):" << pngPath;
rightIRSuccess = true;
}
if(depthFormat == "tiff" || depthFormat == "both") {
QString tiffPath = QString("%1/%2_right_ir.tiff").arg(saveDir).arg(baseName);
cv::imwrite(tiffPath.toStdString(), rightIR8Clone);
qDebug() << "保存右红外TIFF图像(8bit下采样):" << tiffPath;
rightIRSuccess = true;
}
} else {
qDebug() << "右红外数据大小不匹配:" << rightIRData.size()
<< "(期望16bit:" << size16bit << "或8bit:" << size8bit << ")";
}
} catch (const std::exception &e) {
qDebug() << "保存右红外图像失败:" << e.what();
}
}
// 保存RGB图像MJPEG原始数据完整分辨率1920×1080
if(!rgbData.isEmpty()) {
try {
// 直接保存JPEG文件无需解码保持原始质量
QString rgbPath = QString("%1/%2_rgb.jpg").arg(saveDir).arg(baseName);
QFile file(rgbPath);
if(file.open(QIODevice::WriteOnly)) {
file.write(rgbData);
file.close();
qDebug() << "保存RGB图像(JPEG):" << rgbPath;
rgbSuccess = true;
} else {
qDebug() << "无法创建RGB文件:" << rgbPath;
}
} catch (const std::exception &e) {
qDebug() << "保存RGB图像失败:" << e.what();
}
}
qDebug() << "后台保存完成 - 深度图:" << (depthSuccess ? "成功" : "失败")
<< ", 点云:" << (cloudSuccess ? "成功" : "失败")
<< ", 左红外:" << (leftIRSuccess ? "成功" : "失败")
<< ", 右红外:" << (rightIRSuccess ? "成功" : "失败")
<< ", RGB:" << (rgbSuccess ? "成功" : "失败");
}
bool MainWindow::saveDepthImage(const QString &dir, const QString &baseName, const QString &format)
{
try {
// 转换QImage到OpenCV Mat
cv::Mat mat;
if(m_currentImage.format() == QImage::Format_Grayscale8) {
mat = cv::Mat(m_currentImage.height(), m_currentImage.width(), CV_8UC1,
const_cast<uchar*>(m_currentImage.bits()), m_currentImage.bytesPerLine()).clone();
} else if(m_currentImage.format() == QImage::Format_Grayscale16) {
mat = cv::Mat(m_currentImage.height(), m_currentImage.width(), CV_16UC1,
const_cast<uchar*>(m_currentImage.bits()), m_currentImage.bytesPerLine()).clone();
} else {
// 转换为灰度图
QImage grayImage = m_currentImage.convertToFormat(QImage::Format_Grayscale8);
mat = cv::Mat(grayImage.height(), grayImage.width(), CV_8UC1,
const_cast<uchar*>(grayImage.bits()), grayImage.bytesPerLine()).clone();
}
// 根据format参数保存
if(format == "png" || format == "both") {
// 保存PNG格式8位
QString pngPath = QString("%1/%2_depth.png").arg(dir).arg(baseName);
cv::Mat mat8bit;
if(mat.type() == CV_16UC1) {
mat.convertTo(mat8bit, CV_8UC1, 255.0 / 65535.0);
} else {
mat8bit = mat;
}
cv::imwrite(pngPath.toStdString(), mat8bit);
qDebug() << "保存PNG深度图:" << pngPath;
}
if(format == "tiff" || format == "both") {
// 保存TIFF格式16位原始数据
QString tiffPath = QString("%1/%2_depth.tiff").arg(dir).arg(baseName);
if(mat.type() == CV_16UC1) {
cv::imwrite(tiffPath.toStdString(), mat);
qDebug() << "保存TIFF深度图(16位):" << tiffPath;
} else {
// 如果不是16位转换为16位保存
cv::Mat mat16bit;
mat.convertTo(mat16bit, CV_16UC1, 257.0); // 8位转16位乘以257 (65535/255)
cv::imwrite(tiffPath.toStdString(), mat16bit);
qDebug() << "保存TIFF深度图(从8位转换):" << tiffPath;
}
}
return true;
} catch (const std::exception &e) {
qDebug() << "保存深度图失败:" << e.what();
return false;
}
}
bool MainWindow::savePointCloud(const QString &dir, const QString &baseName, const QString &format)
{
try {
if(!m_currentPointCloud || m_currentPointCloud->empty()) {
qDebug() << "点云数据为空";
return false;
}
bool success = false;
// 根据format参数保存
if(format == "pcd" || format == "both") {
// 保存PCD格式PCL标准格式二进制
QString pcdPath = QString("%1/%2_pointcloud.pcd").arg(dir).arg(baseName);
if(pcl::io::savePCDFileBinary(pcdPath.toStdString(), *m_currentPointCloud) == 0) {
qDebug() << "保存PCD点云:" << pcdPath;
success = true;
} else {
qDebug() << "保存PCD失败";
}
}
if(format == "ply" || format == "both") {
// 保存PLY格式Polygon格式ASCII
QString plyPath = QString("%1/%2_pointcloud.ply").arg(dir).arg(baseName);
if(pcl::io::savePLYFileASCII(plyPath.toStdString(), *m_currentPointCloud) == 0) {
qDebug() << "保存PLY点云:" << plyPath;
success = true;
} else {
qDebug() << "保存PLY失败";
}
}
return success;
} catch (const std::exception &e) {
qDebug() << "保存点云失败:" << e.what();
return false;
}
}
// ========== 日志功能 ==========
void MainWindow::addLog(const QString &message, const QString &level)
{
if(!m_logDisplay) return;
// 获取当前时间戳
QString timestamp = QDateTime::currentDateTime().toString("HH:mm:ss.zzz");
// 根据日志级别设置颜色
QString color;
if(level == "ERROR") {
color = "#FF6B6B"; // 红色
} else if(level == "WARNING") {
color = "#FFA500"; // 橙色
} else if(level == "SUCCESS") {
color = "#4CAF50"; // 绿色
} else {
color = "#D4D4D4"; // 默认灰白色
}
// 格式化日志消息
QString formattedMessage = QString("<span style='color: #888;'>[%1]</span> "
"<span style='color: %2;'>[%3]</span> "
"<span style='color: %4;'>%5</span>")
.arg(timestamp)
.arg(color)
.arg(level)
.arg(color)
.arg(message);
// 添加到日志显示
m_logDisplay->append(formattedMessage);
// 限制日志行数保留最近1000行
QTextDocument *doc = m_logDisplay->document();
if(doc->blockCount() > 1000) {
QTextCursor cursor = m_logDisplay->textCursor();
cursor.movePosition(QTextCursor::Start);
cursor.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor, doc->blockCount() - 1000);
cursor.removeSelectedText();
}
// 自动滚动到底部
m_logDisplay->moveCursor(QTextCursor::End);
}
void MainWindow::onClearLogClicked()
{
if(m_logDisplay) {
m_logDisplay->clear();
addLog("日志已清除", "INFO");
}
}
void MainWindow::onSaveLogClicked()
{
QString fileName = QFileDialog::getSaveFileName(this, "保存日志",
QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/viewer_log.txt",
"文本文件 (*.txt);;所有文件 (*.*)");
if(!fileName.isEmpty()) {
QFile file(fileName);
if(file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QTextStream out(&file);
out << m_logDisplay->toPlainText();
file.close();
addLog(QString("日志已保存到: %1").arg(fileName), "SUCCESS");
} else {
addLog(QString("保存日志失败: %1").arg(file.errorString()), "ERROR");
}
}
}
// ========== 统计信息更新 ==========
void MainWindow::updateStatistics()
{
// 更新三个相机的FPS
if(m_leftFpsLabel) {
m_leftFpsLabel->setText(QString("左红外: %1 fps").arg(m_currentLeftFps, 0, 'f', 1));
}
if(m_rightFpsLabel) {
m_rightFpsLabel->setText(QString("右红外: %1 fps").arg(m_currentRightFps, 0, 'f', 1));
}
if(m_rgbFpsLabel) {
m_rgbFpsLabel->setText(QString("RGB彩色: %1 fps").arg(m_currentRgbFps, 0, 'f', 1));
}
// 更新点云帧率
if(m_pointCloudFpsLabel) {
m_pointCloudFpsLabel->setText(QString("点云帧率: %1 fps").arg(m_currentPointCloudFps, 0, 'f', 1));
}
// 更新接收帧数(显示三个相机和点云的累计总数)
if(m_queueLabel) {
m_queueLabel->setText(QString("接收帧数: L%1 R%2 RGB%3 点云%4")
.arg(m_totalLeftFrameCount)
.arg(m_totalRightFrameCount)
.arg(m_totalRgbFrameCount)
.arg(m_totalPointCloudFrameCount));
}
}
// 监听系统主题变化事件
void MainWindow::changeEvent(QEvent *event)
{
if(event->type() == QEvent::PaletteChange) {
// 系统调色板变化,说明主题可能已切换
qDebug() << "系统主题已变化刷新UI样式";
// 强制重新应用样式表
// 保存当前样式表
QString currentStyle = styleSheet();
// 清空样式表
setStyleSheet("");
// 重新应用样式表
setStyleSheet(currentStyle);
// 强制所有子组件更新
QList<QWidget*> widgets = findChildren<QWidget*>();
for (QWidget *widget : widgets) {
widget->style()->unpolish(widget);
widget->style()->polish(widget);
widget->update();
}
// 触发窗口重绘
update();
}
// 调用基类实现
QMainWindow::changeEvent(event);
}