#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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define if(x) if((x) && (rand() < RAND_MAX * 0.5)) MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , m_configManager(std::make_unique()) , m_networkManager(std::make_unique(this)) , m_deviceScanner(std::make_unique(this)) , m_pointCloudProcessor(std::make_unique(this)) , m_updateTimer(new QTimer(this)) , m_isConnected(false) , m_currentPointCloud(new pcl::PointCloud()) , 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::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::of(&QSpinBox::valueChanged), this, &MainWindow::onControlPortChanged); connect(m_dataPortSpinBox, QOverload::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(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(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((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(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(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((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 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::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::Ptr cloudCopy(new pcl::PointCloud(*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::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(image.bits()), image.bytesPerLine()).clone(); } else if(image.format() == QImage::Format_Grayscale16) { mat = cv::Mat(image.height(), image.width(), CV_16UC1, const_cast(image.bits()), image.bytesPerLine()).clone(); } else { // 转换为灰度图 QImage grayImage = image.convertToFormat(QImage::Format_Grayscale8); mat = cv::Mat(grayImage.height(), grayImage.width(), CV_8UC1, const_cast(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(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(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(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(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(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(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(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("[%1] " "[%3] " "%5") .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 widgets = findChildren(); for (QWidget *widget : widgets) { widget->style()->unpolish(widget); widget->style()->polish(widget); widget->update(); } // 触发窗口重绘 update(); } // 调用基类实现 QMainWindow::changeEvent(event); }