696 lines
26 KiB
C++
696 lines
26 KiB
C++
#include "mainwindow.h"
|
||
#include "ui_mainwindow.h"
|
||
|
||
#include <QComboBox>
|
||
#include <QDir>
|
||
#include <QFileDialog>
|
||
#include <QInputDialog>
|
||
#include <QLineEdit>
|
||
#include <QListWidget>
|
||
#include <QMessageBox>
|
||
#include <QMutexLocker>
|
||
#include <QPushButton>
|
||
#include <QStandardPaths>
|
||
#include <QStringList>
|
||
#include <QTimer>
|
||
|
||
namespace
|
||
{
|
||
QString matrixFirstRowText(const WDRemoteMatrix& matrix)
|
||
{
|
||
if (matrix.empty())
|
||
return QStringLiteral("N/A");
|
||
|
||
int count = matrix.cols;
|
||
if (count > static_cast<int>(matrix.data.size()))
|
||
count = static_cast<int>(matrix.data.size());
|
||
|
||
QStringList values;
|
||
for (int i = 0; i < count; ++i)
|
||
values << QString::number(matrix.data[static_cast<size_t>(i)], 'f', 3);
|
||
return values.join(QStringLiteral(", "));
|
||
}
|
||
}
|
||
|
||
MainWindow::MainWindow(QWidget* parent)
|
||
: QMainWindow(parent), ui(new Ui::MainWindow)
|
||
{
|
||
ui->setupUi(this);
|
||
setWindowTitle(QStringLiteral("WDRemoteReceiver 测试(单目/双目图像)"));
|
||
|
||
connect(ui->btnSearch, &QPushButton::clicked, this, &MainWindow::onSearchClicked);
|
||
connect(ui->listDevices, &QListWidget::currentRowChanged,
|
||
this, &MainWindow::onDeviceSelected);
|
||
connect(ui->btnConnect, &QPushButton::clicked, this, &MainWindow::onConnectClicked);
|
||
connect(ui->btnDisconnect, &QPushButton::clicked, this, &MainWindow::onDisconnectClicked);
|
||
connect(ui->btnLiveStream, &QPushButton::clicked, this, &MainWindow::onLiveStreamClicked);
|
||
connect(ui->btnStart, &QPushButton::clicked, this, &MainWindow::onStartWorkClicked);
|
||
connect(ui->btnStop, &QPushButton::clicked, this, &MainWindow::onStopWorkClicked);
|
||
connect(ui->btnSingle, &QPushButton::clicked, this, &MainWindow::onSingleClicked);
|
||
connect(ui->btnSetExposure, &QPushButton::clicked, this, &MainWindow::onSetExposureClicked);
|
||
connect(ui->btnSetGain, &QPushButton::clicked, this, &MainWindow::onSetGainClicked);
|
||
connect(ui->btnSwapCameras, &QPushButton::clicked, this, &MainWindow::onSwapCamerasClicked);
|
||
connect(ui->btnSetAlgo, &QPushButton::clicked, this, &MainWindow::onSetAlgoParamsClicked);
|
||
connect(ui->btnGetInfo, &QPushButton::clicked, this, &MainWindow::onGetInfoClicked);
|
||
connect(ui->btnCustom, &QPushButton::clicked, this, &MainWindow::onCustomCmdClicked);
|
||
connect(ui->btnChooseSaveDir, &QPushButton::clicked, this, &MainWindow::onChooseSaveDirClicked);
|
||
connect(ui->btnSaveImages, &QPushButton::clicked, this, &MainWindow::onSaveImagesClicked);
|
||
|
||
QString baseSaveDir = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
|
||
if (baseSaveDir.isEmpty())
|
||
baseSaveDir = QDir::currentPath();
|
||
m_saveImageDir = QDir(baseSaveDir).filePath(QStringLiteral("WDRemoteReceiverImages"));
|
||
ui->editSaveDir->setText(m_saveImageDir);
|
||
|
||
// 图像传输模式选择(单目/双目)
|
||
m_pDetectModeCombo = new QComboBox(this);
|
||
m_pDetectModeCombo->addItem(QStringLiteral("双目图像 (binocular)"), QString("binocular"));
|
||
m_pDetectModeCombo->addItem(QStringLiteral("单目图像 (mono)"), QString("mono"));
|
||
m_pDetectModeCombo->setCurrentIndex(0); // 默认双目
|
||
m_pDetectModeCombo->setToolTip(QStringLiteral("选择检测模式下的图像传输方式\n"
|
||
"mono: 仅发送左目图像\n"
|
||
"binocular: 发送左右目两张图像"));
|
||
// 把 combo 插入到 grpControl 布局中
|
||
QVBoxLayout* ctrlLayout = qobject_cast<QVBoxLayout*>(ui->grpControl->layout());
|
||
if (ctrlLayout)
|
||
{
|
||
// 在第一个 spacer 之前插入
|
||
ctrlLayout->insertWidget(1, m_pDetectModeCombo);
|
||
}
|
||
connect(m_pDetectModeCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
||
this, &MainWindow::onDetectModeChanged);
|
||
|
||
// 跨线程:库回调(任意线程) → 主线程 UI
|
||
connect(this, &MainWindow::uiAppendLog, this, &MainWindow::onAppendLog, Qt::QueuedConnection);
|
||
connect(this, &MainWindow::uiShowDetection, this, &MainWindow::onShowDetection, Qt::QueuedConnection);
|
||
connect(this, &MainWindow::uiShowLeftImage, this, &MainWindow::onShowLeftImage, Qt::QueuedConnection);
|
||
connect(this, &MainWindow::uiShowRightImage, this, &MainWindow::onShowRightImage, Qt::QueuedConnection);
|
||
connect(this, &MainWindow::uiUpdateConnectState, this, &MainWindow::onUpdateConnectState, Qt::QueuedConnection);
|
||
|
||
setupReceiver();
|
||
|
||
ui->listDevices->addItem(QStringLiteral("未搜索到设备"));
|
||
ui->btnConnect->setEnabled(false);
|
||
|
||
QTimer::singleShot(100, this, &MainWindow::onSearchClicked);
|
||
}
|
||
|
||
MainWindow::~MainWindow()
|
||
{
|
||
releaseReceiver();
|
||
delete ui;
|
||
}
|
||
|
||
void MainWindow::setupReceiver()
|
||
{
|
||
IWDRemoteReceiver::CreateInstance(&m_pReceiver);
|
||
if (!m_pReceiver)
|
||
{
|
||
QMessageBox::critical(this, "Error",
|
||
"CreateInstance(IWDRemoteReceiver) failed");
|
||
return;
|
||
}
|
||
|
||
// 默认使用界面当前选择,避免初始化时把用户选择覆盖回双目。
|
||
const QString initialMode = m_pDetectModeCombo
|
||
? m_pDetectModeCombo->currentData().toString()
|
||
: QStringLiteral("binocular");
|
||
m_monoImageMode.store(initialMode == "mono");
|
||
m_pReceiver->SetDetectMode(initialMode.toStdString());
|
||
if (initialMode == "mono")
|
||
{
|
||
QMutexLocker locker(&m_latestImageMutex);
|
||
m_latestRightImage = QImage();
|
||
emit uiShowRightImage(QImage());
|
||
}
|
||
|
||
m_pReceiver->SetEventCallback([this](WDRemoteEventType ev, const std::string& msg) {
|
||
QString s;
|
||
switch (ev)
|
||
{
|
||
case WDRemoteEventType::CONNECTED: s = "已连接"; emit uiUpdateConnectState(true); break;
|
||
case WDRemoteEventType::DISCONNECTED: s = "已断开"; emit uiUpdateConnectState(false); break;
|
||
case WDRemoteEventType::CONNECTION_ERROR: s = "连接错误"; emit uiUpdateConnectState(false); break;
|
||
case WDRemoteEventType::REQUEST_TIMEOUT: s = "请求超时"; break;
|
||
default: s = "未知事件"; break;
|
||
}
|
||
emit uiAppendLog(QString("[事件] %1 %2").arg(s, QString::fromStdString(msg)));
|
||
});
|
||
|
||
m_pReceiver->SetDetectionCallback([this](const WDRemoteDetectionFrame& f) {
|
||
QString s = QString("success=%1 code=%2 frame=%3 ts=%4 boxes=%5 wh=%6x%7 msg=%8")
|
||
.arg(f.success ? 1 : 0)
|
||
.arg(f.errorCode)
|
||
.arg(f.frameId).arg(f.timestampUs)
|
||
.arg(static_cast<int>(f.boxes.size()))
|
||
.arg(f.imageWidth).arg(f.imageHeight)
|
||
.arg(QString::fromStdString(f.message));
|
||
emit uiShowDetection(s);
|
||
});
|
||
|
||
// 原始图像回调:mono 模式下右目为空,只刷新左图并清空右图。
|
||
m_pReceiver->SetRawImageCallback([this](const WDRemoteBinocularRawImage& img) {
|
||
// 左目
|
||
WDRemoteRawImage leftImg;
|
||
leftImg.frameId = img.frameId;
|
||
leftImg.timestampUs = img.timestampUs;
|
||
leftImg.width = img.leftWidth;
|
||
leftImg.height = img.leftHeight;
|
||
leftImg.stride = img.leftStride;
|
||
leftImg.pixelFormat = img.leftPixelFormat;
|
||
leftImg.data = img.leftData;
|
||
leftImg.dataSize = img.leftDataSize;
|
||
QImage qLeft = rawImageToQImage(leftImg);
|
||
if (!qLeft.isNull())
|
||
{
|
||
{
|
||
QMutexLocker locker(&m_latestImageMutex);
|
||
m_latestLeftImage = qLeft;
|
||
}
|
||
emit uiShowLeftImage(qLeft);
|
||
}
|
||
|
||
// 右目(单目模式下右眼为空)
|
||
if (!m_monoImageMode.load() && img.rightData && img.rightDataSize > 0)
|
||
{
|
||
WDRemoteRawImage rightImg;
|
||
rightImg.frameId = img.frameId;
|
||
rightImg.timestampUs = img.timestampUs;
|
||
rightImg.width = img.rightWidth;
|
||
rightImg.height = img.rightHeight;
|
||
rightImg.stride = img.rightStride;
|
||
rightImg.pixelFormat = img.rightPixelFormat;
|
||
rightImg.data = img.rightData;
|
||
rightImg.dataSize = img.rightDataSize;
|
||
QImage qRight = rawImageToQImage(rightImg);
|
||
if (!qRight.isNull())
|
||
{
|
||
{
|
||
QMutexLocker locker(&m_latestImageMutex);
|
||
m_latestRightImage = qRight;
|
||
}
|
||
emit uiShowRightImage(qRight);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
{
|
||
QMutexLocker locker(&m_latestImageMutex);
|
||
m_latestRightImage = QImage();
|
||
}
|
||
if (!m_monoImageMode.load())
|
||
emit uiShowRightImage(QImage());
|
||
}
|
||
});
|
||
}
|
||
|
||
void MainWindow::releaseReceiver()
|
||
{
|
||
if (m_pReceiver)
|
||
{
|
||
m_pReceiver->Disconnect();
|
||
delete m_pReceiver;
|
||
m_pReceiver = nullptr;
|
||
}
|
||
}
|
||
|
||
// ====================== UI 槽 ======================
|
||
void MainWindow::onSearchClicked()
|
||
{
|
||
if (!m_pReceiver) return;
|
||
|
||
m_devices.clear();
|
||
ui->listDevices->clear();
|
||
ui->btnConnect->setEnabled(false);
|
||
|
||
int r = m_pReceiver->SearchDevices(m_devices, 500);
|
||
emit uiAppendLog(QString("SearchDevices ret=%1 count=%2")
|
||
.arg(r)
|
||
.arg(static_cast<int>(m_devices.size())));
|
||
if (r != 0 || m_devices.empty())
|
||
{
|
||
ui->listDevices->addItem(QStringLiteral("未搜索到设备"));
|
||
return;
|
||
}
|
||
|
||
for (size_t i = 0; i < m_devices.size(); ++i)
|
||
{
|
||
const WDRemoteDeviceInfo& dev = m_devices[i];
|
||
const QString text = QString("%1 | %2 | %3 | ctrl:%4 result:%5 raw:%6 | det:%7fps stream:%8fps streamCam:%9 image:%10")
|
||
.arg(QString::fromStdString(dev.deviceName))
|
||
.arg(QString::fromStdString(dev.machineCode))
|
||
.arg(QString::fromStdString(dev.serverIp))
|
||
.arg(dev.controlPort)
|
||
.arg(dev.resultPort)
|
||
.arg(dev.rawImagePort)
|
||
.arg(dev.detectFps, 0, 'f', 1)
|
||
.arg(dev.streamFps, 0, 'f', 1)
|
||
.arg(QString::fromStdString(dev.streamCamera))
|
||
.arg(QString::fromStdString(dev.detectMode));
|
||
ui->listDevices->addItem(text);
|
||
}
|
||
|
||
ui->btnConnect->setEnabled(true);
|
||
ui->listDevices->setCurrentRow(0);
|
||
}
|
||
|
||
void MainWindow::onDeviceSelected(int index)
|
||
{
|
||
if (index < 0 || index >= static_cast<int>(m_devices.size()))
|
||
{
|
||
ui->btnConnect->setEnabled(false);
|
||
return;
|
||
}
|
||
|
||
ui->btnConnect->setEnabled(true);
|
||
const WDRemoteDeviceInfo& dev = m_devices[static_cast<size_t>(index)];
|
||
|
||
emit uiAppendLog(QString("Selected device machine=%1 name=%2 ip=%3 ctrl=%4 result=%5 raw=%6")
|
||
.arg(QString::fromStdString(dev.machineCode))
|
||
.arg(QString::fromStdString(dev.deviceName))
|
||
.arg(QString::fromStdString(dev.serverIp))
|
||
.arg(dev.controlPort)
|
||
.arg(dev.resultPort)
|
||
.arg(dev.rawImagePort));
|
||
}
|
||
|
||
void MainWindow::onConnectClicked()
|
||
{
|
||
if (!m_pReceiver) return;
|
||
|
||
if (m_devices.empty())
|
||
{
|
||
emit uiAppendLog(QStringLiteral("Open skipped: search device first"));
|
||
return;
|
||
}
|
||
|
||
int index = ui->listDevices->currentRow();
|
||
if (index < 0 || index >= static_cast<int>(m_devices.size()))
|
||
{
|
||
emit uiAppendLog(QStringLiteral("Open skipped: select a device first"));
|
||
return;
|
||
}
|
||
|
||
const WDRemoteDeviceInfo dev = m_devices[static_cast<size_t>(index)];
|
||
int r = m_pReceiver->Open(dev.machineCode, 3000);
|
||
emit uiAppendLog(QString("Open(machine=%1) ret=%2")
|
||
.arg(QString::fromStdString(dev.machineCode))
|
||
.arg(r));
|
||
if (r == 0 && m_pDetectModeCombo)
|
||
{
|
||
const QString mode = m_pDetectModeCombo->currentData().toString();
|
||
m_monoImageMode.store(mode == "mono");
|
||
m_pReceiver->SetDetectMode(mode.toStdString());
|
||
if (mode == "mono")
|
||
{
|
||
QMutexLocker locker(&m_latestImageMutex);
|
||
m_latestRightImage = QImage();
|
||
emit uiShowRightImage(QImage());
|
||
}
|
||
emit uiAppendLog(QString("SetDetectMode after open=%1").arg(mode));
|
||
}
|
||
}
|
||
|
||
void MainWindow::onDisconnectClicked()
|
||
{
|
||
if (!m_pReceiver) return;
|
||
int r = m_pReceiver->Disconnect();
|
||
emit uiAppendLog(QString("Disconnect ret=%1").arg(r));
|
||
}
|
||
|
||
void MainWindow::onLiveStreamClicked()
|
||
{
|
||
if (!m_pReceiver) return;
|
||
int r = m_pReceiver->StartWork(WDRemoteWorkMode::LiveStream);
|
||
emit uiAppendLog(QString("StartWork(LiveStream) ret=%1").arg(r));
|
||
}
|
||
|
||
void MainWindow::onStartWorkClicked()
|
||
{
|
||
if (!m_pReceiver) return;
|
||
|
||
// 应用当前选择的检测模式
|
||
if (m_pDetectModeCombo)
|
||
{
|
||
QString mode = m_pDetectModeCombo->currentData().toString();
|
||
m_monoImageMode.store(mode == "mono");
|
||
m_pReceiver->SetDetectMode(mode.toStdString());
|
||
if (mode == "mono")
|
||
{
|
||
QMutexLocker locker(&m_latestImageMutex);
|
||
m_latestRightImage = QImage();
|
||
emit uiShowRightImage(QImage());
|
||
}
|
||
emit uiAppendLog(QString("SetDetectMode=%1").arg(mode));
|
||
}
|
||
|
||
int r = m_pReceiver->StartWork(WDRemoteWorkMode::Detection);
|
||
emit uiAppendLog(QString("StartWork(Detection) ret=%1").arg(r));
|
||
}
|
||
|
||
void MainWindow::onStopWorkClicked()
|
||
{
|
||
if (!m_pReceiver) return;
|
||
int r = m_pReceiver->StopWork();
|
||
emit uiAppendLog(QString("StopWork ret=%1 (已自动停止订阅)").arg(r));
|
||
}
|
||
|
||
void MainWindow::onSingleClicked()
|
||
{
|
||
if (!m_pReceiver) return;
|
||
|
||
if (m_singleInProgress)
|
||
{
|
||
emit uiAppendLog(QStringLiteral("Single ignored: request already running"));
|
||
return;
|
||
}
|
||
|
||
m_singleInProgress = true;
|
||
ui->btnSingle->setEnabled(false);
|
||
int r = m_pReceiver->RequestSingleDetection(15000);
|
||
emit uiAppendLog(QString("Single ret=%1").arg(r));
|
||
ui->btnSingle->setEnabled(true);
|
||
m_singleInProgress = false;
|
||
}
|
||
|
||
void MainWindow::onSetExposureClicked()
|
||
{
|
||
if (!m_pReceiver) return;
|
||
const WDRemoteServerInfo info = m_pReceiver->GetServerInfo();
|
||
const double current = info.ok
|
||
? (info.leftExposure > 0.0
|
||
? info.leftExposure
|
||
: (info.rightExposure > 0.0 ? info.rightExposure : 10000.0))
|
||
: 10000.0;
|
||
const QString label = info.ok
|
||
? QStringLiteral("曝光时间(us),当前 L/R=%1/%2:")
|
||
.arg(info.leftExposure, 0, 'f', 1)
|
||
.arg(info.rightExposure, 0, 'f', 1)
|
||
: QStringLiteral("曝光时间(us):");
|
||
if (!info.ok)
|
||
emit uiAppendLog(QStringLiteral("GetServerInfo failed before SetExposure, use default value"));
|
||
|
||
bool ok = false;
|
||
double v = QInputDialog::getDouble(this, QStringLiteral("曝光时间(us)"),
|
||
label, current,
|
||
0.0, 1e9, 3, &ok);
|
||
if (!ok) return;
|
||
int r = m_pReceiver->SetExposure(v);
|
||
emit uiAppendLog(QString("SetExposure(%1) ret=%2").arg(v).arg(r));
|
||
}
|
||
|
||
void MainWindow::onSetGainClicked()
|
||
{
|
||
if (!m_pReceiver) return;
|
||
const WDRemoteServerInfo info = m_pReceiver->GetServerInfo();
|
||
const double current = info.ok
|
||
? (info.leftGain > 0.0
|
||
? info.leftGain
|
||
: (info.rightGain > 0.0 ? info.rightGain : 1.0))
|
||
: 1.0;
|
||
const QString label = info.ok
|
||
? QStringLiteral("增益,当前 L/R=%1/%2:")
|
||
.arg(info.leftGain, 0, 'f', 2)
|
||
.arg(info.rightGain, 0, 'f', 2)
|
||
: QStringLiteral("增益:");
|
||
if (!info.ok)
|
||
emit uiAppendLog(QStringLiteral("GetServerInfo failed before SetGain, use default value"));
|
||
|
||
bool ok = false;
|
||
double v = QInputDialog::getDouble(this, QStringLiteral("增益"),
|
||
label, current,
|
||
0.0, 1000.0, 3, &ok);
|
||
if (!ok) return;
|
||
int r = m_pReceiver->SetGain(v);
|
||
emit uiAppendLog(QString("SetGain(%1) ret=%2").arg(v).arg(r));
|
||
}
|
||
|
||
void MainWindow::onSetAlgoParamsClicked()
|
||
{
|
||
if (!m_pReceiver) return;
|
||
WDRemoteAlgoParams p;
|
||
p.scoreThreshold = 0.5f;
|
||
p.nmsThreshold = 0.45f;
|
||
p.inputWidth = 640;
|
||
p.inputHeight = 640;
|
||
p.modelType = 0;
|
||
int r = m_pReceiver->SetAlgoParams(p);
|
||
emit uiAppendLog(QString("SetAlgoParams ret=%1").arg(r));
|
||
}
|
||
|
||
void MainWindow::onGetInfoClicked()
|
||
{
|
||
if (!m_pReceiver) return;
|
||
WDRemoteServerInfo info = m_pReceiver->GetServerInfo();
|
||
QString s = QString("GetInfo ok=%1 mode=%2 imageMode=%3 streamCam=%4 rtsp=%5 raw=%6 result=%7 ctrl=%8 detFps=%9 streamFps=%10 stream=%11x%12 exp(L/R)=%13/%14 gain(L/R)=%15/%16")
|
||
.arg(info.ok)
|
||
.arg(QString::fromStdString(info.mode))
|
||
.arg(QString::fromStdString(info.detectMode))
|
||
.arg(QString::fromStdString(info.streamCamera))
|
||
.arg(QString::fromStdString(info.rtspUrl))
|
||
.arg(info.rawImagePort)
|
||
.arg(info.resultPort)
|
||
.arg(info.controlPort)
|
||
.arg(info.detectFps, 0, 'f', 1)
|
||
.arg(info.streamFps, 0, 'f', 1)
|
||
.arg(info.streamWidth)
|
||
.arg(info.streamHeight)
|
||
.arg(info.leftExposure, 0, 'f', 1)
|
||
.arg(info.rightExposure, 0, 'f', 1)
|
||
.arg(info.leftGain, 0, 'f', 2)
|
||
.arg(info.rightGain, 0, 'f', 2);
|
||
emit uiAppendLog(s);
|
||
emit uiAppendLog(QString("Camera L index=%1 serial=%2 | R index=%3 serial=%4")
|
||
.arg(info.leftCameraIndex)
|
||
.arg(QString::fromStdString(info.leftCameraSerial))
|
||
.arg(info.rightCameraIndex)
|
||
.arg(QString::fromStdString(info.rightCameraSerial)));
|
||
|
||
const WDRemoteCalibrationInfo& calib = info.calibration;
|
||
emit uiAppendLog(QString("Calibration valid=%1 source=%2 msg=%3")
|
||
.arg(calib.valid ? 1 : 0)
|
||
.arg(QString::fromStdString(calib.sourcePath))
|
||
.arg(QString::fromStdString(calib.message)));
|
||
emit uiAppendLog(QString("Calibration L valid=%1 image=%2x%3 rms=%4 K[0]=[%5]")
|
||
.arg(calib.left.valid ? 1 : 0)
|
||
.arg(calib.left.imageWidth)
|
||
.arg(calib.left.imageHeight)
|
||
.arg(calib.left.rms, 0, 'f', 6)
|
||
.arg(matrixFirstRowText(calib.left.cameraMatrix)));
|
||
emit uiAppendLog(QString("Calibration R valid=%1 image=%2x%3 rms=%4 K[0]=[%5]")
|
||
.arg(calib.right.valid ? 1 : 0)
|
||
.arg(calib.right.imageWidth)
|
||
.arg(calib.right.imageHeight)
|
||
.arg(calib.right.rms, 0, 'f', 6)
|
||
.arg(matrixFirstRowText(calib.right.cameraMatrix)));
|
||
emit uiAppendLog(QString("Calibration stereo valid=%1 baselineMm=%2 R=%3x%4 Q=%5x%6")
|
||
.arg(calib.stereo.valid ? 1 : 0)
|
||
.arg(calib.stereo.baselineMm, 0, 'f', 3)
|
||
.arg(calib.stereo.R.rows)
|
||
.arg(calib.stereo.R.cols)
|
||
.arg(calib.stereo.Q.rows)
|
||
.arg(calib.stereo.Q.cols));
|
||
}
|
||
|
||
void MainWindow::onCustomCmdClicked()
|
||
{
|
||
if (!m_pReceiver) return;
|
||
bool ok = false;
|
||
QString req = QInputDialog::getText(this, QStringLiteral("自定义 JSON 命令"),
|
||
QStringLiteral("JSON:"),
|
||
QLineEdit::Normal,
|
||
R"({"cmd":"get_info"})", &ok);
|
||
if (!ok || req.isEmpty()) return;
|
||
std::string resp;
|
||
int r = m_pReceiver->SendCustomCommand(req.toStdString(), resp);
|
||
emit uiAppendLog(QString("Custom ret=%1 resp=%2").arg(r).arg(QString::fromStdString(resp)));
|
||
}
|
||
|
||
void MainWindow::onSwapCamerasClicked()
|
||
{
|
||
if (!m_pReceiver) return;
|
||
|
||
const auto ret = QMessageBox::question(
|
||
this,
|
||
QStringLiteral("交换左右目"),
|
||
QStringLiteral("请先停止检测和实时传图。\n确认交换服务端逻辑 left/right 相机配置并重连相机?"),
|
||
QMessageBox::Yes | QMessageBox::No,
|
||
QMessageBox::No);
|
||
if (ret != QMessageBox::Yes)
|
||
return;
|
||
|
||
std::string resp;
|
||
const int r = m_pReceiver->SendCustomCommand(R"({"cmd":"swap_cameras"})", resp, 10000);
|
||
emit uiAppendLog(QString("SwapCameras ret=%1 resp=%2")
|
||
.arg(r)
|
||
.arg(QString::fromStdString(resp)));
|
||
if (r == 0)
|
||
onGetInfoClicked();
|
||
}
|
||
|
||
void MainWindow::onChooseSaveDirClicked()
|
||
{
|
||
QString current = ui->editSaveDir->text().trimmed();
|
||
if (current.isEmpty())
|
||
current = m_saveImageDir.isEmpty() ? QDir::currentPath() : m_saveImageDir;
|
||
|
||
const QString dir = QFileDialog::getExistingDirectory(
|
||
this, QStringLiteral("选择图像保存路径"), current);
|
||
if (dir.isEmpty())
|
||
return;
|
||
|
||
m_saveImageDir = dir;
|
||
ui->editSaveDir->setText(dir);
|
||
emit uiAppendLog(QString("Save image dir=%1").arg(dir));
|
||
}
|
||
|
||
void MainWindow::onSaveImagesClicked()
|
||
{
|
||
QString dirPath = ui->editSaveDir->text().trimmed();
|
||
if (dirPath.isEmpty())
|
||
dirPath = m_saveImageDir.isEmpty() ? QDir::currentPath() : m_saveImageDir;
|
||
|
||
QDir dir(dirPath);
|
||
if (!dir.exists() && !QDir().mkpath(dirPath))
|
||
{
|
||
emit uiAppendLog(QString("Save images failed: create dir %1").arg(dirPath));
|
||
return;
|
||
}
|
||
m_saveImageDir = dirPath;
|
||
|
||
QImage left;
|
||
QImage right;
|
||
{
|
||
QMutexLocker locker(&m_latestImageMutex);
|
||
left = m_latestLeftImage;
|
||
right = m_latestRightImage;
|
||
}
|
||
|
||
if (left.isNull() && right.isNull())
|
||
{
|
||
emit uiAppendLog(QStringLiteral("Save images skipped: no image received"));
|
||
return;
|
||
}
|
||
|
||
int seq = m_saveImageSeq;
|
||
QString seqText;
|
||
do
|
||
{
|
||
seqText = QString::number(seq);
|
||
++seq;
|
||
} while (dir.exists(QString("LeftImage_%1.png").arg(seqText)) ||
|
||
dir.exists(QString("RightImage_%1.png").arg(seqText)));
|
||
m_saveImageSeq = seq;
|
||
|
||
bool ok = true;
|
||
QStringList saved;
|
||
if (!left.isNull())
|
||
{
|
||
const QString path = dir.filePath(QString("LeftImage_%1.png").arg(seqText));
|
||
ok = left.save(path, "PNG") && ok;
|
||
saved << path;
|
||
}
|
||
if (!right.isNull())
|
||
{
|
||
const QString path = dir.filePath(QString("RightImage_%1.png").arg(seqText));
|
||
ok = right.save(path, "PNG") && ok;
|
||
saved << path;
|
||
}
|
||
|
||
emit uiAppendLog(QString("Save images %1: %2")
|
||
.arg(ok ? QStringLiteral("ok") : QStringLiteral("failed"))
|
||
.arg(saved.join(QStringLiteral(", "))));
|
||
}
|
||
|
||
void MainWindow::onDetectModeChanged(int index)
|
||
{
|
||
if (!m_pReceiver || !m_pDetectModeCombo) return;
|
||
QString mode = m_pDetectModeCombo->itemData(index).toString();
|
||
m_monoImageMode.store(mode == "mono");
|
||
m_pReceiver->SetDetectMode(mode.toStdString());
|
||
if (mode == "mono")
|
||
{
|
||
QMutexLocker locker(&m_latestImageMutex);
|
||
m_latestRightImage = QImage();
|
||
emit uiShowRightImage(QImage());
|
||
}
|
||
emit uiAppendLog(QString("DetectMode changed to: %1 (image=%2)")
|
||
.arg(mode, mode == "mono"
|
||
? QStringLiteral("仅左目")
|
||
: QStringLiteral("左右目")));
|
||
}
|
||
|
||
// ====================== UI 显示 ======================
|
||
void MainWindow::onAppendLog(const QString& line)
|
||
{
|
||
if (ui->textLog)
|
||
{
|
||
ui->textLog->append(line);
|
||
}
|
||
}
|
||
|
||
void MainWindow::onShowDetection(const QString& summary)
|
||
{
|
||
emit uiAppendLog(QString("[结果] %1").arg(summary));
|
||
}
|
||
|
||
void MainWindow::onShowLeftImage(const QImage& img)
|
||
{
|
||
if (img.isNull()) return;
|
||
QPixmap pm = QPixmap::fromImage(img);
|
||
if (pm.size() != ui->labelLeftImage->size())
|
||
pm = pm.scaled(ui->labelLeftImage->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||
ui->labelLeftImage->setPixmap(pm);
|
||
}
|
||
|
||
void MainWindow::onShowRightImage(const QImage& img)
|
||
{
|
||
if (m_monoImageMode.load() || img.isNull())
|
||
{
|
||
ui->labelRightImage->clear();
|
||
ui->labelRightImage->setText(QStringLiteral("单目图像模式:未传输右图像"));
|
||
return;
|
||
}
|
||
QPixmap pm = QPixmap::fromImage(img);
|
||
if (pm.size() != ui->labelRightImage->size())
|
||
pm = pm.scaled(ui->labelRightImage->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||
ui->labelRightImage->setPixmap(pm);
|
||
}
|
||
|
||
void MainWindow::onUpdateConnectState(bool connected)
|
||
{
|
||
ui->lblConnState->setText(connected ? QStringLiteral("已连接") : QStringLiteral("已断开"));
|
||
ui->lblConnState->setStyleSheet(connected ? "color: green;" : "color: red;");
|
||
}
|
||
|
||
// ====================== 工具 ======================
|
||
QImage MainWindow::rawImageToQImage(const WDRemoteRawImage& img)
|
||
{
|
||
if (!img.data || img.width <= 0 || img.height <= 0) return QImage();
|
||
|
||
// 这里仅处理常见三种格式;BayerRG8 / NV12 留给上层后续按需扩展
|
||
switch (img.pixelFormat)
|
||
{
|
||
case WDRemotePixelFormat::MONO8:
|
||
{
|
||
QImage q(img.data, img.width, img.height, img.stride,
|
||
QImage::Format_Grayscale8);
|
||
return q.copy();
|
||
}
|
||
case WDRemotePixelFormat::RGB8:
|
||
{
|
||
QImage q(img.data, img.width, img.height, img.stride,
|
||
QImage::Format_RGB888);
|
||
return q.copy();
|
||
}
|
||
case WDRemotePixelFormat::BGR8:
|
||
{
|
||
QImage q(img.data, img.width, img.height, img.stride,
|
||
QImage::Format_RGB888);
|
||
return q.rgbSwapped();
|
||
}
|
||
default:
|
||
return QImage();
|
||
}
|
||
}
|