螺杆更新

This commit is contained in:
杰仔 2026-05-17 00:38:31 +08:00
parent 683eb03f25
commit e9cb8681d0
34 changed files with 953 additions and 366 deletions

View File

@ -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")
}

View File

@ -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. 项目的协议文档也给搞一份
9. 项目的协议文档也给搞一份ModbusTCP的

View File

@ -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

View File

@ -1,3 +1,7 @@
# 1.1.10 2026-05-15
## build_1
1. 更新算法:螺杆检测
# 1.1.8 2026-05-13
## build_1
1. 更新算法:螺杆检测

View File

@ -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

View File

@ -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<int, WheelMeasureTCPProtocol::CameraMeasureResult> m_tcpResults; // TCP检测结果缓存
// 继续检测下一个设备
void continueSequentialDetection();
};
#endif // WHEELMEASUREPRESENTER_H

View File

@ -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<std::pair<EVzResultDataT
SetWorkStatus(WorkStatus::Error);
// 如果正在进行顺序检测,继续检测下一个设备
if (m_sequentialDetecting) {
m_sequentialCurrentIndex++;
QMetaObject::invokeMethod(this, [this]() {
continueSequentialDetection();
}, Qt::QueuedConnection);
}
return;
}
@ -595,23 +504,6 @@ void WheelMeasurePresenter::processScanData(std::vector<std::pair<EVzResultDataT
LOG_INFO("TCP检测结果已缓存: 相机%d, 错误码=401 (未检测到轮胎)\n", m_currentCameraIndex);
}
// 如果正在进行顺序检测,继续检测下一个设备
if (m_sequentialDetecting) {
m_sequentialCurrentIndex++;
// TCP模式下检查是否所有相机都检测完成
if (m_tcpDetectionMode && m_tcpResults.size() >= 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::vector<std::pair<EVzResultDataT
m_statusUpdate->OnMeasureResult(result);
}
SetWorkStatus(WorkStatus::Completed);
// 如果是TCP触发的检测缓存结果
if (m_tcpDetectionMode) {
WheelMeasureTCPProtocol::CameraMeasureResult tcpResult;
@ -842,25 +732,6 @@ void WheelMeasurePresenter::processScanData(std::vector<std::pair<EVzResultDataT
m_tcpResults[m_currentCameraIndex] = tcpResult;
LOG_INFO("TCP检测结果已缓存: 相机%d, 错误码=%d\n", tcpResult.cameraId, tcpResult.errorCode);
}
// 如果正在进行顺序检测,继续检测下一个设备
if (m_sequentialDetecting) {
m_sequentialCurrentIndex++;
// TCP模式下检查是否所有相机都检测完成
if (m_tcpDetectionMode && m_tcpResults.size() >= 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<WheelMeasureTCPProtocol::CameraMeasureResult> 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 {

View File

@ -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");
}

View File

@ -4,6 +4,9 @@
#include <QDialog>
#include <QTableWidget>
#include <QPushButton>
#include <QSpinBox>
#include <QLabel>
#include <QGroupBox>
#include <vector>
#include <string>
#include "IVrWheelMeasureConfig.h"
@ -70,6 +73,7 @@ private:
std::vector<WheelCameraParam> m_cameraConfigs; // 相机配置列表
const int MAX_CAMERAS = 4; // 最多支持4个相机
WheelMeasurePresenter* m_presenter = nullptr; // Presenter用于访问配置
QSpinBox* m_spinSimultaneousCount = nullptr; // 同时扫描数量
};
#endif // DIALOGCAMERA_H

View File

@ -7,6 +7,7 @@
#include <QThread>
#include <QApplication>
#include <QLabel>
#include <QListView>
#include <cmath>
#include <mutex>
@ -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<int>(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<int>(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) {

View File

@ -5,6 +5,7 @@
#include <QWidget>
#include <QComboBox>
#include <QLineEdit>
#include <QPushButton>
#include <QMessageBox>
#include <QThread>
#include <vector>
@ -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;

View File

@ -109,7 +109,7 @@
<x>130</x>
<y>110</y>
<width>511</width>
<height>271</height>
<height>221</height>
</rect>
</property>
<property name="font">

View File

@ -139,6 +139,7 @@ struct WheelMeasureConfigResult
std::vector<WheelServerInfo> servers; // 服务器列表
WheelMeasureAlgorithmParams algorithmParams; // 算法参数
VrDebugParam debugParam; // 调试参数使用公共VrDebugParam
VrScanConfig scanConfig; // 多相机同时扫描配置
int serverPort = 5900; // 服务器端口
int tcpPort = 5800; // TCP协议端口

View File

@ -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");

View File

@ -224,16 +224,24 @@ void _outputChanneltInfo(char* fileName, std::vector<SSX_rodPoseInfo>& 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<std::vector<SVzNL3DPosition>>& 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<SVzNL3DPoint> 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<std::vector< SVzNL3DPosition>> 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<std::vector< SVzNL3DPosition>> 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<std::vector< SVzNL3DPosition>> 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<SSX_weldSeamInfo> 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();

View File

@ -6,6 +6,9 @@
#include <thread>
#include <mutex>
#include <vector>
#include <queue>
#include <map>
#include <set>
#include <memory>
#include <string>
#include <QObject>
@ -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<int, CameraCallbackContext*> m_cameraContexts;
// 每个相机的独立数据缓存区key = 相机索引 1-based
std::map<int, std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>> m_perCameraDataCache;
std::mutex m_perCameraDataMutex;
// 分批扫描调度相关
std::vector<int> 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);

View File

@ -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
*/

View File

@ -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<int>(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<int>(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<int>(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<int>(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<int>(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<std::mutex> 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<std::mutex> 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<std::mutex> 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<BasePresenter*>(pUserData);
if (!pThis) {
LOG_ERROR("[BasePresenter Detection Callback] pUserData is null\n");
// 提取回调上下文(多相机模式下携带相机索引)
CameraCallbackContext* ctx = static_cast<CameraCallbackContext*>(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<BasePresenter*>(pInfoParam);
if (!pThis) {
LOG_ERROR("[BasePresenter Camera Status Callback] pInfoParam is null\n");
// 提取回调上下文(携带相机索引)
CameraCallbackContext* ctx = static_cast<CameraCallbackContext*>(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<std::mutex> lock(m_perCameraDataMutex);
m_perCameraDataCache[cameraIndex].push_back(std::make_pair(dataType, laserData));
}
void BasePresenter::ClearAllCameraDataCaches()
{
std::lock_guard<std::mutex> lock(m_perCameraDataMutex);
for (auto& pair : m_perCameraDataCache) {
m_dataLoader.FreeLaserScanData(pair.second);
}
m_perCameraDataCache.clear();
}
void BasePresenter::OnCameraScanFinished(int cameraIndex)
{
std::lock_guard<std::mutex> 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<int>(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<int>(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<std::mutex> 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<std::mutex> 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<int>(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<std::mutex> dataLock(m_perCameraDataMutex);
auto it = m_perCameraDataCache.find(cameraIndex);
if (it != m_perCameraDataCache.end() && !it->second.empty()) {
std::lock_guard<std::mutex> 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<std::mutex> lock(m_batchStateMutex);
m_batchStartIndex = batchEnd;
m_batchFinishedCount = 0;
int remaining = static_cast<int>(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<int>(m_batchCameraList.size()));
{
std::lock_guard<std::mutex> lock(m_batchStateMutex);
m_batchInProgress = false;
}
SetWorkStatus(WorkStatus::Completed);
}
}

View File

@ -12,4 +12,8 @@ SUBDIRS += \
GalaxyDevice/GalaxyDevice.pro \
HikDevice/HikDevice.pro \
GlLineLaserDevice/GlLineLaserDevice.pro
# RsLidarDevice/RsLidarDevice.pro
win32-msvc {
SUBDIRS += RsLidarDevice/RsLidarDevice.pro
}

View File

@ -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 |

View File

@ -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

View File

@ -44,6 +44,7 @@ DESKTOP_PROJECTS=(
"WorkpieceHole;workpieceHolePositioning"
"StatorPosition;motorStatorPosition"
"RodAndBarPosition;rodAndBarDetection"
"RodWeldSeam;rodAndBarDetection"
"HolePitPosition;workpieceHolePositioning"
"WheelMeasure;wheelArchHeigthMeasure"

View File

@ -11,3 +11,8 @@ SUBDIRS += \
AuthModule/AuthModule.pro \
HandEyeCalib/HandEyeCalib.pro \
ChessboardDetector/ChessboardDetector.pro
win32-msvc {
SUBDIRS += CloudShow/CloudShow.pro
}

View File

@ -3,4 +3,8 @@ TEMPLATE = subdirs
# 撕裂项目
# SUBDIRS += tcpclient/tcpclient_test.pro
# SUBDIRS += tcpserver/tcpserver_test.pro
# SUBDIRS += RsLidarTest/RsLidarTest.pro
win32-msvc {
SUBDIRS += RsLidarTest/RsLidarTest.pro
}