diff --git a/App/App.pro b/App/App.pro index 77e8147d..9332290f 100644 --- a/App/App.pro +++ b/App/App.pro @@ -5,7 +5,7 @@ TEMPLATE = subdirs # 可用值:GrabBag, # BeltTearing, # LapWeld, Workpiece, ParticleSize, -# BinocularMark, WorkpieceProject, TunnelChannel, WheelMeasure, ScrewPosition, BagThreadPosition, FireBrickPosition, WorkpieceHole, StatorPosition, HoleDetection, RodAndBarPosition +# BinocularMark, WorkpieceProject, TunnelChannel, WheelMeasure, ScrewPosition, BagThreadPosition, FireBrickPosition, WorkpieceHole, StatorPosition, HoleDetection, RodAndBarPosition, RodWeldSeam isEmpty(TARGET_APP) { # 未指定则编译全部 @@ -25,6 +25,7 @@ isEmpty(TARGET_APP) { SUBDIRS += HoleDetection/HoleDetection.pro # 孔洞检测 SUBDIRS += HolePitPosition/HolePitPosition.pro # 坑孔定位 SUBDIRS += RodAndBarPosition/RodAndBarPosition.pro # 棒材定位 + SUBDIRS += RodWeldSeam/RodWeldSeam.pro # 钢筋焊缝定位 } else { equals(TARGET_APP, "GrabBag") { SUBDIRS += GrabBag/GrabBag.pro @@ -58,6 +59,8 @@ isEmpty(TARGET_APP) { SUBDIRS += HolePitPosition/HolePitPosition.pro } else:equals(TARGET_APP, "RodAndBarPosition") { SUBDIRS += RodAndBarPosition/RodAndBarPosition.pro + } else:equals(TARGET_APP, "RodWeldSeam") { + SUBDIRS += RodWeldSeam/RodWeldSeam.pro } else { error("无效的 TARGET_APP: $$TARGET_APP") } diff --git a/App/NewApp.txt b/App/NewApp.txt index 1fea1c41..f90a0957 100644 --- a/App/NewApp.txt +++ b/App/NewApp.txt @@ -1,10 +1,10 @@ -App下新建StatorPosition项目 - 1. 复制workpieceHole进行修改 - 2. 使用的算法是 @AppAlgo/motorStatorPosition +App下新建RodWeldSeam项目 + 1. 复制RodAndBarPosition进行修改 + 2. 使用的算法是 @AppAlgo/rodAndBarDetection 3. 使用VzNLSDK 设备 - 4. 结果参考算法目录下*test.cpp 参数也参考这个内容 + 4. 结果参考算法目录下*test.cpp rodWeldSeamPosition_test接口的测试,参数也参考这个内容 5. 结果列表进行更新 6. 根据算法需要的参数进行修改config,并实现从页面进行修改参数 7. App.pro 中增加编译选项,并且在GrabBagPrj/buildArmPrj.sh编译选项 8. 增加arm下的pkg脚本 - 9. 项目的协议文档也给搞一份 \ No newline at end of file + 9. 项目的协议文档也给搞一份ModbusTCP的 \ No newline at end of file diff --git a/App/ScrewPosition/ScrewPositionApp/Version.h b/App/ScrewPosition/ScrewPositionApp/Version.h index 3287c968..f7ef6558 100644 --- a/App/ScrewPosition/ScrewPositionApp/Version.h +++ b/App/ScrewPosition/ScrewPositionApp/Version.h @@ -3,7 +3,7 @@ #define SCREWPOSITION_APP_NAME "螺杆定位" -#define SCREWPOSITION_VERSION_STRING "1.1.9" +#define SCREWPOSITION_VERSION_STRING "1.1.10" #define SCREWPOSITION_BUILD_STRING "1" #define SCREWPOSITION_FULL_VERSION_STRING "V" SCREWPOSITION_VERSION_STRING "_" SCREWPOSITION_BUILD_STRING diff --git a/App/ScrewPosition/ScrewPositionApp/Version.md b/App/ScrewPosition/ScrewPositionApp/Version.md index 17bad589..115591f8 100644 --- a/App/ScrewPosition/ScrewPositionApp/Version.md +++ b/App/ScrewPosition/ScrewPositionApp/Version.md @@ -1,3 +1,7 @@ +# 1.1.10 2026-05-15 +## build_1 +1. 更新算法:螺杆检测 + # 1.1.8 2026-05-13 ## build_1 1. 更新算法:螺杆检测 diff --git a/App/ScrewPosition/ScrewPositionConfig/Inc/IVrConfig.h b/App/ScrewPosition/ScrewPositionConfig/Inc/IVrConfig.h index 33f1b775..4c47c864 100644 --- a/App/ScrewPosition/ScrewPositionConfig/Inc/IVrConfig.h +++ b/App/ScrewPosition/ScrewPositionConfig/Inc/IVrConfig.h @@ -22,7 +22,7 @@ struct VrCornerParam double minEndingGap = 20.0; double minEndingGap_z = 5.0; double scale = 2.5; - double cornerTh = 60.0; + double cornerTh = 30.0; double jumpCornerTh_1 = 15.0; double jumpCornerTh_2 = 60.0; }; @@ -35,12 +35,12 @@ struct VrOutlierFilterParam struct VrTreeGrowParam { - double yDeviation_max = 20.0; + double yDeviation_max = 5.0; double zDeviation_max = 50.0; int maxLineSkipNum = 10; - double maxSkipDistance = 20.0; - double minLTypeTreeLen = 10.0; - double minVTypeTreeLen = 10.0; + double maxSkipDistance = 30.0; + double minLTypeTreeLen = 50.0; + double minVTypeTreeLen = 50.0; }; struct VrAlgorithmParams diff --git a/App/WheelMeasure/WheelMeasureApp/Presenter/Inc/WheelMeasurePresenter.h b/App/WheelMeasure/WheelMeasureApp/Presenter/Inc/WheelMeasurePresenter.h index 0b6913b5..24dca867 100644 --- a/App/WheelMeasure/WheelMeasureApp/Presenter/Inc/WheelMeasurePresenter.h +++ b/App/WheelMeasure/WheelMeasureApp/Presenter/Inc/WheelMeasurePresenter.h @@ -141,11 +141,6 @@ public: */ void StopAllDetection(); - /** - * @brief 检查是否正在进行顺序检测 - */ - bool IsSequentialDetecting() const { return m_sequentialDetecting; } - /** * @brief 设置默认相机索引 */ @@ -194,19 +189,10 @@ private: IWheelMeasureStatus* m_statusUpdate = nullptr; int m_currentCameraIndex = 1; // 默认相机索引(1-based) - // 顺序检测相关 - bool m_sequentialDetecting = false; // 是否正在顺序检测所有设备 - bool m_stopSequentialRequested = false; // 是否请求停止顺序检测 - int m_sequentialCurrentIndex = 0; // 当前顺序检测的设备索引(0-based) - int m_sequentialTotalCount = 0; // 需要顺序检测的设备总数 - // TCP协议 WheelMeasureTCPProtocol m_tcpProtocol; // TCP服务器协议 bool m_tcpDetectionMode = false; // 是否为TCP触发的检测 QMap m_tcpResults; // TCP检测结果缓存 - - // 继续检测下一个设备 - void continueSequentialDetection(); }; #endif // WHEELMEASUREPRESENTER_H diff --git a/App/WheelMeasure/WheelMeasureApp/Presenter/Src/WheelMeasurePresenter.cpp b/App/WheelMeasure/WheelMeasureApp/Presenter/Src/WheelMeasurePresenter.cpp index 5ef3c16e..be674297 100644 --- a/App/WheelMeasure/WheelMeasureApp/Presenter/Src/WheelMeasurePresenter.cpp +++ b/App/WheelMeasure/WheelMeasureApp/Presenter/Src/WheelMeasurePresenter.cpp @@ -36,9 +36,8 @@ WheelMeasurePresenter::~WheelMeasurePresenter() // 清除状态回调,防止后续回调访问已销毁对象 m_statusUpdate = nullptr; - // 停止顺序检测 - m_stopSequentialRequested = true; - m_sequentialDetecting = false; + // 停止检测 + StopDetection(); // 处理待处理的 Qt 事件,确保 QueuedConnection 的回调不会访问已销毁对象 QCoreApplication::processEvents(); @@ -65,6 +64,9 @@ int WheelMeasurePresenter::InitApp() return ERR_CODE(DEV_CONFIG_ERR); } + // 传入扫描配置到基类 + SetScanConfig(m_configResult.scanConfig); + // 初始化相机 if (!initializeCameras()) { LOG_ERROR("Failed to initialize cameras\n"); @@ -168,6 +170,14 @@ void WheelMeasurePresenter::OnWorkStatusChanged(WorkStatus status) m_statusUpdate->OnWorkStatusChanged(status); } }, Qt::QueuedConnection); + + // TCP模式下检测完成,发送所有结果 + if (status == WorkStatus::Completed && m_tcpDetectionMode) { + LOG_INFO("TCP模式:所有相机检测完成,准备发送结果\n"); + QMetaObject::invokeMethod(this, [this]() { + sendTCPMeasureResults(); + }, Qt::QueuedConnection); + } } void WheelMeasurePresenter::OnCameraCountChanged(int count) @@ -285,117 +295,22 @@ void WheelMeasurePresenter::ResetDetect(int cameraIndex) void WheelMeasurePresenter::StartAllDetection() { - LOG_INFO("Starting sequential detection for all cameras\n"); - - // 计算启用的相机数量 - m_sequentialTotalCount = 0; - for (const auto& cameraConfig : m_configResult.cameras) { - if (cameraConfig.enabled) { - m_sequentialTotalCount++; - } - } - - if (m_sequentialTotalCount == 0) { - LOG_WARNING("No enabled cameras to detect\n"); - if (m_statusUpdate) { - m_statusUpdate->OnStatusUpdate(QString("没有可用的相机设备")); - } - return; - } - - // 初始化顺序检测状态 - m_sequentialDetecting = true; - m_stopSequentialRequested = false; - m_sequentialCurrentIndex = 0; + LOG_INFO("Starting all cameras detection\n"); // 清空之前的检测结果 if (m_statusUpdate) { m_statusUpdate->OnClearMeasureData(); + m_statusUpdate->OnStatusUpdate(QString("开始所有设备的检测")); } - LOG_INFO("Sequential detection started, total cameras: %d\n", m_sequentialTotalCount); - - - m_statusUpdate->OnStatusUpdate(QString("开始所有设备的检测")); - - // 开始检测第一个设备 - continueSequentialDetection(); + // 委托基类,根据 m_scanConfig.simultaneousCount 自动决定扫描策略 + StartDetection(-1); } void WheelMeasurePresenter::StopAllDetection() { - LOG_INFO("Stop sequential detection requested\n"); - - if (m_sequentialDetecting) { - // 设置停止标志,等待当前设备检测完成后停止 - m_stopSequentialRequested = true; - if (m_statusUpdate) { - m_statusUpdate->OnStatusUpdate(QString("正在完成当前设备检测,之后将停止...")); - } - } else { - // 如果不是顺序检测模式,直接停止 - StopDetection(); - } -} - -void WheelMeasurePresenter::continueSequentialDetection() -{ - // 检查是否应该停止 - if (m_stopSequentialRequested) { - LOG_INFO("Sequential detection stopped by user request\n"); - m_sequentialDetecting = false; - m_stopSequentialRequested = false; - if (m_statusUpdate) { - m_statusUpdate->OnStatusUpdate(QString("顺序检测已停止")); - } - SetWorkStatus(WorkStatus::Ready); - return; - } - - // 检查是否还有设备需要检测 - if (m_sequentialCurrentIndex >= m_sequentialTotalCount) { - LOG_INFO("Sequential detection completed, all %d cameras processed\n", m_sequentialTotalCount); - m_sequentialDetecting = false; - if (m_statusUpdate) { - m_statusUpdate->OnStatusUpdate(QString("所有 %1 个设备检测完成").arg(m_sequentialTotalCount)); - } - SetWorkStatus(WorkStatus::Completed); - return; - } - - // 获取当前要检测的相机索引(1-based) - int cameraIndex = m_sequentialCurrentIndex + 1; - m_currentCameraIndex = cameraIndex; - - // 获取相机名称 - QString cameraName; - int enabledIndex = 0; - for (const auto& cameraConfig : m_configResult.cameras) { - if (cameraConfig.enabled) { - enabledIndex++; - if (enabledIndex == cameraIndex) { - cameraName = QString::fromStdString(cameraConfig.name); - break; - } - } - } - - LOG_INFO("Starting detection for camera %d/%d: %s\n", - m_sequentialCurrentIndex + 1, m_sequentialTotalCount, - cameraName.toStdString().c_str()); - - if (m_statusUpdate) { - m_statusUpdate->OnStatusUpdate(QString("正在检测设备 %1/%2: %3") - .arg(m_sequentialCurrentIndex + 1) - .arg(m_sequentialTotalCount) - .arg(cameraName)); - } - - // 清空数据缓存 - ClearDetectionDataCache(); - - // 开始检测当前相机 - StartDetection(cameraIndex); + LOG_INFO("Stop all detection requested\n"); + StopDetection(); } void WheelMeasurePresenter::OnConfigChanged(const WheelMeasureConfigResult& configResult) @@ -403,7 +318,8 @@ void WheelMeasurePresenter::OnConfigChanged(const WheelMeasureConfigResult& conf LOG_INFO("Config changed notification received\n"); m_configResult = configResult; - // 更新基类调试参数 + // 更新基类扫描配置和调试参数 + SetScanConfig(m_configResult.scanConfig); SetDebugParam(m_configResult.debugParam); emit configUpdated(); @@ -505,13 +421,6 @@ void WheelMeasurePresenter::processScanData(std::vector= m_sequentialTotalCount) { - LOG_INFO("TCP模式:所有相机检测完成,准备发送结果\n"); - m_sequentialDetecting = false; - sendTCPMeasureResults(); - } else { - // 继续检测下一个设备 - QMetaObject::invokeMethod(this, [this]() { - continueSequentialDetection(); - }, Qt::QueuedConnection); - } - } - return; } @@ -819,8 +711,6 @@ void WheelMeasurePresenter::processScanData(std::vectorOnMeasureResult(result); } - SetWorkStatus(WorkStatus::Completed); - // 如果是TCP触发的检测,缓存结果 if (m_tcpDetectionMode) { WheelMeasureTCPProtocol::CameraMeasureResult tcpResult; @@ -842,25 +732,6 @@ void WheelMeasurePresenter::processScanData(std::vector= m_sequentialTotalCount) { - LOG_INFO("TCP模式:所有相机检测完成,准备发送结果\n"); - // 先结束顺序检测 - m_sequentialDetecting = false; - // 发送TCP结果 - sendTCPMeasureResults(); - } else { - // 继续检测下一个设备 - QMetaObject::invokeMethod(this, [this]() { - continueSequentialDetection(); - }, Qt::QueuedConnection); - } - } } WheelCameraPlaneCalibParam* WheelMeasurePresenter::getPlaneCalibParam(int cameraIndex) @@ -1063,13 +934,19 @@ bool WheelMeasurePresenter::onTCPDetectionTriggered(int param) void WheelMeasurePresenter::sendTCPMeasureResults() { + // 计算期望的相机数量 + int expectedCount = 0; + for (const auto& cameraConfig : m_configResult.cameras) { + if (cameraConfig.enabled) expectedCount++; + } + LOG_INFO("发送TCP测量结果,共 %d 个相机\n", m_tcpResults.size()); // 构建结果向量(按相机ID排序) std::vector results; // 按相机ID顺序添加结果 - for (int cameraId = 1; cameraId <= m_sequentialTotalCount; ++cameraId) { + for (int cameraId = 1; cameraId <= expectedCount; ++cameraId) { if (m_tcpResults.contains(cameraId)) { results.push_back(m_tcpResults[cameraId]); } else { diff --git a/App/WheelMeasure/WheelMeasureApp/dialogcamera.cpp b/App/WheelMeasure/WheelMeasureApp/dialogcamera.cpp index 2d825700..e3a32725 100644 --- a/App/WheelMeasure/WheelMeasureApp/dialogcamera.cpp +++ b/App/WheelMeasure/WheelMeasureApp/dialogcamera.cpp @@ -19,9 +19,67 @@ DialogCamera::DialogCamera(QWidget *parent) : // 隐藏标题栏 // setWindowFlags(Qt::FramelessWindowHint); + // 增加对话框高度以容纳新控件 + this->resize(700, 600); + // 初始化表格 InitTable(); + // ===== 添加扫描配置区域 ===== + QGroupBox* groupScan = new QGroupBox("扫描配置", this); + groupScan->setGeometry(20, 460, 660, 60); + groupScan->setStyleSheet( + "QGroupBox {" + " color: rgb(221, 225, 233);" + " font-size: 14px;" + " border: 1px solid rgb(60, 62, 70);" + " border-radius: 4px;" + " margin-top: 10px;" + " padding-top: 15px;" + "}" + "QGroupBox::title {" + " subcontrol-origin: margin;" + " left: 10px;" + " padding: 0 5px;" + "}" + ); + + QLabel* labelScan = new QLabel("同时扫描数量:", groupScan); + labelScan->setGeometry(15, 22, 130, 28); + labelScan->setStyleSheet("color: rgb(221, 225, 233); font-size: 14px;"); + + m_spinSimultaneousCount = new QSpinBox(groupScan); + m_spinSimultaneousCount->setGeometry(155, 18, 180, 32); + m_spinSimultaneousCount->setMinimum(0); // 0 = 全部同时 + m_spinSimultaneousCount->setMaximum(4); // 最多4个相机 + m_spinSimultaneousCount->setValue(1); // 默认单相机 + m_spinSimultaneousCount->setStyleSheet( + "QSpinBox {" + " color: rgb(221, 225, 233);" + " background-color: rgb(47, 48, 52);" + " border: 1px solid rgb(60, 62, 70);" + " border-radius: 3px;" + " padding: 3px 8px;" + " font-size: 14px;" + "}" + "QSpinBox::up-button, QSpinBox::down-button {" + " width: 20px;" + "}" + ); + m_spinSimultaneousCount->setToolTip( + "0 = 全部相机同时扫描\n" + "1 = 单相机顺序扫描\n" + "2-4 = 每批N个相机同时扫描" + ); + + QLabel* labelHint = new QLabel("0=全部同时, 1=单相机, 2-4=每批N个", groupScan); + labelHint->setGeometry(350, 22, 290, 28); + labelHint->setStyleSheet("color: rgb(140, 142, 150); font-size: 12px;"); + + // 调整保存和取消按钮位置 + ui->btn_save->move(170, 540); + ui->btn_cancel->move(370, 540); + // 加载已有的相机配置 LoadExistingCameras(); @@ -97,6 +155,11 @@ void DialogCamera::LoadExistingCameras() m_cameraConfigs.push_back(camera); } LOG_INFO("Loaded %zu existing cameras from config\n", m_cameraConfigs.size()); + + // 加载扫描配置 + if (m_spinSimultaneousCount) { + m_spinSimultaneousCount->setValue(configResult->scanConfig.simultaneousCount); + } } else { LOG_WARNING("ConfigResult is null\n"); } @@ -457,12 +520,20 @@ bool DialogCamera::SaveConfigToFile() camera.cameraIP.c_str()); } + // 更新扫描配置 + if (m_spinSimultaneousCount) { + configResult->scanConfig.simultaneousCount = m_spinSimultaneousCount->value(); + LOG_INFO(" simultaneousCount: %d\n", configResult->scanConfig.simultaneousCount); + } + // 保存配置到文件 std::string configFilePath = PathManager::GetInstance().GetConfigFilePath().toStdString(); bool saveResult = vrConfig->SaveConfig(configFilePath, *configResult); if (saveResult) { LOG_INFO("Camera configuration saved successfully to: %s\n", configFilePath.c_str()); + // 同步扫描配置到BasePresenter + m_presenter->SetScanConfig(configResult->scanConfig); } else { LOG_ERROR("Failed to save camera configuration to file\n"); } diff --git a/App/WheelMeasure/WheelMeasureApp/dialogcamera.h b/App/WheelMeasure/WheelMeasureApp/dialogcamera.h index 4404e21d..4f228692 100644 --- a/App/WheelMeasure/WheelMeasureApp/dialogcamera.h +++ b/App/WheelMeasure/WheelMeasureApp/dialogcamera.h @@ -4,6 +4,9 @@ #include #include #include +#include +#include +#include #include #include #include "IVrWheelMeasureConfig.h" @@ -70,6 +73,7 @@ private: std::vector m_cameraConfigs; // 相机配置列表 const int MAX_CAMERAS = 4; // 最多支持4个相机 WheelMeasurePresenter* m_presenter = nullptr; // Presenter用于访问配置 + QSpinBox* m_spinSimultaneousCount = nullptr; // 同时扫描数量 }; #endif // DIALOGCAMERA_H diff --git a/App/WheelMeasure/WheelMeasureApp/dialogcameralevel.cpp b/App/WheelMeasure/WheelMeasureApp/dialogcameralevel.cpp index bdf5068e..6d268085 100644 --- a/App/WheelMeasure/WheelMeasureApp/dialogcameralevel.cpp +++ b/App/WheelMeasure/WheelMeasureApp/dialogcameralevel.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -25,6 +26,46 @@ DialogCameraLevel::DialogCameraLevel(QWidget *parent) { ui->setupUi(this); + // 创建地面高度编辑控件 + QFont font14("Arial", 14); + QFont font14Btn("Arial", 14); + + QLabel* labelPlaneHeight = new QLabel("地面高度:", this); + labelPlaneHeight->setFont(font14); + labelPlaneHeight->setStyleSheet("color: rgb(221, 225, 233);"); + labelPlaneHeight->setGeometry(130, 340, 100, 31); + + m_editPlaneHeight = new QLineEdit(this); + m_editPlaneHeight->setFont(font14); + m_editPlaneHeight->setStyleSheet("color: rgb(221, 225, 233); background-color: rgb(47, 48, 52);"); + m_editPlaneHeight->setGeometry(230, 340, 100, 31); + m_editPlaneHeight->setAlignment(Qt::AlignCenter); + + QLabel* labelPlaneHeightUnit = new QLabel("mm", this); + labelPlaneHeightUnit->setFont(QFont("Arial", 13)); + labelPlaneHeightUnit->setStyleSheet("color: rgb(221, 225, 233);"); + labelPlaneHeightUnit->setGeometry(340, 340, 40, 31); + + m_btnSavePlaneHeight = new QPushButton("保存", this); + m_btnSavePlaneHeight->setFont(font14Btn); + m_btnSavePlaneHeight->setMinimumSize(60, 31); + m_btnSavePlaneHeight->setMaximumSize(60, 31); + m_btnSavePlaneHeight->setGeometry(390, 340, 60, 31); + m_btnSavePlaneHeight->setStyleSheet( + "QPushButton {" + " color: rgb(221, 225, 233);" + " background-color: rgb(60, 63, 65);" + " border: 1px solid #3B3D47;" + " border-radius: 4px;" + "}" + "QPushButton:hover {" + " background-color: rgb(80, 83, 85);" + "}" + "QPushButton:pressed {" + " background-color: rgb(45, 48, 50);" + "}"); + connect(m_btnSavePlaneHeight, &QPushButton::clicked, this, &DialogCameraLevel::on_btn_save_planeHeight_clicked); + // 初始化结果显示区域 ui->label_level_result->setText("请选择相机,然后点击调平按钮\n开始相机调平操作"); ui->label_level_result->setAlignment(Qt::AlignCenter); @@ -67,8 +108,13 @@ void DialogCameraLevel::setConfig(IVrWheelMeasureConfig* config, WheelMeasureCon m_pConfig = config; m_pConfigResult = configResult; + // 重新打开页面时,从文件重新加载配置以获取最新数据 + if (m_pConfig && m_pConfigResult) { + QString configPath = PathManager::GetInstance().GetConfigFilePath(); + *m_pConfigResult = m_pConfig->LoadConfig(configPath.toStdString()); + } + // 如果相机已经选择,重新加载当前相机的标定状态 - // 修复:打开页面时配置可能在相机列表之后设置,导致初始加载失败 if (m_currentCameraIndex >= 0 && m_currentCameraIndex < static_cast(m_cameraList.size())) { checkAndDisplayCalibrationStatus(m_currentCameraIndex); loadCameraRoiRange(m_currentCameraIndex); @@ -281,6 +327,9 @@ bool DialogCameraLevel::performCameraLeveling() void DialogCameraLevel::updateLevelingResults(double planeCalib[9], double planeHeight, double invRMatrix[9]) { + // 更新地面高度编辑框 + m_editPlaneHeight->setText(QString::number(planeHeight, 'f', 2)); + // 构建显示文本 QString resultText; @@ -568,7 +617,8 @@ bool DialogCameraLevel::loadCameraCalibrationData(int cameraIndex, const QString } planeHeight = param.planeHeight; - // 加载该相机的误差补偿值到UI + // 加载该相机的地面高度和误差补偿值到UI + m_editPlaneHeight->setText(QString::number(param.planeHeight, 'f', 2)); ui->edit_error_compensation->setText(QString::number(param.errorCompensation, 'f', 1)); LOG_INFO("Calibration data loaded successfully for camera %d (%s)\n", @@ -611,9 +661,10 @@ void DialogCameraLevel::checkAndDisplayCalibrationStatus(int cameraIndex) LOG_INFO("Displaying existing calibration data for camera %s\n", cameraName.toUtf8().constData()); updateLevelingResults(planeCalib, planeHeight, invRMatrix); } else { - // 没有标定数据 - LOG_INFO("No calibration data found for camera %s\n", cameraName.toUtf8().constData()); - ui->label_level_result->setText(QString("相机: %1\n\n请点击调平按钮开始调平操作").arg(cameraName)); + // 没有标定数据,显示未标定 + LOG_INFO("No calibration data found for camera %s, showing uncalibrated\n", cameraName.toUtf8().constData()); + m_editPlaneHeight->setText("-1.00"); + ui->label_level_result->setText(QString("相机: %1\n\n未标定").arg(cameraName)); ui->label_level_result->setAlignment(Qt::AlignCenter); } } @@ -718,6 +769,52 @@ void DialogCameraLevel::on_btn_save_compensation_clicked() } } +void DialogCameraLevel::on_btn_save_planeHeight_clicked() +{ + if (!m_pConfig || !m_pConfigResult) { + LOG_ERROR("Config is null, cannot save plane height\n"); + return; + } + + if (m_currentCameraIndex < 0 || m_currentCameraIndex >= static_cast(m_cameraList.size())) { + LOG_WARNING("Invalid camera index: %d\n", m_currentCameraIndex); + return; + } + + double planeHeight = m_editPlaneHeight->text().toDouble(); + int cameraIndex = m_currentCameraIndex + 1; + + // 查找或创建相机调平参数 + bool found = false; + for (auto& param : m_pConfigResult->planeCalibParams) { + if (param.cameraIndex == cameraIndex) { + param.planeHeight = planeHeight; + param.isCalibrated = true; + found = true; + break; + } + } + + if (!found) { + WheelCameraPlaneCalibParam newParam; + newParam.cameraIndex = cameraIndex; + newParam.planeHeight = planeHeight; + newParam.isCalibrated = true; + m_pConfigResult->planeCalibParams.push_back(newParam); + } + + // 保存配置到文件 + QString configPath = PathManager::GetInstance().GetConfigFilePath(); + bool saveResult = m_pConfig->SaveConfig(configPath.toStdString(), *m_pConfigResult); + if (saveResult) { + LOG_INFO("Plane height saved successfully for camera %d: %.2f\n", cameraIndex, planeHeight); + // 刷新界面显示 + checkAndDisplayCalibrationStatus(m_currentCameraIndex); + } else { + LOG_ERROR("Failed to save plane height\n"); + } +} + void DialogCameraLevel::loadCameraRoiRange(int cameraIndex) { if (!m_pConfig || !m_pConfigResult) { diff --git a/App/WheelMeasure/WheelMeasureApp/dialogcameralevel.h b/App/WheelMeasure/WheelMeasureApp/dialogcameralevel.h index 785d08e3..b38f0637 100644 --- a/App/WheelMeasure/WheelMeasureApp/dialogcameralevel.h +++ b/App/WheelMeasure/WheelMeasureApp/dialogcameralevel.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -50,6 +51,7 @@ private slots: void on_combo_camera_currentIndexChanged(int index); void on_btn_save_compensation_clicked(); void on_btn_save_roi_clicked(); + void on_btn_save_planeHeight_clicked(); private: Ui::DialogCameraLevel *ui; @@ -62,6 +64,10 @@ private: IVrWheelMeasureConfig* m_pConfig = nullptr; WheelMeasureConfigResult* m_pConfigResult = nullptr; + // planeHeight编辑控件 + QLineEdit* m_editPlaneHeight = nullptr; + QPushButton* m_btnSavePlaneHeight = nullptr; + // 当前选中的相机索引 int m_currentCameraIndex = -1; diff --git a/App/WheelMeasure/WheelMeasureApp/dialogcameralevel.ui b/App/WheelMeasure/WheelMeasureApp/dialogcameralevel.ui index 031d9f49..6d790a9f 100644 --- a/App/WheelMeasure/WheelMeasureApp/dialogcameralevel.ui +++ b/App/WheelMeasure/WheelMeasureApp/dialogcameralevel.ui @@ -109,7 +109,7 @@ 130 110 511 - 271 + 221 diff --git a/App/WheelMeasure/WheelMeasureConfig/Inc/IVrWheelMeasureConfig.h b/App/WheelMeasure/WheelMeasureConfig/Inc/IVrWheelMeasureConfig.h index cc2dd639..d2c03c8a 100644 --- a/App/WheelMeasure/WheelMeasureConfig/Inc/IVrWheelMeasureConfig.h +++ b/App/WheelMeasure/WheelMeasureConfig/Inc/IVrWheelMeasureConfig.h @@ -139,6 +139,7 @@ struct WheelMeasureConfigResult std::vector servers; // 服务器列表 WheelMeasureAlgorithmParams algorithmParams; // 算法参数 VrDebugParam debugParam; // 调试参数(使用公共VrDebugParam) + VrScanConfig scanConfig; // 多相机同时扫描配置 int serverPort = 5900; // 服务器端口 int tcpPort = 5800; // TCP协议端口 diff --git a/App/WheelMeasure/WheelMeasureConfig/Src/VrWheelMeasureConfig.cpp b/App/WheelMeasure/WheelMeasureConfig/Src/VrWheelMeasureConfig.cpp index 1d968d76..74082a49 100644 --- a/App/WheelMeasure/WheelMeasureConfig/Src/VrWheelMeasureConfig.cpp +++ b/App/WheelMeasure/WheelMeasureConfig/Src/VrWheelMeasureConfig.cpp @@ -265,6 +265,16 @@ WheelMeasureConfigResult VrWheelMeasureConfig::LoadConfig(const std::string& fil xml.skipCurrentElement(); } + // 解析多相机扫描配置 + else if (xml.isStartElement() && xml.name() == "ScanConfig") { + result.scanConfig.simultaneousCount = + xml.attributes().value("simultaneousCount").toInt(); + if (result.scanConfig.simultaneousCount < 0) { + result.scanConfig.simultaneousCount = 1; // 默认值 + } + xml.skipCurrentElement(); + } + // 解析服务端配置 else if (xml.isStartElement() && xml.name() == "LocalServerConfig") { while (xml.readNextStartElement()) { @@ -429,6 +439,11 @@ bool VrWheelMeasureConfig::SaveConfig(const std::string& filePath, WheelMeasureC xml.writeAttribute("debugOutputPath", QString::fromStdString(configResult.debugParam.debugOutputPath)); xml.writeEndElement(); // DebugParam + // 保存多相机扫描配置 + xml.writeStartElement("ScanConfig"); + xml.writeAttribute("simultaneousCount", QString::number(configResult.scanConfig.simultaneousCount)); + xml.writeEndElement(); // ScanConfig + // 保存服务端配置 xml.writeStartElement("LocalServerConfig"); xml.writeStartElement("ServerPort"); diff --git a/AppAlgo/rodAndBarDetection/Arm/aarch64/libbaseAlgorithm.so b/AppAlgo/rodAndBarDetection/Arm/aarch64/libbaseAlgorithm.so index 8de4bd90..744760d5 100644 Binary files a/AppAlgo/rodAndBarDetection/Arm/aarch64/libbaseAlgorithm.so and b/AppAlgo/rodAndBarDetection/Arm/aarch64/libbaseAlgorithm.so differ diff --git a/AppAlgo/rodAndBarDetection/Arm/aarch64/librodAndBarDetection.so b/AppAlgo/rodAndBarDetection/Arm/aarch64/librodAndBarDetection.so index 31cff452..29a79fea 100644 Binary files a/AppAlgo/rodAndBarDetection/Arm/aarch64/librodAndBarDetection.so and b/AppAlgo/rodAndBarDetection/Arm/aarch64/librodAndBarDetection.so differ diff --git a/AppAlgo/rodAndBarDetection/Windows/x64/Debug/baseAlgorithm.dll b/AppAlgo/rodAndBarDetection/Windows/x64/Debug/baseAlgorithm.dll index 76b582d0..0e52c22a 100644 Binary files a/AppAlgo/rodAndBarDetection/Windows/x64/Debug/baseAlgorithm.dll and b/AppAlgo/rodAndBarDetection/Windows/x64/Debug/baseAlgorithm.dll differ diff --git a/AppAlgo/rodAndBarDetection/Windows/x64/Debug/baseAlgorithm.pdb b/AppAlgo/rodAndBarDetection/Windows/x64/Debug/baseAlgorithm.pdb index 8de5f896..74c28537 100644 Binary files a/AppAlgo/rodAndBarDetection/Windows/x64/Debug/baseAlgorithm.pdb and b/AppAlgo/rodAndBarDetection/Windows/x64/Debug/baseAlgorithm.pdb differ diff --git a/AppAlgo/rodAndBarDetection/Windows/x64/Debug/rodAndBarDetection.dll b/AppAlgo/rodAndBarDetection/Windows/x64/Debug/rodAndBarDetection.dll index 9e76517d..f9fdf117 100644 Binary files a/AppAlgo/rodAndBarDetection/Windows/x64/Debug/rodAndBarDetection.dll and b/AppAlgo/rodAndBarDetection/Windows/x64/Debug/rodAndBarDetection.dll differ diff --git a/AppAlgo/rodAndBarDetection/Windows/x64/Debug/rodAndBarDetection.pdb b/AppAlgo/rodAndBarDetection/Windows/x64/Debug/rodAndBarDetection.pdb index 7c20aeae..7e28e986 100644 Binary files a/AppAlgo/rodAndBarDetection/Windows/x64/Debug/rodAndBarDetection.pdb and b/AppAlgo/rodAndBarDetection/Windows/x64/Debug/rodAndBarDetection.pdb differ diff --git a/AppAlgo/rodAndBarDetection/Windows/x64/Release/baseAlgorithm.dll b/AppAlgo/rodAndBarDetection/Windows/x64/Release/baseAlgorithm.dll index 08427f3a..f496d714 100644 Binary files a/AppAlgo/rodAndBarDetection/Windows/x64/Release/baseAlgorithm.dll and b/AppAlgo/rodAndBarDetection/Windows/x64/Release/baseAlgorithm.dll differ diff --git a/AppAlgo/rodAndBarDetection/Windows/x64/Release/baseAlgorithm.pdb b/AppAlgo/rodAndBarDetection/Windows/x64/Release/baseAlgorithm.pdb index f6539018..4801f34a 100644 Binary files a/AppAlgo/rodAndBarDetection/Windows/x64/Release/baseAlgorithm.pdb and b/AppAlgo/rodAndBarDetection/Windows/x64/Release/baseAlgorithm.pdb differ diff --git a/AppAlgo/rodAndBarDetection/Windows/x64/Release/rodAndBarDetection.dll b/AppAlgo/rodAndBarDetection/Windows/x64/Release/rodAndBarDetection.dll index f1523275..f11aa48f 100644 Binary files a/AppAlgo/rodAndBarDetection/Windows/x64/Release/rodAndBarDetection.dll and b/AppAlgo/rodAndBarDetection/Windows/x64/Release/rodAndBarDetection.dll differ diff --git a/AppAlgo/rodAndBarDetection/Windows/x64/Release/rodAndBarDetection.pdb b/AppAlgo/rodAndBarDetection/Windows/x64/Release/rodAndBarDetection.pdb index bc8d2244..f3fa2712 100644 Binary files a/AppAlgo/rodAndBarDetection/Windows/x64/Release/rodAndBarDetection.pdb and b/AppAlgo/rodAndBarDetection/Windows/x64/Release/rodAndBarDetection.pdb differ diff --git a/AppAlgo/rodAndBarDetection/rodAndBarDetection_test.cpp b/AppAlgo/rodAndBarDetection/rodAndBarDetection_test.cpp index 03132034..669351b4 100644 --- a/AppAlgo/rodAndBarDetection/rodAndBarDetection_test.cpp +++ b/AppAlgo/rodAndBarDetection/rodAndBarDetection_test.cpp @@ -224,16 +224,24 @@ void _outputChanneltInfo(char* fileName, std::vector& screwInfo sw.close(); } -void _outputPlatePiseInfo(char* fileName, SSX_pointPoseInfo& centerInfo) +void _outputPlatePiseInfo(char* fileName, SSX_platePoseInfo& centerInfo) { std::ofstream sw(fileName); char dataStr[250]; - sprintf_s(dataStr, 250, "定位盘: center_( %g, %g, %g ), normalDir_( %g, %g, %g ), xDir_( %g, %g, %g ), yDir_( %g, %g, %g )", - centerInfo.center.x, centerInfo.center.y, centerInfo.center.z, - centerInfo.normalDir.x, centerInfo.normalDir.y, centerInfo.normalDir.z, - centerInfo.xDir.x, centerInfo.xDir.y, centerInfo.xDir.z, - centerInfo.yDir.x, centerInfo.yDir.y, centerInfo.yDir.z); - sw << dataStr << std::endl; + sprintf_s(dataStr, 250, "定位盘: \n"); + sw << dataStr << std::endl; + sprintf_s(dataStr, 250, " holeLT_(% g, % g, % g)\n", centerInfo.holeLT.x, centerInfo.holeLT.y, centerInfo.holeLT.z); + sw << dataStr << std::endl; + sprintf_s(dataStr, 250, " holeRB_(% g, % g, % g)\n", centerInfo.holeRB.x, centerInfo.holeRB.y, centerInfo.holeRB.z); + sw << dataStr << std::endl; + sprintf_s(dataStr, 250, " center_(% g, % g, % g)\n", centerInfo.center.x, centerInfo.center.y, centerInfo.center.z); + sw << dataStr << std::endl; + sprintf_s(dataStr, 250, " normalDir_(% g, % g, % g)\n", centerInfo.normalDir.x, centerInfo.normalDir.y, centerInfo.normalDir.z); + sw << dataStr << std::endl; + sprintf_s(dataStr, 250, " xDir_(% g, % g, % g)\n", centerInfo.xDir.x, centerInfo.xDir.y, centerInfo.xDir.z); + sw << dataStr << std::endl; + sprintf_s(dataStr, 250, " yDir_(% g, % g, % g)\n", centerInfo.yDir.x, centerInfo.yDir.y, centerInfo.yDir.z); + sw << dataStr << std::endl; sw.close(); } @@ -325,9 +333,13 @@ void _outputRGBDScan_RGBD( else if (pt3D->nPointIdx == 2) { rgb = { 250, 0, 0 }; - size = 5; + size = 3; + } + else if (pt3D->nPointIdx == 4) + { + rgb = { 250, 250, 0 }; + size = 6; } - else //if (pt3D->nPointIdx == 0) { rgb = { 200, 200, 200 }; @@ -366,16 +378,17 @@ void _outputRGBDScan_RGBD( //输出法向 size = 1; - double len = 60; + double len1 = 30; + double len2 = 200; lineIdx = 0; for (int i = 0; i < objNum; i++) { - SVzNL3DPoint pt0 = { screwInfo[i].center.x - len * screwInfo[i].axialDir.x, - screwInfo[i].center.y - len * screwInfo[i].axialDir.y, - screwInfo[i].center.z - len * screwInfo[i].axialDir.z }; - SVzNL3DPoint pt1 = { screwInfo[i].center.x + len * screwInfo[i].axialDir.x, - screwInfo[i].center.y + len * screwInfo[i].axialDir.y, - screwInfo[i].center.z + len * screwInfo[i].axialDir.z }; + SVzNL3DPoint pt0 = { screwInfo[i].center.x - len1 * screwInfo[i].axialDir.x, + screwInfo[i].center.y - len1 * screwInfo[i].axialDir.y, + screwInfo[i].center.z - len1 * screwInfo[i].axialDir.z }; + SVzNL3DPoint pt1 = { screwInfo[i].center.x + len2 * screwInfo[i].axialDir.x, + screwInfo[i].center.y + len2 * screwInfo[i].axialDir.y, + screwInfo[i].center.z + len2 * screwInfo[i].axialDir.z }; //显示法向量 sw << "Poly_" << lineIdx << "_2" << std::endl; sw << "{" << (float)pt0.x << "," << (float)pt0.y << "," << (float)pt0.z << "}-"; @@ -386,22 +399,6 @@ void _outputRGBDScan_RGBD( sw << "{" << (int)rgb.r << "," << (int)rgb.g << "," << (int)rgb.b << "," << size << "}" << std::endl; lineIdx++; } - //多输出一个,修正显示工具bug - SVzNL3DPoint pt0 = { screwInfo[0].center.x - len * screwInfo[0].axialDir.x, - screwInfo[0].center.y - len * screwInfo[0].axialDir.y, - screwInfo[0].center.z - len * screwInfo[0].axialDir.z }; - SVzNL3DPoint pt1 = { screwInfo[0].center.x + len * screwInfo[0].axialDir.x, - screwInfo[0].center.y + len * screwInfo[0].axialDir.y, - screwInfo[0].center.z + len * screwInfo[0].axialDir.z }; - //显示法向量 - sw << "Poly_" << lineIdx << "_2" << std::endl; - sw << "{" << (float)pt0.x << "," << (float)pt0.y << "," << (float)pt0.z << "}-"; - sw << "{0,0}-{0,0}-"; - sw << "{" << (int)rgb.r << "," << (int)rgb.g << "," << (int)rgb.b << "," << size << "}" << std::endl; - sw << "{" << pt1.x << "," << pt1.y << "," << pt1.z << "}-"; - sw << "{0,0}-{0,0}-"; - sw << "{" << (int)rgb.r << "," << (int)rgb.g << "," << (int)rgb.b << "," << size << "}" << std::endl; - lineIdx++; } sw.close(); } @@ -409,7 +406,7 @@ void _outputRGBDScan_RGBD( void _outputRGBDScan_RGBD_centerPose( char* fileName, std::vector>& scanLines, - SSX_pointPoseInfo& poseInfo + SSX_platePoseInfo& poseInfo ) { int lineNum = (int)scanLines.size(); @@ -477,7 +474,7 @@ void _outputRGBDScan_RGBD_centerPose( } { - sw << "Line_" << lineIdx << "_0_1" << std::endl; + sw << "Line_" << lineIdx << "_0_3" << std::endl; rgb = { 250, 0, 0 }; size = 8; float x = (float)poseInfo.center.x; @@ -487,14 +484,28 @@ void _outputRGBDScan_RGBD_centerPose( sw << "{0,0}-{0,0}-"; sw << "{" << rgb.r << "," << rgb.g << "," << rgb.b << "," << size << " }" << std::endl; + x = (float)poseInfo.holeLT.x; + y = (float)poseInfo.holeLT.y; + z = (float)poseInfo.holeLT.z; + sw << "{" << x << "," << y << "," << z << "}-"; + sw << "{0,0}-{0,0}-"; + sw << "{" << rgb.r << "," << rgb.g << "," << rgb.b << "," << size << " }" << std::endl; + + x = (float)poseInfo.holeRB.x; + y = (float)poseInfo.holeRB.y; + z = (float)poseInfo.holeRB.z; + sw << "{" << x << "," << y << "," << z << "}-"; + sw << "{0,0}-{0,0}-"; + sw << "{" << rgb.r << "," << rgb.g << "," << rgb.b << "," << size << " }" << std::endl; + //输出法向 size = 1; double len = 60; lineIdx = 0; { - SVzNL3DPoint pt0 = { poseInfo.center.x - len * poseInfo.normalDir.x, - poseInfo.center.y - len * poseInfo.normalDir.y, - poseInfo.center.z - len * poseInfo.normalDir.z }; + SVzNL3DPoint pt0 = { poseInfo.center.x, // - len * poseInfo.normalDir.x, + poseInfo.center.y, // - len * poseInfo.normalDir.y, + poseInfo.center.z }; // - len * poseInfo.normalDir.z }; SVzNL3DPoint pt1 = { poseInfo.center.x + len * poseInfo.normalDir.x, poseInfo.center.y + len * poseInfo.normalDir.y, poseInfo.center.z + len * poseInfo.normalDir.z }; @@ -535,6 +546,37 @@ void _outputRGBDScan_RGBD_centerPose( sw << "{0,0}-{0,0}-"; sw << "{" << (int)rgb.r << "," << (int)rgb.g << "," << (int)rgb.b << "," << size << "}" << std::endl; lineIdx++; + + rgb = { 0, 250, 0 }; + basePt = { poseInfo.holeLT.x - len * poseInfo.xDir.x, + poseInfo.holeLT.y - len * poseInfo.xDir.y, + poseInfo.holeLT.z - len * poseInfo.xDir.z }; + pt2 = { poseInfo.holeLT.x + len * poseInfo.xDir.x, + poseInfo.holeLT.y + len * poseInfo.xDir.y, + poseInfo.holeLT.z + len * poseInfo.xDir.z }; + sw << "Poly_" << lineIdx << "_2" << std::endl; + sw << "{" << (float)basePt.x << "," << (float)basePt.y << "," << (float)basePt.z << "}-"; + sw << "{0,0}-{0,0}-"; + sw << "{" << (int)rgb.r << "," << (int)rgb.g << "," << (int)rgb.b << "," << size << "}" << std::endl; + sw << "{" << pt2.x << "," << pt2.y << "," << pt2.z << "}-"; + sw << "{0,0}-{0,0}-"; + sw << "{" << (int)rgb.r << "," << (int)rgb.g << "," << (int)rgb.b << "," << size << "}" << std::endl; + lineIdx++; + + basePt = { poseInfo.holeRB.x - len * poseInfo.xDir.x, + poseInfo.holeRB.y - len * poseInfo.xDir.y, + poseInfo.holeRB.z - len * poseInfo.xDir.z }; + pt2 = { poseInfo.holeRB.x + len * poseInfo.xDir.x, + poseInfo.holeRB.y + len * poseInfo.xDir.y, + poseInfo.holeRB.z + len * poseInfo.xDir.z }; + sw << "Poly_" << lineIdx << "_2" << std::endl; + sw << "{" << (float)basePt.x << "," << (float)basePt.y << "," << (float)basePt.z << "}-"; + sw << "{0,0}-{0,0}-"; + sw << "{" << (int)rgb.r << "," << (int)rgb.g << "," << (int)rgb.b << "," << size << "}" << std::endl; + sw << "{" << pt2.x << "," << pt2.y << "," << pt2.z << "}-"; + sw << "{0,0}-{0,0}-"; + sw << "{" << (int)rgb.r << "," << (int)rgb.g << "," << (int)rgb.b << "," << size << "}" << std::endl; + lineIdx++; } } sw.close(); @@ -727,16 +769,22 @@ void _outputRGBDScan_RGBD_weldSeam( int centerFlag = pt3D->nPointIdx >> 4; if (centerFlag > 0) { - rgb = { 180, 0, 0 }; - size = 2; + if (centerFlag <= 2) + { + rgb = { 180, 0, 0 }; + size = 2; + } + else if(centerFlag == 4) + { + rgb = { 0, 250, 0 }; + size = 4; + } } else { rgb = objColor[pt3D->nPointIdx % 8]; size = 2; } - - } else //if (pt3D->nPointIdx == 0) { @@ -754,52 +802,75 @@ void _outputRGBDScan_RGBD_weldSeam( if (objNum > 0) { - sw << "Line_" << lineIdx << "_0_" << objNum << std::endl; - size = 12; + std::vector weldPoints; for (int i = 0; i < objNum; i++) { - if (i == 0) - rgb = { 250, 255, 0 }; + if (weldSeamInfo[i].weldType == KeWD_WELD_POINT) + weldPoints.push_back(weldSeamInfo[i].center); else - rgb = { 250, 0, 0 }; - float x = (float)weldSeamInfo[i].center.x; - float y = (float)weldSeamInfo[i].center.y; - float z = (float)weldSeamInfo[i].center.z; + { + weldPoints.push_back(weldSeamInfo[i].startPt); + weldPoints.push_back(weldSeamInfo[i].center); + weldPoints.push_back(weldSeamInfo[i].endPt); + } + } + + sw << "Line_" << lineIdx << "_0_" << (int)weldPoints.size() << std::endl; + size = 15; + for (int i = 0; i < (int)weldPoints.size(); i++) + { + rgb = { 250, 0, 0 }; + float x = (float)weldPoints[i].x; + float y = (float)weldPoints[i].y; + float z = (float)weldPoints[i].z; sw << "{" << x << "," << y << "," << z << "}-"; sw << "{0,0}-{0,0}-"; sw << "{" << rgb.r << "," << rgb.g << "," << rgb.b << "," << size << " }" << std::endl; } //输出法向 - size = 8; - double len = 60; + size = 4; + double len1 = 20; + double len2 = 20; lineIdx = 0; for (int i = 0; i < objNum; i++) { - if (i == 0) - rgb = { 250, 255, 0 }; + SVzNL3DPoint pt0, pt1; + if (weldSeamInfo[i].weldType == KeWD_WELD_POINT) + { + pt0 = weldSeamInfo[i].center; + pt1 = { weldSeamInfo[i].center.x + len1 * weldSeamInfo[i].axialDir.x, + weldSeamInfo[i].center.y + len1 * weldSeamInfo[i].axialDir.y, + weldSeamInfo[i].center.z + len1 * weldSeamInfo[i].axialDir.z }; + } else - rgb = { 250, 0, 0 }; - SVzNL3DPoint pt0 = { weldSeamInfo[i].center.x, weldSeamInfo[i].center.y, weldSeamInfo[i].center.z }; - SVzNL3DPoint pt2 = { weldSeamInfo[i].center.x + len * weldSeamInfo[i].normalDir.x, - weldSeamInfo[i].center.y + len * weldSeamInfo[i].normalDir.y, - weldSeamInfo[i].center.z + len * weldSeamInfo[i].normalDir.z }; + { + pt0 = weldSeamInfo[i].startPt; + pt1 = weldSeamInfo[i].endPt; + } + + SVzNL3DPoint pt2 = { weldSeamInfo[i].center.x, weldSeamInfo[i].center.y, weldSeamInfo[i].center.z }; + SVzNL3DPoint pt3 = { weldSeamInfo[i].center.x + len2 * weldSeamInfo[i].normalDir.x, + weldSeamInfo[i].center.y + len2 * weldSeamInfo[i].normalDir.y, + weldSeamInfo[i].center.z + len2 * weldSeamInfo[i].normalDir.z }; //显示轴向量 - sw << "Poly_" << lineIdx << "_2" << std::endl; - sw << "{" << (float)weldSeamInfo[i].startPt.x << "," << (float)weldSeamInfo[i].startPt.y << "," << (float)weldSeamInfo[i].startPt.z << "}-"; - sw << "{0,0}-{0,0}-"; - sw << "{" << (int)rgb.r << "," << (int)rgb.g << "," << (int)rgb.b << "," << size << "}" << std::endl; - sw << "{" << (float)weldSeamInfo[i].endPt.x << "," << (float)weldSeamInfo[i].endPt.y << "," << (float)weldSeamInfo[i].endPt.z << "}-"; - sw << "{0,0}-{0,0}-"; - sw << "{" << (int)rgb.r << "," << (int)rgb.g << "," << (int)rgb.b << "," << size << "}" << std::endl; - lineIdx++; - //显示法向量 + rgb = { 0, 0, 250 }; sw << "Poly_" << lineIdx << "_2" << std::endl; sw << "{" << (float)pt0.x << "," << (float)pt0.y << "," << (float)pt0.z << "}-"; sw << "{0,0}-{0,0}-"; sw << "{" << (int)rgb.r << "," << (int)rgb.g << "," << (int)rgb.b << "," << size << "}" << std::endl; + sw << "{" << (float)pt1.x << "," << (float)pt1.y << "," << (float)pt1.z << "}-"; + sw << "{0,0}-{0,0}-"; + sw << "{" << (int)rgb.r << "," << (int)rgb.g << "," << (int)rgb.b << "," << size << "}" << std::endl; + lineIdx++; + //显示法向量 + rgb = { 250, 0, 0 }; + sw << "Poly_" << lineIdx << "_2" << std::endl; sw << "{" << (float)pt2.x << "," << (float)pt2.y << "," << (float)pt2.z << "}-"; sw << "{0,0}-{0,0}-"; sw << "{" << (int)rgb.r << "," << (int)rgb.g << "," << (int)rgb.b << "," << size << "}" << std::endl; + sw << "{" << (float)pt3.x << "," << (float)pt3.y << "," << (float)pt3.z << "}-"; + sw << "{0,0}-{0,0}-"; + sw << "{" << (int)rgb.r << "," << (int)rgb.g << "," << (int)rgb.b << "," << size << "}" << std::endl; lineIdx++; } lineIdx++; @@ -807,27 +878,38 @@ void _outputRGBDScan_RGBD_weldSeam( sw.close(); } -#define SCREW_TEST_GROUP 1 +#define SCREW_TEST_GROUP 7 void screwTest(void) { const char* dataPath[SCREW_TEST_GROUP] = { "F:/ShangGu/项目/冠钦项目/螺杆测量/数据/模拟数据/", //0 + "F:/ShangGu/项目/冠钦项目/螺杆测量/配天现场点云/螺杆点云2/上方两根/", //1 + "F:/ShangGu/项目/冠钦项目/螺杆测量/配天现场点云/螺杆点云3/", //2 + "F:/ShangGu/项目/冠钦项目/螺杆测量/配天现场点云/螺杆点云4/位置1/", //3 + "F:/ShangGu/项目/冠钦项目/螺杆测量/配天现场点云/螺杆点云4/位置2/", //4 + "F:/ShangGu/项目/冠钦项目/螺杆测量/配天现场点云/螺杆点云4/位置2未动螺杆拧进去100mm左右/", //5 + "F:/ShangGu/项目/冠钦项目/螺杆测量/配天现场点云/螺杆点云4/位置2向前100mm螺杆拧进去100mm左右/", //6 }; SVzNLRange fileIdx[SCREW_TEST_GROUP] = { - {1,4}, + {1,4},{1,30},{1,11}, + {1,20}, {1,20}, {1,5}, {1,21} }; const char* ver = wd_rodAndBarDetectionVersion(); printf("ver:%s\n", ver); - for (int grp = 0; grp < SCREW_TEST_GROUP; grp++) + for (int grp = 3; grp < SCREW_TEST_GROUP; grp++) { for (int fidx = fileIdx[grp].nMin; fidx <= fileIdx[grp].nMax; fidx++) { - //fidx =7; + //fidx =3; char _scan_file[256]; - sprintf_s(_scan_file, "%sLaserData_%d.txt", dataPath[grp], fidx); + + if(0 == grp) + sprintf_s(_scan_file, "%sLaserData_%d.txt", dataPath[grp], fidx); + else + sprintf_s(_scan_file, "%s%d_LaserData_Jl26C299.txt", dataPath[grp], fidx); std::vector> scanLines; wdReadLaserScanPointFromFile_XYZ_vector(_scan_file, scanLines); @@ -838,10 +920,16 @@ void screwTest(void) long t1 = (long)GetTickCount64();//统计时间 - double rodDiameter = 10.0; + double rodDiameter; + if (grp == 0) + rodDiameter = 10.0; + else + rodDiameter = 28.0; //现场螺杆直径28mm + + //double rodDiameter = 10.0; SSG_cornerParam cornerParam; - cornerParam.cornerTh = 60; //45度角 + cornerParam.cornerTh = 30; //45度角 cornerParam.scale = rodDiameter/4; // algoParam.bagParam.bagH / 8; // 15; // algoParam.bagParam.bagH / 8; cornerParam.minEndingGap = 20; // algoParam.bagParam.bagW / 4; cornerParam.minEndingGap_z = 5.0; @@ -853,12 +941,12 @@ void screwTest(void) filterParam.outlierTh = 5; SSG_treeGrowParam growParam; - growParam.maxLineSkipNum = 10; - growParam.yDeviation_max = 20.0; - growParam.maxSkipDistance = 20.0; - growParam.zDeviation_max = 50.0;// - growParam.minLTypeTreeLen = 10; //mm, 螺杆长度 - growParam.minVTypeTreeLen = 10; //mm + growParam.maxLineSkipNum = 30; + growParam.yDeviation_max = 5.0; + growParam.maxSkipDistance = 30.0; + growParam.zDeviation_max = 50;// + growParam.minLTypeTreeLen = 50; //mm, 螺杆长度 + growParam.minVTypeTreeLen = 50; //mm bool isHorizonScan = true; //true:激光线平行槽道;false:激光线垂直槽道 int errCode = 0; @@ -874,7 +962,7 @@ void screwTest(void) &errCode); long t2 = (long)GetTickCount64(); - printf("%s: %d(ms)!\n", _scan_file, (int)(t2 - t1)); + printf("%s: %d(ms), errCode=%d\n", _scan_file, (int)(t2 - t1), errCode); //输出测试结果 sprintf_s(_scan_file, "%sresult\\%d_result.txt", dataPath[grp], fidx); _outputRGBDScan_RGBD(_scan_file, scanLines, screwInfo); @@ -892,7 +980,7 @@ void locatingPlateTest(void) }; SVzNLRange fileIdx[LOCATING_PALTE_TEST_GROUP] = { - {1,16}, + {1,17}, }; const char* ver = wd_rodAndBarDetectionVersion(); @@ -902,7 +990,7 @@ void locatingPlateTest(void) { for (int fidx = fileIdx[grp].nMin; fidx <= fileIdx[grp].nMax; fidx++) { - //fidx =4; + //fidx =2; char _scan_file[256]; sprintf_s(_scan_file, "%sLaserData_%d.txt", dataPath[grp], fidx); @@ -926,7 +1014,7 @@ void locatingPlateTest(void) cornerParam.jumpCornerTh_2 = 60; int errCode = 0; - SSX_pointPoseInfo centerPose = sx_getLocationPlatePose( + SSX_platePoseInfo centerPose = sx_getLocationPlatePose( scanLines, cornerParam, & errCode); @@ -942,21 +1030,22 @@ void locatingPlateTest(void) } } -#define ROD_POSITION_TEST_GROUP 1 +#define ROD_POSITION_TEST_GROUP 2 void rodPositionTest(void) { const char* dataPath[ROD_POSITION_TEST_GROUP] = { - "F:/ShangGu/项目/冠钦项目/棒材抓取/", //0 + "F:/ShangGu/项目/冠钦项目/矩森棒材抓取/", //0 + "F:/ShangGu/项目/冠钦项目/胶布圆棒抓取/模拟测试数据/", //1 }; SVzNLRange fileIdx[ROD_POSITION_TEST_GROUP] = { - {1,8}, + {1,8}, {1,5} }; const char* ver = wd_rodAndBarDetectionVersion(); printf("ver:%s\n", ver); - for (int grp = 0; grp < ROD_POSITION_TEST_GROUP; grp++) + for (int grp = 1; grp < ROD_POSITION_TEST_GROUP; grp++) { SSG_planeCalibPara poseCalibPara; //初始化成单位阵 @@ -980,7 +1069,10 @@ void rodPositionTest(void) { //fidx =1; char _scan_file[256]; - sprintf_s(_scan_file, "%sLaserData_%d.txt", dataPath[grp], fidx); + if(1 == grp) + sprintf_s(_scan_file, "%s%d_LaserData_Hi229156.txt", dataPath[grp], fidx); + else + sprintf_s(_scan_file, "%sLaserData_%d.txt", dataPath[grp], fidx); std::vector> scanLines; wdReadLaserScanPointFromFile_XYZ_vector(_scan_file, scanLines); @@ -992,8 +1084,16 @@ void rodPositionTest(void) long t1 = (long)GetTickCount64();//统计时间 SSX_rodParam rodParam; - rodParam.diameter = 52.0; //圆棒直径 - rodParam.len = 290; + if (0 == grp) + { + rodParam.diameter = 52.0; //圆棒直径 + rodParam.len = 290; + } + else + { + rodParam.diameter = 68.0; //圆棒直径 + rodParam.len = 187; + } SSG_cornerParam cornerParam; cornerParam.cornerTh = 60; //45度角 @@ -1079,7 +1179,7 @@ void rodWeldSeamPosition_test(void) { //fidx =1; char _scan_file[256]; - sprintf_s(_scan_file, "%sLaserData_%d.txt", dataPath[grp], fidx); + sprintf_s(_scan_file, "%s%d_LaserData_ID019567.txt", dataPath[grp], fidx); std::vector> scanLines; wdReadLaserScanPointFromFile_XYZ_vector(_scan_file, scanLines); @@ -1091,8 +1191,8 @@ void rodWeldSeamPosition_test(void) long t1 = (long)GetTickCount64();//统计时间 SSX_rodParam rodParam; - rodParam.diameter = 52.0; //圆棒直径 - rodParam.len = 290; + rodParam.diameter = 16.0; //钢筋直径 + rodParam.len = 50; SSG_cornerParam cornerParam; cornerParam.cornerTh = 60; //45度角 @@ -1103,18 +1203,19 @@ void rodWeldSeamPosition_test(void) cornerParam.jumpCornerTh_2 = 60; SSG_outlierFilterParam filterParam; - filterParam.continuityTh = 5.0; //噪声滤除。当相邻点的z跳变大于此门限时,检查是否为噪声。若长度小于outlierLen, 视为噪声 - filterParam.outlierTh = 5; + filterParam.continuityTh = 4.0; //噪声滤除。当相邻点的z跳变大于此门限时,检查是否为噪声。若长度小于outlierLen, 视为噪声 + filterParam.outlierTh = 4; SSG_treeGrowParam growParam; growParam.maxLineSkipNum = 5; - growParam.yDeviation_max = 10.0; - growParam.maxSkipDistance = 10.0; - growParam.zDeviation_max = 10.0;// - growParam.minLTypeTreeLen = 100; //mm, 螺杆长度 - growParam.minVTypeTreeLen = 100; //mm + growParam.yDeviation_max = 5.0; + growParam.maxSkipDistance = 20.0; + growParam.zDeviation_max = 3.0;// + growParam.minLTypeTreeLen = 50; //mm, 螺杆长度 + growParam.minVTypeTreeLen = 50; //mm bool isHorizonScan = true; //true:激光线平行槽道;false:激光线垂直槽道 + double weldSeanRange = 100; //焊缝距钢筋交叉点的范围(最大值) int errCode = 0; std::vector weldSeamInfo; sx_rebarWeldSeamPositioning( @@ -1124,6 +1225,7 @@ void rodWeldSeamPosition_test(void) filterParam, growParam, rodParam, + weldSeanRange, weldSeamInfo, &errCode); long t2 = (long)GetTickCount64(); @@ -1140,13 +1242,13 @@ void rodWeldSeamPosition_test(void) int main() { #if 1 //螺杆定位测试 -#if 0 +#if 1 screwTest(); #else locatingPlateTest(); #endif #else //棒材抓取定位测试 -#if 0 +#if 1 rodPositionTest(); #else rodWeldSeamPosition_test(); diff --git a/AppUtils/AppCommon/Inc/BasePresenter.h b/AppUtils/AppCommon/Inc/BasePresenter.h index c8cbed5c..288c1b63 100644 --- a/AppUtils/AppCommon/Inc/BasePresenter.h +++ b/AppUtils/AppCommon/Inc/BasePresenter.h @@ -6,6 +6,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -19,6 +22,19 @@ // 前向声明 class IYModbusTCPServer; +class BasePresenter; + +/** + * @brief 相机回调上下文 + * + * 携带 BasePresenter 指针和相机索引,解决多相机同时扫描时 + * 静态回调函数中无法识别数据来源相机的问题 + */ +struct CameraCallbackContext +{ + BasePresenter* presenter; + int cameraIndex; +}; /** * @brief 基础Presenter类 @@ -149,6 +165,18 @@ public: */ void SetWorkStatus(WorkStatus status); + /** + * @brief 设置扫描配置 + * @param config 扫描配置 + */ + void SetScanConfig(const VrScanConfig& config) { m_scanConfig = config; } + + /** + * @brief 获取扫描配置 + * @return 扫描配置 + */ + VrScanConfig GetScanConfig() const { return m_scanConfig; } + // ============ ModbusTCP 服务相关 ============ /** @@ -417,7 +445,6 @@ protected: m_debugParam = debugParam; } - // ============ 公共实现方法 ============ /** @@ -523,6 +550,46 @@ private: */ void StopCameraReconnectTimer(); + // ============ 多相机同时扫描相关方法 ============ + + /** + * @brief 添加数据到指定相机的独立缓存(同时扫描模式专用) + * @param cameraIndex 相机索引(1-based) + * @param dataType 数据类型 + * @param laserData 激光数据 + */ + void AddDetectionDataToCameraCache(int cameraIndex, EVzResultDataType dataType, + const SVzLaserLineData& laserData); + + /** + * @brief 清空所有相机的独立数据缓存 + */ + void ClearAllCameraDataCaches(); + + /** + * @brief 相机扫描完成通知(Swing_Finish 时调用) + * + * 增加批次完成计数 + * + * @param cameraIndex 完成扫描的相机索引 + */ + void OnCameraScanFinished(int cameraIndex); + + /** + * @brief 启动一批相机的扫描 + * + * 从 m_batchStartIndex 开始,取 m_batchSize 个相机同时启动扫描 + */ + void StartBatchScan(); + + /** + * @brief 检查当前批次是否全部完成并逐个处理 + * + * 在算法线程中调用,当前批次全部扫描完成后逐个处理每个相机的数据, + * 处理完后自动推进到下一批或结束 + */ + void ProcessBatchIfReady(); + private slots: /** * @brief 相机重连定时器触发 @@ -577,6 +644,26 @@ protected: // 调试数据异步存储 DebugDataSaver m_debugDataSaver; + // ============ 多相机同时扫描相关成员 ============ + + // 扫描配置 + VrScanConfig m_scanConfig; + + // 每个相机的回调上下文(key = 相机索引 1-based) + std::map m_cameraContexts; + + // 每个相机的独立数据缓存区(key = 相机索引 1-based) + std::map>> m_perCameraDataCache; + std::mutex m_perCameraDataMutex; + + // 分批扫描调度相关 + std::vector m_batchCameraList; // 本轮需扫描的相机列表 + int m_batchStartIndex = 0; // 当前批次起始位置 + int m_batchSize = 0; // 当前批次相机数 + int m_batchFinishedCount = 0; // 当前批次已完成计数 + bool m_batchInProgress = false; // 是否正在分批扫描 + std::mutex m_batchStateMutex; // 保护分批调度状态 + private: // 启动ModbusTCP服务(在Init中调用) int StartModbusServer(int port = 5020); diff --git a/AppUtils/AppCommon/Inc/VrCommonConfig.h b/AppUtils/AppCommon/Inc/VrCommonConfig.h index 9506d074..a3fb9333 100644 --- a/AppUtils/AppCommon/Inc/VrCommonConfig.h +++ b/AppUtils/AppCommon/Inc/VrCommonConfig.h @@ -234,6 +234,32 @@ struct VrDebugParam VrDebugParam() = default; }; +/** + * @brief 多相机扫描配置 + * + * 控制同时进行扫描的相机数量: + * - 1: 单相机模式(默认,完全兼容现有行为) + * - 0: 所有已连接相机同时扫描 + * - N: 每批 N 个相机同时扫描(N > 1) + */ +struct VrScanConfig +{ + int simultaneousCount = 1; + + VrScanConfig& operator=(const VrScanConfig& other) { + if (this != &other) { + simultaneousCount = other.simultaneousCount; + } + return *this; + } + + VrScanConfig(const VrScanConfig& other) + : simultaneousCount(other.simultaneousCount) { + } + + VrScanConfig() = default; +}; + /** * @brief 配置改变通知接口(前向声明ConfigResult) */ diff --git a/AppUtils/AppCommon/Src/BasePresenter.cpp b/AppUtils/AppCommon/Src/BasePresenter.cpp index 2b3aa8fb..ad21be3b 100644 --- a/AppUtils/AppCommon/Src/BasePresenter.cpp +++ b/AppUtils/AppCommon/Src/BasePresenter.cpp @@ -61,6 +61,16 @@ BasePresenter::~BasePresenter() } } m_vrEyeDeviceList.clear(); + + // 清理相机回调上下文 + for (auto& pair : m_cameraContexts) { + delete pair.second; + } + m_cameraContexts.clear(); + + // 清理各相机独立数据缓存 + ClearAllCameraDataCaches(); + LOG_INFO("BasePresenter destructor finished\n"); } @@ -98,59 +108,138 @@ int BasePresenter::Init() int BasePresenter::StartDetection(int cameraIndex, bool isAuto) { - LOG_INFO("[BasePresenter] StartDetection - cameraIndex=%d, isAuto=%d\n", cameraIndex, isAuto); + LOG_INFO("[BasePresenter] StartDetection - cameraIndex=%d, isAuto=%d, simultaneousCount=%d\n", + cameraIndex, isAuto, m_scanConfig.simultaneousCount); - // 设置当前相机索引 - if (cameraIndex >= 0 && cameraIndex != -1) { + // ===== 分支1: cameraIndex > 0,单相机模式 ===== + if (cameraIndex > 0) { m_currentCameraIndex = cameraIndex; - } - int currentCamera = m_currentCameraIndex; + int currentCamera = m_currentCameraIndex; - // 检查相机列表是否为空 - if (m_vrEyeDeviceList.empty()) { - LOG_ERROR("[BasePresenter] No camera device found\n"); + if (m_vrEyeDeviceList.empty()) { + LOG_ERROR("[BasePresenter] No camera device found\n"); + return ERR_CODE(DEV_NOT_FIND); + } + + ClearDetectionDataCache(); + + int arrayIndex = currentCamera - 1; + if (arrayIndex < 0 || arrayIndex >= static_cast(m_vrEyeDeviceList.size()) || + m_vrEyeDeviceList[arrayIndex].second == nullptr) { + LOG_ERROR("[BasePresenter] Camera %d is not connected or invalid\n", currentCamera); + return ERR_CODE(DEV_NOT_FIND); + } + + SetWorkStatus(WorkStatus::Working); + + IVrEyeDevice* pDevice = m_vrEyeDeviceList[arrayIndex].second; + EVzResultDataType eDataType = GetDetectionDataType(); + VzNL_OnNotifyStatusCBEx statusCallback = GetCameraStatusCallback(); + VzNL_AutoOutputLaserLineExCB detectCallback = GetDetectionCallback(); + + // 使用相机专属的回调上下文 + CameraCallbackContext* ctx = m_cameraContexts[currentCamera]; + pDevice->SetStatusCallback(statusCallback, ctx); + + int nRet = pDevice->StartDetect(detectCallback, eDataType, ctx); + LOG_INFO("[BasePresenter] Camera %d start detection result: %d\n", currentCamera, nRet); + + if (nRet == SUCCESS) { + StartAlgoDetectThread(); + } + LOG_INFO("[BasePresenter] StartDetection finish\n"); + return nRet; + } + + // ===== 分支2: cameraIndex <= 0 且为单相机模式,使用默认相机 ===== + if (m_scanConfig.simultaneousCount == 1) { + cameraIndex = m_currentCameraIndex; + if (cameraIndex <= 0) cameraIndex = 1; + m_currentCameraIndex = cameraIndex; + int currentCamera = m_currentCameraIndex; + + if (m_vrEyeDeviceList.empty()) { + LOG_ERROR("[BasePresenter] No camera device found\n"); + return ERR_CODE(DEV_NOT_FIND); + } + + ClearDetectionDataCache(); + + int arrayIndex = currentCamera - 1; + if (arrayIndex < 0 || arrayIndex >= static_cast(m_vrEyeDeviceList.size()) || + m_vrEyeDeviceList[arrayIndex].second == nullptr) { + LOG_ERROR("[BasePresenter] Camera %d is not connected or invalid\n", currentCamera); + return ERR_CODE(DEV_NOT_FIND); + } + + SetWorkStatus(WorkStatus::Working); + + IVrEyeDevice* pDevice = m_vrEyeDeviceList[arrayIndex].second; + EVzResultDataType eDataType = GetDetectionDataType(); + VzNL_OnNotifyStatusCBEx statusCallback = GetCameraStatusCallback(); + VzNL_AutoOutputLaserLineExCB detectCallback = GetDetectionCallback(); + + CameraCallbackContext* ctx = m_cameraContexts[currentCamera]; + pDevice->SetStatusCallback(statusCallback, ctx); + + int nRet = pDevice->StartDetect(detectCallback, eDataType, ctx); + LOG_INFO("[BasePresenter] Camera %d start detection result: %d\n", currentCamera, nRet); + + if (nRet == SUCCESS) { + StartAlgoDetectThread(); + } + LOG_INFO("[BasePresenter] StartDetection finish\n"); + return nRet; + } + + // ===== 分支3: 多相机同时/分批扫描 ===== + LOG_INFO("[BasePresenter] 进入多相机分批扫描模式\n"); + + // 停止当前正在进行的检测 + if (m_batchInProgress || m_bAlgoDetectThreadRunning) { + StopDetection(); + } + + // 收集本轮所有已连接的相机 + m_batchCameraList.clear(); + for (int i = 0; i < static_cast(m_vrEyeDeviceList.size()); i++) { + if (m_vrEyeDeviceList[i].second != nullptr) { + m_batchCameraList.push_back(i + 1); // 1-based index + } + } + if (m_batchCameraList.empty()) { + LOG_ERROR("[BasePresenter] No connected cameras for batch scan\n"); return ERR_CODE(DEV_NOT_FIND); } - // 清空检测数据缓存 - ClearDetectionDataCache(); - - int nRet = SUCCESS; - - // 启动指定相机(cameraIndex为相机ID,从1开始编号) - int arrayIndex = currentCamera - 1; // 转换为数组索引(从0开始) - - // 检查相机是否连接 - if (arrayIndex < 0 || arrayIndex >= static_cast(m_vrEyeDeviceList.size()) || - m_vrEyeDeviceList[arrayIndex].second == nullptr) { - LOG_ERROR("[BasePresenter] Camera %d is not connected or invalid\n", currentCamera); - return ERR_CODE(DEV_NOT_FIND); + // 计算每批大小 + int totalCameras = static_cast(m_batchCameraList.size()); + if (m_scanConfig.simultaneousCount == 0) { + m_batchSize = totalCameras; + } else { + m_batchSize = std::min(m_scanConfig.simultaneousCount, totalCameras); } + // 初始化分批状态 + m_batchStartIndex = 0; + m_batchFinishedCount = 0; + m_batchInProgress = true; + + // 清空所有 per-camera 缓存 + ClearAllCameraDataCaches(); + SetWorkStatus(WorkStatus::Working); - IVrEyeDevice* pDevice = m_vrEyeDeviceList[arrayIndex].second; - - // 获取数据类型(由子类决定) - EVzResultDataType eDataType = GetDetectionDataType(); - - // 设置状态回调 - VzNL_OnNotifyStatusCBEx statusCallback = GetCameraStatusCallback(); - pDevice->SetStatusCallback(statusCallback, this); - - // 获取检测回调函数(由子类提供) - VzNL_AutoOutputLaserLineExCB detectCallback = GetDetectionCallback(); - - // 开始检测 - nRet = pDevice->StartDetect(detectCallback, eDataType, this); - LOG_INFO("[BasePresenter] Camera %d start detection result: %d\n", currentCamera, nRet); - - if (nRet == SUCCESS) { - // 启动算法检测线程 + // 启动算法检测线程 + if (!m_bAlgoDetectThreadRunning) { StartAlgoDetectThread(); } - LOG_INFO("[BasePresenter] StartDetection finish \n"); - return nRet; + + // 启动第一批相机扫描 + StartBatchScan(); + + LOG_INFO("[BasePresenter] 分批扫描已启动: total=%d, batchSize=%d\n", totalCameras, m_batchSize); + return SUCCESS; } int BasePresenter::StopDetection() @@ -173,6 +262,15 @@ int BasePresenter::StopDetection() // 停止算法检测线程 StopAlgoDetectThread(); + // 清理多相机分批扫描状态 + { + std::lock_guard lock(m_batchStateMutex); + m_batchInProgress = false; + m_batchFinishedCount = 0; + m_batchCameraList.clear(); + } + ClearAllCameraDataCaches(); + return SUCCESS; } @@ -420,9 +518,19 @@ int BasePresenter::OpenDevice(int cameraIndex, const char* cameraName, const cha pDevice = nullptr; } else { - // 设置状态回调(调用子类提供的回调函数) + // 释放旧的回调上下文(重连场景) + auto oldCtx = m_cameraContexts.find(cameraIndex); + if (oldCtx != m_cameraContexts.end()) { + delete oldCtx->second; + } + + // 创建新的回调上下文,携带相机索引 + CameraCallbackContext* ctx = new CameraCallbackContext{this, cameraIndex}; + m_cameraContexts[cameraIndex] = ctx; + + // 设置状态回调(使用带相机索引的上下文) VzNL_OnNotifyStatusCBEx callback = GetCameraStatusCallback(); - nRet = pDevice->SetStatusCallback(callback, this); + nRet = pDevice->SetStatusCallback(callback, ctx); LOG_DEBUG("[BasePresenter] SetStatusCallback result: %d\n", nRet); if (nRet != SUCCESS) { delete pDevice; @@ -451,22 +559,25 @@ void BasePresenter::AlgoDetectThreadFunc() { std::unique_lock lock(m_algoDetectMutex); - // 等待检测触发(子类需要调用 m_algoDetectCondition.notify_one() 来触发) + // 等待检测触发 m_algoDetectCondition.wait(lock); if(!m_bAlgoDetectThreadRunning){ break; } - LOG_INFO("[BasePresenter] 检测线程被唤醒,开始执行检测任务\n"); - - // 执行检测任务 - int nRet = DetectTask(); - - if(nRet != SUCCESS){ - LOG_ERROR("[BasePresenter] 检测任务执行失败,错误码: %d\n", nRet); + if (m_scanConfig.simultaneousCount > 1 || m_scanConfig.simultaneousCount == 0) { + // 多相机分批模式:检查批次是否完成并处理 + ProcessBatchIfReady(); } else { - LOG_INFO("[BasePresenter] 检测任务执行成功\n"); + // 单相机模式:直接执行检测任务 + LOG_INFO("[BasePresenter] 检测线程被唤醒,开始执行检测任务\n"); + int nRet = DetectTask(); + if(nRet != SUCCESS){ + LOG_ERROR("[BasePresenter] 检测任务执行失败,错误码: %d\n", nRet); + } else { + LOG_INFO("[BasePresenter] 检测任务执行成功\n"); + } } } @@ -543,7 +654,13 @@ int BasePresenter::DetectTask() int nRet = ProcessAlgoDetection(m_detectionDataCache); LOG_INFO("[BasePresenter] ProcessAlgoDetection 执行结果: %d\n", nRet); - SetWorkStatus(WorkStatus::Completed); + // 批量模式下不在这里设置Completed,由ProcessBatchIfReady统一设置 + { + std::lock_guard lock(m_batchStateMutex); + if (!m_batchInProgress) { + SetWorkStatus(WorkStatus::Completed); + } + } LOG_INFO("[BasePresenter] DetectTask - 检测任务执行成功\n"); return nRet; @@ -618,12 +735,14 @@ void BasePresenter::_StaticDetectionCallback(EVzResultDataType eDataType, SVzLas return; } - // 获取 BasePresenter 实例指针 - BasePresenter* pThis = reinterpret_cast(pUserData); - if (!pThis) { - LOG_ERROR("[BasePresenter Detection Callback] pUserData is null\n"); + // 提取回调上下文(多相机模式下携带相机索引) + CameraCallbackContext* ctx = static_cast(pUserData); + if (!ctx || !ctx->presenter) { + LOG_ERROR("[BasePresenter Detection Callback] invalid context\n"); return; } + BasePresenter* pThis = ctx->presenter; + int cameraIndex = ctx->cameraIndex; // 创建 SVzLaserLineData 副本 SVzLaserLineData lineData; @@ -680,8 +799,14 @@ void BasePresenter::_StaticDetectionCallback(EVzResultDataType eDataType, SVzLas lineData.fSwingAngle = pLaserLinePoint->fSwingAngle; lineData.bEndOnceScan = pLaserLinePoint->bEndOnceScan; - // 添加到检测数据缓存 - pThis->AddDetectionDataToCache(eDataType, lineData); + // 根据扫描模式分流存储 + if (pThis->m_scanConfig.simultaneousCount > 1 || pThis->m_scanConfig.simultaneousCount == 0) { + // 多相机同时扫描模式:存入该相机的独立缓存 + pThis->AddDetectionDataToCameraCache(cameraIndex, eDataType, lineData); + } else { + // 单相机模式:存入共享缓存 + pThis->AddDetectionDataToCache(eDataType, lineData); + } } // 通用的静态相机状态回调函数实现 @@ -689,43 +814,44 @@ void BasePresenter::_StaticCameraStatusCallback(EVzDeviceWorkStatus eStatus, voi { LOG_DEBUG("[BasePresenter Camera Status Callback] received: status=%d\n", (int)eStatus); - // 获取 BasePresenter 实例指针 - BasePresenter* pThis = reinterpret_cast(pInfoParam); - if (!pThis) { - LOG_ERROR("[BasePresenter Camera Status Callback] pInfoParam is null\n"); + // 提取回调上下文(携带相机索引) + CameraCallbackContext* ctx = static_cast(pInfoParam); + if (!ctx || !ctx->presenter) { + LOG_ERROR("[BasePresenter Camera Status Callback] invalid context\n"); return; } + BasePresenter* pThis = ctx->presenter; + int cameraIndex = ctx->cameraIndex; switch (eStatus) { case EVzDeviceWorkStatus::keDeviceWorkStatus_Offline: { - LOG_WARNING("[BasePresenter Camera Status Callback] Camera device offline/disconnected\n"); + LOG_WARNING("[BasePresenter Camera Status Callback] Camera %d offline/disconnected\n", cameraIndex); - // 更新相机连接状态 pThis->m_bCameraConnected = false; - - // 通知子类相机状态变更(这里暂时通知相机1,实际应用中可能需要区分) - pThis->OnCameraStatusChanged(1, false); + pThis->OnCameraStatusChanged(cameraIndex, false); break; } case EVzDeviceWorkStatus::keDeviceWorkStatus_Eye_Reconnect: { - LOG_INFO("[BasePresenter Camera Status Callback] Camera device online/connected\n"); + LOG_INFO("[BasePresenter Camera Status Callback] Camera %d online/connected\n", cameraIndex); - // 更新相机连接状态 pThis->m_bCameraConnected = true; - - // 通知子类相机状态变更 - pThis->OnCameraStatusChanged(1, true); + pThis->OnCameraStatusChanged(cameraIndex, true); break; } case EVzDeviceWorkStatus::keDeviceWorkStatus_Device_Swing_Finish: { - LOG_INFO("[BasePresenter Camera Status Callback] Received scan finish signal from camera\n"); + LOG_INFO("[BasePresenter Camera Status Callback] Camera %d scan finished\n", cameraIndex); - // 通知算法检测线程开始处理 + // 多相机模式下记录批次完成 + if (pThis->m_scanConfig.simultaneousCount > 1 || pThis->m_scanConfig.simultaneousCount == 0) { + pThis->OnCameraScanFinished(cameraIndex); + } + + // 唤醒算法检测线程 pThis->m_algoDetectCondition.notify_one(); break; } @@ -919,3 +1045,168 @@ int BasePresenter::OnModbusWriteRegisters(uint8_t unitId, uint16_t startAddress, return 0; } + +// ============ 多相机同时扫描方法实现 ============ + +void BasePresenter::AddDetectionDataToCameraCache(int cameraIndex, + EVzResultDataType dataType, const SVzLaserLineData& laserData) +{ + std::lock_guard lock(m_perCameraDataMutex); + m_perCameraDataCache[cameraIndex].push_back(std::make_pair(dataType, laserData)); +} + +void BasePresenter::ClearAllCameraDataCaches() +{ + std::lock_guard lock(m_perCameraDataMutex); + for (auto& pair : m_perCameraDataCache) { + m_dataLoader.FreeLaserScanData(pair.second); + } + m_perCameraDataCache.clear(); +} + +void BasePresenter::OnCameraScanFinished(int cameraIndex) +{ + std::lock_guard lock(m_batchStateMutex); + m_batchFinishedCount++; + LOG_INFO("[BasePresenter] Camera %d finished, batch progress: %d/%d\n", + cameraIndex, m_batchFinishedCount, m_batchSize); +} + +void BasePresenter::StartBatchScan() +{ + int batchEnd = std::min(m_batchStartIndex + m_batchSize, static_cast(m_batchCameraList.size())); + int batchCount = batchEnd - m_batchStartIndex; + + LOG_INFO("[BasePresenter] StartBatchScan: cameras [%d, %d), count=%d\n", + m_batchStartIndex, batchEnd, batchCount); + + if (batchCount <= 0) { + LOG_WARNING("[BasePresenter] StartBatchScan: empty batch\n"); + return; + } + + EVzResultDataType eDataType = GetDetectionDataType(); + VzNL_AutoOutputLaserLineExCB detectCallback = GetDetectionCallback(); + VzNL_OnNotifyStatusCBEx statusCallback = GetCameraStatusCallback(); + + for (int i = m_batchStartIndex; i < batchEnd; i++) { + int cameraIndex = m_batchCameraList[i]; + int arrIndex = cameraIndex - 1; + + if (arrIndex < 0 || arrIndex >= static_cast(m_vrEyeDeviceList.size())) { + LOG_WARNING("[BasePresenter] Camera %d index out of range, skipping\n", cameraIndex); + continue; + } + + IVrEyeDevice* pDevice = m_vrEyeDeviceList[arrIndex].second; + if (!pDevice) { + LOG_WARNING("[BasePresenter] Camera %d device is null, skipping\n", cameraIndex); + continue; + } + + // 清空该相机的独立缓存 + { + std::lock_guard lock(m_perCameraDataMutex); + auto it = m_perCameraDataCache.find(cameraIndex); + if (it != m_perCameraDataCache.end()) { + m_dataLoader.FreeLaserScanData(it->second); + m_perCameraDataCache.erase(it); + } + } + + // 使用相机专属的回调上下文 + CameraCallbackContext* ctx = m_cameraContexts[cameraIndex]; + pDevice->SetStatusCallback(statusCallback, ctx); + + int nRet = pDevice->StartDetect(detectCallback, eDataType, ctx); + LOG_INFO("[BasePresenter] Camera %d start detect: %d\n", cameraIndex, nRet); + } + + LOG_INFO("[BasePresenter] Batch scan started: %d cameras\n", batchCount); +} + +void BasePresenter::ProcessBatchIfReady() +{ + // 检查当前批次是否全部完成 + bool batchComplete = false; + { + std::lock_guard lock(m_batchStateMutex); + if (!m_batchInProgress) return; + batchComplete = (m_batchFinishedCount >= m_batchSize); + } + + if (!batchComplete) { + // 批次未完成,可能是被提前唤醒(Offline 等事件) + return; + } + + int batchEnd = std::min(m_batchStartIndex + m_batchSize, static_cast(m_batchCameraList.size())); + + LOG_INFO("[BasePresenter] 批次完成,处理相机 [%d, %d)\n", m_batchStartIndex, batchEnd); + + // 逐个处理批次内每个相机的数据 + for (int i = m_batchStartIndex; i < batchEnd; i++) { + int cameraIndex = m_batchCameraList[i]; + + // 将相机数据从 per-camera 缓存移到 m_detectionDataCache + { + std::lock_guard dataLock(m_perCameraDataMutex); + auto it = m_perCameraDataCache.find(cameraIndex); + if (it != m_perCameraDataCache.end() && !it->second.empty()) { + std::lock_guard detLock(m_detectionDataMutex); + m_dataLoader.FreeLaserScanData(m_detectionDataCache); + m_detectionDataCache.clear(); + m_detectionDataCache = std::move(it->second); + m_perCameraDataCache.erase(it); + } else { + LOG_WARNING("[BasePresenter] 相机%d 无扫描数据,跳过\n", cameraIndex); + continue; + } + } + + m_currentCameraIndex = cameraIndex; + + LOG_INFO("[BasePresenter] 处理相机 %d (%d/%d)\n", + cameraIndex, i - m_batchStartIndex + 1, m_batchSize); + + int nRet = DetectTask(); + if (nRet != SUCCESS) { + LOG_ERROR("[BasePresenter] 相机%d 检测失败: %d\n", cameraIndex, nRet); + } + } + + // 推进到下一批 + bool hasMore = false; + { + std::lock_guard lock(m_batchStateMutex); + + m_batchStartIndex = batchEnd; + m_batchFinishedCount = 0; + + int remaining = static_cast(m_batchCameraList.size()) - m_batchStartIndex; + if (remaining > 0) { + if (m_scanConfig.simultaneousCount == 0) { + m_batchSize = remaining; + } else { + m_batchSize = std::min(m_scanConfig.simultaneousCount, remaining); + } + hasMore = true; + } + } + + if (hasMore) { + LOG_INFO("[BasePresenter] 启动下一批扫描: start=%d, size=%d\n", m_batchStartIndex, m_batchSize); + // 在主线程中启动下一批扫描 + QMetaObject::invokeMethod(this, [this]() { + StartBatchScan(); + }, Qt::QueuedConnection); + } else { + LOG_INFO("[BasePresenter] 所有批次扫描完成,共 %d 个相机\n", + static_cast(m_batchCameraList.size())); + { + std::lock_guard lock(m_batchStateMutex); + m_batchInProgress = false; + } + SetWorkStatus(WorkStatus::Completed); + } +} diff --git a/Device/Device.pro b/Device/Device.pro index f1d5f863..1c4cd072 100644 --- a/Device/Device.pro +++ b/Device/Device.pro @@ -12,4 +12,8 @@ SUBDIRS += \ GalaxyDevice/GalaxyDevice.pro \ HikDevice/HikDevice.pro \ GlLineLaserDevice/GlLineLaserDevice.pro -# RsLidarDevice/RsLidarDevice.pro + + +win32-msvc { + SUBDIRS += RsLidarDevice/RsLidarDevice.pro +} diff --git a/GrabBagPrj/AppList.md b/GrabBagPrj/AppList.md index b7a86108..f71abf6b 100644 --- a/GrabBagPrj/AppList.md +++ b/GrabBagPrj/AppList.md @@ -15,8 +15,9 @@ | 9 | 颗粒尺寸检测 | ParticleSize | 1.0.0.0 | | 10 | 双目标记检测 | BinocularMarkServer | 1.0.0.4 | | 11 | 铁路隧道槽道测量 | TunnelChannel | 1.0.0.3 | -| 12 | 螺杆定位 | ScrewPosition | 1.1.8.1 | +| 12 | 螺杆定位 | ScrewPosition | 1.1.10.1 | | 13 | 包裹拆线位置定位 | BagThreadPosition | 1.0.0.4 | | 14 | 工件孔定位 | WorkpieceHole | 1.1.5.1 | | 16 | 坑孔定位 | HolePitPosition | 无 | +| 17 | 钢筋焊缝定位 | RodWeldSeam | 1.0.0.1 | diff --git a/GrabBagPrj/GrabBagPrj.pro b/GrabBagPrj/GrabBagPrj.pro index 76857a3d..2eed2289 100644 --- a/GrabBagPrj/GrabBagPrj.pro +++ b/GrabBagPrj/GrabBagPrj.pro @@ -27,6 +27,8 @@ Device.depends = Utils AppUtils AppUtils.depends = Utils Module App.depends = Utils VrNets Module Device AppUtils Tools.depends = Module Utils Robot +Test.depends = Device Module # Test 测试 -SUBDIRS += ../Test/Test.pro +Test.file = ../Test/Test.pro +SUBDIRS += Test diff --git a/GrabBagPrj/project_registry.sh b/GrabBagPrj/project_registry.sh index 795adbf7..2ca0d992 100644 --- a/GrabBagPrj/project_registry.sh +++ b/GrabBagPrj/project_registry.sh @@ -44,6 +44,7 @@ DESKTOP_PROJECTS=( "WorkpieceHole;workpieceHolePositioning" "StatorPosition;motorStatorPosition" "RodAndBarPosition;rodAndBarDetection" + "RodWeldSeam;rodAndBarDetection" "HolePitPosition;workpieceHolePositioning" "WheelMeasure;wheelArchHeigthMeasure" diff --git a/Module/Module.pro b/Module/Module.pro index bdbe3842..1f8b2fd2 100644 --- a/Module/Module.pro +++ b/Module/Module.pro @@ -11,3 +11,8 @@ SUBDIRS += \ AuthModule/AuthModule.pro \ HandEyeCalib/HandEyeCalib.pro \ ChessboardDetector/ChessboardDetector.pro + + +win32-msvc { + SUBDIRS += CloudShow/CloudShow.pro +} \ No newline at end of file diff --git a/Test/Test.pro b/Test/Test.pro index 01f69e7f..07edc0d4 100644 --- a/Test/Test.pro +++ b/Test/Test.pro @@ -3,4 +3,8 @@ TEMPLATE = subdirs # 撕裂项目 # SUBDIRS += tcpclient/tcpclient_test.pro # SUBDIRS += tcpserver/tcpserver_test.pro -# SUBDIRS += RsLidarTest/RsLidarTest.pro \ No newline at end of file + + +win32-msvc { + SUBDIRS += RsLidarTest/RsLidarTest.pro +}