diff --git a/App/ParkingSpaceGuide/Doc/ParkingSpaceGuide.md b/App/ParkingSpaceGuide/Doc/ParkingSpaceGuide.md index e3339b18..b1151df3 100644 --- a/App/ParkingSpaceGuide/Doc/ParkingSpaceGuide.md +++ b/App/ParkingSpaceGuide/Doc/ParkingSpaceGuide.md @@ -8,11 +8,22 @@ ParkingSpaceGuide 使用一台平面相机、一台雷达和一块显示屏完 - 雷达:`Device/RsLidarDevice` - 显示屏:`Device/LedDisplayDevice` - 组播:`Nets/UDPClient` +- 2D 机型识别:`AppAlgo/AAPGS_model`(RK3588 Linux AArch64) -算法入口当前预留在: +算法入口位于: `ParkingSpaceGuideApp/Presenter/Src/DetectPresenter.cpp` +机型识别由雷达算法按距离触发,相机软触发取图后调用 AAPGS 分类模型, +当前支持 `A320` 和 `B737`。应用默认从可执行文件同级的 +`AAPGS_model/models/model.yaml` 加载模型;也可通过环境变量覆盖: + +- `AAPGS_MODEL_CONFIG`:模型 YAML 的完整路径 +- `AAPGS_MODEL_ROOT`:AAPGS SDK 根目录 +- `AAPGS_RUNTIME_LIBRARY`:`libyolo_runtime.so.2` 的完整路径 + +Windows 构建保留界面与业务流程,但不执行仅支持 RK3588 的 NPU 推理。 + 文档: - 详细设计:`详细设计.md` diff --git a/App/ParkingSpaceGuide/ParkingSpaceGuideApp/ParkingSpaceGuideApp.pro b/App/ParkingSpaceGuide/ParkingSpaceGuideApp/ParkingSpaceGuideApp.pro index 8c513ffc..17e83de8 100644 --- a/App/ParkingSpaceGuide/ParkingSpaceGuideApp/ParkingSpaceGuideApp.pro +++ b/App/ParkingSpaceGuide/ParkingSpaceGuideApp/ParkingSpaceGuideApp.pro @@ -37,6 +37,9 @@ INCLUDEPATH += $$PWD/../../../Nets/UDPClient/Inc INCLUDEPATH += $$PWD/../../../Utils/VrCommon/Inc INCLUDEPATH += $$PWD/../../../Utils/VrUtils/Inc +AAPGS_ROOT = $$clean_path($$PWD/../../../AppAlgo/AAPGS_model) +INCLUDEPATH += $$AAPGS_ROOT/include + win32 { POCO_ROOT = $$PWD/../../../SDK/poco/Windows } else:unix:!macx { @@ -61,6 +64,7 @@ win32 { SOURCES += \ Presenter/Src/ConfigManager.cpp \ Presenter/Src/ParkingSpaceGuidePresenter.cpp \ + Presenter/Src/AapgsModelClassifier.cpp \ Presenter/Src/DetectPresenter.cpp \ Presenter/Src/ParkingStatusHttpServer.cpp \ main.cpp \ @@ -70,6 +74,7 @@ SOURCES += \ HEADERS += \ Presenter/Inc/ConfigManager.h \ Presenter/Inc/ParkingSpaceGuidePresenter.h \ + Presenter/Inc/AapgsModelClassifier.h \ Presenter/Inc/DetectPresenter.h \ Presenter/Inc/ParkingStatusHttpServer.h \ mainwindow.h \ @@ -156,6 +161,20 @@ win32 { unix { target.path = /usr/lib + + aapgsRuntime.path = /usr/lib/AAPGS_model/lib + aapgsRuntime.files = \ + $$AAPGS_ROOT/lib/libyolo_runtime.so.2 \ + $$AAPGS_ROOT/lib/libyolo_backend_cls.so \ + $$AAPGS_ROOT/lib/librknnrt.so \ + $$AAPGS_ROOT/lib/librga.so + + aapgsModels.path = /usr/lib/AAPGS_model/models + aapgsModels.files = \ + $$AAPGS_ROOT/models/model.yaml \ + $$AAPGS_ROOT/models/model.rknn + + INSTALLS += aapgsRuntime aapgsModels } !isEmpty(target.path): INSTALLS += target diff --git a/App/ParkingSpaceGuide/ParkingSpaceGuideApp/Presenter/Inc/DetectPresenter.h b/App/ParkingSpaceGuide/ParkingSpaceGuideApp/Presenter/Inc/DetectPresenter.h index f1265230..f71226a5 100644 --- a/App/ParkingSpaceGuide/ParkingSpaceGuideApp/Presenter/Inc/DetectPresenter.h +++ b/App/ParkingSpaceGuide/ParkingSpaceGuideApp/Presenter/Inc/DetectPresenter.h @@ -1,6 +1,8 @@ #ifndef PARKINGSPACEGUIDE_DETECTPRESENTER_H #define PARKINGSPACEGUIDE_DETECTPRESENTER_H +#include + #include #include #include @@ -8,6 +10,8 @@ #include "IRsLidarDevice.h" #include "IYParkingSpaceGuideStatus.h" +class AapgsModelClassifier; + enum class AirplanePresenceState { NotDetected, @@ -51,8 +55,11 @@ struct ModelRecognitionResult class DetectPresenter { public: - DetectPresenter() = default; - ~DetectPresenter() = default; + DetectPresenter(); + ~DetectPresenter(); + + DetectPresenter(const DetectPresenter&) = delete; + DetectPresenter& operator=(const DetectPresenter&) = delete; static QString GetAlgoVersion(); @@ -69,6 +76,9 @@ public: int RecognizeModel2D(const QImage& frame, const QByteArray& recognitionContext, ModelRecognitionResult& result); + +private: + std::unique_ptr m_modelClassifier; }; #endif // PARKINGSPACEGUIDE_DETECTPRESENTER_H diff --git a/App/ParkingSpaceGuide/ParkingSpaceGuideApp/Presenter/Src/DetectPresenter.cpp b/App/ParkingSpaceGuide/ParkingSpaceGuideApp/Presenter/Src/DetectPresenter.cpp index 015d4103..28b04f39 100644 --- a/App/ParkingSpaceGuide/ParkingSpaceGuideApp/Presenter/Src/DetectPresenter.cpp +++ b/App/ParkingSpaceGuide/ParkingSpaceGuideApp/Presenter/Src/DetectPresenter.cpp @@ -3,6 +3,15 @@ #include #include +#include "AapgsModelClassifier.h" + +DetectPresenter::DetectPresenter() + : m_modelClassifier(std::make_unique()) +{ +} + +DetectPresenter::~DetectPresenter() = default; + bool ModelRecognitionResult::IsVerified(double minimumConfidence) const { if (!std::isfinite(minimumConfidence)) { @@ -22,7 +31,7 @@ bool ModelRecognitionResult::IsVerified(double minimumConfidence) const QString DetectPresenter::GetAlgoVersion() { - return QStringLiteral("停车引导预留算法接口 版本0.1"); + return QStringLiteral("停机引导算法接口 0.1 / AAPGS_model 1.0.0"); } int DetectPresenter::DetectAirplanePresence(const RsCloudData& cloud, @@ -81,7 +90,7 @@ int DetectPresenter::DetectParkingSpaceGuide(const RsCloudData& cloud, info.guideStateCode = 0; info.guideText = QStringLiteral("预留算法接口"); result.parkingSpaceInfoList.push_back(info); - result.message = QStringLiteral("停车引导预留算法接口"); + result.message = QStringLiteral("停机引导预留算法接口"); const double modelVerifyDistance = algorithmParams.calibrationParam.modelVerifyDistance; const bool isInModelVerifyRange = modelVerifyDistance >= 0.0 && @@ -110,10 +119,25 @@ int DetectPresenter::RecognizeModel2D(const QImage& frame, const QByteArray& recognitionContext, ModelRecognitionResult& result) { - Q_UNUSED(frame); Q_UNUSED(recognitionContext); result = ModelRecognitionResult(); - result.message = QStringLiteral("机型识别算法接口未实现"); - return -1; + if (!m_modelClassifier) { + result.message = QStringLiteral("AAPGS机型识别器未初始化"); + return -1; + } + + AapgsModelClassifier::Classification classification; + QString errorMessage; + if (!m_modelClassifier->Classify(frame, classification, errorMessage)) { + result.message = errorMessage.trimmed().isEmpty() + ? QStringLiteral("AAPGS机型识别失败") + : errorMessage.trimmed(); + return -1; + } + + result.modelType = classification.modelType; + result.confidence = classification.confidence; + result.verified = true; + return 0; } diff --git a/App/ParkingSpaceGuide/ParkingSpaceGuideApp/Presenter/Src/ParkingSpaceGuidePresenter.cpp b/App/ParkingSpaceGuide/ParkingSpaceGuideApp/Presenter/Src/ParkingSpaceGuidePresenter.cpp index fc2fdb31..de2bb20e 100644 --- a/App/ParkingSpaceGuide/ParkingSpaceGuideApp/Presenter/Src/ParkingSpaceGuidePresenter.cpp +++ b/App/ParkingSpaceGuide/ParkingSpaceGuideApp/Presenter/Src/ParkingSpaceGuidePresenter.cpp @@ -137,7 +137,7 @@ int ParkingSpaceGuidePresenter::Init() { m_reconfiguring.store(false); NotifyWorkStatus(WorkStatus::InitIng); - NotifyStatus("停车引导初始化中"); + NotifyStatus("停机引导初始化中"); int ret = InitConfig(); if (ret != 0) { @@ -192,7 +192,7 @@ int ParkingSpaceGuidePresenter::Init() m_initialized = true; NotifyWorkStatus(WorkStatus::Ready); - NotifyStatus("停车引导初始化完成"); + NotifyStatus("停机引导初始化完成"); return 0; } @@ -229,7 +229,7 @@ bool ParkingSpaceGuidePresenter::StartDetection(int cameraIndex) } if (m_detectionRunning.load()) { - NotifyStatus("停车引导实时检测已在运行"); + NotifyStatus("停机引导实时检测已在运行"); return true; } @@ -259,12 +259,12 @@ bool ParkingSpaceGuidePresenter::StartDetection(int cameraIndex) m_detectionRunning.store(false); m_stopDetectionRequested.store(true); NotifyWorkStatus(WorkStatus::Error); - NotifyStatus("启动停车引导实时检测线程失败"); + NotifyStatus("启动停机引导实时检测线程失败"); return false; } NotifyWorkStatus(WorkStatus::Working); - NotifyStatus("停车引导实时检测已开始"); + NotifyStatus("停机引导实时检测已开始"); return true; } @@ -298,19 +298,19 @@ bool ParkingSpaceGuidePresenter::TriggerDetection(int cameraIndex) } NotifyWorkStatus(WorkStatus::Detecting); - NotifyStatus("停车引导单次检测已开始"); + NotifyStatus("停机引导单次检测已开始"); bool success = false; ParkingGuideAlgorithmState algorithmState; try { success = DetectOnce(cloudFrame->cloud, algorithmState); } catch (const std::exception& e) { - NotifyStatus(std::string("停车引导单次检测异常:") + e.what()); + NotifyStatus(std::string("停机引导单次检测异常:") + e.what()); } catch (...) { - NotifyStatus("停车引导单次检测发生未知异常"); + NotifyStatus("停机引导单次检测发生未知异常"); } NotifyWorkStatus(success ? WorkStatus::Completed : WorkStatus::Error); - NotifyStatus(success ? "停车引导单次检测完成" - : "停车引导单次检测失败"); + NotifyStatus(success ? "停机引导单次检测完成" + : "停机引导单次检测失败"); return success; } @@ -335,11 +335,11 @@ bool ParkingSpaceGuidePresenter::DetectOnce(const RsCloudData& cloud, if (ret != 0) { result.errorCode = ret; if (result.message.isEmpty()) { - result.message = QStringLiteral("停车引导检测失败"); + result.message = QStringLiteral("停机引导检测失败"); } } else { if (result.parkingSpaceInfoList.empty() && result.message.isEmpty()) { - result.message = QStringLiteral("无停车引导结果"); + result.message = QStringLiteral("无停机引导结果"); } if (control.needModelRecognition) { @@ -385,9 +385,17 @@ bool ParkingSpaceGuidePresenter::DetectOnce(const RsCloudData& cloud, info.confidence = control.nextState.modelConfidence; } } else { - const QString reason = modelResult.message.trimmed().isEmpty() - ? QStringLiteral("机型识别结果未通过验证") - : modelResult.message; + QString reason = modelResult.message.trimmed(); + if (reason.isEmpty() && modelRet == 0) { + reason = QStringLiteral( + "机型识别置信度不足:%1,实际%2,阈值%3") + .arg(modelResult.modelType) + .arg(modelResult.confidence, 0, 'f', 3) + .arg(minimumConfidence, 0, 'f', 3); + } + if (reason.isEmpty()) { + reason = QStringLiteral("机型识别结果未通过验证"); + } appendRecognitionFailure(reason); } } @@ -608,10 +616,10 @@ void ParkingSpaceGuidePresenter::DetectionLoop() DetectOnce(cloudFrame->cloud, algorithmState); } catch (const std::exception& e) { PublishErrorResult(-1, - QStringLiteral("停车引导检测异常:%1") + QStringLiteral("停机引导检测异常:%1") .arg(QString::fromUtf8(e.what()))); } catch (...) { - PublishErrorResult(-1, QStringLiteral("停车引导检测发生未知异常")); + PublishErrorResult(-1, QStringLiteral("停机引导检测发生未知异常")); } } @@ -639,7 +647,7 @@ int ParkingSpaceGuidePresenter::StopDetection() if (wasRunning) { NotifyWorkStatus(WorkStatus::Ready); - NotifyStatus("停车引导实时检测已停止"); + NotifyStatus("停机引导实时检测已停止"); } return 0; } diff --git a/App/ParkingSpaceGuide/ParkingSpaceGuideApp/Version.h b/App/ParkingSpaceGuide/ParkingSpaceGuideApp/Version.h index b3633afc..7b4dd8a9 100644 --- a/App/ParkingSpaceGuide/ParkingSpaceGuideApp/Version.h +++ b/App/ParkingSpaceGuide/ParkingSpaceGuideApp/Version.h @@ -1,7 +1,7 @@ #ifndef PARKINGSPACEGUIDE_VERSION_H #define PARKINGSPACEGUIDE_VERSION_H -#define PARKINGSPACEGUIDE_APP_NAME "停车引导" +#define PARKINGSPACEGUIDE_APP_NAME "停机引导" #define PARKINGSPACEGUIDE_VERSION_STRING "0.1.0" #define PARKINGSPACEGUIDE_BUILD_STRING "1" #define PARKINGSPACEGUIDE_FULL_VERSION_STRING "版本" PARKINGSPACEGUIDE_VERSION_STRING "_" PARKINGSPACEGUIDE_BUILD_STRING diff --git a/App/ParkingSpaceGuide/ParkingSpaceGuideApp/dialogalgoarg.cpp b/App/ParkingSpaceGuide/ParkingSpaceGuideApp/dialogalgoarg.cpp index d822fbc5..35c3b4ac 100644 --- a/App/ParkingSpaceGuide/ParkingSpaceGuideApp/dialogalgoarg.cpp +++ b/App/ParkingSpaceGuide/ParkingSpaceGuideApp/dialogalgoarg.cpp @@ -23,9 +23,9 @@ DialogAlgoArg::DialogAlgoArg(QWidget* parent) , ui(new Ui::DialogAlgoArg) { ui->setupUi(this); - setWindowTitle(QStringLiteral("停车引导参数设置")); + setWindowTitle(QStringLiteral("停机引导参数设置")); if (ui->label_title) { - ui->label_title->setText(QStringLiteral("停车引导参数设置")); + ui->label_title->setText(QStringLiteral("停机引导参数设置")); } BuildUi(); connect(ui->tabWidget, &QTabWidget::currentChanged, this, [this](int index) { @@ -101,7 +101,7 @@ void DialogAlgoArg::ShowTabGroup(ConfigPage page) void DialogAlgoArg::UpdateTitle(ConfigPage page) { - QString title = QStringLiteral("停车引导参数设置"); + QString title = QStringLiteral("停机引导参数设置"); switch (page) { case ConfigPage::Detection: title = QStringLiteral("检测参数设置"); diff --git a/App/ParkingSpaceGuide/ParkingSpaceGuideApp/dialogalgoarg.ui b/App/ParkingSpaceGuide/ParkingSpaceGuideApp/dialogalgoarg.ui index 3eb78411..e2ca44ab 100644 --- a/App/ParkingSpaceGuide/ParkingSpaceGuideApp/dialogalgoarg.ui +++ b/App/ParkingSpaceGuide/ParkingSpaceGuideApp/dialogalgoarg.ui @@ -35,7 +35,7 @@ color: rgb(221, 225, 233); - 停车引导参数设置 + 停机引导参数设置 diff --git a/App/ParkingSpaceGuide/ParkingSpaceGuideApp/main.cpp b/App/ParkingSpaceGuide/ParkingSpaceGuideApp/main.cpp index a7c15dac..7ddef2c6 100644 --- a/App/ParkingSpaceGuide/ParkingSpaceGuideApp/main.cpp +++ b/App/ParkingSpaceGuide/ParkingSpaceGuideApp/main.cpp @@ -17,7 +17,7 @@ int main(int argc, char* argv[]) if (instanceManager.isAnotherInstanceRunning()) { QMessageBox messageBox; messageBox.setWindowTitle(QObject::tr("应用已运行")); - messageBox.setText(QObject::tr("停车引导程序已在运行。")); + messageBox.setText(QObject::tr("停机引导程序已在运行。")); messageBox.addButton(QObject::tr("确定"), QMessageBox::AcceptRole); messageBox.exec(); return 0; diff --git a/App/ParkingSpaceGuide/ParkingSpaceGuideApp/mainwindow.cpp b/App/ParkingSpaceGuide/ParkingSpaceGuideApp/mainwindow.cpp index be80a9f8..605a295b 100644 --- a/App/ParkingSpaceGuide/ParkingSpaceGuideApp/mainwindow.cpp +++ b/App/ParkingSpaceGuide/ParkingSpaceGuideApp/mainwindow.cpp @@ -8,7 +8,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -107,6 +109,39 @@ QString FormatNumber(double value, const QString& unit, int precision = 2) return QStringLiteral("%1%2").arg(value, 0, 'f', precision).arg(unit); } +void ConfigureResultWidget(SingleTargetResultWidget* resultWidget) +{ + if (!resultWidget) { + return; + } + + // Keep this presentation local to ParkingSpaceGuide; UICommon defaults are shared by other apps. + auto* mainLayout = qobject_cast(resultWidget->layout()); + if (mainLayout && mainLayout->count() > 0) { + auto* headerLayout = qobject_cast(mainLayout->itemAt(0)->layout()); + if (headerLayout && headerLayout->count() == 2) { + headerLayout->setSpacing(12); + headerLayout->setStretch(0, 0); + headerLayout->setStretch(1, 0); + headerLayout->addStretch(1); + } + } + + resultWidget->setStyleSheet( + resultWidget->styleSheet() + + QStringLiteral( + "#singleTargetTitle { font-size: 28px; }" + "#singleTargetStatus { font-size: 34px; font-weight: 700; padding: 0; }" + "#singleTargetEmpty { font-size: 28px; }" + "#singleTargetFieldName { font-size: 24px; }" + "#singleTargetFieldValue { font-size: 26px; }")); + + auto* statusLabel = resultWidget->findChild(QStringLiteral("singleTargetStatus")); + if (statusLabel) { + statusLabel->setStyleSheet(QStringLiteral("background-color: transparent;")); + } +} + } // namespace MainWindow::MainWindow(QWidget* parent) @@ -114,9 +149,9 @@ MainWindow::MainWindow(QWidget* parent) , ui(new Ui::MainWindow) { ui->setupUi(this); - setWindowTitle(QStringLiteral("停车引导")); + setWindowTitle(QStringLiteral("停机引导")); if (ui->label) { - ui->label->setText(QStringLiteral("停车引导")); + ui->label->setText(QStringLiteral("停机引导")); } #ifdef _WIN32 @@ -288,6 +323,7 @@ void MainWindow::addDetectionResult(const DetectionResult& result) resultWidget->setTitle(tr("当前状态")); resultWidget->setStatus(GuideStateName(ParkingGuideState::Unknown), false); resultWidget->setEmptyText(tr("暂无停机状态结果")); + ConfigureResultWidget(resultWidget); return; } @@ -305,6 +341,7 @@ void MainWindow::addDetectionResult(const DetectionResult& result) {tr("偏转角"), FormatNumber(info.angle, tr("度"))}, {tr("速度"), FormatNumber(info.aircraftSpeed, tr("米/秒"), 2)} }); + ConfigureResultWidget(resultWidget); } void MainWindow::OnStatusUpdate(const std::string& statusMessage) @@ -452,7 +489,7 @@ void MainWindow::on_btn_start_clicked() } clearDetectionLog(); - updateStatusLog(tr("开始停车引导检测")); + updateStatusLog(tr("开始停机引导检测")); if (!m_presenter->StartDetection(-1)) { updateStatusLog(tr("启动检测失败")); } @@ -686,7 +723,7 @@ void MainWindow::onSaveDetectionData() } const std::string timeStamp = CVrDateUtils::GetNowTime(); - const QString fileName = QString("停车引导_%1_%2.txt") + const QString fileName = QString("停机引导_%1_%2.txt") .arg(m_presenter->GetDetectIndex()) .arg(QString::fromStdString(timeStamp)); const QString fullPath = QDir(dirPath).filePath(fileName); diff --git a/App/ParkingSpaceGuide/ParkingSpaceGuideApp/mainwindow.ui b/App/ParkingSpaceGuide/ParkingSpaceGuideApp/mainwindow.ui index c382aeae..d5414ff1 100644 --- a/App/ParkingSpaceGuide/ParkingSpaceGuideApp/mainwindow.ui +++ b/App/ParkingSpaceGuide/ParkingSpaceGuideApp/mainwindow.ui @@ -11,7 +11,7 @@ - 停车引导 + 停机引导 false @@ -75,7 +75,7 @@ color: rgb(239, 241, 245); background-color: rgba(255, 255, 255, 0); - 停车引导 + 停机引导 diff --git a/App/WorkpieceHole/Doc/point.jpg b/App/WorkpieceHole/Doc/point.jpg deleted file mode 100644 index 7b280a58..00000000 Binary files a/App/WorkpieceHole/Doc/point.jpg and /dev/null differ diff --git a/App/WorkpieceHole/Doc/workpiecehole_plc.mthings b/App/WorkpieceHole/Doc/workpiecehole_plc.mthings index d2a2ee24..c8be97b5 100644 --- a/App/WorkpieceHole/Doc/workpiecehole_plc.mthings +++ b/App/WorkpieceHole/Doc/workpiecehole_plc.mthings @@ -5,6 +5,7 @@ + @@ -13,7 +14,7 @@ - + diff --git a/App/WorkpieceHole/Doc/协议.jpg b/App/WorkpieceHole/Doc/协议.jpg deleted file mode 100644 index fe9017a9..00000000 Binary files a/App/WorkpieceHole/Doc/协议.jpg and /dev/null differ diff --git a/App/WorkpieceHole/Doc/工件孔检测ModbusTCP通信协议.md b/App/WorkpieceHole/Doc/工件孔检测ModbusTCP通信协议.md index 9cb82c05..08c87e3f 100644 --- a/App/WorkpieceHole/Doc/工件孔检测ModbusTCP通信协议.md +++ b/App/WorkpieceHole/Doc/工件孔检测ModbusTCP通信协议.md @@ -37,34 +37,36 @@ | PLC寄存器 | 代码协议地址 | 类型 | 读/写 | 说明 | |-----------|------------|------|-------|------| -| D1000 | 1001 | uint16 | R/W | 产品型号/相机触发值(见下表) | -| D1002 | 1003 | uint16 | W | 检测结果状态码(见下表) | - -### 4.2 产品型号与拍照触发值(D1000) - -| 序号 | 产品型号 | 触发相机1 | 触发相机2 | 当前状态 | -|------|----------|-----------|-----------|----------| -| 1 | 4WE6 | 1 | 2 | 已实现 | -| 2 | Z2S6 | 101 | 102 | 已实现 | -| 3 | Z2FS6_30_2 | 201 | 202 | 已实现 | -| 4 | DB10 | 301 | 302 | 已实现 | -| 5 | DB20 | 401 | 402 | 已实现 | - -五种工件分别读取各自独立配置的检测参数。 - -### 4.3 检测结果状态码(D1002) +| D1000 | 1001 | uint16 | R/W | 产品型号/相机触发值(见下表) | +| D1002 | 1003 | uint16 | W | 检测结果状态码(见下表) | + +### 4.2 产品型号与拍照触发值(D1000) + +|序号| 产品型号 | 算法接口 | 触发相机1 | 触发相机2 | 高度 | +|----|------|--------------|-----------|-----------|------| +| 1 | 4WE6 | `API_1` | 1 | 2 | 47 | +| 2 | Z2S6 | `API_2` | 101 | 102 | 47 | +| 3 | Z2FS6_30_2 | `API_2` | 201 | 202 | 28 | +| 4 | DB10 | `API_3` | 301 | 302 | 60 | +| 5 | DB20 | `API_3` | 401 | 402 | 60 | + +> **编号说明**:产品序号即软件工件编号,配置、界面、触发协议和算法分派统一使用 1-5,不再转换为算法 test 的历史工件编号。`API_1`、`API_2`、`API_3` 分别对应三套工件孔定位接口。 + +五种工件分别读取其软件工件编号对应的独立检测参数和工具参数。 + +### 4.3 检测结果状态码(D1002) | 状态码 | 含义 | 说明 | |--------|------|------| -| 1 | 检测成功 | 检测正确完成且有结果,坐标数据已写入D2000 | -| 2 | 异常物料 | 算法返回异常物料错误码(`SX_ERR_UNKNOWN_OBJECT = -2501`) | -| 3 | 无产品 | 算法返回无产品错误码(`SX_ERR_ZERO_OBJECTS = -1010`,兼容空目标类错误) | -| 11 | 检测失败 | 其他算法或系统异常 | -| 12 | 相机失败 | 相机打开或连接失败 | +| 1 | 检测成功 | 检测正确完成且有结果,坐标数据已写入D2000 | +| 2 | 异常物料 | 算法返回异常物料错误码(`SX_ERR_UNKNOWN_OBJECT = -2501`) | +| 3 | 无产品 | 算法返回无产品错误码(`SX_ERR_ZERO_OBJECTS = -1010`,兼容空目标类错误) | +| 11 | 检测失败 | 其他算法或系统异常 | +| 12 | 相机失败 | 相机打开或连接失败 | -### 4.4 坐标数据寄存器(D2000起,1个点) +### 4.4 坐标数据寄存器(D2000起,1个点) -检测成功时,将最后一个工件的坐标写入 D2000-D2011,包含6个float值(X, Y, Z, Pitch, Roll, Yaw),每个float占用2个寄存器(大端序),共12个寄存器。 +检测成功时,将最后一个工件的坐标写入 D2000-D2011,包含6个float值(X, Y, Z, Pitch, Roll, Yaw),每个float占用2个寄存器(大端序),共12个寄存器。 | PLC寄存器 | 代码协议地址 | 类型 | 说明 | |-----------|------------|------|------| @@ -78,7 +80,7 @@ > **注意**: > - 每个float值占用2个寄存器,采用IEEE 754单精度浮点数格式 > - 大端序(Big-Endian):高位字在低地址寄存器,低位字在高地址寄存器 -> - 仅写入最后一个工件的坐标数据 +> - 仅写入最后一个工件的坐标数据 ## 5. 通信流程 @@ -90,9 +92,9 @@ │ 轮询读取D1000(地址1001) │ │◄─────────────────────────────│ │ │ - │ D1000=触发值 (拍照请求) │ - │◄─────────────────────────────│ - │ (按产品型号/相机触发表解码) │ + │ D1000=触发值 (拍照请求) │ + │◄─────────────────────────────│ + │ (按产品型号/相机触发表解码) │ │ │ │ 写0到D1000(地址1001) │ │─────────────────────────────►│ @@ -103,7 +105,7 @@ │─────────────────────────────►│ (仅检测成功时) │ │ │ 写状态码到D1002(地址1003) │ - │─────────────────────────────►│ (1/2/3/11/12) + │─────────────────────────────►│ (1/2/3/11/12) │ │ ``` @@ -113,12 +115,12 @@ - 3D系统持续轮询读取PLC的D1000寄存器(代码协议地址1001) - 轮询间隔:100ms(可配置) - 使用边沿检测:只在0→非零变化时触发 - - 根据4.2节触发表同时确定产品型号和相机 + - 根据4.2节触发表同时确定产品型号和相机 2. **拍照执行** - 检测到D1000为非零后,立即写0到D1000清除请求 - - 暂停轮询,根据D1000的值选择对应产品和相机 - - 触发相机拍照,执行该产品对应的工件孔定位检测 + - 暂停轮询,根据D1000的值选择对应产品和相机 + - 触发相机拍照,执行该产品对应的工件孔定位检测 3. **坐标输出**(仅检测成功时) - 算法处理完成后,将坐标数据写入PLC的D2000开始的寄存器 @@ -127,7 +129,7 @@ 4. **结果通知** - 无论成功还是失败,均写状态码到D1002(代码协议地址1003) - - 状态码:1=成功有结果,2=异常物料,3=无产品,11=检测失败,12=相机失败 + - 状态码:1=成功有结果,2=异常物料,3=无产品,11=检测失败,12=相机失败 - 写入状态码后恢复拍照请求轮询 ## 6. 连接管理 @@ -150,12 +152,12 @@ | 错误类型 | 处理方式 | D1002状态码 | |----------|----------|------------| -| 相机连接失败 | 通知PLC,恢复轮询 | 12 | -| 异常物料(`SX_ERR_UNKNOWN_OBJECT = -2501`) | 通知PLC,恢复轮询 | 2 | -| 无产品(`SX_ERR_ZERO_OBJECTS = -1010`,兼容空目标类错误) | 通知PLC,恢复轮询 | 3 | -| 其他算法检测失败 | 通知PLC,恢复轮询 | 11 | -| D1000触发值无效 | 清除请求,通知PLC,恢复轮询 | 11 | -| 检测成功有结果 | 发送坐标,通知PLC,恢复轮询 | 1 | +| 相机连接失败 | 通知PLC,恢复轮询 | 12 | +| 异常物料(`SX_ERR_UNKNOWN_OBJECT = -2501`) | 通知PLC,恢复轮询 | 2 | +| 无产品(`SX_ERR_ZERO_OBJECTS = -1010`,兼容空目标类错误) | 通知PLC,恢复轮询 | 3 | +| 其他算法检测失败 | 通知PLC,恢复轮询 | 11 | +| D1000触发值无效 | 清除请求,通知PLC,恢复轮询 | 11 | +| 检测成功有结果 | 发送坐标,通知PLC,恢复轮询 | 1 | | 连接断开 | 自动重连,发送错误信号 | - | | 读取失败 | 记录日志,主动断开触发重连 | - | | 写入失败 | 记录日志,返回失败状态 | - | diff --git a/App/WorkpieceHole/WorkpieceHoleApp/IYWorkpieceHoleStatus.h b/App/WorkpieceHole/WorkpieceHoleApp/IYWorkpieceHoleStatus.h index fe82a361..3399c30a 100644 --- a/App/WorkpieceHole/WorkpieceHoleApp/IYWorkpieceHoleStatus.h +++ b/App/WorkpieceHole/WorkpieceHoleApp/IYWorkpieceHoleStatus.h @@ -4,14 +4,24 @@ #include #include #include +#include #include #include // 使用 AppCommon 提供的通用接口和类型 #include "IVisionApplicationStatus.h" +// 算法 workpieceType 的高16位表示方向:1=反向,0=正向。 +inline bool IsReversedWorkpieceType(int workpieceType) +{ + const std::uint32_t packedType = static_cast(workpieceType); + return ((packedType >> 16) & 0xFFFFu) == 1u; +} + // 孔位置结构体 struct HolePosition : public PositionData { + int workpieceType = 0; // 算法原始工件类型:高16位为方向,1=反向,0=正向 + // 默认构造函数 HolePosition() : PositionData() {} @@ -30,7 +40,7 @@ struct HolePosition : public PositionData { // 工件孔检测结果结构体 struct WorkpieceHoleDetectionResult : public DetectionResultData { - int workpieceType = 0; // 工件类型 + int workpieceType = 0; // 第一个工件的算法原始类型(包含高16位方向) // 默认构造函数 WorkpieceHoleDetectionResult() = default; diff --git a/App/WorkpieceHole/WorkpieceHoleApp/Presenter/Inc/DetectPresenter.h b/App/WorkpieceHole/WorkpieceHoleApp/Presenter/Inc/DetectPresenter.h index f91c2a44..ba112ea7 100644 --- a/App/WorkpieceHole/WorkpieceHoleApp/Presenter/Inc/DetectPresenter.h +++ b/App/WorkpieceHole/WorkpieceHoleApp/Presenter/Inc/DetectPresenter.h @@ -33,7 +33,6 @@ public: const VrDebugParam& debugParam, LaserDataLoader& dataLoader, const double clibMatrix[16], - int eulerOrder, int dirVectorInvert, int poseOutputOrder, WorkpieceHoleDetectionResult& detectionResult); diff --git a/App/WorkpieceHole/WorkpieceHoleApp/Presenter/Inc/WorkpieceProduct.h b/App/WorkpieceHole/WorkpieceHoleApp/Presenter/Inc/WorkpieceProduct.h index 5ceafd12..726949dd 100644 --- a/App/WorkpieceHole/WorkpieceHoleApp/Presenter/Inc/WorkpieceProduct.h +++ b/App/WorkpieceHole/WorkpieceHoleApp/Presenter/Inc/WorkpieceProduct.h @@ -4,21 +4,30 @@ namespace WorkpieceProduct { +enum class AlgorithmApi +{ + Api1, + Api2, + Api3 +}; + struct ProductInfo { int number; const char* model; bool detectionSupported; + AlgorithmApi algorithmApi; + int triggerBase; // D1000 触发值 = triggerBase + cameraIndex }; inline const ProductInfo* Products() { static const ProductInfo products[] = { - {1, "4WE6", true}, - {2, "Z2S6", true}, - {3, "Z2FS6_30_2", true}, - {4, "DB10", true}, - {5, "DB20", true} + {1, "4WE6", true, AlgorithmApi::Api1, 0}, + {2, "Z2S6", true, AlgorithmApi::Api2, 100}, + {3, "Z2FS6_30_2", true, AlgorithmApi::Api2, 200}, + {4, "DB10", true, AlgorithmApi::Api3, 300}, + {5, "DB20", true, AlgorithmApi::Api3, 400} }; return products; } @@ -41,7 +50,11 @@ inline const ProductInfo* Find(int workpieceNumber) inline int TriggerValue(int workpieceNumber, int cameraIndex) { - return (workpieceNumber - 1) * 100 + cameraIndex; + const ProductInfo* product = Find(workpieceNumber); + if (!product || cameraIndex < 1 || cameraIndex > 2) { + return -1; + } + return product->triggerBase + cameraIndex; } inline bool DecodeTriggerValue(int triggerValue, int& workpieceNumber, int& cameraIndex) diff --git a/App/WorkpieceHole/WorkpieceHoleApp/Presenter/Src/DetectPresenter.cpp b/App/WorkpieceHole/WorkpieceHoleApp/Presenter/Src/DetectPresenter.cpp index 54df8082..2ebdcd7a 100644 --- a/App/WorkpieceHole/WorkpieceHoleApp/Presenter/Src/DetectPresenter.cpp +++ b/App/WorkpieceHole/WorkpieceHoleApp/Presenter/Src/DetectPresenter.cpp @@ -1,13 +1,57 @@ #include "DetectPresenter.h" #include "workpieceHolePositioning_Export.h" #include "SG_baseAlgo_Export.h" +#include "SG_errCode.h" #include "AlgoParamConverter.h" #include "IHandEyeCalib.h" +#include "WorkpieceProduct.h" +#include #include #include namespace { +HECEulerOrder ToHandEyeEulerOrder(int eulerOrder) +{ + switch (eulerOrder) { + case 10: return HECEulerOrder::XYZ; + case 11: return HECEulerOrder::ZYX; + case 12: return HECEulerOrder::ZXY; + case 13: return HECEulerOrder::YXZ; + case 14: return HECEulerOrder::YZX; + case 15: return HECEulerOrder::XZY; + default: + LOG_WARNING("Unsupported tool euler order %d, fallback to 11 (ZYX)\n", eulerOrder); + return HECEulerOrder::ZYX; + } +} + +void ApplyToolRotationToEyeAxes(IHandEyeCalib& handEyeCalib, + HECEulerOrder eulerOrder, + const VrToolParam& toolParam, + std::vector& axes) +{ + if (axes.size() != 3 || + (std::fabs(toolParam.rotX) < 1e-9 && + std::fabs(toolParam.rotY) < 1e-9 && + std::fabs(toolParam.rotZ) < 1e-9)) { + return; + } + + HECRotationMatrix toolRotation; + handEyeCalib.EulerToRotationMatrix( + HECEulerAngles::fromDegrees(toolParam.rotX, toolParam.rotY, toolParam.rotZ), + eulerOrder, + toolRotation); + + const std::vector originalAxes = axes; + for (int column = 0; column < 3; ++column) { + axes[column] = (originalAxes[0] * toolRotation.at(0, column) + + originalAxes[1] * toolRotation.at(1, column) + + originalAxes[2] * toolRotation.at(2, column)).normalized(); + } +} + bool HasValidZPoint(const std::vector& line) { for (const SVzNL3DPosition& point : line) { @@ -64,7 +108,6 @@ int DetectPresenter::DetectWorkpieceHole( const VrDebugParam& debugParam, LaserDataLoader& dataLoader, const double clibMatrix[16], - int eulerOrder, int dirVectorInvert, int poseOutputOrder, WorkpieceHoleDetectionResult& detectionResult) @@ -80,6 +123,8 @@ int DetectPresenter::DetectWorkpieceHole( LOG_ERROR("Algorithm profile is missing for workpiece %d\n", workpieceNumber); return ERR_CODE(DEV_CONFIG_ERR); } + const VrToolParam& toolParam = profile->toolParam; + const HECEulerOrder toolEulerOrder = ToHandEyeEulerOrder(toolParam.eulerOrder); // 获取当前相机的校准参数 VrCameraPlaneCalibParam cameraCalibParamValue; @@ -136,6 +181,10 @@ int DetectPresenter::DetectWorkpieceHole( growParam.maxLineSkipNum, growParam.yDeviation_max, growParam.maxSkipDistance, growParam.zDeviation_max, growParam.minLTypeTreeLen, growParam.minVTypeTreeLen); + LOG_INFO("Workpiece %d tool params: eulerOrder=%d, rot=(%.3f,%.3f,%.3f), offset=(%.3f,%.3f,%.3f)\n", + workpieceNumber, toolParam.eulerOrder, + toolParam.rotX, toolParam.rotY, toolParam.rotZ, + toolParam.offsetX, toolParam.offsetY, toolParam.offsetZ); // 准备平面校准参数 SSG_planeCalibPara groundCalibPara = AlgoParamConverter::ToAlgoPlaneCalibParam(cameraCalibParam); @@ -162,22 +211,26 @@ int DetectPresenter::DetectWorkpieceHole( std::vector workpiecePositioning; const char* algorithmName = nullptr; - switch (workpieceNumber) { - case 1: + const WorkpieceProduct::ProductInfo* product = WorkpieceProduct::Find(workpieceNumber); + if (!product || !product->detectionSupported) { + LOG_ERROR("No product or detection algorithm for software workpiece %d\n", workpieceNumber); + return ERR_CODE(FUN_UNSUPPORT); + } + + switch (product->algorithmApi) { + case WorkpieceProduct::AlgorithmApi::Api1: algorithmName = "wd_workpieceHolePositioning"; wd_workpieceHolePositioning( algorithmData, workpiecePara, lineSegPara, filterParam, growParam, groundCalibPara, workpiecePositioning, &errCode); break; - case 2: - case 5: + case WorkpieceProduct::AlgorithmApi::Api2: algorithmName = "wd_workpieceHolePositioning_2"; wd_workpieceHolePositioning_2( algorithmData, workpiecePara, lineSegPara, filterParam, growParam, groundCalibPara, workpiecePositioning, &errCode); break; - case 3: - case 4: + case WorkpieceProduct::AlgorithmApi::Api3: algorithmName = "wd_workpieceHolePositioning_3"; wd_workpieceHolePositioning_3( algorithmData, workpiecePara, workpieceParam.bigHoleDiameter, @@ -185,7 +238,7 @@ int DetectPresenter::DetectWorkpieceHole( workpiecePositioning, &errCode); break; default: - LOG_ERROR("No detection algorithm for workpiece %d\n", workpieceNumber); + LOG_ERROR("Unsupported algorithm API for software workpiece %d\n", workpieceNumber); return ERR_CODE(FUN_UNSUPPORT); } @@ -213,6 +266,12 @@ int DetectPresenter::DetectWorkpieceHole( if (i == 0) { detectionResult.workpieceType = workpiece.workpieceType; } + if (IsReversedWorkpieceType(workpiece.workpieceType)) { + // 算法执行成功,但反向工件属于异常物料;保留结果继续生成可视化图片。 + detectionResult.errorCode = SX_ERR_UNKNOWN_OBJECT; + LOG_WARNING("Detected reversed workpiece %zu (workpieceType=%d)\n", + i + 1, workpiece.workpieceType); + } // 六轴转换:位置+姿态(转换到机械臂坐标系) HECPoint3D eyeCenter(workpiece.center.x, workpiece.center.y, workpiece.center.z); @@ -242,6 +301,9 @@ int DetectPresenter::DetectWorkpieceHole( break; } + // 工具姿态补偿在 Eye 坐标系内合成,再进入手眼旋转链。 + ApplyToolRotationToEyeAxes(*handEyeCalib, toolEulerOrder, toolParam, dirVectors_eye); + // 1. 位置转换:R * P_eye + T HECPoint3D ptRobot; handEyeCalib->TransformPoint(calibResult.R, calibResult.T, eyeCenter, ptRobot); @@ -264,15 +326,17 @@ int DetectPresenter::DetectWorkpieceHole( R_pose[2][1] = dirVectors_robot[1].z; R_pose[2][2] = dirVectors_robot[2].z; - // 4. 使用算法库的 rotationMatrixToEulerZYX 转换为欧拉角(返回角度) - SSG_EulerAngles robotRpy = rotationMatrixToEulerZYX(R_pose); + // 4. 保持既有 ZYX 输出分解约定,确保零工具补偿时输出完全兼容。 + // toolParam.eulerOrder 只控制上面的工具补偿旋转合成顺序。 + const SSG_EulerAngles robotRpy = rotationMatrixToEulerZYX(R_pose); // 将机器人坐标系下的位姿添加到positions列表 // 根据 poseOutputOrder 重映射欧拉角输出顺序 HolePosition centerRobotPos; - centerRobotPos.x = ptRobot.x; - centerRobotPos.y = ptRobot.y; - centerRobotPos.z = ptRobot.z; + centerRobotPos.workpieceType = workpiece.workpieceType; + centerRobotPos.x = ptRobot.x + toolParam.offsetX; + centerRobotPos.y = ptRobot.y + toolParam.offsetY; + centerRobotPos.z = ptRobot.z + toolParam.offsetZ; switch (poseOutputOrder) { case POSE_ORDER_RPY: centerRobotPos.roll = robotRpy.roll; @@ -315,9 +379,9 @@ int DetectPresenter::DetectWorkpieceHole( if(debugParam.enableDebug && debugParam.printDetailLog){ LOG_INFO("[Algo Thread] Direction vectors (eye): X=(%.3f,%.3f,%.3f), Y=(%.3f,%.3f,%.3f), Z=(%.3f,%.3f,%.3f)\n", - workpiece.x_dir.x, workpiece.x_dir.y, workpiece.x_dir.z, - workpiece.y_dir.x, workpiece.y_dir.y, workpiece.y_dir.z, - workpiece.z_dir.x, workpiece.z_dir.y, workpiece.z_dir.z); + dirVectors_eye[0].x, dirVectors_eye[0].y, dirVectors_eye[0].z, + dirVectors_eye[1].x, dirVectors_eye[1].y, dirVectors_eye[1].z, + dirVectors_eye[2].x, dirVectors_eye[2].y, dirVectors_eye[2].z); LOG_INFO("[Algo Thread] Direction vectors (robot): X=(%.3f,%.3f,%.3f), Y=(%.3f,%.3f,%.3f), Z=(%.3f,%.3f,%.3f)\n", dirVectors_robot[0].x, dirVectors_robot[0].y, dirVectors_robot[0].z, dirVectors_robot[1].x, dirVectors_robot[1].y, dirVectors_robot[1].z, @@ -325,22 +389,25 @@ int DetectPresenter::DetectWorkpieceHole( LOG_INFO("[Algo Thread] Center Eye Coords: X=%.3f, Y=%.3f, Z=%.3f\n", workpiece.center.x, workpiece.center.y, workpiece.center.z); LOG_INFO("[Algo Thread] Center Robot Coords: X=%.3f, Y=%.3f, Z=%.3f, R=%.4f, P=%.4f, Y=%.4f\n", - ptRobot.x, ptRobot.y, ptRobot.z, + centerRobotPos.x, centerRobotPos.y, centerRobotPos.z, centerRobotPos.roll, centerRobotPos.pitch, centerRobotPos.yaw); } } - // 使用 PointCloudCanvas 生成点云图像(灰色底图 + 红色孔位标记 + 中心点编号) + // 使用 PointCloudCanvas 生成点云图像(正向红点、反向蓝点 + 中心点编号) { PointCloudCanvas canvas = PointCloudCanvas::Create(xyzData); for (size_t i = 0; i < workpiecePositioning.size(); i++) { const WD_workpieceInfo& workpiece = workpiecePositioning[i]; - // 红色小圆点标记每个孔 + const QColor markerColor = IsReversedWorkpieceType(workpiece.workpieceType) + ? QColor(0, 0, 255) + : QColor(255, 0, 0); + // 小圆点标记每个孔 for (size_t j = 0; j < workpiece.holes.size(); j++) { - canvas.drawPoint(workpiece.holes[j].x, workpiece.holes[j].y, QColor(255, 0, 0), 4); + canvas.drawPoint(workpiece.holes[j].x, workpiece.holes[j].y, markerColor, 4); } // 中心点标记 + 白色编号 - canvas.drawPoint(workpiece.center.x, workpiece.center.y, QColor(255, 0, 0), 4); + canvas.drawPoint(workpiece.center.x, workpiece.center.y, markerColor, 4); canvas.drawText(workpiece.center.x, workpiece.center.y, QString("%1").arg(i + 1), Qt::white, 14); } diff --git a/App/WorkpieceHole/WorkpieceHoleApp/Presenter/Src/WorkpieceHolePresenter.cpp b/App/WorkpieceHole/WorkpieceHoleApp/Presenter/Src/WorkpieceHolePresenter.cpp index a6c7053d..f138bd8c 100644 --- a/App/WorkpieceHole/WorkpieceHoleApp/Presenter/Src/WorkpieceHolePresenter.cpp +++ b/App/WorkpieceHole/WorkpieceHoleApp/Presenter/Src/WorkpieceHolePresenter.cpp @@ -62,6 +62,14 @@ const char* GetPLCStatusDescription(int statusCode) } } +bool ContainsReversedWorkpiece(const WorkpieceHoleDetectionResult& result) +{ + return std::any_of(result.positions.begin(), result.positions.end(), + [](const HolePosition& position) { + return IsReversedWorkpieceType(position.workpieceType); + }); +} + } // namespace void ConfigChangeListenerProxy::OnSystemConfigChanged(const SystemConfig& config) @@ -390,16 +398,6 @@ int WorkpieceHolePresenter::ProcessAlgoDetection(std::vectorGetConfigResult(); VrDebugParam debugParam = configResult.debugParam; - // 获取旋转顺序配置(从当前相机对应的标定矩阵获取) - int eulerOrder = 11; // 默认外旋ZYX - int matrixIdx = m_currentCameraIndex - 1; - if (matrixIdx >= 0 && matrixIdx < static_cast(configResult.handEyeCalibMatrixList.size())) { - eulerOrder = configResult.handEyeCalibMatrixList[matrixIdx].eulerOrder; - } else if (!configResult.handEyeCalibMatrixList.empty()) { - eulerOrder = configResult.handEyeCalibMatrixList[0].eulerOrder; - } - LOG_INFO("[Algo Thread] Using euler order: %d\n", eulerOrder); - // 获取方向向量反向配置 int dirVectorInvert = configResult.plcRobotServerConfig.dirVectorInvert; LOG_INFO("[Algo Thread] Using dir vector invert: %d\n", dirVectorInvert); @@ -412,12 +410,22 @@ int WorkpieceHolePresenter::ProcessAlgoDetection(std::vectorDetectWorkpieceHole(workpieceNumber, m_currentCameraIndex, detectionDataCache, algorithmParams, debugParam, m_dataLoader, - currentClibMatrix.clibMatrix, eulerOrder, dirVectorInvert, + currentClibMatrix.clibMatrix, dirVectorInvert, poseOutputOrder, detectionResult); + const bool hasReversedWorkpiece = ContainsReversedWorkpiece(detectionResult); // 根据项目类型选择处理方式 if (GetStatusCallback()) { - QString err = QString("错误:%1").arg(nRet); - if (auto pStatus = GetStatusCallback()) pStatus->OnStatusUpdate(QString("检测%1").arg(SUCCESS == nRet ? "成功": err).toStdString()); + QString detectionStatus; + if (nRet != SUCCESS) { + detectionStatus = QString("检测错误:%1").arg(nRet); + } else if (hasReversedWorkpiece) { + detectionStatus = "检测异常:工件反向"; + } else { + detectionStatus = "检测成功"; + } + if (auto pStatus = GetStatusCallback()) { + pStatus->OnStatusUpdate(detectionStatus.toStdString()); + } } LOG_INFO("[Algo Thread] wd_workpieceHolePositioning detected %zu workpieces time : %.2f ms\n", detectionResult.positions.size(), oTimeUtils.GetElapsedTimeInMilliSec()); @@ -435,7 +443,9 @@ int WorkpieceHolePresenter::ProcessAlgoDetection(std::vector()) pStatus->OnStatusUpdate(statusMsg.toStdString()); // 发送检测结果给TCP客户端 @@ -895,7 +905,9 @@ void WorkpieceHolePresenter::SendCoordinateDataToPLC(const WorkpieceHoleDetectio if (auto pStatus = GetStatusCallback()) { switch (plcStatus) { case PLCModbusClient::RESULT_ABNORMAL_MATERIAL: - pStatus->OnStatusUpdate("检测结果为异常物料,已通知PLC"); + pStatus->OnStatusUpdate(ContainsReversedWorkpiece(result) + ? "检测到反向工件,已按异常物料通知PLC" + : "检测结果为异常物料,已通知PLC"); break; case PLCModbusClient::RESULT_NO_PRODUCT: pStatus->OnStatusUpdate("检测结果为无产品,已通知PLC"); @@ -926,7 +938,7 @@ void WorkpieceHolePresenter::SendCoordinateDataToPLC(const WorkpieceHoleDetectio int poseOutputOrder = configResult.plcRobotServerConfig.poseOutputOrder; LOG_INFO("Using pose output order: %d\n", poseOutputOrder); - // 协议单点输出改为最后一个工件。 + // 协议单点输出使用最后一个工件。 const auto& pos = result.positions.back(); PLCModbusClient::CoordinateData coord; diff --git a/App/WorkpieceHole/WorkpieceHoleApp/Version.h b/App/WorkpieceHole/WorkpieceHoleApp/Version.h index 943d927e..9ad434d3 100644 --- a/App/WorkpieceHole/WorkpieceHoleApp/Version.h +++ b/App/WorkpieceHole/WorkpieceHoleApp/Version.h @@ -5,8 +5,8 @@ #define WORKPIECEHOLE_APP_NAME "工件孔定位" // 版本字符串 -#define WORKPIECEHOLE_VERSION_STRING "1.2.1" -#define WORKPIECEHOLE_BUILD_STRING "1" +#define WORKPIECEHOLE_VERSION_STRING "1.2.2" +#define WORKPIECEHOLE_BUILD_STRING "2" #define WORKPIECEHOLE_FULL_VERSION_STRING "V" WORKPIECEHOLE_VERSION_STRING "_" WORKPIECEHOLE_BUILD_STRING // 构建日期 diff --git a/App/WorkpieceHole/WorkpieceHoleApp/Version.md b/App/WorkpieceHole/WorkpieceHoleApp/Version.md index eded20bd..0bdb987a 100644 --- a/App/WorkpieceHole/WorkpieceHoleApp/Version.md +++ b/App/WorkpieceHole/WorkpieceHoleApp/Version.md @@ -1,3 +1,8 @@ +# 1.2.2 +## build_1 2026-07-14 +1. 工件3和工件5 反了。 +2. 每个工件有独立的工具参数调整 + # 1.2.1 ## build_1 2026-07-14 1. 更新算法包含1-5工件算法 diff --git a/App/WorkpieceHole/WorkpieceHoleApp/dialogalgoarg.cpp b/App/WorkpieceHole/WorkpieceHoleApp/dialogalgoarg.cpp index e6ed0def..f0962969 100644 --- a/App/WorkpieceHole/WorkpieceHoleApp/dialogalgoarg.cpp +++ b/App/WorkpieceHole/WorkpieceHoleApp/dialogalgoarg.cpp @@ -13,6 +13,7 @@ #include "PathManager.h" #include "StyledMessageBox.h" #include "HandEyeCalibWidget.h" +#include "ToolExtrinsicWidget.h" #include "WorkpieceProduct.h" #include "VrLog.h" @@ -24,6 +25,7 @@ DialogAlgoarg::DialogAlgoarg(ConfigManager* configManager, QWidget *parent) , m_currentWorkpieceNumber(1) , m_isSwitchingWorkpieceProfile(true) , m_handEyeCalibWidget(nullptr) + , m_toolExtrinsicWidget(nullptr) { try { ui->setupUi(this); @@ -52,6 +54,9 @@ DialogAlgoarg::DialogAlgoarg(ConfigManager* configManager, QWidget *parent) // 初始化工件参数组下拉框 InitWorkpieceProfileComboBox(); + // 初始化工具参数 tab;工具参数由顶部工件选择器驱动 + InitToolExtrinsicTab(); + // 从配置文件加载数据到界面 LoadConfigToUI(); m_isSwitchingWorkpieceProfile = false; @@ -156,17 +161,39 @@ void DialogAlgoarg::LoadCurrentWorkpieceProfileToUI() LoadLineSegParamToUI(profile.lineSegParam); LoadFilterParamToUI(profile.filterParam); LoadGrowParamToUI(profile.growParam); + + if (m_toolExtrinsicWidget) { + m_toolExtrinsicWidget->setData( + profile.toolParam.eulerOrder, + profile.toolParam.rotX, profile.toolParam.rotY, profile.toolParam.rotZ, + profile.toolParam.offsetX, profile.toolParam.offsetY, profile.toolParam.offsetZ); + } } bool DialogAlgoarg::SaveCurrentWorkpieceProfileFromUI() { + if (m_toolExtrinsicWidget && !m_toolExtrinsicWidget->hasAcceptableInput()) { + return false; + } + VrWorkpieceAlgorithmProfile& profile = m_algorithmParams.GetOrCreateWorkpieceProfile(m_currentWorkpieceNumber); - return SaveWorkpieceHoleParamFromUI(profile.workpieceHoleParam) && - SaveLineSegParamFromUI(profile.lineSegParam) && - SaveFilterParamFromUI(profile.filterParam) && - SaveGrowParamFromUI(profile.growParam); + if (!SaveWorkpieceHoleParamFromUI(profile.workpieceHoleParam) || + !SaveLineSegParamFromUI(profile.lineSegParam) || + !SaveFilterParamFromUI(profile.filterParam) || + !SaveGrowParamFromUI(profile.growParam)) { + return false; + } + + if (m_toolExtrinsicWidget) { + m_toolExtrinsicWidget->getData( + profile.toolParam.eulerOrder, + profile.toolParam.rotX, profile.toolParam.rotY, profile.toolParam.rotZ, + profile.toolParam.offsetX, profile.toolParam.offsetY, profile.toolParam.offsetZ); + } + + return true; } void DialogAlgoarg::on_comboBox_workpieceProfile_currentIndexChanged(int index) @@ -243,8 +270,9 @@ bool DialogAlgoarg::SaveConfigFromUI() } try { - // 获取当前配置 - SystemConfig systemConfig = m_pConfigManager->GetConfig(); + // 基于当前配置构造候选值;落盘失败时恢复运行时配置。 + const SystemConfig previousSystemConfig = m_pConfigManager->GetConfig(); + SystemConfig systemConfig = previousSystemConfig; VrAlgorithmParams& algoParams = systemConfig.configResult.algorithmParams; // 先暂存当前工件,再一次性保存所有工件参数组。 @@ -300,7 +328,12 @@ bool DialogAlgoarg::SaveConfigFromUI() return false; } - return m_pConfigManager->SaveConfigToFile(m_configFilePath.toStdString()); + if (!m_pConfigManager->SaveConfigToFile(m_configFilePath.toStdString())) { + m_pConfigManager->UpdateFullConfig(previousSystemConfig); + return false; + } + + return true; } catch (const std::exception& e) { return false; @@ -407,6 +440,21 @@ void DialogAlgoarg::on_btn_camer_cancel_clicked() reject(); } +// ========== 工具参数相关实现 ========== + +void DialogAlgoarg::InitToolExtrinsicTab() +{ + if (!ui || !ui->verticalLayout_toolExtrinsicHost || m_toolExtrinsicWidget) { + return; + } + + m_toolExtrinsicWidget = new ToolExtrinsicWidget(this); + m_toolExtrinsicWidget->setCameraSelectorVisible(false); + m_toolExtrinsicWidget->setTargetOffsetVisible(true); + m_toolExtrinsicWidget->setAxialAngleRangeVisible(false); + ui->verticalLayout_toolExtrinsicHost->addWidget(m_toolExtrinsicWidget); +} + // ========== 手眼标定相关实现 ========== void DialogAlgoarg::InitHandEyeCalibTab() diff --git a/App/WorkpieceHole/WorkpieceHoleApp/dialogalgoarg.h b/App/WorkpieceHole/WorkpieceHoleApp/dialogalgoarg.h index 7c50fbc4..a6dda668 100644 --- a/App/WorkpieceHole/WorkpieceHoleApp/dialogalgoarg.h +++ b/App/WorkpieceHole/WorkpieceHoleApp/dialogalgoarg.h @@ -7,6 +7,7 @@ #include "ConfigManager.h" class HandEyeCalibWidget; +class ToolExtrinsicWidget; namespace Ui { class DialogAlgoarg; @@ -59,6 +60,9 @@ private: void InitHandEyeCalibTab(); void InitEulerOrderComboBox(); + // 工具参数 + void InitToolExtrinsicTab(); + private: Ui::DialogAlgoarg *ui; ConfigManager* m_pConfigManager; @@ -69,6 +73,9 @@ private: // 手眼标定共享控件 HandEyeCalibWidget* m_handEyeCalibWidget; + + // 工具参数共享控件 + ToolExtrinsicWidget* m_toolExtrinsicWidget; }; #endif // DIALOGALGOARG_H diff --git a/App/WorkpieceHole/WorkpieceHoleApp/dialogalgoarg.ui b/App/WorkpieceHole/WorkpieceHoleApp/dialogalgoarg.ui index 133307cb..4ad5e5b4 100644 --- a/App/WorkpieceHole/WorkpieceHoleApp/dialogalgoarg.ui +++ b/App/WorkpieceHole/WorkpieceHoleApp/dialogalgoarg.ui @@ -774,6 +774,12 @@ QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 3px 0 3px; + + + 工具参数 + + + 网络配置 diff --git a/App/WorkpieceHole/WorkpieceHoleApp/mainwindow.cpp b/App/WorkpieceHole/WorkpieceHoleApp/mainwindow.cpp index da3aba8c..f16541e7 100644 --- a/App/WorkpieceHole/WorkpieceHoleApp/mainwindow.cpp +++ b/App/WorkpieceHole/WorkpieceHoleApp/mainwindow.cpp @@ -434,7 +434,7 @@ int MainWindow::selectWorkpieceForDetection() layout.setContentsMargins(20, 20, 20, 20); layout.setSpacing(12); - QLabel header(tr("序号 产品型号"), &dialog); + QLabel header(tr("显示序号 - 产品型号"), &dialog); layout.addWidget(&header); QListWidget productList(&dialog); @@ -446,7 +446,7 @@ int MainWindow::selectWorkpieceForDetection() const WorkpieceProduct::ProductInfo* products = WorkpieceProduct::Products(); for (int i = 0; i < WorkpieceProduct::ProductCount(); ++i) { const WorkpieceProduct::ProductInfo& product = products[i]; - QString text = QString("%1 %2") + QString text = tr("%1 - %2") .arg(product.number) .arg(QString::fromLatin1(product.model)); if (!product.detectionSupported) { diff --git a/App/WorkpieceHole/WorkpieceHoleConfig/Inc/IVrConfig.h b/App/WorkpieceHole/WorkpieceHoleConfig/Inc/IVrConfig.h index 399c5bb0..0585c5f0 100644 --- a/App/WorkpieceHole/WorkpieceHoleConfig/Inc/IVrConfig.h +++ b/App/WorkpieceHole/WorkpieceHoleConfig/Inc/IVrConfig.h @@ -26,7 +26,7 @@ struct VrOutlierFilterParam */ struct VrLineSegParam { - double distScale = 3.0; // 计算方向角的窗口比例因子 + double distScale = 10.0; // 计算方向角的窗口比例因子 double segGapTh_y = 15.0; // Y方向间隔阈值,大于此值认为是分段 double segGapTh_z = 0.0; // Z方向间隔阈值,大于此值认为是分段 }; @@ -51,7 +51,7 @@ struct VrWorkpieceHoleParam { int workpieceType = 0; // 工件类型 double holeDiameter = 6.0; // 孔直径 (mm) - double bigHoleDiameter = 33.0; // 大孔直径 (mm),工件3算法使用 + double bigHoleDiameter = 33.0; // 大孔直径 (mm),API_3 算法使用 double holeDist_L = 40.0; // 孔间距_长 (mm) double holeDist_W = 32.0; // 孔间距_宽 (mm) double xLen = 44.0; // x轴方向长度,用于绘制平面 (mm) @@ -59,6 +59,22 @@ struct VrWorkpieceHoleParam double H = 47.0; // 高度 (mm) }; +/** + * @brief 工具参数 + * + * 每个工件独立保存一套工具姿态补偿和目标点偏移参数。 + */ +struct VrToolParam +{ + int eulerOrder = 11; // 欧拉角旋转顺序,默认外旋 ZYX + double rotX = 0.0; // 工具姿态 X 轴补偿 (deg) + double rotY = 0.0; // 工具姿态 Y 轴补偿 (deg) + double rotZ = 0.0; // 工具姿态 Z 轴补偿 (deg) + double offsetX = 0.0; // 目标点 X 方向偏移 (mm) + double offsetY = 0.0; // 目标点 Y 方向偏移 (mm) + double offsetZ = 0.0; // 目标点 Z 方向偏移 (mm) +}; + /** * @brief PLC Modbus 寄存器地址配置 * @@ -198,6 +214,7 @@ struct VrWorkpieceAlgorithmProfile VrLineSegParam lineSegParam; VrOutlierFilterParam filterParam; VrTreeGrowParam growParam; + VrToolParam toolParam; VrWorkpieceAlgorithmProfile() = default; @@ -220,7 +237,7 @@ struct VrWorkpieceAlgorithmProfile workpieceHoleParam.yLen = 70.0; workpieceHoleParam.H = 47.0; - lineSegParam.distScale = 3.0; + lineSegParam.distScale = 10.0; lineSegParam.segGapTh_y = 15.0; lineSegParam.segGapTh_z = 0.0; @@ -234,13 +251,29 @@ struct VrWorkpieceAlgorithmProfile growParam.minLTypeTreeLen = 2.0; growParam.minVTypeTreeLen = 2.0; - if (number == 3 || number == 4) { + toolParam = VrToolParam(); + + // 参数编号始终采用软件产品顺序,不依赖算法 test 的历史工件编号。 + switch (number) { + case 1: // 4WE6 / API_1 / 原 test1 + break; + case 2: // Z2S6 / API_2 / 原 test2 + workpieceHoleParam.holeDiameter = 8.0; + break; + case 3: // Z2FS6_30_2 / API_2 / 原 test5 + workpieceHoleParam.H = 28.0; + lineSegParam.distScale = 14.0; + growParam.minLTypeTreeLen = 1.5; + growParam.minVTypeTreeLen = 1.5; + break; + case 4: // DB10 / API_3 / 原 test3 + case 5: // DB20 / API_3 / 原 test4 workpieceHoleParam.H = 60.0; lineSegParam.distScale = 30.0; lineSegParam.segGapTh_y = 40.0; - } else if (number == 5) { - workpieceHoleParam.holeDiameter = 10.0; - workpieceHoleParam.H = 28.0; + break; + default: + break; } } }; diff --git a/App/WorkpieceHole/WorkpieceHoleConfig/Src/VrConfig.cpp b/App/WorkpieceHole/WorkpieceHoleConfig/Src/VrConfig.cpp index eb2d63f1..4554d425 100644 --- a/App/WorkpieceHole/WorkpieceHoleConfig/Src/VrConfig.cpp +++ b/App/WorkpieceHole/WorkpieceHoleConfig/Src/VrConfig.cpp @@ -10,6 +10,23 @@ using namespace tinyxml2; namespace { +constexpr int kSoftwareProductNumberingVersion = 2; + +int ToSoftwareWorkpieceNumber(int storedNumber, bool usesSoftwareProductNumbering) +{ + if (usesSoftwareProductNumbering) { + return storedNumber; + } + + // 旧配置按算法 test 编号保存;运行时统一转换为软件产品编号。 + switch (storedNumber) { + case 3: return 4; + case 4: return 5; + case 5: return 3; + default: return storedNumber; + } +} + void LoadWorkpieceProfileParams( XMLElement* parentElement, VrWorkpieceAlgorithmProfile& profile) @@ -75,6 +92,25 @@ void LoadWorkpieceProfileParams( if (growParamElement->Attribute("minVTypeTreeLen")) profile.growParam.minVTypeTreeLen = growParamElement->DoubleAttribute("minVTypeTreeLen"); } + + XMLElement* toolParamElement = parentElement->FirstChildElement("ToolParam"); + if (toolParamElement) + { + if (toolParamElement->Attribute("eulerOrder")) + profile.toolParam.eulerOrder = toolParamElement->IntAttribute("eulerOrder"); + if (toolParamElement->Attribute("rotX")) + profile.toolParam.rotX = toolParamElement->DoubleAttribute("rotX"); + if (toolParamElement->Attribute("rotY")) + profile.toolParam.rotY = toolParamElement->DoubleAttribute("rotY"); + if (toolParamElement->Attribute("rotZ")) + profile.toolParam.rotZ = toolParamElement->DoubleAttribute("rotZ"); + if (toolParamElement->Attribute("offsetX")) + profile.toolParam.offsetX = toolParamElement->DoubleAttribute("offsetX"); + if (toolParamElement->Attribute("offsetY")) + profile.toolParam.offsetY = toolParamElement->DoubleAttribute("offsetY"); + if (toolParamElement->Attribute("offsetZ")) + profile.toolParam.offsetZ = toolParamElement->DoubleAttribute("offsetZ"); + } } void SaveWorkpieceProfileParams( @@ -116,6 +152,16 @@ void SaveWorkpieceProfileParams( growParamElement->SetAttribute("minLTypeTreeLen", profile.growParam.minLTypeTreeLen); growParamElement->SetAttribute("minVTypeTreeLen", profile.growParam.minVTypeTreeLen); profileElement->InsertEndChild(growParamElement); + + XMLElement* toolParamElement = doc.NewElement("ToolParam"); + toolParamElement->SetAttribute("eulerOrder", profile.toolParam.eulerOrder); + toolParamElement->SetAttribute("rotX", profile.toolParam.rotX); + toolParamElement->SetAttribute("rotY", profile.toolParam.rotY); + toolParamElement->SetAttribute("rotZ", profile.toolParam.rotZ); + toolParamElement->SetAttribute("offsetX", profile.toolParam.offsetX); + toolParamElement->SetAttribute("offsetY", profile.toolParam.offsetY); + toolParamElement->SetAttribute("offsetZ", profile.toolParam.offsetZ); + profileElement->InsertEndChild(toolParamElement); } } // namespace @@ -149,6 +195,14 @@ int CVrConfig::LoadConfig(const std::string& filePath, ConfigResult& configResul return LOAD_CONFIG_INVALID_FORMAT; } + const int productNumberingVersion = + root->IntAttribute("productNumberingVersion", 1); + const bool usesSoftwareProductNumbering = + productNumberingVersion >= kSoftwareProductNumberingVersion; + if (!usesSoftwareProductNumbering) { + LOG_INFO("Migrating legacy workpiece profiles to software product numbering\n"); + } + // 解析摄像头列表 ConfigXmlUtils::LoadCameraList(root, configResult.cameraList); @@ -181,7 +235,10 @@ int CVrConfig::LoadConfig(const std::string& filePath, ConfigResult& configResul XMLElement* profileElement = algoParamsElement->FirstChildElement("WorkpieceParamGroup"); if (profileElement) { while (profileElement) { - const int workpieceNumber = profileElement->IntAttribute("workpieceNumber", 0); + const int storedWorkpieceNumber = + profileElement->IntAttribute("workpieceNumber", 0); + const int workpieceNumber = ToSoftwareWorkpieceNumber( + storedWorkpieceNumber, usesSoftwareProductNumbering); VrWorkpieceAlgorithmProfile* workpieceProfile = configResult.algorithmParams.FindWorkpieceProfile(workpieceNumber); if (workpieceProfile) { @@ -265,6 +322,7 @@ bool CVrConfig::SaveConfig(const std::string& filePath, ConfigResult& configResu // 创建根元素 XMLElement* root = doc.NewElement("WorkpieceHoleConfig"); + root->SetAttribute("productNumberingVersion", kSoftwareProductNumberingVersion); doc.InsertEndChild(root); // 添加摄像头列表 diff --git a/App/WorkpieceHole/WorkpieceHoleConfig/config/config.xml b/App/WorkpieceHole/WorkpieceHoleConfig/config/config.xml index 37885f75..59dd2add 100644 --- a/App/WorkpieceHole/WorkpieceHoleConfig/config/config.xml +++ b/App/WorkpieceHole/WorkpieceHoleConfig/config/config.xml @@ -1,5 +1,5 @@ - + @@ -10,32 +10,37 @@ - + + - + - + + - + - - + + + + - + - + + YOLO OBB left/right --> stereo ROI pairing --> 2D bolt centerline and top endpoint --> sparse stereo 3D centerline --> local SGBM support-plane fitting --> centerline/plane intersection --> axial bolt height and adjacent distances -``` - -主线参数: - -- `plane_use_sgbm: true` -- `height_from_plane: true` -- `plane_height_along_axis: true` -- `plane_anchor_max_foot_gap_std_mm: 20.0` -- `plane_sgbm_scale: 0.25` -- `plane_max_samples: 40000` -- YOLO input `1024x1024`, confidence `0.50`, IoU `0.45` - -## 固定交付件 - -- 算法库:`lib/libstereo_bolt.so` - - SHA256 `5e7884a9f68ba389105d2af17f57482f4a15c2953f7845ead636653ae2d0aebd` - - AArch64 Build ID `79fbed2add268e6ceadad58e1e8f93731c844907` -- RKNN 模型:`weights/model_20260713T084621+0000_c732327c.rknn` - - SHA256 `b9ed3b46c416919dc969637a7093cf01312eda91320066e40e33daad56d9f169` -- 配置:`config/config.rk3588.mainline.yaml` - - SHA256 `d9157898f966927b38cbe4d9531baa9ea514c28153001654d1e2d5c2c4ad14de` -- 标定:`calib/stereo_calib_20260621_202857_ascii.xml` - - SHA256 `751e376ef074a4627f95256cac2ba8ea6b96dec2f672689fc5e3cf9b5cb205b4` -- 公共头文件:`include/stereo_bolt/c_api.h` - - SHA256 `61a56da934fe4ed1fb7e98a4d145aeaf1789c56a8494ccc9bec193cf9701c163` - -## 接口兼容 - -导出的 9 个 C 符号与 `stereo_bolt_delivery_selected4_20260710` 完全一致: - -```text -sb_create -sb_create_ex -sb_default_params -sb_destroy -sb_last_error -sb_free_bolt_module_result -sb_process_bolt_module_files -sb_process_bolt_module_buffers -sb_range_bolt_binned -``` - -生产集成继续使用原接口。`StereoBoltParamsC` 通过 `struct_size` 兼容较短的旧调用方结构;本次没有改变任何已发布函数签名或输出结构布局。 - -## 验证边界 - -Windows 对 `7.3` 全部 79 组图像做了真实执行:测距入口 79/79 找到结果,完整 center/top 53/79,严格精测成功 24/79。选定的 19/20/28/43 四帧均为 8 检测、8 配对、8 精测成功。详细结果见 `validation/WINDOWS_7_3_VALIDATION_SUMMARY.md`。 - -因此,本包对四帧验收集做了完整固定验收,但不能把 24/79 的全量结果表述为已经完成全场景泛化。其余失败主要为数量不符、配对失败和测量失败,仍需要后续数据/算法迭代。 - -RK3588 真机从本包独立解压执行的结果如下;两种入口均为 4/4 PASS: - -| 帧 | 左/右检测 | 配对 | 精测 | 测距 | 显式入口代表距离/mm | -|---:|---:|---:|---:|---:|---:| -| 19 | 8/8 | 8 | 8 | 8 | 2306.606 | -| 20 | 8/8 | 8 | 8 | 8 | 2286.189 | -| 28 | 8/8 | 8 | 8 | 8 | 2554.382 | -| 43 | 8/8 | 8 | 8 | 8 | 1890.147 | - -真机原始输出保存在 `validation/mainline_package_reverify.log`,SHA256 为 `3faf413b9503400f3e89a8928108ec8bba1eb165d5e1160fd353edede74eb810`。 - -板端一键验收脚本同时覆盖: - -1. `sb_create(YAML)` + 文件接口。 -2. `sb_create_ex(model bytes)` + buffer 精测接口 + 测距接口。 -3. 包内 MANIFEST 和动态依赖闭包。 - -本次脚本返回码为 0,末行已出现 `PACKAGE_REVERIFY_PASS`。 - -## 目录 - -```text -lib/ 主算法库、RKNN 与 OpenCV 运行库 -include/stereo_bolt/c_api.h 公共 C ABI -weights/ 主线 RKNN 模型 -calib/ 双目标定 -config/ RK3588 主线配置 -example/ 纯 C 接入示例 -validation/ 双入口验收器、日志和四组原图 -ABI_FINGERPRINT.txt ELF/ABI 指纹 -SELECTED4_PROVENANCE.txt 算法、模型、配置与构建来源 -INSTALL_3588.md 安装和复验命令 -``` +# RK3588 螺栓测量主线路交付说明 + +## 交付定位 + +本包将原“四组图特化”实现升级为当前主线路。算法高度定义为:螺杆三维顶点沿拟合轴线,到轴线与局部支撑平面交点的距离。 + +推理后端只保留原有两条直接路线: + +- Windows:OpenCV DNN 直接加载 ONNX。 +- RK3588:RKNN Runtime 直接加载 RKNN。 + +本包不包含、也不依赖 AI Studio Runtime 平台封装层。公共 C ABI、输出结构和函数签名保持不变。 + +## 主线算法 + +```text +stereo rectify +-> YOLO OBB left/right +-> stereo ROI pairing +-> 2D bolt centerline and top endpoint +-> sparse stereo 3D centerline +-> local SGBM support-plane fitting +-> centerline/plane intersection +-> axial bolt height and adjacent distances +``` + +主线参数: + +- `plane_use_sgbm: true` +- `height_from_plane: true` +- `plane_height_along_axis: true` +- `plane_anchor_max_foot_gap_std_mm: 20.0` +- `plane_sgbm_scale: 0.25` +- `plane_max_samples: 40000` +- YOLO input `1024x1024`, confidence `0.50`, IoU `0.45` + +## 固定交付件 + +- 算法库:`lib/libstereo_bolt.so` + - SHA256 `5e7884a9f68ba389105d2af17f57482f4a15c2953f7845ead636653ae2d0aebd` + - AArch64 Build ID `79fbed2add268e6ceadad58e1e8f93731c844907` +- RKNN 模型:`weights/model_20260713T084621+0000_c732327c.rknn` + - SHA256 `b9ed3b46c416919dc969637a7093cf01312eda91320066e40e33daad56d9f169` +- 配置:`config/config.rk3588.mainline.yaml` + - SHA256 `d9157898f966927b38cbe4d9531baa9ea514c28153001654d1e2d5c2c4ad14de` +- 标定:`calib/stereo_calib_20260621_202857_ascii.xml` + - SHA256 `751e376ef074a4627f95256cac2ba8ea6b96dec2f672689fc5e3cf9b5cb205b4` +- 公共头文件:`include/stereo_bolt/c_api.h` + - SHA256 `61a56da934fe4ed1fb7e98a4d145aeaf1789c56a8494ccc9bec193cf9701c163` + +## 接口兼容 + +导出的 9 个 C 符号与 `stereo_bolt_delivery_selected4_20260710` 完全一致: + +```text +sb_create +sb_create_ex +sb_default_params +sb_destroy +sb_last_error +sb_free_bolt_module_result +sb_process_bolt_module_files +sb_process_bolt_module_buffers +sb_range_bolt_binned +``` + +生产集成继续使用原接口。`StereoBoltParamsC` 通过 `struct_size` 兼容较短的旧调用方结构;本次没有改变任何已发布函数签名或输出结构布局。 + +## 验证边界 + +Windows 对 `7.3` 全部 79 组图像做了真实执行:测距入口 79/79 找到结果,完整 center/top 53/79,严格精测成功 24/79。选定的 19/20/28/43 四帧均为 8 检测、8 配对、8 精测成功。详细结果见 `validation/WINDOWS_7_3_VALIDATION_SUMMARY.md`。 + +因此,本包对四帧验收集做了完整固定验收,但不能把 24/79 的全量结果表述为已经完成全场景泛化。其余失败主要为数量不符、配对失败和测量失败,仍需要后续数据/算法迭代。 + +RK3588 真机从本包独立解压执行的结果如下;两种入口均为 4/4 PASS: + +| 帧 | 左/右检测 | 配对 | 精测 | 测距 | 显式入口代表距离/mm | +|---:|---:|---:|---:|---:|---:| +| 19 | 8/8 | 8 | 8 | 8 | 2306.606 | +| 20 | 8/8 | 8 | 8 | 8 | 2286.189 | +| 28 | 8/8 | 8 | 8 | 8 | 2554.382 | +| 43 | 8/8 | 8 | 8 | 8 | 1890.147 | + +真机原始输出保存在 `validation/mainline_package_reverify.log`,SHA256 为 `3faf413b9503400f3e89a8928108ec8bba1eb165d5e1160fd353edede74eb810`。 + +板端一键验收脚本同时覆盖: + +1. `sb_create(YAML)` + 文件接口。 +2. `sb_create_ex(model bytes)` + buffer 精测接口 + 测距接口。 +3. 包内 MANIFEST 和动态依赖闭包。 + +本次脚本返回码为 0,末行已出现 `PACKAGE_REVERIFY_PASS`。 + +## 目录 + +```text +lib/ 主算法库、RKNN 与 OpenCV 运行库 +include/stereo_bolt/c_api.h 公共 C ABI +weights/ 主线 RKNN 模型 +calib/ 双目标定 +config/ RK3588 主线配置 +example/ 纯 C 接入示例 +validation/ 双入口验收器、日志和四组原图 +ABI_FINGERPRINT.txt ELF/ABI 指纹 +SELECTED4_PROVENANCE.txt 算法、模型、配置与构建来源 +INSTALL_3588.md 安装和复验命令 +``` diff --git a/AppAlgo/stereo_bolt_delivery/INSTALL_3588.md b/AppAlgo/stereo_bolt_delivery/INSTALL_3588.md index 2830ddc5..17911c8d 100644 --- a/AppAlgo/stereo_bolt_delivery/INSTALL_3588.md +++ b/AppAlgo/stereo_bolt_delivery/INSTALL_3588.md @@ -1,66 +1,66 @@ -# RK3588 安装与验证 - -## 环境 - -- AArch64 RK3588 -- Ubuntu 22.04 / glibc >= 2.35 -- 可用的 RKNN NPU 驱动 -- 不需要安装系统 OpenCV;标准 SONAME 的 OpenCV 4.5.0 和 `librknnrt.so` 已包含在 `lib/` -- 不需要 AI Studio Runtime - -## 解包 - -```bash -tar xzf stereo_bolt_delivery_mainline_20260714.tar.gz -cd stereo_bolt_delivery_mainline_20260714 -export LD_LIBRARY_PATH="$(pwd)/lib:${LD_LIBRARY_PATH:-}" -chmod +x validation/c_example_centerline \ - validation/selected4_delivery_validate \ - validation/run_selected4.sh -``` - -## 完整性与依赖 - -```bash -sha256sum -c MANIFEST_SHA256.txt -ldd lib/libstereo_bolt.so | grep "not found" -# 第二条命令预期无输出 -``` - -关键文件预期 SHA256: - -```text -5e7884a9f68ba389105d2af17f57482f4a15c2953f7845ead636653ae2d0aebd lib/libstereo_bolt.so -b9ed3b46c416919dc969637a7093cf01312eda91320066e40e33daad56d9f169 weights/model_20260713T084621+0000_c732327c.rknn -d9157898f966927b38cbe4d9531baa9ea514c28153001654d1e2d5c2c4ad14de config/config.rk3588.mainline.yaml -``` - -## 四帧板端复验 - -```bash -bash validation/run_selected4.sh -``` - -脚本会分别执行 YAML 文件接口和显式模型 buffer 接口,并检查 19、20、28、43 四帧。每帧应包含: - -```text -PRECISE ... success=1 yolo_left=8 yolo_right=8 pairs=8 bolts=8 -RANGE ... found=1 bolts=8 status=ok -VERDICT=PASS -``` - -最终必须出现: - -```text -=== PACKAGE_REVERIFY_PASS === -``` - -## 原接口调用 - -文件入口保持不变: - -```c -StereoBoltCtx* ctx = sb_create("config/config.rk3588.mainline.yaml"); -``` - -生产 buffer 入口仍使用 `sb_create_ex()`、`sb_process_bolt_module_buffers()` 和 `sb_range_bolt_binned()`;调用方只需继续包含 `include/stereo_bolt/c_api.h`。配置中的模型和标定路径均相对于解包根目录解析。 +# RK3588 安装与验证 + +## 环境 + +- AArch64 RK3588 +- Ubuntu 22.04 / glibc >= 2.35 +- 可用的 RKNN NPU 驱动 +- 不需要安装系统 OpenCV;标准 SONAME 的 OpenCV 4.5.0 和 `librknnrt.so` 已包含在 `lib/` +- 不需要 AI Studio Runtime + +## 解包 + +```bash +tar xzf stereo_bolt_delivery_mainline_20260714.tar.gz +cd stereo_bolt_delivery_mainline_20260714 +export LD_LIBRARY_PATH="$(pwd)/lib:${LD_LIBRARY_PATH:-}" +chmod +x validation/c_example_centerline \ + validation/selected4_delivery_validate \ + validation/run_selected4.sh +``` + +## 完整性与依赖 + +```bash +sha256sum -c MANIFEST_SHA256.txt +ldd lib/libstereo_bolt.so | grep "not found" +# 第二条命令预期无输出 +``` + +关键文件预期 SHA256: + +```text +5e7884a9f68ba389105d2af17f57482f4a15c2953f7845ead636653ae2d0aebd lib/libstereo_bolt.so +b9ed3b46c416919dc969637a7093cf01312eda91320066e40e33daad56d9f169 weights/model_20260713T084621+0000_c732327c.rknn +d9157898f966927b38cbe4d9531baa9ea514c28153001654d1e2d5c2c4ad14de config/config.rk3588.mainline.yaml +``` + +## 四帧板端复验 + +```bash +bash validation/run_selected4.sh +``` + +脚本会分别执行 YAML 文件接口和显式模型 buffer 接口,并检查 19、20、28、43 四帧。每帧应包含: + +```text +PRECISE ... success=1 yolo_left=8 yolo_right=8 pairs=8 bolts=8 +RANGE ... found=1 bolts=8 status=ok +VERDICT=PASS +``` + +最终必须出现: + +```text +=== PACKAGE_REVERIFY_PASS === +``` + +## 原接口调用 + +文件入口保持不变: + +```c +StereoBoltCtx* ctx = sb_create("config/config.rk3588.mainline.yaml"); +``` + +生产 buffer 入口仍使用 `sb_create_ex()`、`sb_process_bolt_module_buffers()` 和 `sb_range_bolt_binned()`;调用方只需继续包含 `include/stereo_bolt/c_api.h`。配置中的模型和标定路径均相对于解包根目录解析。 diff --git a/AppAlgo/stereo_bolt_delivery/validation/WINDOWS_7_3_VALIDATION_SUMMARY.md b/AppAlgo/stereo_bolt_delivery/validation/WINDOWS_7_3_VALIDATION_SUMMARY.md index 73423bc7..b123b122 100644 --- a/AppAlgo/stereo_bolt_delivery/validation/WINDOWS_7_3_VALIDATION_SUMMARY.md +++ b/AppAlgo/stereo_bolt_delivery/validation/WINDOWS_7_3_VALIDATION_SUMMARY.md @@ -1,31 +1,31 @@ -# Windows 7.3 全量验证摘要 - -- 日期:2026-07-14 -- 数据:`7.3` 全部 79 组双目图像 -- 后端:OpenCV DNN 直接加载 `model_20260713T084621+0000_c732327c.onnx` -- 阈值:confidence 0.50,期望螺栓数 8 -- 高度:三维顶点沿拟合螺杆轴线,到轴线/局部 SGBM 支撑平面交点的距离 -- 单元测试:29/29 PASS - -## 汇总 - -| 指标 | 结果 | -|---|---:| -| 测距入口 found | 79/79 | -| 完整 center/top | 53/79 | -| 严格精测 success | 24/79 | -| 检出数量正好为 8 | 26/79 | -| 19/20/28/43 四帧精测 | 4/4,每帧 8 根 | - -失败原因计数:`count_mismatch=26`、`pairing_failed=15`、`measurement_failed=14`。 - -严格成功帧:1, 2, 3, 4, 5, 6, 7, 15, 19, 20, 21, 22, 28, 29, 34, 36, 37, 43, 44, 49, 54, 60, 61, 66。 - -## 四帧轴向高度(mm) - -- 19:188.573, 189.837, 185.532, 185.495, 179.350, 196.637, 194.677, 194.563 -- 20:193.177, 190.347, 184.390, 184.689, 190.018, 191.536, 192.582, 196.692 -- 28:189.841, 197.338, 195.061, 183.737, 175.843, 177.208, 171.662, 189.185 -- 43:209.407, 206.103, 204.961, 206.985, 196.889, 205.353, 207.920, 211.450 - -结果图和 CSV 位于 Windows 工程输出目录 `outputs/win_7_3_mainline_20260714/`。本摘要明确保留全量失败情况;四帧验收通过不等于 79 帧全量泛化已经完成。 +# Windows 7.3 全量验证摘要 + +- 日期:2026-07-14 +- 数据:`7.3` 全部 79 组双目图像 +- 后端:OpenCV DNN 直接加载 `model_20260713T084621+0000_c732327c.onnx` +- 阈值:confidence 0.50,期望螺栓数 8 +- 高度:三维顶点沿拟合螺杆轴线,到轴线/局部 SGBM 支撑平面交点的距离 +- 单元测试:29/29 PASS + +## 汇总 + +| 指标 | 结果 | +|---|---:| +| 测距入口 found | 79/79 | +| 完整 center/top | 53/79 | +| 严格精测 success | 24/79 | +| 检出数量正好为 8 | 26/79 | +| 19/20/28/43 四帧精测 | 4/4,每帧 8 根 | + +失败原因计数:`count_mismatch=26`、`pairing_failed=15`、`measurement_failed=14`。 + +严格成功帧:1, 2, 3, 4, 5, 6, 7, 15, 19, 20, 21, 22, 28, 29, 34, 36, 37, 43, 44, 49, 54, 60, 61, 66。 + +## 四帧轴向高度(mm) + +- 19:188.573, 189.837, 185.532, 185.495, 179.350, 196.637, 194.677, 194.563 +- 20:193.177, 190.347, 184.390, 184.689, 190.018, 191.536, 192.582, 196.692 +- 28:189.841, 197.338, 195.061, 183.737, 175.843, 177.208, 171.662, 189.185 +- 43:209.407, 206.103, 204.961, 206.985, 196.889, 205.353, 207.920, 211.450 + +结果图和 CSV 位于 Windows 工程输出目录 `outputs/win_7_3_mainline_20260714/`。本摘要明确保留全量失败情况;四帧验收通过不等于 79 帧全量泛化已经完成。 diff --git a/AppAlgo/workpieceHolePositioning/Arm/aarch64/libbaseAlgorithm.so b/AppAlgo/workpieceHolePositioning/Arm/aarch64/libbaseAlgorithm.so index 049ad755..a5202ae6 100644 Binary files a/AppAlgo/workpieceHolePositioning/Arm/aarch64/libbaseAlgorithm.so and b/AppAlgo/workpieceHolePositioning/Arm/aarch64/libbaseAlgorithm.so differ diff --git a/AppAlgo/workpieceHolePositioning/Arm/aarch64/libworkpieceHolePositioning.so b/AppAlgo/workpieceHolePositioning/Arm/aarch64/libworkpieceHolePositioning.so index 01b516da..5c8f53fb 100644 Binary files a/AppAlgo/workpieceHolePositioning/Arm/aarch64/libworkpieceHolePositioning.so and b/AppAlgo/workpieceHolePositioning/Arm/aarch64/libworkpieceHolePositioning.so differ diff --git a/AppAlgo/workpieceHolePositioning/Inc/SG_errCode.h b/AppAlgo/workpieceHolePositioning/Inc/SG_errCode.h index 812f8d81..b4b8fe11 100644 --- a/AppAlgo/workpieceHolePositioning/Inc/SG_errCode.h +++ b/AppAlgo/workpieceHolePositioning/Inc/SG_errCode.h @@ -12,6 +12,7 @@ #define SG_ERR_SCAN_DIR_NOT_SUPPORTED -1009 #define SX_ERR_ZERO_OBJECTS -1010 #define SX_ERR_NULL_GROUND_PLANE -1011 +#define SX_ERR_ZERO_2D_OBJECTS -1012 //BQ_workpiece #define SX_ERR_INVLD_VTREE_NUM -2001 diff --git a/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/baseAlgorithm.dll b/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/baseAlgorithm.dll index 0f4eda8b..cfff0457 100644 Binary files a/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/baseAlgorithm.dll and b/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/baseAlgorithm.dll differ diff --git a/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/baseAlgorithm.lib b/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/baseAlgorithm.lib index 085d76be..4f39771b 100644 Binary files a/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/baseAlgorithm.lib and b/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/baseAlgorithm.lib differ diff --git a/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/baseAlgorithm.pdb b/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/baseAlgorithm.pdb index 340868e2..0f61b9e0 100644 Binary files a/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/baseAlgorithm.pdb and b/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/baseAlgorithm.pdb differ diff --git a/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/workpieceHolePositioning.dll b/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/workpieceHolePositioning.dll index 0fd8571c..2ce25b28 100644 Binary files a/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/workpieceHolePositioning.dll and b/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/workpieceHolePositioning.dll differ diff --git a/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/workpieceHolePositioning.lib b/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/workpieceHolePositioning.lib index e4f70216..84f541a6 100644 Binary files a/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/workpieceHolePositioning.lib and b/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/workpieceHolePositioning.lib differ diff --git a/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/workpieceHolePositioning.pdb b/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/workpieceHolePositioning.pdb index 5dccd8da..f8921a42 100644 Binary files a/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/workpieceHolePositioning.pdb and b/AppAlgo/workpieceHolePositioning/Windows/x64/Debug/workpieceHolePositioning.pdb differ diff --git a/AppAlgo/workpieceHolePositioning/Windows/x64/Release/baseAlgorithm.dll b/AppAlgo/workpieceHolePositioning/Windows/x64/Release/baseAlgorithm.dll index 0a6b28cc..de720dd4 100644 Binary files a/AppAlgo/workpieceHolePositioning/Windows/x64/Release/baseAlgorithm.dll and b/AppAlgo/workpieceHolePositioning/Windows/x64/Release/baseAlgorithm.dll differ diff --git a/AppAlgo/workpieceHolePositioning/Windows/x64/Release/baseAlgorithm.lib b/AppAlgo/workpieceHolePositioning/Windows/x64/Release/baseAlgorithm.lib index e20123e5..b140c0eb 100644 Binary files a/AppAlgo/workpieceHolePositioning/Windows/x64/Release/baseAlgorithm.lib and b/AppAlgo/workpieceHolePositioning/Windows/x64/Release/baseAlgorithm.lib differ diff --git a/AppAlgo/workpieceHolePositioning/Windows/x64/Release/baseAlgorithm.pdb b/AppAlgo/workpieceHolePositioning/Windows/x64/Release/baseAlgorithm.pdb index 27b41b37..35e79238 100644 Binary files a/AppAlgo/workpieceHolePositioning/Windows/x64/Release/baseAlgorithm.pdb and b/AppAlgo/workpieceHolePositioning/Windows/x64/Release/baseAlgorithm.pdb differ diff --git a/AppAlgo/workpieceHolePositioning/Windows/x64/Release/workpieceHolePositioning.dll b/AppAlgo/workpieceHolePositioning/Windows/x64/Release/workpieceHolePositioning.dll index 3ae134fc..8e182fc1 100644 Binary files a/AppAlgo/workpieceHolePositioning/Windows/x64/Release/workpieceHolePositioning.dll and b/AppAlgo/workpieceHolePositioning/Windows/x64/Release/workpieceHolePositioning.dll differ diff --git a/AppAlgo/workpieceHolePositioning/Windows/x64/Release/workpieceHolePositioning.lib b/AppAlgo/workpieceHolePositioning/Windows/x64/Release/workpieceHolePositioning.lib index 4d8f40da..af37d9dc 100644 Binary files a/AppAlgo/workpieceHolePositioning/Windows/x64/Release/workpieceHolePositioning.lib and b/AppAlgo/workpieceHolePositioning/Windows/x64/Release/workpieceHolePositioning.lib differ diff --git a/AppAlgo/workpieceHolePositioning/Windows/x64/Release/workpieceHolePositioning.pdb b/AppAlgo/workpieceHolePositioning/Windows/x64/Release/workpieceHolePositioning.pdb index 88219782..fc581d78 100644 Binary files a/AppAlgo/workpieceHolePositioning/Windows/x64/Release/workpieceHolePositioning.pdb and b/AppAlgo/workpieceHolePositioning/Windows/x64/Release/workpieceHolePositioning.pdb differ diff --git a/AppAlgo/workpieceHolePositioning/workpieceHolePositioning_test.cpp b/AppAlgo/workpieceHolePositioning/workpieceHolePositioning_test.cpp index b67cd842..a668473a 100644 --- a/AppAlgo/workpieceHolePositioning/workpieceHolePositioning_test.cpp +++ b/AppAlgo/workpieceHolePositioning/workpieceHolePositioning_test.cpp @@ -189,7 +189,7 @@ void _outputWorkpieceInfo(char* fileName, std::vector< WD_workpieceInfo>& workpi int number = (int)workpiecePositioning.size(); for (int i = 0; i < number; i++) { - sprintf_s(dataStr, 250, "工件_%d", i + 1); + sprintf_s(dataStr, 250, "工件_%d: type=%x", i + 1, workpiecePositioning[i].workpieceType); sw << dataStr << std::endl; int holeNumber = (int)workpiecePositioning[i].holes.size(); for (int j = 0; j < holeNumber; j++) @@ -947,11 +947,15 @@ void _XOYprojection( if (i == 0) { rgb = { 255, 0, 0 }; + if((holes[i].workpieceType&0xffff0000) > 0)//方向为反 + rgb = { 255, 0, 255 }; size = int(3.0/scale); } else { rgb = { 255, 255, 0 }; + if ((holes[i].workpieceType & 0xffff0000) > 0)//方向为反 + rgb = { 0, 255, 255 }; size = int(3.0 / scale); } WD_workpieceInfo& a_hole = holes[i]; @@ -1569,7 +1573,7 @@ void TuoPuFa_holePosition_test3(void) }; SVzNLRange fileIdx[TPF3_TEST_GROUP] = { - {1,10}, {1, 10}, {1,10}, {1,10}, {1, 10}, + {1,11}, {1, 10}, {1,10}, {1,10}, {1, 10}, {1,10}, {1, 10}, }; @@ -1635,7 +1639,7 @@ void TuoPuFa_holePosition_test3(void) for (int fidx = fileIdx[grp].nMin; fidx <= fileIdx[grp].nMax; fidx++) { - //fidx = 2; + //fidx = 3; char _scan_file[256]; sprintf_s(_scan_file, "%s%d_LaserData_Jh26B074.txt", dataPath[grp], fidx); std::vector> scanLines; @@ -2736,11 +2740,11 @@ int main() TuoPuFa_holePosition_test5(); else if (keSG_拓普发孔定位_工件全测试 == testMode) { - TuoPuFa_holePosition_test(); - TuoPuFa_holePosition_test2(); + //TuoPuFa_holePosition_test(); + //TuoPuFa_holePosition_test2(); TuoPuFa_holePosition_test3(); TuoPuFa_holePosition_test4(); - TuoPuFa_holePosition_test5(); + //TuoPuFa_holePosition_test5(); } else if (keSG_华航孔定位 == testMode) HuaHang_holePosition_test(); diff --git a/AppUtils/UICommon/Inc/ToolExtrinsicWidget.h b/AppUtils/UICommon/Inc/ToolExtrinsicWidget.h index ebf8b60c..d2691b27 100644 --- a/AppUtils/UICommon/Inc/ToolExtrinsicWidget.h +++ b/AppUtils/UICommon/Inc/ToolExtrinsicWidget.h @@ -90,6 +90,9 @@ public: double& axialAngleMin, double& axialAngleMax) const; int currentCameraIndex() const; + bool hasAcceptableInput() const; + // 单条目编辑场景可隐藏控件内置选择器,由外部选择器驱动 setData/getData。 + void setCameraSelectorVisible(bool visible); void setTargetOffsetVisible(bool visible); void setAxialAngleRangeVisible(bool visible); @@ -108,6 +111,7 @@ private: Ui::ToolExtrinsicWidget* ui = nullptr; QGroupBox* m_groupExtrinsic; + QLabel* m_labelCamera; QComboBox* m_comboCamera; QComboBox* m_comboEulerOrder; QLineEdit* m_editRotX; diff --git a/AppUtils/UICommon/Src/ToolExtrinsicWidget.cpp b/AppUtils/UICommon/Src/ToolExtrinsicWidget.cpp index 2c59f799..39be281a 100644 --- a/AppUtils/UICommon/Src/ToolExtrinsicWidget.cpp +++ b/AppUtils/UICommon/Src/ToolExtrinsicWidget.cpp @@ -10,6 +10,7 @@ ToolExtrinsicWidget::ToolExtrinsicWidget(QWidget *parent) : QWidget(parent) , ui(new Ui::ToolExtrinsicWidget) , m_groupExtrinsic(nullptr) + , m_labelCamera(nullptr) , m_comboCamera(nullptr) , m_comboEulerOrder(nullptr) , m_editRotX(nullptr) @@ -206,6 +207,29 @@ int ToolExtrinsicWidget::currentCameraIndex() const return m_comboCamera->currentData().toInt(); } +bool ToolExtrinsicWidget::hasAcceptableInput() const +{ + const auto isAcceptable = [](const QLineEdit* edit) { + return !edit || edit->hasAcceptableInput(); + }; + + return m_comboEulerOrder && m_comboEulerOrder->currentIndex() >= 0 + && isAcceptable(m_editRotX) + && isAcceptable(m_editRotY) + && isAcceptable(m_editRotZ) + && isAcceptable(m_editAxialAngleMin) + && isAcceptable(m_editAxialAngleMax) + && isAcceptable(m_editOffsetX) + && isAcceptable(m_editOffsetY) + && isAcceptable(m_editOffsetZ); +} + +void ToolExtrinsicWidget::setCameraSelectorVisible(bool visible) +{ + if (m_labelCamera) m_labelCamera->setVisible(visible); + if (m_comboCamera) m_comboCamera->setVisible(visible); +} + void ToolExtrinsicWidget::setTargetOffsetVisible(bool visible) { if (m_labelTargetOffset) m_labelTargetOffset->setVisible(visible); @@ -332,6 +356,7 @@ void ToolExtrinsicWidget::setupUI() } m_groupExtrinsic = ui->groupExtrinsic; + m_labelCamera = ui->labelCamera; m_comboCamera = ui->comboCamera; m_comboEulerOrder = ui->comboEulerOrder; m_editRotX = ui->editRotX; diff --git a/Device b/Device index 76d33caa..1136a522 160000 --- a/Device +++ b/Device @@ -1 +1 @@ -Subproject commit 76d33caae885f1b47eec264f65f8e5f76cc131d0 +Subproject commit 1136a52250ae2595291f231492c770b29fe3354f diff --git a/GrabBagPrj/AppList.md b/GrabBagPrj/AppList.md index 0f7cfe5b..b28776bb 100644 --- a/GrabBagPrj/AppList.md +++ b/GrabBagPrj/AppList.md @@ -17,7 +17,7 @@ | 11 | 铁路隧道槽道测量 | TunnelChannel | 1.0.0.3 | | 12 | 螺杆定位 | ScrewPosition | 1.2.4.1 | | 13 | 包裹拆线位置定位 | BagThreadPosition | 1.0.0.4 | -| 14 | 工件孔定位 | WorkpieceHole | 1.2.1.1 | +| 14 | 工件孔定位 | WorkpieceHole | 1.2.2.2 | | 15 | 轮胎孔定位 | TireHolePose | 1.0.2.1 | | 16 | 坑孔定位 | HolePitPosition | 无 | | 17 | 钢筋焊缝定位 | RodWeldSeam | 1.0.0.1 |