diff --git a/App/DroneScrewbolt/DroneScrewCtrlApp/DroneScrewCtrlApp.pro b/App/DroneScrewbolt/DroneScrewCtrlApp/DroneScrewCtrlApp.pro index 8bd7bc0c..ad831f30 100644 --- a/App/DroneScrewbolt/DroneScrewCtrlApp/DroneScrewCtrlApp.pro +++ b/App/DroneScrewbolt/DroneScrewCtrlApp/DroneScrewCtrlApp.pro @@ -41,6 +41,7 @@ SOURCES += \ mainwindow.cpp \ dialogalgoarg.cpp \ dialogcamerasetting.cpp \ + dialoghistoryresult.cpp \ resultitem.cpp \ Presenter/Src/ConfigManager.cpp \ Presenter/Src/DroneScrewCtrlPresenter.cpp @@ -49,6 +50,7 @@ HEADERS += \ mainwindow.h \ dialogalgoarg.h \ dialogcamerasetting.h \ + dialoghistoryresult.h \ resultitem.h \ Version.h \ IYDroneScrewCtrlStatus.h \ @@ -59,6 +61,7 @@ FORMS += \ mainwindow.ui \ dialogalgoarg.ui \ dialogcamerasetting.ui \ + dialoghistoryresult.ui \ resultitem.ui RESOURCES += \ diff --git a/App/DroneScrewbolt/DroneScrewCtrlApp/IYDroneScrewCtrlStatus.h b/App/DroneScrewbolt/DroneScrewCtrlApp/IYDroneScrewCtrlStatus.h index fd7313f6..76eeca30 100644 --- a/App/DroneScrewbolt/DroneScrewCtrlApp/IYDroneScrewCtrlStatus.h +++ b/App/DroneScrewbolt/DroneScrewCtrlApp/IYDroneScrewCtrlStatus.h @@ -19,6 +19,13 @@ struct CtrlDetectionBox int height{0}; bool hasPhysicalHeight{false}; float physicalHeightMm{0.0f}; + bool hasStereoMatch{false}; + bool trusted{false}; + int matchConfidence{1}; + QString matchStatus; + int pairId{-1}; + int leftIndex{-1}; + int rightIndex{-1}; }; /** @@ -43,6 +50,10 @@ struct CtrlDetectionFrame bool success{true}; int errorCode{0}; QString message; + bool isFinalResult{false}; + double rankScore{0.0}; + int rankSampleCount{0}; + qint64 saveIndex{0}; }; Q_DECLARE_METATYPE(CtrlDetectionFrame) diff --git a/App/DroneScrewbolt/DroneScrewCtrlApp/Presenter/Inc/DroneScrewCtrlPresenter.h b/App/DroneScrewbolt/DroneScrewCtrlApp/Presenter/Inc/DroneScrewCtrlPresenter.h index e8901d89..ce2c67d8 100644 --- a/App/DroneScrewbolt/DroneScrewCtrlApp/Presenter/Inc/DroneScrewCtrlPresenter.h +++ b/App/DroneScrewbolt/DroneScrewCtrlApp/Presenter/Inc/DroneScrewCtrlPresenter.h @@ -53,6 +53,7 @@ public: bool SetServerExposure(double exposureTime); bool SetServerGain(double gain); bool PushAlgoParams(const DroneScrewAlgoUiParams& params); + bool QueryServerInfo(WDRemoteServerInfo& info, int timeoutMs = 3000); QImage GetLastDisplayImage() const; diff --git a/App/DroneScrewbolt/DroneScrewCtrlApp/Presenter/Src/DroneScrewCtrlPresenter.cpp b/App/DroneScrewbolt/DroneScrewCtrlApp/Presenter/Src/DroneScrewCtrlPresenter.cpp index a49d678e..2a467de9 100644 --- a/App/DroneScrewbolt/DroneScrewCtrlApp/Presenter/Src/DroneScrewCtrlPresenter.cpp +++ b/App/DroneScrewbolt/DroneScrewCtrlApp/Presenter/Src/DroneScrewCtrlPresenter.cpp @@ -577,10 +577,24 @@ bool DroneScrewCtrlPresenter::PushAlgoParams(const DroneScrewAlgoUiParams& param p.inputWidth = params.inputWidth; p.inputHeight = params.inputHeight; p.modelType = params.modelType; - p.modelPath = params.modelPath; + p.expectedBoltCount = params.expectedBoltCount; return m_pRemote->SetAlgoParams(p) == 0; } +bool DroneScrewCtrlPresenter::QueryServerInfo(WDRemoteServerInfo& info, int timeoutMs) +{ + info = WDRemoteServerInfo{}; + if (!m_pRemote || !m_bConnected.load()) + return false; + + info = m_pRemote->GetServerInfo(timeoutMs); + if (!info.ok) + return false; + + updateStreamInfo(info); + return true; +} + // ===================================================================== // WDRemoteReceiver 回调 // ===================================================================== @@ -748,6 +762,10 @@ CtrlDetectionFrame DroneScrewCtrlPresenter::toCtrlFrame(const WDRemoteDetectionF frame.success = f.success; frame.errorCode = f.errorCode; frame.message = QString::fromStdString(f.message); + frame.isFinalResult = f.isFinalResult; + frame.rankScore = f.rankScore; + frame.rankSampleCount = f.rankSampleCount; + frame.saveIndex = static_cast(f.saveIndex); frame.boxes.reserve(f.boxes.size()); for (const auto& b : f.boxes) { @@ -760,6 +778,13 @@ CtrlDetectionFrame DroneScrewCtrlPresenter::toCtrlFrame(const WDRemoteDetectionF cb.height = b.height; cb.hasPhysicalHeight = b.hasPhysicalHeight; cb.physicalHeightMm = b.physicalHeightMm; + cb.hasStereoMatch = b.hasStereoMatch; + cb.trusted = b.trusted; + cb.matchConfidence = b.matchConfidence; + cb.matchStatus = QString::fromStdString(b.matchStatus); + cb.pairId = b.pairId; + cb.leftIndex = b.leftIndex; + cb.rightIndex = b.rightIndex; frame.boxes.push_back(cb); } frame.distances.reserve(f.distances.size()); diff --git a/App/DroneScrewbolt/DroneScrewCtrlApp/Version.h b/App/DroneScrewbolt/DroneScrewCtrlApp/Version.h index be512ecc..677305f5 100644 --- a/App/DroneScrewbolt/DroneScrewCtrlApp/Version.h +++ b/App/DroneScrewbolt/DroneScrewCtrlApp/Version.h @@ -3,8 +3,8 @@ #define DRONESCREWCTRL_APP_NAME "无人机螺杆控制端" #define DRONESCREWCTRL_VERSION_STRING "1.0.0" -#define DRONESCREWCTRL_BUILD_STRING "4" -#define DRONESCREWCTRL_FULL_VERSION_STRING "V1.0.0_4" +#define DRONESCREWCTRL_BUILD_STRING "5" +#define DRONESCREWCTRL_FULL_VERSION_STRING "V1.0.0_5" inline const char* GetDroneScrewCtrlFullVersion() { return DRONESCREWCTRL_FULL_VERSION_STRING; } diff --git a/App/DroneScrewbolt/DroneScrewCtrlApp/config/config.xml b/App/DroneScrewbolt/DroneScrewCtrlApp/config/config.xml index f17d7cf9..364a1a68 100644 --- a/App/DroneScrewbolt/DroneScrewCtrlApp/config/config.xml +++ b/App/DroneScrewbolt/DroneScrewCtrlApp/config/config.xml @@ -19,7 +19,7 @@ inputWidth="640" inputHeight="640" modelType="0" - modelPath="" /> + expectedBoltCount="8" /> #include +#include #include +#include +#include #include #include +#include +#include + +namespace +{ +void SetupKeyboardEdit(QLineEdit* edit, QObject* filter, Qt::InputMethodHints hints) +{ + if (!edit) + return; + + edit->setAttribute(Qt::WA_InputMethodEnabled, true); + edit->setInputMethodHints(hints); + edit->installEventFilter(filter); +} + +void ShowSystemKeyboard(QWidget* focusWidget) +{ + if (!focusWidget) + return; + + focusWidget->setFocus(Qt::MouseFocusReason); + + QInputMethod* inputMethod = QGuiApplication::inputMethod(); + if (inputMethod) + inputMethod->show(); + + QTimer::singleShot(0, focusWidget, []() { + QInputMethod* delayedInputMethod = QGuiApplication::inputMethod(); + if (delayedInputMethod) + delayedInputMethod->show(); + }); +} +} DialogAlgoArg::DialogAlgoArg(QWidget* parent) : QDialog(parent) @@ -20,6 +56,8 @@ DialogAlgoArg::DialogAlgoArg(QWidget* parent) ui->edit_width->setValidator(new QIntValidator(0, 8192, this)); ui->edit_height->setValidator(new QIntValidator(0, 8192, this)); ui->edit_modelType->setValidator(new QIntValidator(0, 10, this)); + ui->edit_expectedBoltCount->setValidator(new QIntValidator(1, 64, this)); + setupInputKeyboard(); createDisplayRows(); } @@ -57,7 +95,7 @@ void DialogAlgoArg::loadToUi(const DroneScrewAlgoUiParams& p) if (ui->edit_width) ui->edit_width->setText(QString::number(p.inputWidth)); if (ui->edit_height) ui->edit_height->setText(QString::number(p.inputHeight)); if (ui->edit_modelType) ui->edit_modelType->setText(QString::number(p.modelType)); - if (ui->edit_model) ui->edit_model->setText(QString::fromStdString(p.modelPath)); + if (ui->edit_expectedBoltCount) ui->edit_expectedBoltCount->setText(QString::number(p.expectedBoltCount)); } DroneScrewAlgoUiParams DialogAlgoArg::collectFromUi() const @@ -68,10 +106,46 @@ DroneScrewAlgoUiParams DialogAlgoArg::collectFromUi() const if (ui->edit_width) { bool ok; int v = ui->edit_width->text().toInt(&ok); if (ok) p.inputWidth = v; } if (ui->edit_height) { bool ok; int v = ui->edit_height->text().toInt(&ok); if (ok) p.inputHeight = v; } if (ui->edit_modelType) { bool ok; int v = ui->edit_modelType->text().toInt(&ok); if (ok) p.modelType = v; } - if (ui->edit_model) p.modelPath = ui->edit_model->text().toStdString(); + if (ui->edit_expectedBoltCount) { bool ok; int v = ui->edit_expectedBoltCount->text().toInt(&ok); if (ok) p.expectedBoltCount = v; } return p; } +bool DialogAlgoArg::eventFilter(QObject* watched, QEvent* event) +{ + QLineEdit* edit = qobject_cast(watched); + if (edit) + { + switch (event->type()) + { + case QEvent::FocusIn: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::TouchEnd: + ShowSystemKeyboard(edit); + break; + default: + break; + } + } + + return QDialog::eventFilter(watched, event); +} + +void DialogAlgoArg::setupInputKeyboard() +{ + const Qt::InputMethodHints numberHints = + Qt::ImhPreferNumbers | Qt::ImhFormattedNumbersOnly; + const Qt::InputMethodHints integerHints = + Qt::ImhPreferNumbers | Qt::ImhDigitsOnly; + + SetupKeyboardEdit(ui->edit_score, this, numberHints); + SetupKeyboardEdit(ui->edit_nms, this, numberHints); + SetupKeyboardEdit(ui->edit_width, this, integerHints); + SetupKeyboardEdit(ui->edit_height, this, integerHints); + SetupKeyboardEdit(ui->edit_modelType, this, integerHints); + SetupKeyboardEdit(ui->edit_expectedBoltCount, this, integerHints); +} + void DialogAlgoArg::createDisplayRows() { if (!ui->fl_main) diff --git a/App/DroneScrewbolt/DroneScrewCtrlApp/dialogalgoarg.h b/App/DroneScrewbolt/DroneScrewCtrlApp/dialogalgoarg.h index 9edad02d..c63fea49 100644 --- a/App/DroneScrewbolt/DroneScrewCtrlApp/dialogalgoarg.h +++ b/App/DroneScrewbolt/DroneScrewCtrlApp/dialogalgoarg.h @@ -27,8 +27,10 @@ private slots: void on_btn_reset_clicked(); private: + bool eventFilter(QObject* watched, QEvent* event) override; void loadToUi(const DroneScrewAlgoUiParams& p); DroneScrewAlgoUiParams collectFromUi() const; + void setupInputKeyboard(); void createDisplayRows(); void loadDisplayToUi(const DroneScrewDisplayOption& options); DroneScrewDisplayOption collectDisplayFromUi() const; diff --git a/App/DroneScrewbolt/DroneScrewCtrlApp/dialogalgoarg.ui b/App/DroneScrewbolt/DroneScrewCtrlApp/dialogalgoarg.ui index 7ac562f6..37f60ddc 100644 --- a/App/DroneScrewbolt/DroneScrewCtrlApp/dialogalgoarg.ui +++ b/App/DroneScrewbolt/DroneScrewCtrlApp/dialogalgoarg.ui @@ -74,11 +74,12 @@ QLineEdit { color: rgb(239, 241, 245); background-color: rgb(47, 48, 52); border - 模型路径 + 期望个数 - + 040 + 1 ~ 64 diff --git a/App/DroneScrewbolt/DroneScrewCtrlApp/dialogcamerasetting.cpp b/App/DroneScrewbolt/DroneScrewCtrlApp/dialogcamerasetting.cpp index 3b17ccc7..6d5bd32b 100644 --- a/App/DroneScrewbolt/DroneScrewCtrlApp/dialogcamerasetting.cpp +++ b/App/DroneScrewbolt/DroneScrewCtrlApp/dialogcamerasetting.cpp @@ -2,6 +2,42 @@ #include "ui_dialogcamerasetting.h" #include +#include +#include +#include +#include +#include + +namespace +{ +void SetupKeyboardEdit(QLineEdit* edit, QObject* filter, Qt::InputMethodHints hints) +{ + if (!edit) + return; + + edit->setAttribute(Qt::WA_InputMethodEnabled, true); + edit->setInputMethodHints(hints); + edit->installEventFilter(filter); +} + +void ShowSystemKeyboard(QWidget* focusWidget) +{ + if (!focusWidget) + return; + + focusWidget->setFocus(Qt::MouseFocusReason); + + QInputMethod* inputMethod = QGuiApplication::inputMethod(); + if (inputMethod) + inputMethod->show(); + + QTimer::singleShot(0, focusWidget, []() { + QInputMethod* delayedInputMethod = QGuiApplication::inputMethod(); + if (delayedInputMethod) + delayedInputMethod->show(); + }); +} +} DialogCameraSetting::DialogCameraSetting(QWidget* parent) : QDialog(parent) @@ -13,6 +49,7 @@ DialogCameraSetting::DialogCameraSetting(QWidget* parent) // 输入校验:曝光为微秒,增益为倍数 ui->edit_exposure->setValidator(new QDoubleValidator(1.0, 1000000.0, 1, this)); ui->edit_gain->setValidator(new QDoubleValidator(0.0, 100.0, 2, this)); + setupInputKeyboard(); } DialogCameraSetting::~DialogCameraSetting() @@ -51,6 +88,39 @@ void DialogCameraSetting::collectFromUi() if (ui->edit_gain) { bool ok; double v = ui->edit_gain->text().toDouble(&ok); if (ok) m_camera.gain = v; } } +bool DialogCameraSetting::eventFilter(QObject* watched, QEvent* event) +{ + QLineEdit* edit = qobject_cast(watched); + if (edit) + { + switch (event->type()) + { + case QEvent::FocusIn: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::TouchEnd: + ShowSystemKeyboard(edit); + break; + default: + break; + } + } + + return QDialog::eventFilter(watched, event); +} + +void DialogCameraSetting::setupInputKeyboard() +{ + const Qt::InputMethodHints ipHints = + Qt::ImhPreferNumbers | Qt::ImhNoPredictiveText; + const Qt::InputMethodHints numberHints = + Qt::ImhPreferNumbers | Qt::ImhFormattedNumbersOnly; + + SetupKeyboardEdit(ui->edit_ip, this, ipHints); + SetupKeyboardEdit(ui->edit_exposure, this, numberHints); + SetupKeyboardEdit(ui->edit_gain, this, numberHints); +} + void DialogCameraSetting::on_btn_ok_clicked() { collectFromUi(); diff --git a/App/DroneScrewbolt/DroneScrewCtrlApp/dialogcamerasetting.h b/App/DroneScrewbolt/DroneScrewCtrlApp/dialogcamerasetting.h index 290dc4c1..1251216c 100644 --- a/App/DroneScrewbolt/DroneScrewCtrlApp/dialogcamerasetting.h +++ b/App/DroneScrewbolt/DroneScrewCtrlApp/dialogcamerasetting.h @@ -37,9 +37,12 @@ private slots: void on_btn_reset_clicked(); private: + bool eventFilter(QObject* watched, QEvent* event) override; void loadToUi(const QString& serverIp, const DroneScrewCameraParams& cam); void collectFromUi(); // 把界面值写回 m_serverIp / m_camera + void setupInputKeyboard(); + private: Ui::DialogCameraSetting* ui{nullptr}; QString m_serverIp; diff --git a/App/DroneScrewbolt/DroneScrewCtrlApp/dialoghistoryresult.cpp b/App/DroneScrewbolt/DroneScrewCtrlApp/dialoghistoryresult.cpp new file mode 100644 index 00000000..9577773c --- /dev/null +++ b/App/DroneScrewbolt/DroneScrewCtrlApp/dialoghistoryresult.cpp @@ -0,0 +1,166 @@ +#include "dialoghistoryresult.h" +#include "ui_dialoghistoryresult.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +DialogHistoryResult::DialogHistoryResult(const QString& historyDir, QWidget* parent) + : QDialog(parent) + , ui(new Ui::DialogHistoryResult) + , m_historyDir(historyDir) +{ + ui->setupUi(this); + setWindowTitle(QStringLiteral("历史汇总结果")); + m_listModel = new QStringListModel(this); + ui->list_history->setModel(m_listModel); + connect(ui->list_history->selectionModel(), + &QItemSelectionModel::currentChanged, + this, + &DialogHistoryResult::onHistoryCurrentChanged); + reloadHistory(); +} + +DialogHistoryResult::~DialogHistoryResult() +{ + delete ui; +} + +void DialogHistoryResult::SetHistoryDir(const QString& historyDir) +{ + if (m_historyDir == historyDir) + return; + + m_historyDir = historyDir; + reloadHistory(); +} + +void DialogHistoryResult::on_btn_refresh_clicked() +{ + reloadHistory(); +} + +void DialogHistoryResult::on_btn_close_clicked() +{ + close(); +} + +void DialogHistoryResult::reloadHistory() +{ + if (!ui->list_history || !ui->edit_detail || !m_listModel) + return; + + m_listModel->setStringList(QStringList()); + ui->edit_detail->clear(); + m_filePaths.clear(); + + QDir dir(m_historyDir); + if (!dir.exists()) + { + ui->edit_detail->setPlainText(QStringLiteral("暂无历史数据")); + return; + } + + QFileInfoList files = dir.entryInfoList(QStringList() << QStringLiteral("*.txt"), + QDir::Files | QDir::Readable, + QDir::NoSort); + std::sort(files.begin(), files.end(), [](const QFileInfo& lhs, const QFileInfo& rhs) { + return lhs.lastModified() > rhs.lastModified(); + }); + + QStringList titles; + for (const QFileInfo& fileInfo : files) + { + const QString filePath = fileInfo.absoluteFilePath(); + const QString content = readHistoryFile(filePath); + titles << makeItemTitle(fileInfo, content); + m_filePaths << filePath; + } + m_listModel->setStringList(titles); + + if (m_filePaths.isEmpty()) + { + ui->edit_detail->setPlainText(QStringLiteral("暂无历史数据")); + return; + } + + ui->list_history->setCurrentIndex(m_listModel->index(0, 0)); +} + +void DialogHistoryResult::showRecord(int row) +{ + if (!ui->edit_detail) + return; + + if (row < 0 || row >= m_filePaths.size()) + { + ui->edit_detail->clear(); + return; + } + + ui->edit_detail->setPlainText(readHistoryFile(m_filePaths.at(row))); +} + +void DialogHistoryResult::onHistoryCurrentChanged(const QModelIndex& current, + const QModelIndex& previous) +{ + Q_UNUSED(previous); + showRecord(current.row()); +} + +QString DialogHistoryResult::readHistoryFile(const QString& filePath) const +{ + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) + return QStringLiteral("读取历史数据失败:%1").arg(file.errorString()); + + QTextStream in(&file); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + in.setCodec("UTF-8"); +#endif + return in.readAll(); +} + +QString DialogHistoryResult::makeItemTitle(const QFileInfo& fileInfo, const QString& content) const +{ + QString timeText = fieldValue(content, QStringLiteral("保存时间")); + if (timeText.isEmpty()) + timeText = fileInfo.lastModified().toString(QStringLiteral("yyyy-MM-dd HH:mm:ss")); + + const QString frameId = fieldValue(content, QStringLiteral("frameId")); + const QString saveIndex = fieldValue(content, QStringLiteral("saveIndex")); + const QString heightCount = fieldValue(content, QStringLiteral("height_mm_count")); + const QString spacingCount = fieldValue(content, QStringLiteral("rod_spacing_mm_count")); + + QStringList parts; + parts << timeText; + if (!saveIndex.isEmpty()) + parts << QStringLiteral("#%1").arg(saveIndex); + if (!frameId.isEmpty()) + parts << QStringLiteral("帧%1").arg(frameId); + if (!heightCount.isEmpty()) + parts << QStringLiteral("高度%1").arg(heightCount); + if (!spacingCount.isEmpty()) + parts << QStringLiteral("杆距%1").arg(spacingCount); + + return parts.join(QStringLiteral(" ")); +} + +QString DialogHistoryResult::fieldValue(const QString& content, const QString& fieldName) const +{ + const QString prefix = fieldName + QStringLiteral(":"); + const QStringList lines = content.split(QLatin1Char('\n')); + for (const QString& line : lines) + { + const QString trimmed = line.trimmed(); + if (trimmed.startsWith(prefix)) + return trimmed.mid(prefix.size()).trimmed(); + } + return QString(); +} diff --git a/App/DroneScrewbolt/DroneScrewCtrlApp/dialoghistoryresult.h b/App/DroneScrewbolt/DroneScrewCtrlApp/dialoghistoryresult.h new file mode 100644 index 00000000..8b29a9be --- /dev/null +++ b/App/DroneScrewbolt/DroneScrewCtrlApp/dialoghistoryresult.h @@ -0,0 +1,43 @@ +#ifndef DRONESCREWCTRL_DIALOG_HISTORY_RESULT_H +#define DRONESCREWCTRL_DIALOG_HISTORY_RESULT_H + +#include +#include +#include + +namespace Ui { class DialogHistoryResult; } + +class QFileInfo; +class QModelIndex; +class QStringListModel; + +class DialogHistoryResult : public QDialog +{ + Q_OBJECT + +public: + explicit DialogHistoryResult(const QString& historyDir, QWidget* parent = nullptr); + ~DialogHistoryResult() override; + + void SetHistoryDir(const QString& historyDir); + +private slots: + void on_btn_refresh_clicked(); + void on_btn_close_clicked(); + +private: + void reloadHistory(); + void showRecord(int row); + void onHistoryCurrentChanged(const QModelIndex& current, const QModelIndex& previous); + QString readHistoryFile(const QString& filePath) const; + QString makeItemTitle(const QFileInfo& fileInfo, const QString& content) const; + QString fieldValue(const QString& content, const QString& fieldName) const; + +private: + Ui::DialogHistoryResult* ui{nullptr}; + QStringListModel* m_listModel{nullptr}; + QString m_historyDir; + QStringList m_filePaths; +}; + +#endif // DRONESCREWCTRL_DIALOG_HISTORY_RESULT_H diff --git a/App/DroneScrewbolt/DroneScrewCtrlApp/dialoghistoryresult.ui b/App/DroneScrewbolt/DroneScrewCtrlApp/dialoghistoryresult.ui new file mode 100644 index 00000000..e5920de0 --- /dev/null +++ b/App/DroneScrewbolt/DroneScrewCtrlApp/dialoghistoryresult.ui @@ -0,0 +1,82 @@ + + + DialogHistoryResult + + + 00980640 + + + 760480 + + 历史汇总结果 + + QDialog { background-color: rgb(25, 26, 28); } +QLabel { color: rgb(239, 241, 245); background: transparent; } +QListView { background-color: rgb(37, 38, 42); border: 1px solid rgb(51, 53, 60); color: rgb(239, 241, 245); font-size: 15px; outline: none; } +QListView::item { min-height: 42px; padding: 8px 10px; border-bottom: 1px solid rgb(51, 53, 60); } +QListView::item:selected { background-color: rgb(60, 120, 180); color: rgb(255, 255, 255); } +QPlainTextEdit { background-color: rgb(37, 38, 42); border: 1px solid rgb(51, 53, 60); color: rgb(239, 241, 245); font-size: 16px; padding: 10px; } +QPushButton { background-color: rgb(47, 48, 52); color: rgb(221, 225, 233); border: 1px solid rgb(75, 78, 86); border-radius: 4px; padding: 8px 18px; font-size: 15px; font-weight: 600; } +QPushButton:hover { background-color: rgb(58, 60, 66); } +QPushButton:pressed { background-color: rgb(37, 38, 42); } + + + 24 + 20 + 24 + 20 + 16 + + + 20true + 历史汇总结果 + + + + + 14 + + + 3300 + 43016777215 + QAbstractItemView::NoEditTriggers + Qt::ScrollBarAlwaysOff + QAbstractItemView::SingleSelection + + + + + true + QPlainTextEdit::NoWrap + 暂无历史数据 + + + + + + + + + Qt::Horizontal + 4020 + + + + + 10044 + 刷新 + + + + + 10044 + 关闭 + + + + + + + + + diff --git a/App/DroneScrewbolt/DroneScrewCtrlApp/mainwindow.cpp b/App/DroneScrewbolt/DroneScrewCtrlApp/mainwindow.cpp index 36a08295..d8d13f54 100644 --- a/App/DroneScrewbolt/DroneScrewCtrlApp/mainwindow.cpp +++ b/App/DroneScrewbolt/DroneScrewCtrlApp/mainwindow.cpp @@ -5,10 +5,12 @@ #include "Presenter/Inc/ConfigManager.h" #include "dialogalgoarg.h" #include "dialogcamerasetting.h" +#include "dialoghistoryresult.h" #include "resultitem.h" #include "Version.h" #include +#include #include #include #include @@ -31,6 +33,9 @@ #include #include #include +#include +#include +#include #include "DeviceStatusWidget.h" #include "DetectLogHelper.h" @@ -75,6 +80,69 @@ QString windowButtonStyle(const QString& imagePath) .arg(imagePath); } +bool isDistanceDisplayable(const CtrlDetectionDistance& distance) +{ + return distance.fromId >= 0; +} + +int displayableDistanceCount(const std::vector& distances) +{ + int count = 0; + for (const auto& distance : distances) + { + if (isDistanceDisplayable(distance)) + ++count; + } + return count; +} + +bool hasValidHistoryPhysicalHeight(const CtrlDetectionBox& box) +{ + return box.hasPhysicalHeight && + std::isfinite(static_cast(box.physicalHeightMm)) && + box.physicalHeightMm > 0.0f; +} + +bool isHistoryDistanceValid(const CtrlDetectionDistance& distance) +{ + return distance.fromId >= 0 && + std::isfinite(static_cast(distance.distanceMm)); +} + +QString boolText(bool value) +{ + return value ? QStringLiteral("true") : QStringLiteral("false"); +} + +QString finalFailureText(const CtrlDetectionFrame& frame) +{ + return frame.message.isEmpty() ? QStringLiteral("检测失败") : frame.message; +} + +QString physicalHeightHistoryErrorText(const CtrlDetectionFrame& frame, + const CtrlDetectionBox& box) +{ + if (!frame.success) + return finalFailureText(frame); + + QStringList parts; + parts << QStringLiteral("未测到物理高度"); + + const QString status = box.matchStatus.trimmed(); + if (!status.isEmpty() && status != QStringLiteral("ok")) + parts << QStringLiteral("状态:%1").arg(status); + + if (!box.hasStereoMatch) + parts << QStringLiteral("双目未匹配"); + else if (!box.trusted) + parts << QStringLiteral("结果不可信"); + + if (box.hasPhysicalHeight) + parts << QStringLiteral("高度无效:%1 mm").arg(box.physicalHeightMm, 0, 'f', 2); + + return parts.join(QStringLiteral(" ")); +} + void applyPullingDialogTheme(QProgressDialog& dialog) { const QColor windowColor(kThemeWindowR, kThemeWindowG, kThemeWindowB); @@ -244,7 +312,10 @@ void MainWindow::initUi() if (ui->btn_algo_config) ui->btn_algo_config->setStyleSheet(iconButtonStyle(QStringLiteral(":/common/resource/config_algo.png"))); if (ui->btn_test) + { ui->btn_test->setStyleSheet(iconButtonStyle(QStringLiteral(":/common/resource/config_data_test.png"))); + ui->btn_test->setToolTip(QStringLiteral("历史数据")); + } if (ui->btn_close) ui->btn_close->setStyleSheet(windowButtonStyle(QStringLiteral(":/common/resource/close.png"))); if (ui->btn_hide) @@ -292,6 +363,9 @@ void MainWindow::OnRtspFrame(const QImage& frame) void MainWindow::OnDetectionResult(const CtrlDetectionFrame& result) { + if (result.isFinalResult) + saveFinalResultHistory(result); + if (!ui->detect_result_list) return; auto upsertResultItem = [this, &result](int row, @@ -342,17 +416,22 @@ void MainWindow::OnDetectionResult(const CtrlDetectionFrame& result) int row = 0; if (m_resultDisplayMode == ResultDisplayMode::Distance) { + const int distanceCount = displayableDistanceCount(result.distances); const QString head = QStringLiteral("帧#%1 测距 %2") .arg(result.frameId) - .arg(static_cast(result.distances.size())); - if (result.distances.empty()) + .arg(distanceCount); + if (distanceCount <= 0) { upsertResultItem(row++, head, -1, ResultItem::DisplayMode::Distance); } else { for (size_t i = 0; i < result.distances.size(); ++i) + { + if (!isDistanceDisplayable(result.distances[i])) + continue; upsertResultItem(row++, head, static_cast(i), ResultItem::DisplayMode::Distance); + } } } else if (result.boxes.empty() && result.distances.empty()) @@ -420,6 +499,22 @@ void MainWindow::onServerInfoReceived(const WDRemoteServerInfo& info) .arg(static_cast(info.algoScore), 0, 'f', 2)); } +bool MainWindow::refreshServerInfoFromServer() +{ + if (!m_pPresenter) + return false; + + WDRemoteServerInfo info; + if (!m_pPresenter->QueryServerInfo(info)) + { + appendLog(QStringLiteral("读取服务器参数失败,使用当前缓存/本地配置")); + return false; + } + + onServerInfoReceived(info); + return true; +} + void MainWindow::OnStatusMessage(const QString& msg) { appendLog(msg); @@ -448,6 +543,155 @@ void MainWindow::clearResultList() ui->detect_result_list->clear(); } +QString MainWindow::historyRootDir() const +{ + QString homePath = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); + if (homePath.isEmpty()) + homePath = QDir::homePath(); + + return QDir(homePath).filePath(QStringLiteral("DroneScrewCtrlApp/history")); +} + +QString MainWindow::finalResultHistoryKey(const CtrlDetectionFrame& result) const +{ + return QStringLiteral("%1:%2:%3") + .arg(result.saveIndex) + .arg(result.frameId) + .arg(result.timestampUs); +} + +QString MainWindow::makeFinalResultHistoryText(const CtrlDetectionFrame& result, + const QDateTime& savedAt) const +{ + QString text; + QTextStream out(&text); + + int validHeightCount = 0; + for (const CtrlDetectionBox& box : result.boxes) + { + if (hasValidHistoryPhysicalHeight(box)) + ++validHeightCount; + } + + int validDistanceCount = 0; + for (const CtrlDetectionDistance& distance : result.distances) + { + if (isHistoryDistanceValid(distance)) + ++validDistanceCount; + } + + out << QStringLiteral("保存时间:") << savedAt.toString(QStringLiteral("yyyy-MM-dd HH:mm:ss.zzz")) << "\n"; + out << QStringLiteral("结果类型:final") << "\n"; + out << QStringLiteral("frameId:") << result.frameId << "\n"; + out << QStringLiteral("saveIndex:") << result.saveIndex << "\n"; + out << QStringLiteral("timestampUs:") << result.timestampUs << "\n"; + out << QStringLiteral("success:") << boolText(result.success) << "\n"; + out << QStringLiteral("errorCode:") << result.errorCode << "\n"; + if (!result.message.isEmpty()) + out << QStringLiteral("message:") << result.message << "\n"; + out << QStringLiteral("rankScore:") << QString::number(result.rankScore, 'f', 6) << "\n"; + out << QStringLiteral("rankSampleCount:") << result.rankSampleCount << "\n"; + out << QStringLiteral("target_count:") << static_cast(result.boxes.size()) << "\n"; + out << QStringLiteral("height_mm_count:") << validHeightCount << "\n"; + + if (result.boxes.empty()) + out << QStringLiteral("height_mm_error:未测到物理高度") << "\n"; + + for (size_t i = 0; i < result.boxes.size(); ++i) + { + const CtrlDetectionBox& box = result.boxes[i]; + const int targetIndex = static_cast(i + 1); + const QString prefix = QStringLiteral("height_mm_%1").arg(targetIndex); + const QString status = box.matchStatus.trimmed().isEmpty() + ? QStringLiteral("-") + : box.matchStatus.trimmed(); + + if (hasValidHistoryPhysicalHeight(box)) + out << prefix << QStringLiteral(":") << QString::number(box.physicalHeightMm, 'f', 2) << "\n"; + else + { + out << prefix << QStringLiteral(":ERROR") << "\n"; + out << prefix << QStringLiteral("_error:") << physicalHeightHistoryErrorText(result, box) << "\n"; + } + out << prefix << QStringLiteral("_status:") << status << "\n"; + out << prefix << QStringLiteral("_trusted:") << boolText(box.trusted) << "\n"; + out << prefix << QStringLiteral("_stereo_match:") << boolText(box.hasStereoMatch) << "\n"; + out << prefix << QStringLiteral("_score:") << QString::number(box.score, 'f', 6) << "\n"; + } + + out << QStringLiteral("rod_spacing_mm_count:") << validDistanceCount << "\n"; + if (validDistanceCount <= 0) + out << QStringLiteral("rod_spacing_mm_error:未测到杆间距离") << "\n"; + + int distanceIndex = 1; + for (const CtrlDetectionDistance& distance : result.distances) + { + if (!isHistoryDistanceValid(distance)) + continue; + + const QString prefix = QStringLiteral("rod_spacing_mm_%1").arg(distanceIndex); + const QString fromText = QString::number(distance.fromId + 1); + const QString toText = distance.toId >= 0 + ? QString::number(distance.toId + 1) + : QStringLiteral("-"); + + out << prefix << QStringLiteral(":") << QString::number(distance.distanceMm, 'f', 2) << "\n"; + out << prefix << QStringLiteral("_from:") << fromText << "\n"; + out << prefix << QStringLiteral("_to:") << toText << "\n"; + out << prefix << QStringLiteral("_pair:") << fromText << QStringLiteral("-") << toText << "\n"; + ++distanceIndex; + } + + return text; +} + +bool MainWindow::saveFinalResultHistory(const CtrlDetectionFrame& result) +{ + if (!result.isFinalResult) + return false; + + const bool hasStableKey = (result.saveIndex > 0 || result.frameId > 0 || result.timestampUs > 0); + const QString historyKey = finalResultHistoryKey(result); + if (hasStableKey && historyKey == m_lastStoredFinalResultKey) + return true; + + const QString dirPath = historyRootDir(); + if (!QDir().mkpath(dirPath)) + { + appendLog(QStringLiteral("创建历史结果目录失败:%1").arg(dirPath)); + return false; + } + + const QDateTime savedAt = QDateTime::currentDateTime(); + QString fileName = savedAt.toString(QStringLiteral("yyyyMMdd_HHmmss_zzz")); + if (result.saveIndex > 0) + fileName += QStringLiteral("_save_%1").arg(result.saveIndex); + if (result.frameId > 0) + fileName += QStringLiteral("_frame_%1").arg(result.frameId); + fileName += QStringLiteral(".txt"); + + QFile file(QDir(dirPath).filePath(fileName)); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) + { + appendLog(QStringLiteral("保存历史结果失败:%1").arg(file.errorString())); + return false; + } + + QTextStream out(&file); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + out.setCodec("UTF-8"); +#endif + out << makeFinalResultHistoryText(result, savedAt); + out.flush(); + file.close(); + + if (hasStableKey) + m_lastStoredFinalResultKey = historyKey; + + appendLog(QStringLiteral("已保存最终汇总结果:%1").arg(QDir(dirPath).filePath(fileName))); + return true; +} + void MainWindow::resizeEvent(QResizeEvent* event) { QMainWindow::resizeEvent(event); @@ -563,8 +807,34 @@ void MainWindow::on_btn_start_clicked() appendLog(QStringLiteral("正在请求 Server 停止...")); ui->btn_start->setEnabled(false); ui->btn_detect_start->setEnabled(false); + + QProgressDialog* finalizingDialog = nullptr; + if (m_uiState == UiState::Detect) + { + finalizingDialog = new QProgressDialog(QStringLiteral("正在汇总计算..."), + QString(), + 0, + 0, + this); + finalizingDialog->setWindowTitle(QStringLiteral("提示")); + finalizingDialog->setCancelButton(nullptr); + finalizingDialog->setWindowModality(Qt::ApplicationModal); + finalizingDialog->setMinimumDuration(0); + finalizingDialog->setAutoClose(false); + finalizingDialog->setAutoReset(false); + applyPullingDialogTheme(*finalizingDialog); + finalizingDialog->show(); + applyPullingDialogTheme(*finalizingDialog); + } QApplication::processEvents(); const bool ok = m_pPresenter && m_pPresenter->StopAll(); + if (finalizingDialog) + { + finalizingDialog->close(); + delete finalizingDialog; + finalizingDialog = nullptr; + } + QApplication::processEvents(); if (ok) { appendLog(QStringLiteral("已请求 Server 停止")); @@ -621,6 +891,7 @@ void MainWindow::on_btn_camera_clicked() { // 「相机」按钮 → 相机设置:Server IP + 曝光时间(μs) + 增益 auto cfg0 = ConfigManager::Instance().GetConfig(); + refreshServerInfoFromServer(); DialogCameraSetting dlg(this); @@ -661,6 +932,7 @@ void MainWindow::on_btn_camera_clicked() void MainWindow::on_btn_algo_config_clicked() { auto cfg = ConfigManager::Instance().GetConfig(); + refreshServerInfoFromServer(); DialogAlgoArg dlg(this); // 优先使用从 Server 读取的参数,没有则用本地配置 @@ -671,8 +943,8 @@ void MainWindow::on_btn_algo_config_clicked() serverParams.nmsThreshold = m_cachedServerInfo.algoNms; serverParams.inputWidth = m_cachedServerInfo.algoWidth; serverParams.inputHeight = m_cachedServerInfo.algoHeight; - serverParams.modelPath = m_cachedServerInfo.algoModel; serverParams.modelType = m_cachedServerInfo.algoModelType; + serverParams.expectedBoltCount = m_cachedServerInfo.algoExpectedBoltCount; dlg.SetParams(serverParams); } else @@ -696,11 +968,8 @@ void MainWindow::on_btn_algo_config_clicked() void MainWindow::on_btn_test_clicked() { - m_resultDisplayMode = ResultDisplayMode::Precision; - clearResultList(); - // 「测试」按钮 → 单次检测请求 - if (m_pPresenter && m_pPresenter->TriggerSingleDetection()) - appendLog(QStringLiteral("已发送单次检测请求")); + DialogHistoryResult dlg(historyRootDir(), this); + dlg.exec(); } // ======================== 页面布局 ======================== diff --git a/App/DroneScrewbolt/DroneScrewCtrlApp/mainwindow.h b/App/DroneScrewbolt/DroneScrewCtrlApp/mainwindow.h index dd950bab..777bd8b9 100644 --- a/App/DroneScrewbolt/DroneScrewCtrlApp/mainwindow.h +++ b/App/DroneScrewbolt/DroneScrewCtrlApp/mainwindow.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -44,7 +45,7 @@ private slots: void on_btn_hide_clicked(); void on_btn_camera_clicked(); // 连接配置 void on_btn_algo_config_clicked(); // 算法参数 - void on_btn_test_clicked(); // 单次检测 + void on_btn_test_clicked(); // 历史数据 void onServerInfoReceived(const WDRemoteServerInfo& info); // 服务器参数接收 protected: @@ -57,6 +58,12 @@ private: void appendLog(const QString& msg); void clampResultList(); void clearResultList(); + bool refreshServerInfoFromServer(); + QString historyRootDir() const; + QString finalResultHistoryKey(const CtrlDetectionFrame& result) const; + QString makeFinalResultHistoryText(const CtrlDetectionFrame& result, + const QDateTime& savedAt) const; + bool saveFinalResultHistory(const CtrlDetectionFrame& result); // 顶部按钮状态机 enum class UiState { Idle, Live, Detect }; @@ -89,6 +96,7 @@ private: int m_maxResultItems{200}; WDRemoteServerInfo m_cachedServerInfo; // 从 Server 读取的参数缓存 + QString m_lastStoredFinalResultKey; }; #endif // DRONESCREWCTRL_MAINWINDOW_H diff --git a/App/DroneScrewbolt/DroneScrewCtrlApp/mainwindow.ui b/App/DroneScrewbolt/DroneScrewCtrlApp/mainwindow.ui index 72c9c8aa..89ef2197 100644 --- a/App/DroneScrewbolt/DroneScrewCtrlApp/mainwindow.ui +++ b/App/DroneScrewbolt/DroneScrewCtrlApp/mainwindow.ui @@ -239,7 +239,7 @@ - 单次检测 + 历史数据 image: url(:/common/resource/config_data_test.png); background-color: rgb(38, 40, 47); border: none; diff --git a/App/DroneScrewbolt/DroneScrewCtrlApp/resultitem.cpp b/App/DroneScrewbolt/DroneScrewCtrlApp/resultitem.cpp index 2f15ddf9..9db89ae6 100644 --- a/App/DroneScrewbolt/DroneScrewCtrlApp/resultitem.cpp +++ b/App/DroneScrewbolt/DroneScrewCtrlApp/resultitem.cpp @@ -3,6 +3,7 @@ #include #include +#include namespace { @@ -31,6 +32,46 @@ QString failedText(const CtrlDetectionFrame& frame) return frame.message.isEmpty() ? QStringLiteral("检测失败") : frame.message; } +bool hasValidPhysicalHeight(const CtrlDetectionBox& box) +{ + return box.hasPhysicalHeight && + std::isfinite(static_cast(box.physicalHeightMm)) && + box.physicalHeightMm > 0.0f; +} + +QString physicalHeightErrorText(const CtrlDetectionFrame& frame, + const CtrlDetectionBox& box) +{ + if (!frame.success) + return failedText(frame); + + QStringList parts; + parts << QStringLiteral("未测到物理高度"); + + const QString status = box.matchStatus.trimmed(); + if (!status.isEmpty() && status != QStringLiteral("ok")) + parts << QStringLiteral("状态:%1").arg(status); + + if (!box.hasStereoMatch) + parts << QStringLiteral("双目未匹配"); + else if (!box.trusted) + parts << QStringLiteral("结果不可信"); + + if (box.hasPhysicalHeight) + parts << QStringLiteral("高度无效:%1 mm").arg(box.physicalHeightMm, 0, 'f', 2); + + return parts.join(QStringLiteral(" ")); +} + +void setStatusErrorStyle(Ui::ResultItem* ui) +{ + if (!ui || !ui->label_status) + return; + + ui->label_status->setStyleSheet(QStringLiteral( + "color: rgb(255, 105, 105); background-color: rgb(37, 38, 42); font-size: 22px; font-weight: 700;")); +} + QString oneDistanceText(const CtrlDetectionDistance& d) { const double meters = d.distanceMm / 1000.0; @@ -42,14 +83,27 @@ QString oneDistanceMmText(const CtrlDetectionDistance& d) return QStringLiteral("%1 mm").arg(d.distanceMm, 0, 'f', 2); } +bool isDistanceDisplayable(const CtrlDetectionDistance& distance) +{ + return distance.fromId >= 0; +} + QString distanceText(const CtrlDetectionFrame& frame, int targetIndex) { if (targetIndex >= 0 && targetIndex < static_cast(frame.distances.size())) - return oneDistanceText(frame.distances[static_cast(targetIndex)]); + { + const CtrlDetectionDistance& distance = frame.distances[static_cast(targetIndex)]; + return isDistanceDisplayable(distance) + ? oneDistanceText(distance) + : QStringLiteral("未测到距离"); + } QStringList distanceParts; for (size_t i = 0; i < frame.distances.size(); ++i) - distanceParts << oneDistanceText(frame.distances[i]); + { + if (isDistanceDisplayable(frame.distances[i])) + distanceParts << oneDistanceText(frame.distances[i]); + } return distanceParts.isEmpty() ? QStringLiteral("未测到距离") : distanceParts.join(" "); @@ -58,11 +112,19 @@ QString distanceText(const CtrlDetectionFrame& frame, int targetIndex) QString distanceMmText(const CtrlDetectionFrame& frame, int targetIndex) { if (targetIndex >= 0 && targetIndex < static_cast(frame.distances.size())) - return oneDistanceMmText(frame.distances[static_cast(targetIndex)]); + { + const CtrlDetectionDistance& distance = frame.distances[static_cast(targetIndex)]; + return isDistanceDisplayable(distance) + ? oneDistanceMmText(distance) + : QStringLiteral("未测到距离"); + } QStringList distanceParts; for (size_t i = 0; i < frame.distances.size(); ++i) - distanceParts << oneDistanceMmText(frame.distances[i]); + { + if (isDistanceDisplayable(frame.distances[i])) + distanceParts << oneDistanceMmText(frame.distances[i]); + } return distanceParts.isEmpty() ? QStringLiteral("未测到距离") : distanceParts.join(" "); @@ -74,7 +136,8 @@ QString distanceIndexText(const CtrlDetectionFrame& frame, int targetIndex) if (targetIndex >= 0 && targetIndex < total) { const CtrlDetectionDistance& d = frame.distances[static_cast(targetIndex)]; - return QString::number(d.toId + 1); + if (isDistanceDisplayable(d)) + return QString::number(d.fromId + 1); } return QStringLiteral("-"); } @@ -145,11 +208,16 @@ void ResultItem::setResultData(int targetIndex, { const bool precisionMode = (mode == DisplayMode::Precision); const bool precisionDistanceMode = (mode == DisplayMode::PrecisionDistance); + const QString finalPrefix = + (frame.isFinalResult && frame.saveIndex > 0) + ? QStringLiteral("#%1 ").arg(frame.saveIndex) + : QString(); setResultTextStyle(ui); setMinimumHeight((precisionMode || precisionDistanceMode) ? 104 : 108); if (ui->lb_id_t) - ui->lb_id_t->setText(QStringLiteral("ID")); + ui->lb_id_t->setText(frame.isFinalResult ? QStringLiteral("FINAL") + : QStringLiteral("ID")); if (ui->lb_status_t) { if (mode == DisplayMode::Distance) @@ -165,19 +233,19 @@ void ResultItem::setResultData(int targetIndex, if (ui->label_id) { if (precisionMode && targetIndex >= 0) - ui->label_id->setText(QString("%1/%2") - .arg(targetIndex + 1) - .arg(static_cast(frame.boxes.size()))); + ui->label_id->setText(finalPrefix + QString("%1/%2") + .arg(targetIndex + 1) + .arg(static_cast(frame.boxes.size()))); else if (precisionDistanceMode) { - ui->label_id->setText(precisionDistanceIndexText(frame, targetIndex)); + ui->label_id->setText(finalPrefix + precisionDistanceIndexText(frame, targetIndex)); } else if (mode == DisplayMode::Distance) { - ui->label_id->setText(distanceIndexText(frame, targetIndex)); + ui->label_id->setText(finalPrefix + distanceIndexText(frame, targetIndex)); } else - ui->label_id->setText(QStringLiteral("0/0")); + ui->label_id->setText(finalPrefix + QStringLiteral("0/0")); } if (mode == DisplayMode::Distance || precisionDistanceMode) @@ -198,11 +266,16 @@ void ResultItem::setResultData(int targetIndex, if (targetIndex >= 0 && targetIndex < static_cast(frame.boxes.size())) { const CtrlDetectionBox& b = frame.boxes[static_cast(targetIndex)]; - if (b.hasPhysicalHeight) + if (hasValidPhysicalHeight(b)) { ui->label_status->setText(QStringLiteral("%1 mm") .arg(b.physicalHeightMm, 0, 'f', 2)); } + else if (precisionMode) + { + ui->label_status->setText(physicalHeightErrorText(frame, b)); + setStatusErrorStyle(ui); + } else { const QString text = QStringLiteral("score=%1 w=%2 h=%3") diff --git a/App/DroneScrewbolt/DroneScrewCtrlConfig/Inc/IVrConfig.h b/App/DroneScrewbolt/DroneScrewCtrlConfig/Inc/IVrConfig.h index 1c1d84c1..f9ba3876 100644 --- a/App/DroneScrewbolt/DroneScrewCtrlConfig/Inc/IVrConfig.h +++ b/App/DroneScrewbolt/DroneScrewCtrlConfig/Inc/IVrConfig.h @@ -31,7 +31,7 @@ struct DroneScrewAlgoUiParams int inputWidth{640}; int inputHeight{640}; int modelType{0}; - std::string modelPath; + int expectedBoltCount{8}; }; /** diff --git a/App/DroneScrewbolt/DroneScrewCtrlConfig/Src/VrConfig.cpp b/App/DroneScrewbolt/DroneScrewCtrlConfig/Src/VrConfig.cpp index 1616094c..43e9346f 100644 --- a/App/DroneScrewbolt/DroneScrewCtrlConfig/Src/VrConfig.cpp +++ b/App/DroneScrewbolt/DroneScrewCtrlConfig/Src/VrConfig.cpp @@ -76,7 +76,7 @@ int CVrDroneScrewCtrlConfig::LoadConfig(const std::string& filePath, result.algo.inputWidth = IntAttr(a, "inputWidth", result.algo.inputWidth); result.algo.inputHeight = IntAttr(a, "inputHeight", result.algo.inputHeight); result.algo.modelType = IntAttr(a, "modelType", result.algo.modelType); - result.algo.modelPath = SafeAttr(a, "modelPath", result.algo.modelPath); + result.algo.expectedBoltCount = IntAttr(a, "expectedBoltCount", result.algo.expectedBoltCount); } if (XMLElement* c = root->FirstChildElement("Camera")) { @@ -121,7 +121,7 @@ bool CVrDroneScrewCtrlConfig::SaveConfig(const std::string& filePath, a->SetAttribute("inputWidth", result.algo.inputWidth); a->SetAttribute("inputHeight", result.algo.inputHeight); a->SetAttribute("modelType", result.algo.modelType); - a->SetAttribute("modelPath", result.algo.modelPath.c_str()); + a->SetAttribute("expectedBoltCount", result.algo.expectedBoltCount); root->InsertEndChild(a); XMLElement* c = doc.NewElement("Camera"); diff --git a/App/DroneScrewbolt/DroneScrewServer/Algo/DroneScrewAlgoStub.cpp b/App/DroneScrewbolt/DroneScrewServer/Algo/DroneScrewAlgoStub.cpp index cf66bc06..8b271c5a 100644 --- a/App/DroneScrewbolt/DroneScrewServer/Algo/DroneScrewAlgoStub.cpp +++ b/App/DroneScrewbolt/DroneScrewServer/Algo/DroneScrewAlgoStub.cpp @@ -707,7 +707,15 @@ void DroneScrewAlgoStub::appendBoltBox(const StereoBoltModuleBoltC& bolt, if (mapRectifiedLeftRoi(bolt.left_roi, result.imageWidth, result.imageHeight, box)) { box.hasPhysicalHeight = true; + // height_mm is the physical bolt height used by CtrlApp display and txt storage. box.physicalHeightMm = static_cast(bolt.height_mm); + box.hasStereoMatch = bolt.idx_L >= 0 && bolt.idx_R >= 0 && bolt.pair_id >= 0; + box.trusted = bolt.confidence == 0; + box.matchConfidence = bolt.confidence; + box.matchStatus = bolt.status; + box.pairId = bolt.pair_id; + box.leftIndex = bolt.idx_L; + box.rightIndex = bolt.idx_R; result.boxes.push_back(box); } } diff --git a/App/DroneScrewbolt/DroneScrewServer/Algo/IDroneScrewAlgo.h b/App/DroneScrewbolt/DroneScrewServer/Algo/IDroneScrewAlgo.h index 6ffb93f8..4d839dba 100644 --- a/App/DroneScrewbolt/DroneScrewServer/Algo/IDroneScrewAlgo.h +++ b/App/DroneScrewbolt/DroneScrewServer/Algo/IDroneScrewAlgo.h @@ -18,6 +18,13 @@ struct DroneScrewBox int height{0}; bool hasPhysicalHeight{false}; float physicalHeightMm{0.0f}; + bool hasStereoMatch{false}; + bool trusted{false}; + int matchConfidence{1}; + std::string matchStatus; + int pairId{-1}; + int leftIndex{-1}; + int rightIndex{-1}; }; /** @@ -43,6 +50,10 @@ struct DroneScrewResult bool success{true}; int errorCode{0}; std::string message; + bool isFinalResult{false}; + double rankScore{0.0}; + int rankSampleCount{0}; + uint64_t saveIndex{0}; }; /** diff --git a/App/DroneScrewbolt/DroneScrewServer/DroneScrewServerPresenter.cpp b/App/DroneScrewbolt/DroneScrewServer/DroneScrewServerPresenter.cpp index 3151302e..4ef1330a 100644 --- a/App/DroneScrewbolt/DroneScrewServer/DroneScrewServerPresenter.cpp +++ b/App/DroneScrewbolt/DroneScrewServer/DroneScrewServerPresenter.cpp @@ -16,8 +16,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -69,6 +71,7 @@ constexpr int kDetectPipelineDistance = 1; constexpr const char* kImageSaveRootDir = "/home/cat"; constexpr size_t kMaxImageSaveQueueDepth = 100; constexpr size_t kImageSaveWorkerCount = 4; +constexpr int kDefaultPrecisionExpectedCount = 8; const char* mvsSdkErrorName(int code) { @@ -252,6 +255,256 @@ void logDetectionResultFixed(const char* tag, int frameNo, const DroneScrewResul } } +bool positiveFinite(double value) +{ + return std::isfinite(value) && value > 0.0; +} + +int expectedBoltCountOrDefault(int expectedCount) +{ + return expectedCount > 0 ? expectedCount : kDefaultPrecisionExpectedCount; +} + +int expectedDistanceCount(int expectedCount) +{ + return std::max(0, expectedCount - 1); +} + +int physicalHeightCount(const DroneScrewResult& result) +{ + int count = 0; + for (const auto& b : result.boxes) + { + if (b.hasPhysicalHeight && positiveFinite(b.physicalHeightMm)) + ++count; + } + return count; +} + +bool boltStatusOk(const DroneScrewBox& box) +{ + return box.matchStatus == "ok"; +} + +int stereoMatchedPhysicalHeightCount(const DroneScrewResult& result) +{ + int count = 0; + for (const auto& b : result.boxes) + { + if (b.hasPhysicalHeight && + positiveFinite(b.physicalHeightMm) && + b.hasStereoMatch) + { + ++count; + } + } + return count; +} + +int trustedStatusOkPhysicalHeightCount(const DroneScrewResult& result) +{ + int count = 0; + for (const auto& b : result.boxes) + { + if (b.hasPhysicalHeight && + positiveFinite(b.physicalHeightMm) && + b.hasStereoMatch && + b.trusted && + boltStatusOk(b)) + { + ++count; + } + } + return count; +} + +int scoreOkPhysicalHeightCount(const DroneScrewResult& result, double scoreThreshold) +{ + int count = 0; + for (const auto& b : result.boxes) + { + if (b.hasPhysicalHeight && + positiveFinite(b.physicalHeightMm) && + static_cast(b.score) >= scoreThreshold) + { + ++count; + } + } + return count; +} + +int validDistanceCount(const DroneScrewResult& result) +{ + int count = 0; + for (const auto& d : result.distances) + { + if (positiveFinite(d.distanceMm)) + ++count; + } + return count; +} + +double averageDetectionScore(const DroneScrewResult& result) +{ + double sum = 0.0; + int count = 0; + for (const auto& b : result.boxes) + { + if (!b.hasPhysicalHeight || + !positiveFinite(b.physicalHeightMm) || + !std::isfinite(static_cast(b.score))) + { + continue; + } + sum += static_cast(b.score); + ++count; + } + return count > 0 ? (sum / static_cast(count)) : 0.0; +} + +double medianValue(std::vector values) +{ + if (values.empty()) + return 0.0; + std::sort(values.begin(), values.end()); + const size_t mid = values.size() / 2; + if ((values.size() % 2) != 0) + return values[mid]; + return (values[mid - 1] + values[mid]) * 0.5; +} + +std::vector sessionMedianPhysicalHeights(const std::vector& results, + int expectedCount) +{ + std::vector medians(static_cast(expectedCount), 0.0); + for (int i = 0; i < expectedCount; ++i) + { + std::vector values; + for (const auto& result : results) + { + if (!result.success || result.errorCode != 0) + continue; + if (i >= static_cast(result.boxes.size())) + continue; + const auto& b = result.boxes[static_cast(i)]; + if (b.hasPhysicalHeight && positiveFinite(b.physicalHeightMm)) + values.push_back(static_cast(b.physicalHeightMm)); + } + medians[static_cast(i)] = medianValue(std::move(values)); + } + return medians; +} + +std::vector sessionMedianDistances(const std::vector& results, + int distanceCount) +{ + std::vector medians(static_cast(distanceCount), 0.0); + for (int i = 0; i < distanceCount; ++i) + { + std::vector values; + for (const auto& result : results) + { + if (!result.success || result.errorCode != 0) + continue; + if (i >= static_cast(result.distances.size())) + continue; + const auto& d = result.distances[static_cast(i)]; + if (positiveFinite(d.distanceMm)) + values.push_back(static_cast(d.distanceMm)); + } + medians[static_cast(i)] = medianValue(std::move(values)); + } + return medians; +} + +double geometryStabilityScore(const DroneScrewResult& result, + const std::vector& medianHeights, + const std::vector& medianDistances) +{ + double relErrorSum = 0.0; + int compareCount = 0; + + const int heightLimit = std::min(static_cast(medianHeights.size()), + static_cast(result.boxes.size())); + for (int i = 0; i < heightLimit; ++i) + { + const auto& b = result.boxes[static_cast(i)]; + const double median = medianHeights[static_cast(i)]; + if (!b.hasPhysicalHeight || !positiveFinite(b.physicalHeightMm) || + !positiveFinite(median)) + { + continue; + } + const double denom = std::max(std::fabs(median), 1.0); + relErrorSum += std::fabs(static_cast(b.physicalHeightMm) - median) / denom; + ++compareCount; + } + + const int distanceLimit = std::min(static_cast(medianDistances.size()), + static_cast(result.distances.size())); + for (int i = 0; i < distanceLimit; ++i) + { + const auto& d = result.distances[static_cast(i)]; + const double median = medianDistances[static_cast(i)]; + if (!positiveFinite(d.distanceMm) || !positiveFinite(median)) + continue; + const double denom = std::max(std::fabs(median), 1.0); + relErrorSum += std::fabs(static_cast(d.distanceMm) - median) / denom; + ++compareCount; + } + + if (compareCount <= 0) + return 0.0; + + const double avgRelError = relErrorSum / static_cast(compareCount); + return 1.0 / (1.0 + avgRelError * 10.0); +} + +double precisionResultRankScore(const DroneScrewResult& result, + int expectedCount, + double scoreThreshold, + const std::vector& medianHeights, + const std::vector& medianDistances) +{ + const int expectedDistances = expectedDistanceCount(expectedCount); + const int heightCount = physicalHeightCount(result); + const int distanceCount = validDistanceCount(result); + const int matchedCount = stereoMatchedPhysicalHeightCount(result); + const int trustedStatusOkCount = trustedStatusOkPhysicalHeightCount(result); + const int scoreOkCount = scoreOkPhysicalHeightCount(result, scoreThreshold); + + const bool statusOk = result.success && result.errorCode == 0; + const bool countComplete = heightCount == expectedCount; + const bool distanceComplete = distanceCount == expectedDistances; + const bool allMatched = countComplete && matchedCount == heightCount; + const bool allTrustedStatusOk = countComplete && trustedStatusOkCount == heightCount; + const bool allScoreOk = countComplete && scoreOkCount == heightCount; + + const double heightRatio = + expectedCount > 0 + ? std::max(0.0, 1.0 - std::fabs(static_cast(heightCount - expectedCount)) / + static_cast(expectedCount)) + : 1.0; + const double distanceRatio = + expectedDistances > 0 + ? std::max(0.0, 1.0 - std::fabs(static_cast(distanceCount - expectedDistances)) / + static_cast(expectedDistances)) + : 1.0; + const double completeness = (heightRatio + distanceRatio) * 0.5; + const double avgScore = averageDetectionScore(result); + const double stability = geometryStabilityScore(result, medianHeights, medianDistances); + + return (statusOk ? 1000000000.0 : 0.0) + + (countComplete ? 100000000.0 : 0.0) + + (distanceComplete ? 50000000.0 : 0.0) + + (allMatched ? 10000000.0 : 0.0) + + (allTrustedStatusOk ? 5000000.0 : 0.0) + + (allScoreOk ? 1000000.0 : 0.0) + + completeness * 10000.0 + + avgScore * 1000.0 + + stability * 100.0; +} + QString savedImageFileName(unsigned long long index, const QString& prefix) { return QStringLiteral("%1_%2Image.png").arg(index).arg(prefix); @@ -259,10 +512,13 @@ QString savedImageFileName(unsigned long long index, const QString& prefix) bool saveDetectionResultText(const QString& dirPath, unsigned long long imageIndex, - const DroneScrewResult& result) + const DroneScrewResult& result, + const QString& fileName = QString()) { - const QString filePath = QDir(dirPath).filePath( - QStringLiteral("%1_result.txt").arg(imageIndex)); + const QString resultFileName = + fileName.isEmpty() ? QStringLiteral("%1_result.txt").arg(imageIndex) + : fileName; + const QString filePath = QDir(dirPath).filePath(resultFileName); QFile file(filePath); if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { @@ -280,30 +536,43 @@ bool saveDetectionResultText(const QString& dirPath, out << "success:" << (result.success ? 1 : 0) << '\n'; out << "errorCode:" << result.errorCode << '\n'; out << "message:" << QString::fromStdString(result.message) << '\n'; + out << "isFinalResult:" << (result.isFinalResult ? 1 : 0) << '\n'; + out << "rankScore:" << QString::number(result.rankScore, 'f', 6) << '\n'; + out << "rankSampleCount:" << result.rankSampleCount << '\n'; + out << "saveIndex:" << static_cast(result.saveIndex) << '\n'; int physicalHeightCount = 0; for (const auto& b : result.boxes) { - if (b.hasPhysicalHeight) + if (b.hasPhysicalHeight && positiveFinite(b.physicalHeightMm)) ++physicalHeightCount; } - out << "physicalHeightCount:" << physicalHeightCount << '\n'; + out << "heightCount:" << physicalHeightCount << '\n'; int physicalHeightIndex = 1; for (const auto& b : result.boxes) { - if (!b.hasPhysicalHeight) + if (!b.hasPhysicalHeight || !positiveFinite(b.physicalHeightMm)) continue; - out << "physicalHeight" << physicalHeightIndex << "Id:" << physicalHeightIndex << '\n'; - out << "physicalHeight" << physicalHeightIndex << "Mm:" + out << "height" << physicalHeightIndex << "Id:" << physicalHeightIndex << '\n'; + out << "height" << physicalHeightIndex << "_mm:" << QString::number(b.physicalHeightMm, 'f', 3) << '\n'; ++physicalHeightIndex; } - out << "rodSpacingCount:" << static_cast(result.distances.size()) << '\n'; + int rodSpacingCount = 0; + for (const auto& d : result.distances) + { + if (positiveFinite(d.distanceMm)) + ++rodSpacingCount; + } + out << "rodSpacingCount:" << rodSpacingCount << '\n'; + int rodSpacingIndex = 1; for (size_t i = 0; i < result.distances.size(); ++i) { const auto& d = result.distances[i]; - const int index = static_cast(i + 1); + if (!positiveFinite(d.distanceMm)) + continue; + const int index = rodSpacingIndex++; out << "rodSpacing" << index << "FromId:" << (d.fromId + 1) << '\n'; out << "rodSpacing" << index << "ToId:" << (d.toId + 1) << '\n'; out << "rodSpacing" << index << "Mm:" @@ -1795,6 +2064,107 @@ QJsonObject DroneScrewServerPresenter::getCalibrationInfo() const return calibration; } +bool DroneScrewServerPresenter::getLastFinalDetectionResult(DroneScrewResult& result) const +{ + std::lock_guard lk(m_detectionResultMutex); + if (!m_hasLastFinalResult) + return false; + result = m_lastFinalResult; + return true; +} + +void DroneScrewServerPresenter::resetPrecisionResultSamples() +{ + std::lock_guard lk(m_detectionResultMutex); + m_precisionResultSamples.clear(); + m_lastFinalResult = DroneScrewResult{}; + m_hasLastFinalResult = false; +} + +void DroneScrewServerPresenter::clearLastFinalDetectionResult() +{ + std::lock_guard lk(m_detectionResultMutex); + m_lastFinalResult = DroneScrewResult{}; + m_hasLastFinalResult = false; +} + +void DroneScrewServerPresenter::recordPrecisionResultSample(const DroneScrewResult& result, + unsigned long long imageIndex) +{ + if (imageIndex == 0) + return; + + DroneScrewResult stored = result; + stored.saveIndex = imageIndex; + stored.isFinalResult = false; + + std::lock_guard lk(m_detectionResultMutex); + m_precisionResultSamples.push_back(PrecisionResultSample{stored, imageIndex}); +} + +bool DroneScrewServerPresenter::selectBestPrecisionResult(DroneScrewResult& result, + unsigned long long& imageIndex, + double& rankScore, + int& sampleCount) const +{ + std::vector samples; + { + std::lock_guard lk(m_detectionResultMutex); + samples = m_precisionResultSamples; + } + + sampleCount = static_cast(samples.size()); + if (samples.empty()) + return false; + + std::vector results; + results.reserve(samples.size()); + for (const auto& sample : samples) + results.push_back(sample.result); + + const int expectedCount = expectedBoltCountOrDefault(m_algoParams.expectedBoltCount); + const int distanceCount = expectedDistanceCount(expectedCount); + const double scoreThreshold = std::max(0.0, static_cast(m_algoParams.scoreThreshold)); + const std::vector medianHeights = + sessionMedianPhysicalHeights(results, expectedCount); + const std::vector medianDistances = + sessionMedianDistances(results, distanceCount); + + size_t bestIndex = 0; + double bestScore = -std::numeric_limits::infinity(); + for (size_t i = 0; i < samples.size(); ++i) + { + const double score = precisionResultRankScore(samples[i].result, + expectedCount, + scoreThreshold, + medianHeights, + medianDistances); + if (score > bestScore || + (std::fabs(score - bestScore) < 1e-9 && + samples[i].imageIndex > samples[bestIndex].imageIndex)) + { + bestScore = score; + bestIndex = i; + } + } + + result = samples[bestIndex].result; + imageIndex = samples[bestIndex].imageIndex; + rankScore = bestScore; + result.isFinalResult = true; + result.rankScore = bestScore; + result.rankSampleCount = sampleCount; + result.saveIndex = imageIndex; + return true; +} + +void DroneScrewServerPresenter::setLastFinalDetectionResult(const DroneScrewResult& result) +{ + std::lock_guard lk(m_detectionResultMutex); + m_lastFinalResult = result; + m_hasLastFinalResult = true; +} + int DroneScrewServerPresenter::startDetectionWork() { if (m_bLiveStreaming.load()) @@ -1824,6 +2194,7 @@ int DroneScrewServerPresenter::startDetectionWork() return ERR_CODE(DRONESCREW_ERR_CAMERA_NOT_CONNECTED); } + resetPrecisionResultSamples(); m_detectPipelineMode = kDetectPipelinePrecision; m_activeTriggerFps = static_cast(kPrecisionFrameRate); @@ -1864,6 +2235,7 @@ int DroneScrewServerPresenter::stopDetectionWork() if (m_bLiveStreaming.load()) { LOG_INFO("[DETECT] stop ignored: live stream keeps binocular detection running\n"); + clearLastFinalDetectionResult(); return 0; } @@ -1871,6 +2243,7 @@ int DroneScrewServerPresenter::stopDetectionWork() { stopGpioTriggerLoop(); stopImageSaveThread(); + clearLastFinalDetectionResult(); LOG_WARN("Detection not started\n"); return 0; } @@ -1879,7 +2252,46 @@ int DroneScrewServerPresenter::stopDetectionWork() m_bThreadExit = true; if (m_detectThread.joinable()) m_detectThread.join(); + + DroneScrewResult finalResult; + unsigned long long finalImageIndex = 0; + double finalRankScore = 0.0; + int finalSampleCount = 0; + const bool hasFinalResult = selectBestPrecisionResult(finalResult, + finalImageIndex, + finalRankScore, + finalSampleCount); + QString finalDir; + { + std::lock_guard lk(m_imageSaveMutex); + finalDir = m_imageSaveSessionDir; + } + stopImageSaveThread(); + if (hasFinalResult) + { + finalResult.isFinalResult = true; + finalResult.rankScore = finalRankScore; + finalResult.rankSampleCount = finalSampleCount; + finalResult.saveIndex = finalImageIndex; + if (!finalDir.isEmpty()) + saveDetectionResultText(finalDir, finalImageIndex, finalResult, + QStringLiteral("final_result.txt")); + setLastFinalDetectionResult(finalResult); + LOG_INFO("[DETECT-final] selected index=%llu frame=%llu score=%.6f samples=%d boxes=%zu distances=%zu dir=%s\n", + finalImageIndex, + static_cast(finalResult.frameId), + finalRankScore, + finalSampleCount, + finalResult.boxes.size(), + finalResult.distances.size(), + finalDir.toStdString().c_str()); + } + else + { + clearLastFinalDetectionResult(); + LOG_WARN("[DETECT-final] no saved precision result samples\n"); + } m_bIsDetecting = false; m_detectPipelineMode = kDetectPipelinePrecision; m_activeTriggerFps = static_cast(kPrecisionFrameRate); @@ -2472,7 +2884,7 @@ void DroneScrewServerPresenter::stopImageSaveThread() } } -void DroneScrewServerPresenter::enqueueImageSave(const MvsImageData& leftImg, +bool DroneScrewServerPresenter::enqueueImageSave(const MvsImageData& leftImg, const MvsImageData& rightImg, const DroneScrewResult& result, unsigned long long index) @@ -2493,9 +2905,9 @@ void DroneScrewServerPresenter::enqueueImageSave(const MvsImageData& leftImg, { std::lock_guard lk(m_imageSaveMutex); if (m_imageSaveThreadExit.load() || m_imageSaveSessionDir.isEmpty()) - return; + return false; if (dropIfQueueFull()) - return; + return false; } auto copyMono8 = [](const MvsImageData& src, ImageSaveBuffer& dst) -> bool { @@ -2522,27 +2934,30 @@ void DroneScrewServerPresenter::enqueueImageSave(const MvsImageData& leftImg, ImageSaveJob job; job.index = index; job.result = result; + job.result.isFinalResult = false; + job.result.saveIndex = index; job.hasResult = true; if (!copyMono8(leftImg, job.left) || !copyMono8(rightImg, job.right)) { LOG_WARN("[SAVE] skip image save: unsupported image pair index=%llu left=%ux%u pf=0x%x right=%ux%u pf=0x%x\n", index, leftImg.width, leftImg.height, leftImg.pixelFormat, rightImg.width, rightImg.height, rightImg.pixelFormat); - return; + return false; } { std::lock_guard lk(m_imageSaveMutex); if (m_imageSaveThreadExit.load() || m_imageSaveSessionDir.isEmpty()) - return; + return false; if (dropIfQueueFull()) - return; + return false; job.dirPath = m_imageSaveSessionDir; m_imageSaveQueue.emplace_back(std::move(job)); } m_imageSaveCv.notify_one(); + return true; } void DroneScrewServerPresenter::imageSaveThreadFunc(int workerIndex) @@ -2877,8 +3292,16 @@ std::string DroneScrewServerPresenter::detectMode() const void DroneScrewServerPresenter::handleUpdateAlgoParams(const DroneScrewAlgoParams& params) { - m_algoParams = params; - if (m_pAlgo) m_pAlgo->UpdateParams(params); + DroneScrewAlgoParams merged = m_algoParams; + merged.scoreThreshold = params.scoreThreshold; + merged.nmsThreshold = params.nmsThreshold; + merged.inputWidth = params.inputWidth; + merged.inputHeight = params.inputHeight; + merged.modelType = params.modelType; + merged.expectedBoltCount = expectedBoltCountOrDefault(params.expectedBoltCount); + + m_algoParams = merged; + if (m_pAlgo) m_pAlgo->UpdateParams(merged); saveConfiguration(); } @@ -3192,7 +3615,11 @@ void DroneScrewServerPresenter::detectThreadFunc() result.boxes.size(), result.distances.size()); if (!distanceMode) - enqueueImageSave(leftImg, rightImg, result, ++savedFrameCnt); + { + const unsigned long long saveIndex = ++savedFrameCnt; + if (enqueueImageSave(leftImg, rightImg, result, saveIndex)) + recordPrecisionResultSample(result, saveIndex); + } emit detectionResult(result); diff --git a/App/DroneScrewbolt/DroneScrewServer/DroneScrewServerPresenter.h b/App/DroneScrewbolt/DroneScrewServer/DroneScrewServerPresenter.h index a71fa543..15e97051 100644 --- a/App/DroneScrewbolt/DroneScrewServer/DroneScrewServerPresenter.h +++ b/App/DroneScrewbolt/DroneScrewServer/DroneScrewServerPresenter.h @@ -98,6 +98,7 @@ public: QString getRtspUrl() const { return m_rtspAdvertiseUrl; } QJsonObject getRuntimeInfo() const; QJsonObject getCalibrationInfo() const; + bool getLastFinalDetectionResult(DroneScrewResult& result) const; int startDetectionWork(); int stopDetectionWork(); int startLiveStream(); @@ -237,11 +238,20 @@ private: void startImageSaveThread(const QString& modeName); void stopImageSaveThread(); void imageSaveThreadFunc(int workerIndex); - void enqueueImageSave(const MvsImageData& leftImg, + bool enqueueImageSave(const MvsImageData& leftImg, const MvsImageData& rightImg, const DroneScrewResult& result, unsigned long long index); QString imageSaveModeName() const; + void resetPrecisionResultSamples(); + void clearLastFinalDetectionResult(); + void recordPrecisionResultSample(const DroneScrewResult& result, + unsigned long long imageIndex); + bool selectBestPrecisionResult(DroneScrewResult& result, + unsigned long long& imageIndex, + double& rankScore, + int& sampleCount) const; + void setLastFinalDetectionResult(const DroneScrewResult& result); private: struct ImageSaveBuffer @@ -263,6 +273,12 @@ private: bool hasResult{false}; }; + struct PrecisionResultSample + { + DroneScrewResult result; + unsigned long long imageIndex{0}; + }; + // 双目相机设备 IMvsDevice* m_pLeftCamera{nullptr}; IMvsDevice* m_pRightCamera{nullptr}; @@ -354,6 +370,11 @@ private: std::deque m_imageSaveQueue; QString m_imageSaveSessionDir; unsigned long long m_imageSaveDropped{0}; + + mutable std::mutex m_detectionResultMutex; + std::vector m_precisionResultSamples; + DroneScrewResult m_lastFinalResult; + bool m_hasLastFinalResult{false}; }; #endif // DRONESCREW_SERVER_PRESENTER_H diff --git a/App/DroneScrewbolt/DroneScrewServer/DroneScrewZmqProtocol.cpp b/App/DroneScrewbolt/DroneScrewServer/DroneScrewZmqProtocol.cpp index 052c7639..5b2634f1 100644 --- a/App/DroneScrewbolt/DroneScrewServer/DroneScrewZmqProtocol.cpp +++ b/App/DroneScrewbolt/DroneScrewServer/DroneScrewZmqProtocol.cpp @@ -152,6 +152,10 @@ void fillDetectionJson(const DroneScrewResult& result, QJsonObject& obj) obj["success"] = result.success; obj["errorCode"] = result.errorCode; obj["msg"] = QString::fromStdString(result.message); + obj["isFinalResult"] = result.isFinalResult; + obj["rankScore"] = result.rankScore; + obj["rankSampleCount"] = result.rankSampleCount; + obj["saveIndex"] = static_cast(result.saveIndex); QJsonArray boxes; for (const auto& b : result.boxes) @@ -164,7 +168,17 @@ void fillDetectionJson(const DroneScrewResult& result, QJsonObject& obj) jb["w"] = b.width; jb["h"] = b.height; if (b.hasPhysicalHeight) - jb["physicalHeightMm"] = static_cast(b.physicalHeightMm); + { + const double heightMm = static_cast(b.physicalHeightMm); + jb["height_mm"] = heightMm; + jb["hasStereoMatch"] = b.hasStereoMatch; + jb["trusted"] = b.trusted; + jb["matchConfidence"] = b.matchConfidence; + jb["matchStatus"] = QString::fromStdString(b.matchStatus); + jb["pairId"] = b.pairId; + jb["leftIndex"] = b.leftIndex; + jb["rightIndex"] = b.rightIndex; + } boxes.append(jb); } obj["boxes"] = boxes; @@ -468,7 +482,24 @@ QByteArray DroneScrewZmqProtocol::handleControlMessage(const QByteArray& reqData const int ret = m_stopWorkHandler ? m_stopWorkHandler() : fallbackHandlerError(); if (ret != 0) + { setErrorResp(resp, ret, "stop detection failed"); + } + else if (m_finalDetectionResultProvider) + { + DroneScrewResult finalResult; + if (m_finalDetectionResultProvider(finalResult)) + { + finalResult.isFinalResult = true; + fillDetectionJson(finalResult, resp); + resp["hasFinalResult"] = true; + publishDetectionResult(finalResult); + } + else + { + resp["hasFinalResult"] = false; + } + } } else if (cmd == "start_stream") { diff --git a/App/DroneScrewbolt/DroneScrewServer/DroneScrewZmqProtocol.h b/App/DroneScrewbolt/DroneScrewServer/DroneScrewZmqProtocol.h index 61375c30..665980ed 100644 --- a/App/DroneScrewbolt/DroneScrewServer/DroneScrewZmqProtocol.h +++ b/App/DroneScrewbolt/DroneScrewServer/DroneScrewZmqProtocol.h @@ -72,6 +72,10 @@ public: { m_stopWorkHandler = std::move(handler); } + void setFinalDetectionResultProvider(std::function provider) + { + m_finalDetectionResultProvider = std::move(provider); + } void setStartLiveStreamHandler(std::function handler) { m_startLiveStreamHandler = std::move(handler); @@ -139,6 +143,7 @@ private: std::function m_singleDetectionHandler; std::function m_startWorkHandler; std::function m_stopWorkHandler; + std::function m_finalDetectionResultProvider; std::function m_startLiveStreamHandler; std::function m_stopLiveStreamHandler; std::function m_swapCameraRolesHandler; diff --git a/App/DroneScrewbolt/DroneScrewServer/Version.h b/App/DroneScrewbolt/DroneScrewServer/Version.h index 15ce1d15..d464c631 100644 --- a/App/DroneScrewbolt/DroneScrewServer/Version.h +++ b/App/DroneScrewbolt/DroneScrewServer/Version.h @@ -8,6 +8,6 @@ #define DRONESCREWSERVER_COMPANY_NAME "VisionTech" #define DRONESCREWSERVER_COPYRIGHT "Copyright (C) 2026" #define DRONESCREWSERVER_VERSION_STRING "1.0.0" -#define DRONESCREWSERVER_VERSION_BUILD "4" +#define DRONESCREWSERVER_VERSION_BUILD "5" #endif // DRONESCREW_VERSION_H diff --git a/App/DroneScrewbolt/DroneScrewServer/main.cpp b/App/DroneScrewbolt/DroneScrewServer/main.cpp index 345f0618..eda58c42 100644 --- a/App/DroneScrewbolt/DroneScrewServer/main.cpp +++ b/App/DroneScrewbolt/DroneScrewServer/main.cpp @@ -348,6 +348,9 @@ int main(int argc, char* argv[]) zmq.setStopWorkHandler([&presenter]() { return presenter.stopDetectionWork(); }); + zmq.setFinalDetectionResultProvider([&presenter](DroneScrewResult& result) { + return presenter.getLastFinalDetectionResult(result); + }); zmq.setStartLiveStreamHandler([&presenter]() { return presenter.startLiveStream(); }); diff --git a/AppAlgo/stereo_bolt_delivery/ABI_FINGERPRINT.txt b/AppAlgo/stereo_bolt_delivery/ABI_FINGERPRINT.txt index 01510689..e408d546 100644 --- a/AppAlgo/stereo_bolt_delivery/ABI_FINGERPRINT.txt +++ b/AppAlgo/stereo_bolt_delivery/ABI_FINGERPRINT.txt @@ -1,8 +1,8 @@ # ABI fingerprint of libstereo_bolt.so # Your board MUST match these or the .so will not load / not run. -# generated: 2026-07-01 (rebuilt on RK3588; OBB-axis ranging stability fix) +# generated: 2026-07-02 (rebuilt on RK3588; v5 flat-top endpoint + 0703 OBB model; C ABI unchanged) # libstereo_bolt.so SHA256: -# 67d9406c1ab69c36b8f3b363fe6cf38567fdac97c25fa160e4c80a4d63b172ae +# 8fb1cf6dfae4f655da7e96ea50d858b3971674cb6676dff0b3873a2da250b9af [arch] AArch64 diff --git a/AppAlgo/stereo_bolt_delivery/CHANGELOG.txt b/AppAlgo/stereo_bolt_delivery/CHANGELOG.txt index c7a56003..ad842492 100644 --- a/AppAlgo/stereo_bolt_delivery/CHANGELOG.txt +++ b/AppAlgo/stereo_bolt_delivery/CHANGELOG.txt @@ -1,6 +1,55 @@ CHANGELOG — libstereo_bolt delivery package ============================================ +## 2026-07-02 v5 — flat-top endpoint + 0703 OBB model + +Rebuilt on RK3588 from source eb96ff4 (branch qc/axis-dir-consensus). +C ABI unchanged (same 9 exported symbols) — no consumer recompile needed, +only replace lib/ + weights/ + config/. + +Changes vs v4: +- Model: best_obb_1024_0703_20260702.rknn (0703 annotated finetune; far/oblique + recall recovered — 0702 far scene 0-detect frames 131/167 -> ~0). +- config.rk3588.12mp.yaml: model_path -> 0703 rknn, imgsz 1024, task obb, + measurement.top_morphology: "flat_top" (ellipse-center tip + tight foot band + for black flat-top studs), 0621 calib (stereo_calib_20260621_202857). +- Ranging: physical-length consensus pairing repair + graded confidence. + Board-verified on 0702 12MP: 8/8 bolts, status=ok, per-bolt 1.5-2.2m coherent. +- Board timing (12MP single frame, steady state): ~450-475ms total + (yolo ~180ms, cmatch ~115ms, rectify ~90ms, measure+so+qc ~65ms, plane + skipped in height_from_plane:false). +- NOTE: flat-top height ACCURACY still under validation (single-angle 10-frame + board test median 150.6mm vs 160 nominal; needs multi-angle aggregation + + on-site caliper zero-point before precision sign-off). Ranging + timing are + production-ready; precise height functional but accuracy-pending. + +## 2026-07-01 v4 — precise partial-output contract fix + +Current delivery contents: +- Model: best_obb_1024.rknn (OBB, imgsz 1024, RKNN) +- Precise measurement and ranging share the same OBB model. +- Runtime libraries are bundled in lib/: libstereo_bolt.so, OpenCV 4.5.0, librknnrt.so. +- No target-board apt install is required for runtime OpenCV. + +Behavior change: +- Precise measurement no longer exports left-only fallback heights as product + results. If a bolt is detected in the left image but has no right detection + / right ROI (`idx_R < 0`), it stays in the failure diagnostics only. +- `data.bolts[]` now contains only complete right-paired bolts that measured + cleanly. This avoids misleading fake heights when the right YOLO detector + misses part of the expected bolt set. +- C ABI is unchanged: function signatures and struct layouts are unchanged. + +Performance / validation: +- RK3588, precision_153313_954, 7 full-resolution frames, expected=4: + every frame correctly returns `success=0`, `reason=count_mismatch`, + `L=4`, `R=2`, `pairs=4`, `data.n_bolts=2`. The two missing-right bolts are + not exported as heights. +- RK3588, distance_153304_403, 21 binned frames: + found=21/21, distance mean=1652.360 mm, min=1652.091, max=1652.582, + elapsed mean=156.493 ms. +- Windows ctest: 23/23 pass, including the right-eye-missing fallback guard. + ## 2026-07-01 v3 — stable OBB-axis ranging summary Current delivery contents: diff --git a/AppAlgo/stereo_bolt_delivery/DELIVERY.md b/AppAlgo/stereo_bolt_delivery/DELIVERY.md index d6c12e74..c5d54827 100644 --- a/AppAlgo/stereo_bolt_delivery/DELIVERY.md +++ b/AppAlgo/stereo_bolt_delivery/DELIVERY.md @@ -58,7 +58,7 @@ StereoBoltParamsC sb_default_params(void); /* 预填默认值,再覆盖你 |---|---| | `lib/libstereo_bolt.so` | 预编译共享库(aarch64,含 `sb_create_ex` 内存接口) | | `include/stereo_bolt/c_api.h` | C ABI 头文件(唯一对外接口) | -| `weights/best_obb_1024.rknn` | NPU 模型 —— OBB 检测(精测+测距共用,imgsz 1024,FP16) | +| `weights/best_obb_1024_0703_20260702.rknn` | NPU 模型 —— OBB 检测(精测+测距共用,imgsz 1024,FP16) | | `calib/stereo_calib_20260621_202857_ascii.xml` | 12MP 双目标定(**0621 装配 baseline 482mm**,Halcon 位姿已修正) | | `config/config.rk3588.12mp.yaml` | 仅 `sb_create`(兼容)用 + 作参数参考;**`sb_create_ex` 不需要它** | | `lib/*.so` | 算法库、OpenCV 4.5.0 运行库、RKNN 运行时(aarch64 2.3.2),运行时通过 `LD_LIBRARY_PATH=./lib` 加载 | @@ -130,8 +130,8 @@ void sb_free_bolt_module_result(StereoBoltModuleResultC* out); /* 用完必须 **坐标系/单位**:高度、间距、3D 坐标 = **mm**,**校正后左相机坐标系**(x 右 / y 下 / z=深度,典型 z≈1600–2200mm)。 -**★ 部分输出契约(去 all-or-nothing)**:即使 `success==0`(数量不符 `count_mismatch`,无论检到的多还是少于 `expected`), -`data.bolts[]` 仍含**所有干净测出的螺栓**(各带 height/top/foot/confidence);`failure`(reason + 完整 ROI + 配对状态)始终填充,能看出缺哪颗、为何缺。**别只看返回码就丢结果——照样遍历 `data.bolts[]`,按 `confidence` 自行取舍。** +**★ 部分输出契约(但不导出假高度)**:即使 `success==0`(数量不符 `count_mismatch`,无论检到的多还是少于 `expected`), +`data.bolts[]` 只包含**完整右目配对且测量干净的螺栓**(各带 height/top/foot/confidence)。`idx_R < 0` 的 left-only fallback 不作为产品高度输出,避免误导。`failure`(reason + 完整 ROI + 配对状态)始终填充,能看出缺哪颗、为何缺。 **最简用法(`sb_create_ex`)**: ```c @@ -200,7 +200,8 @@ sb_status_t sb_range_bolt_binned( | 板上吞吐@1024 | **约 400ms/帧** | **约 153ms/帧 ≈ 6.5Hz** | | 定位 | 触发式"测得准" | 无人机实时定位反馈 | -> 测距实测于 RK3588(best_obb_1024.rknn,distance_153304_403,21 帧):均值 153.146ms,距离均值 1652.360mm,跨度 0.491mm。 +> 测距历史基准于 RK3588(v4 旧模型 best_obb_1024.rknn,distance_153304_403,21 帧):均值 153.146ms,距离均值 1652.360mm,跨度 0.491mm。 +> v5(0703 模型)板上测距复核 0702-12mp:8/8 螺杆 status=ok,逐杆距离 1.5–2.2m 连贯;整帧 ~450–475ms。 > 本交付为单模型 OBB@1024(精测+测距共用)。 --- @@ -217,10 +218,10 @@ cmake --build build # -> bolt_demo(精测)+ range_demo(测距) # 精测:传标定 XML + 模型 + 左右图 + 期望螺栓数 LD_LIBRARY_PATH=../lib:$LD_LIBRARY_PATH \ ./build/bolt_demo ../calib/stereo_calib_20260621_202857_ascii.xml \ - ../weights/best_obb_1024.rknn L.bmp R.bmp 6 + ../weights/best_obb_1024_0703_20260702.rknn L.bmp R.bmp 6 # 测距:传标定 + 模型 + 左右图(demo 内部 2×2 binning;生产直接喂相机 binned buffer) LD_LIBRARY_PATH=../lib:$LD_LIBRARY_PATH \ ./build/range_demo ../calib/stereo_calib_20260621_202857_ascii.xml \ - ../weights/best_obb_1024.rknn L.bmp R.bmp + ../weights/best_obb_1024_0703_20260702.rknn L.bmp R.bmp ``` diff --git a/AppAlgo/stereo_bolt_delivery/INSTALL_3588.md b/AppAlgo/stereo_bolt_delivery/INSTALL_3588.md index 4e80f8a1..0b752d21 100644 --- a/AppAlgo/stereo_bolt_delivery/INSTALL_3588.md +++ b/AppAlgo/stereo_bolt_delivery/INSTALL_3588.md @@ -34,7 +34,7 @@ cd stereo_bolt_delivery stereo_bolt_delivery/ ├── lib/ ← 算法库 + OpenCV 4.5.0 + librknnrt(共 9 个 .so) ├── include/stereo_bolt/ ← c_api.h(唯一对外头文件) -├── weights/ ← best_obb_1024.rknn(NPU OBB 模型) +├── weights/ ← best_obb_1024_0703_20260702.rknn(NPU OBB 模型) ├── calib/ ← 出厂标定 XML(0621 装配,baseline 482mm) ├── config/ ← config.rk3588.12mp.yaml(兼容接口用) ├── example/ ← 最小 C 示例 + CMakeLists @@ -93,7 +93,7 @@ StereoBoltCtx* ctx = sb_create_ex(&calib, &model, &p); // 只调一次(慢, ``` > 兼容方式:`sb_create("config/config.rk3588.12mp.yaml")`(读文件,需工作目录正确)。 -> 模型:`weights/best_obb_1024.rknn`(精测 + 测距共用,OBB imgsz 1024)。 +> 模型:`weights/best_obb_1024_0703_20260702.rknn`(精测 + 测距共用,OBB imgsz 1024)。 > 标定:`calib/stereo_calib_20260621_202857_ascii.xml`(0621 装配,baseline 482 mm)。 ### 高度测量模式 diff --git a/AppAlgo/stereo_bolt_delivery/config/config.rk3588.yaml b/AppAlgo/stereo_bolt_delivery/config/config.rk3588.yaml index 8857b58c..a4ab9e16 100644 --- a/AppAlgo/stereo_bolt_delivery/config/config.rk3588.yaml +++ b/AppAlgo/stereo_bolt_delivery/config/config.rk3588.yaml @@ -145,6 +145,7 @@ bolt: measurement: top_extraction_method: "percentile_on_axis" top_percentile: 0.99 + top_morphology: "flat_top" height_from_plane: false plane_use_sgbm: true plane_sgbm_scale: 0.5 @@ -161,7 +162,7 @@ yolo: enabled: true model_path: "weights/best.rknn" backend: "rknn" - task: "auto" + task: "obb" imgsz: 1024 conf: 0.5 dead_zone_min_valid_pts: 50 diff --git a/AppAlgo/stereo_bolt_delivery/include/stereo_bolt/c_api.h b/AppAlgo/stereo_bolt_delivery/include/stereo_bolt/c_api.h index 5f8e8d22..0b76998c 100644 --- a/AppAlgo/stereo_bolt_delivery/include/stereo_bolt/c_api.h +++ b/AppAlgo/stereo_bolt_delivery/include/stereo_bolt/c_api.h @@ -108,11 +108,12 @@ typedef struct { typedef struct { int success; /* 1 == all `expected` bolts measured ok */ - /* Partial-result contract: `data.bolts` holds every bolt that measured cleanly - * (each with its own height/status/confidence) EVEN when success == 0 -- e.g. on - * count_mismatch you still get the bolts that did measure. `failure` is always - * populated too (reason + full left/right ROIs + per-pair status) so you can see - * which bolts are missing and why. When success == 1, failure.reason is empty. */ + /* Partial-result contract: `data.bolts` holds complete right-paired bolts that + * measured cleanly (each with its own height/status/confidence) EVEN when + * success == 0. Left-only fallback heights (idx_R < 0) are not exported as + * product measurements; `failure` is always populated too (reason + full + * left/right ROIs + per-pair status) so you can see which bolts are missing + * and why. When success == 1, failure.reason is empty. */ StereoBoltModuleSuccessC data; StereoBoltModuleFailureC failure; } StereoBoltModuleResultC; diff --git a/AppAlgo/stereo_bolt_delivery/lib/libstereo_bolt.so b/AppAlgo/stereo_bolt_delivery/lib/libstereo_bolt.so index 0949f766..046c0225 100644 Binary files a/AppAlgo/stereo_bolt_delivery/lib/libstereo_bolt.so and b/AppAlgo/stereo_bolt_delivery/lib/libstereo_bolt.so differ diff --git a/AppAlgo/stereo_bolt_delivery/weights/best.rknn b/AppAlgo/stereo_bolt_delivery/weights/best.rknn index 71e738c6..0eab129c 100644 Binary files a/AppAlgo/stereo_bolt_delivery/weights/best.rknn and b/AppAlgo/stereo_bolt_delivery/weights/best.rknn differ diff --git a/Module/WDRemoteReceiver/Inc/WDRemoteDataTypes.h b/Module/WDRemoteReceiver/Inc/WDRemoteDataTypes.h index b578f8bd..c542a27c 100644 --- a/Module/WDRemoteReceiver/Inc/WDRemoteDataTypes.h +++ b/Module/WDRemoteReceiver/Inc/WDRemoteDataTypes.h @@ -57,6 +57,13 @@ struct WDRemoteDetectionBox int height{0}; bool hasPhysicalHeight{false}; float physicalHeightMm{0.0f}; + bool hasStereoMatch{false}; + bool trusted{false}; + int matchConfidence{1}; + std::string matchStatus; + int pairId{-1}; + int leftIndex{-1}; + int rightIndex{-1}; }; /** @@ -79,6 +86,10 @@ struct WDRemoteDetectionFrame bool success{true}; int errorCode{0}; std::string message; + bool isFinalResult{false}; + double rankScore{0.0}; + int rankSampleCount{0}; + uint64_t saveIndex{0}; std::vector boxes; std::vector distances; }; @@ -92,8 +103,8 @@ struct WDRemoteAlgoParams float nmsThreshold{0.45f}; int inputWidth{640}; int inputHeight{640}; - std::string modelPath; int modelType{0}; // 0=YOLO, 1=自定义 + int expectedBoltCount{8}; }; struct WDRemoteMatrix @@ -177,6 +188,7 @@ struct WDRemoteServerInfo int algoHeight{640}; std::string algoModel; int algoModelType{0}; + int algoExpectedBoltCount{8}; WDRemoteCalibrationInfo calibration; std::string rawJson; // 原始 JSON 应答,方便扩展字段 }; diff --git a/Module/WDRemoteReceiver/Src/WDRemoteReceiver.cpp b/Module/WDRemoteReceiver/Src/WDRemoteReceiver.cpp index 625e44e8..336360ba 100644 --- a/Module/WDRemoteReceiver/Src/WDRemoteReceiver.cpp +++ b/Module/WDRemoteReceiver/Src/WDRemoteReceiver.cpp @@ -306,6 +306,7 @@ void fillRuntimeFields(const Json::Value& root, WDRemoteServerInfo& info) info.algoHeight = root.get("algoHeight", 640).asInt(); info.algoModel = root.get("algoModel", "").asString(); info.algoModelType = root.get("algoModelType", 0).asInt(); + info.algoExpectedBoltCount = root.get("algoExpectedBoltCount", 8).asInt(); fillCalibrationInfo(root, info.calibration); } @@ -829,6 +830,17 @@ int WDRemoteReceiver::StopWork(WDRemoteWorkMode mode, int timeoutMs) const int detectCode = responseErrorCode(detectResp); if (detectCode != 0) return detectCode; + WDRemoteDetectionFrame finalFrame; + if (parseDetectionJson(detectResp, finalFrame) && finalFrame.isFinalResult) + { + DetectionCallback cb; + { + std::lock_guard lk(m_mtxCallback); + cb = m_cbDetection; + } + if (cb) cb(finalFrame); + } + if (m_workState == WorkState::Detection) { StopSubscribeRawImage(); @@ -881,8 +893,8 @@ int WDRemoteReceiver::SetAlgoParams(const WDRemoteAlgoParams& params, int timeou req["nms"] = params.nmsThreshold; req["width"] = params.inputWidth; req["height"] = params.inputHeight; - req["model"] = params.modelPath; req["modelType"] = params.modelType; + req["expectedBoltCount"] = params.expectedBoltCount; Json::FastWriter w; std::string resp; int r = sendCommandAndParse(w.write(req), resp, timeoutMs); @@ -1279,6 +1291,11 @@ bool WDRemoteReceiver::parseDetectionJson(const std::string& jsonStr, outFrame.success = root.get("success", true).asBool(); outFrame.errorCode = root.get("errorCode", 0).asInt(); outFrame.message = root.get("msg", "").asString(); + outFrame.isFinalResult = root.get("isFinalResult", + root.get("resultFinal", false)).asBool(); + outFrame.rankScore = root.get("rankScore", 0.0).asDouble(); + outFrame.rankSampleCount = root.get("rankSampleCount", 0).asInt(); + outFrame.saveIndex = static_cast(root.get("saveIndex", 0).asInt64()); outFrame.boxes.clear(); outFrame.distances.clear(); @@ -1297,8 +1314,15 @@ bool WDRemoteReceiver::parseDetectionJson(const std::string& jsonStr, box.y = b.get("y", 0 ).asInt(); box.width = b.get("w", 0 ).asInt(); box.height = b.get("h", 0 ).asInt(); - box.hasPhysicalHeight = b.isMember("physicalHeightMm"); - box.physicalHeightMm = static_cast(b.get("physicalHeightMm", 0.0).asDouble()); + box.hasPhysicalHeight = b.isMember("height_mm"); + box.physicalHeightMm = static_cast(b.get("height_mm", 0.0).asDouble()); + box.hasStereoMatch = b.get("hasStereoMatch", false).asBool(); + box.trusted = b.get("trusted", false).asBool(); + box.matchConfidence = b.get("matchConfidence", 1).asInt(); + box.matchStatus = b.get("matchStatus", "").asString(); + box.pairId = b.get("pairId", -1).asInt(); + box.leftIndex = b.get("leftIndex", -1).asInt(); + box.rightIndex = b.get("rightIndex", -1).asInt(); outFrame.boxes.push_back(box); } }