From 551fbc575a08e04fceaeb4c5662432a3dc2db997 Mon Sep 17 00:00:00 2001 From: yiyi Date: Wed, 10 Sep 2025 00:31:27 +0800 Subject: [PATCH] belt server build & pkg --- BeltTearingApp/BeltTearingApp.pro | 22 +- BeltTearingConfig/BeltTearingConfig.pro | 1 - BeltTearingConfig/Inc/IVrBeltTearingConfig.h | 2 + BeltTearingConfig/Src/VrBeltTearingConfig.cpp | 26 +- BeltTearingConfig/config/config.xml | 11 +- BeltTearingServer/BeltTearingAlgo.cpp | 7 - BeltTearingServer/BeltTearingPresenter.cpp | 553 ++++-- BeltTearingServer/BeltTearingPresenter.h | 70 +- BeltTearingServer/BeltTearingServer.pro | 47 +- BeltTearingServer/PathManager.cpp | 59 + BeltTearingServer/PathManager.h | 48 + BeltTearingServer/Version.h | 29 + BeltTearingServer/main.cpp | 47 +- GrabBagApp/GrabBagApp.pro | 14 +- GrabBagApp/dialogcamera.cpp | 11 +- GrabBagApp/dialogcameralevel.cpp | 1680 ++++++++--------- GrabBagApp/dialogconfig.cpp | 7 +- GrabBagConfig/GrabBagConfig.pro | 4 +- GrabBagConfig/Inc/IVrConfig.h | 678 +++---- GrabBagPrj/pkg_belttearingserver.sh | 89 +- GrabBagPrj/{pkg_app.sh => pkg_grabbagapp.sh} | 0 Module/ModbusTCPServer/ModbusTCPServer.pro | 5 +- VrNets/VrModbus.pro | 7 +- VrUtils/Inc/IVrUtils.h | 6 +- VrUtils/Inc/VrLog.h | 1 + 25 files changed, 1961 insertions(+), 1463 deletions(-) create mode 100644 BeltTearingServer/PathManager.cpp create mode 100644 BeltTearingServer/PathManager.h create mode 100644 BeltTearingServer/Version.h rename GrabBagPrj/{pkg_app.sh => pkg_grabbagapp.sh} (100%) diff --git a/BeltTearingApp/BeltTearingApp.pro b/BeltTearingApp/BeltTearingApp.pro index 1e84946..c42b08e 100644 --- a/BeltTearingApp/BeltTearingApp.pro +++ b/BeltTearingApp/BeltTearingApp.pro @@ -5,10 +5,10 @@ greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TEMPLATE = app CONFIG += c++17 -QMAKE_CXXFLAGS += /utf-8 - -SUBDIRS += ../BeltTearingConfig/BeltTearingConfig.pro \ - ../VrNet/VrTcpClient.pro +# Add /utf-8 flag only for MSVC builds to enforce UTF-8 encoding +win32-msvc { + QMAKE_CXXFLAGS += /utf-8 +} # Include paths INCLUDEPATH += $$PWD/Presenter/Inc @@ -26,9 +26,9 @@ win32:CONFIG(debug, debug|release) { LIBS += -L../VrNets/release -lVrTcpClient LIBS += -L../VrUtils/release -lVrUtils }else:unix:!macx { - LIBS += -L../VrUtils -lVrUtils LIBS += -L../BeltTearingConfig -lBeltTearingConfig LIBS += -L../VrNets -lVrTcpClient + LIBS += -L../VrUtils -lVrUtils } # You can make your code fail to compile if it uses deprecated APIs. @@ -46,10 +46,10 @@ SOURCES += \ widgets/ImageGridWidget.cpp \ widgets/ImageGridWithTableWidget.cpp \ widgets/ImageTileWidget.cpp \ - Presenter/Src/PathManager.cpp \ - Presenter/Src/BeltTearingPresenter.cpp \ + widgets/TearingDataTableWidget.cpp \ widgets/StyledMessageBox.cpp \ - widgets/TearingDataTableWidget.cpp + Presenter/Src/PathManager.cpp \ + Presenter/Src/BeltTearingPresenter.cpp HEADERS += \ IStatusUpdate.h \ @@ -61,10 +61,10 @@ HEADERS += \ widgets/ImageGridWidget.h \ widgets/ImageGridWithTableWidget.h \ widgets/ImageTileWidget.h \ - Presenter/Inc/PathManager.h \ - Presenter/Inc/BeltTearingPresenter.h \ + widgets/TearingDataTableWidget.h \ widgets/StyledMessageBox.h \ - widgets/TearingDataTableWidget.h + Presenter/Inc/PathManager.h \ + Presenter/Inc/BeltTearingPresenter.h FORMS += \ dialognetconfig.ui \ diff --git a/BeltTearingConfig/BeltTearingConfig.pro b/BeltTearingConfig/BeltTearingConfig.pro index 837afd8..c2e91d3 100644 --- a/BeltTearingConfig/BeltTearingConfig.pro +++ b/BeltTearingConfig/BeltTearingConfig.pro @@ -16,7 +16,6 @@ CONFIG += staticlib # 头文件路径 INCLUDEPATH += $$PWD/Inc -INCLUDEPATH += $$PWD/../VrUtils/tinyxml2 # 源文件 HEADERS += \ diff --git a/BeltTearingConfig/Inc/IVrBeltTearingConfig.h b/BeltTearingConfig/Inc/IVrBeltTearingConfig.h index 1d5a0a9..ae6c88e 100644 --- a/BeltTearingConfig/Inc/IVrBeltTearingConfig.h +++ b/BeltTearingConfig/Inc/IVrBeltTearingConfig.h @@ -91,6 +91,8 @@ struct BeltTearingConfigResult DebugParam debugParam; // 调试参数 BeltTearingProjectType projectType; // 项目类型 + + int serverPort = 5900; // 服务端端口 }; /** diff --git a/BeltTearingConfig/Src/VrBeltTearingConfig.cpp b/BeltTearingConfig/Src/VrBeltTearingConfig.cpp index eb24827..5b7e27c 100644 --- a/BeltTearingConfig/Src/VrBeltTearingConfig.cpp +++ b/BeltTearingConfig/Src/VrBeltTearingConfig.cpp @@ -61,12 +61,11 @@ BeltTearingConfigResult VrBeltTearingConfig::LoadConfig(const std::string& fileP xml.readNext(); // 解析服务器配置 - if (xml.isStartElement() && xml.name() == "Servers") { + if (xml.isStartElement() && xml.name() == "ClientServers") { while (xml.readNextStartElement()) { if (xml.name() == "Server") { ServerInfo server; - server.name = xml.attributes().value("name").toString().toStdString(); - server.ip = xml.attributes().value("ip").toString().toStdString(); + server.name = xml.attributes().value("name").toString().toStdString(); server.ip = xml.attributes().value("ip").toString().toStdString(); server.port = xml.attributes().value("port").toInt(); result.servers.push_back(server); xml.skipCurrentElement(); @@ -134,6 +133,16 @@ BeltTearingConfigResult VrBeltTearingConfig::LoadConfig(const std::string& fileP } xml.skipCurrentElement(); } + + // 解析服务端配置 + else if (xml.isStartElement() && xml.name() == "LocalServerConfig") { + while (xml.readNextStartElement()) { + if (xml.name() == "ServerPort") { + result.serverPort = xml.attributes().value("port").toInt(); + xml.skipCurrentElement(); + } + } + } } file.close(); @@ -169,7 +178,7 @@ bool VrBeltTearingConfig::SaveConfig(const std::string& filePath, BeltTearingCon // 保存服务器配置 if (!configResult.servers.empty()) { - xml.writeStartElement("Servers"); + xml.writeStartElement("ClientServers"); for (const auto& server : configResult.servers) { xml.writeStartElement("Server"); xml.writeAttribute("name", QString::fromStdString(server.name)); @@ -177,7 +186,7 @@ bool VrBeltTearingConfig::SaveConfig(const std::string& filePath, BeltTearingCon xml.writeAttribute("port", QString::number(server.port)); xml.writeEndElement(); // Server } - xml.writeEndElement(); // Servers + xml.writeEndElement(); // ClientServers } // 保存算法参数 @@ -221,6 +230,13 @@ bool VrBeltTearingConfig::SaveConfig(const std::string& filePath, BeltTearingCon xml.writeAttribute("type", typeStr); xml.writeEndElement(); // ProjectType + // 保存服务端配置 + xml.writeStartElement("LocalServerConfig"); + xml.writeStartElement("ServerPort"); + xml.writeAttribute("port", QString::number(configResult.serverPort)); + xml.writeEndElement(); // ServerPort + xml.writeEndElement(); // LocalServerConfig + xml.writeEndElement(); // BeltTearingConfig xml.writeEndDocument(); diff --git a/BeltTearingConfig/config/config.xml b/BeltTearingConfig/config/config.xml index ab8a3cb..7cc1dba 100644 --- a/BeltTearingConfig/config/config.xml +++ b/BeltTearingConfig/config/config.xml @@ -1,11 +1,16 @@ - - + + - + + + + + + diff --git a/BeltTearingServer/BeltTearingAlgo.cpp b/BeltTearingServer/BeltTearingAlgo.cpp index ccb45e0..996a662 100644 --- a/BeltTearingServer/BeltTearingAlgo.cpp +++ b/BeltTearingServer/BeltTearingAlgo.cpp @@ -93,13 +93,7 @@ std::vector BeltTearingAlgo::Predit(std::vector 0) { - // std::cout << "未知的撕裂数量: " << beltTearings_unknown.size() << std::endl; - // } - // if (beltTearings_growing.size() > 0) { - // std::cout << "进行中的撕裂数量: " << beltTearings_growing.size() << std::endl; - // } if (beltTearings_new.size() > 0) //收集撕裂点 { @@ -116,6 +110,5 @@ std::vector BeltTearingAlgo::Predit(std::vector #include #include #include #include -#include -#include #include #include -#include + +#include "VrLog.h" BeltTearingPresenter::BeltTearingPresenter(QObject *parent) : QObject(parent) , m_tcpServer(new QTcpServer(this)) - , m_dataTimer(new QTimer(this)) + , m_cameraInitTimer(new QTimer(this)) , m_port(0) + , m_config(nullptr) + , m_eyeDevice(nullptr) + , m_beltTearingAlgo(new BeltTearingAlgo()) + , m_cameraInitialized(false) + , m_cameraDetecting(false) { + // 打印版本信息 + LOG_INFO("Initializing %s %s\n", getProductName().toStdString().c_str(), getVersionString().toStdString().c_str()); + LOG_INFO("Build: %s\n", getBuildInfo().toStdString().c_str()); + // 连接信号槽 connect(m_tcpServer, &QTcpServer::newConnection, this, &BeltTearingPresenter::onNewConnection); - connect(m_dataTimer, &QTimer::timeout, this, &BeltTearingPresenter::onSendSimulatedData); + connect(m_cameraInitTimer, &QTimer::timeout, this, &BeltTearingPresenter::onCameraInitTimer); + + // 创建配置实例 + IVrBeltTearingConfig::CreateInstance(&m_config); + if (m_config) { + m_config->SetConfigChangeNotify(this); + } + + // 初始化相机 + initializeCamera(); } BeltTearingPresenter::~BeltTearingPresenter() { stopServer(); + stopCamera(); + + if (m_beltTearingAlgo) { + delete m_beltTearingAlgo; + m_beltTearingAlgo = nullptr; + } + + if (m_config) { + delete m_config; + m_config = nullptr; + } +} + +bool BeltTearingPresenter::loadConfiguration(const QString& configFilePath) +{ + if (!m_config) { + LOG_WARNING("Config instance not created\n"); + return false; + } + + m_configFilePath = configFilePath; + + try { + // 加载配置文件 + m_configResult = m_config->LoadConfig(configFilePath.toStdString()); + + LOG_INFO("Configuration loaded successfully:\n"); + LOG_INFO(" Server Port: %d\n", m_configResult.serverPort); + LOG_INFO(" Project Type: %d\n", static_cast(m_configResult.projectType)); + LOG_INFO(" Servers count: %d\n", m_configResult.servers.size()); + + // 应用算法参数 + applyAlgorithmParameters(m_configResult.algorithmParams); + + // 输出算法参数信息 + const auto& params = m_configResult.algorithmParams; + LOG_INFO("Algorithm Parameters:\n"); + LOG_INFO(" Tear Threshold: %f\n", params.beltTearingParam.tearThreshold); + LOG_INFO(" Min Tear Length: %f\n", params.beltTearingParam.minTearLength); + LOG_INFO(" Max Tear Width: %f\n", params.beltTearingParam.maxTearWidth); + LOG_INFO(" Blur Size: %d\n", params.imageProcessingParam.blurSize); + LOG_INFO(" Check Interval: %d\n", params.monitoringParam.checkInterval); + + return true; + + } catch (const std::exception& e) { + LOG_ERROR("Error loading configuration: %s\n", e.what()); + return false; + } +} + +void BeltTearingPresenter::applyAlgorithmParameters(const BeltTearingAlgorithmParams& params) +{ + if (!m_beltTearingAlgo) { + return; + } + + // 重置算法参数 + m_beltTearingAlgo->ResetParameter(); + + // 应用算法参数 + LOG_DEBUG("Applying algorithm parameters...\n"); + LOG_DEBUG(" Tear Threshold: %f\n", params.beltTearingParam.tearThreshold); + LOG_DEBUG(" Min Tear Length: %f\n", params.beltTearingParam.minTearLength); + LOG_DEBUG(" Max Tear Width: %f\n", params.beltTearingParam.maxTearWidth); + LOG_DEBUG(" Blur Size: %d\n", params.imageProcessingParam.blurSize); + LOG_DEBUG(" Canny Threshold 1: %f\n", params.imageProcessingParam.cannyThreshold1); + LOG_DEBUG(" Canny Threshold 2: %f\n", params.imageProcessingParam.cannyThreshold2); + LOG_DEBUG(" Check Interval: %d ms\n", params.monitoringParam.checkInterval); + LOG_DEBUG(" Alert Threshold: %f\n", params.monitoringParam.alertThreshold); + + // 调试参数 + if (m_configResult.debugParam.enableDebug) { + LOG_DEBUG("Debug mode enabled:\n"); + LOG_DEBUG(" Save debug images: %s\n", (m_configResult.debugParam.saveDebugImage ? "Yes" : "No")); + LOG_DEBUG(" Print detail log: %s\n", (m_configResult.debugParam.printDetailLog ? "Yes" : "No")); + } + + // TODO: 根据BeltTearingAlgo的实际接口设置参数 + // 例如:m_beltTearingAlgo->SetTearThreshold(params.beltTearingParam.tearThreshold); +} + +void BeltTearingPresenter::OnConfigChanged(const BeltTearingConfigResult& configResult) +{ + LOG_INFO("Configuration changed notification received\n"); + + // 更新配置结果 + m_configResult = configResult; + + // 重新应用算法参数 + applyAlgorithmParameters(configResult.algorithmParams); + + // 如果服务器端口改变,可能需要重启服务器 + if (m_tcpServer->isListening() && m_tcpServer->serverPort() != configResult.serverPort) { + LOG_INFO("Server port changed, restarting server...\n"); + stopServer(); + startServer(configResult.serverPort); + } +} + +bool BeltTearingPresenter::initializeCamera() +{ + if (m_eyeDevice) { + return true; // 已经初始化过 + } + + int result = IVrEyeDevice::CreateObject(&m_eyeDevice); + if (result != 0 || !m_eyeDevice) { + LOG_ERROR("Failed to create VrEyeDevice object, error code: %d\n", result); + return false; + } + + result = m_eyeDevice->InitDevice(); + if (result != 0) { + LOG_ERROR("Failed to initialize VrEyeDevice, error code: %d\n", result); + delete m_eyeDevice; + m_eyeDevice = nullptr; + return false; + } + + // 设置状态回调 + result = m_eyeDevice->SetStatusCallback(OnStatusCallback, this); + if (result != 0) { + LOG_WARNING("Failed to set status callback, error code: %d\n", result); + } + + LOG_INFO("VrEyeDevice initialized successfully\n"); + return true; +} + +void BeltTearingPresenter::startCamera() +{ + if (!m_eyeDevice) { + if (!initializeCamera()) { + // 启动定时器持续重试初始化 + LOG_WARNING("Camera initialization failed, starting retry timer...\n"); + m_cameraInitTimer->start(5000); // 每5秒重试一次 + return; + } + } + + // 尝试打开设备 + int result = m_eyeDevice->OpenDevice(nullptr); + if (result != 0) { + LOG_WARNING("Failed to open camera device, error code: %d, retrying...\n", result); + + // 启动定时器持续重试连接 + m_cameraInitTimer->start(3000); // 每3秒重试一次 + return; + } + + // 停止重试定时器 + m_cameraInitTimer->stop(); + m_cameraInitialized = true; + + // 开始检测 + result = m_eyeDevice->StartDetect(OnPointCloudCallback, keResultDataType_Position, this); + if (result != 0) { + LOG_ERROR("Failed to start detection, error code: %d\n", result); + return; + } + + m_cameraDetecting = true; +} + +void BeltTearingPresenter::stopCamera() +{ + m_cameraInitTimer->stop(); + + if (m_eyeDevice) { + if (m_cameraDetecting) { + m_eyeDevice->StopDetect(); + m_cameraDetecting = false; + } + + if (m_cameraInitialized) { + m_eyeDevice->CloseDevice(); + m_cameraInitialized = false; + } + + delete m_eyeDevice; + m_eyeDevice = nullptr; + } + + LOG_INFO("Camera stopped\n"); +} + +void BeltTearingPresenter::onCameraInitTimer() +{ + LOG_DEBUG("Retrying camera initialization...\n"); + + // 尝试重新初始化和连接相机 + if (!m_eyeDevice) { + if (!initializeCamera()) { + return; // 继续重试 + } + } + + // 尝试连接相机 + int result = m_eyeDevice->OpenDevice(nullptr); + if (result == 0) { + // 连接成功,开始检测 + m_cameraInitTimer->stop(); + m_cameraInitialized = true; + + result = m_eyeDevice->StartDetect(OnPointCloudCallback, keResultDataType_Position, this); + if (result == 0) { + m_cameraDetecting = true; + LOG_INFO("Camera connection restored successfully!\n"); + } else { + LOG_ERROR("Failed to start detection after reconnection, error code: %d\n", result); + } + } else { + LOG_WARNING("Still unable to connect to camera, continuing retry...\n"); + } +} + +void BeltTearingPresenter::OnStatusCallback(EVzDeviceWorkStatus eStatus, void* pExtData, unsigned int nDataLength, void* pInfoParam) +{ + BeltTearingPresenter* presenter = static_cast(pInfoParam); + if (!presenter) return; + + LOG_DEBUG("Camera status changed: %d\n", static_cast(eStatus)); + + // 处理相机状态变化 + switch (eStatus) { + case keDeviceWorkStatus_Eye_Comming: + LOG_INFO("Camera connected\n"); + break; + case keDeviceWorkStatus_Offline: + LOG_WARNING("Camera disconnected, attempting to reconnect...\n"); + presenter->m_cameraInitialized = false; + presenter->m_cameraDetecting = false; + // 开始重连尝试 + if (!presenter->m_cameraInitTimer->isActive()) { + presenter->m_cameraInitTimer->start(3000); + } + break; + default: + break; + } +} + +void BeltTearingPresenter::OnPointCloudCallback(EVzResultDataType eDataType, SVzLaserLineData* pLaserLinePoint, void* pParam) +{ + if(keResultDataType_Position != eDataType) return; + + BeltTearingPresenter* presenter = static_cast(pParam); + if (!presenter || !pLaserLinePoint) return; + + // 处理点云数据 + presenter->processPointCloudData(pLaserLinePoint); +} + +void BeltTearingPresenter::processPointCloudData(const SVzLaserLineData* pLaserLine) +{ + if (!m_beltTearingAlgo || !pLaserLine) return; + + try { + + SVzNL3DLaserLine sNL3DLaserLine; + sNL3DLaserLine.nTimeStamp = pLaserLine->llTimeStamp; + sNL3DLaserLine.p3DPosition = static_cast(pLaserLine->p3DPoint); + sNL3DLaserLine.nPositionCnt = pLaserLine->nPointCount; + // 调用算法进行检测 + std::vector results = m_beltTearingAlgo->Predit(&sNL3DLaserLine, pLaserLine->llFrameIdx); + + // 如果有检测结果,发送到网络客户端 + if (!results.empty()) { + LOG_INFO("Detected %d belt tearing(s)\n", results.size()); + sendTearingResults(results); + } + + } catch (const std::exception& e) { + LOG_ERROR("Error in algorithm processing: %s\n", e.what()); + } +} + +void BeltTearingPresenter::sendTearingResults(const std::vector& results) +{ + if (results.empty() || m_clients.isEmpty()) { + return; + } + + // 将检测结果转换为JSON格式 + QJsonArray tearingArray; + + for (const auto& result : results) { + QJsonObject tearingObj; + // tearingObj["level"] = QString::number(result.level); + tearingObj["id"] = QString::number(result.tearID); + tearingObj["tearStatus"] = QString::number(static_cast(result.tearStatus)); + tearingObj["tearType"] = QString::number(result.tearType); + tearingObj["statLineIdx"] = QString::number(result.statLineIdx); + tearingObj["endLineIdx"] = QString::number(result.endLineIdx); + tearingObj["tearDepth"] = QString::number(result.tearDepth); + tearingObj["tearWidth"] = QString::number(result.tearWidth); + // tearingObj["tearLength"] = QString::number(result.tearLength); + tearingObj["roiLeft"] = QString::number(result.roi.left); + tearingObj["roiRight"] = QString::number(result.roi.right); + tearingObj["roiTop"] = QString::number(result.roi.top); + tearingObj["roiBottom"] = QString::number(result.roi.bottom); + // tearingObj["imageFile"] = QString::fromStdString(result.imageFile); + // tearingObj["older"] = QString::fromStdString(result.older); + tearingObj["timestamp"] = QDateTime::currentDateTime().toString(Qt::ISODate); + + tearingArray.append(tearingObj); + } + + // 转换为JSON字符串 + QJsonDocument doc(tearingArray); + QByteArray message = doc.toJson(QJsonDocument::Compact); + + // 创建数据包 + QByteArray package; + QDataStream stream(&package, QIODevice::WriteOnly); + stream.setByteOrder(QDataStream::BigEndian); + stream << quint8(static_cast(ByteDataType::Text)) << quint32(message.size()); + package.append(message); + package.append("___END___\r\n"); + + // 发送到所有连接的客户端 + for (auto it = m_clients.begin(); it != m_clients.end(); ++it) { + QTcpSocket *socket = it.value(); + if (socket->state() == QAbstractSocket::ConnectedState) { + socket->write(package); + } + } + + LOG_INFO("Sent tearing detection results to %d client(s)\n", m_clients.size()); } bool BeltTearingPresenter::startServer(quint16 port) @@ -36,9 +385,9 @@ bool BeltTearingPresenter::startServer(quint16 port) bool result = m_tcpServer->listen(QHostAddress::Any, port); if (result) { - std::cout << "TCP server started on port " << port << std::endl; + LOG_INFO("TCP server started on port %d\n", port); } else { - std::cout << "Failed to start TCP server: " << m_tcpServer->errorString().toStdString() << std::endl; + LOG_ERROR("Failed to start TCP server: %s\n", m_tcpServer->errorString().toStdString().c_str()); } return result; @@ -57,25 +406,9 @@ void BeltTearingPresenter::stopServer() m_tcpServer->close(); } - // 停止数据发送定时器 - stopSendingSimulatedData(); - m_port = 0; } -void BeltTearingPresenter::startSendingSimulatedData() -{ - // 启动定时器,每2秒发送一次模拟数据 - m_dataTimer->start(1000); -} - -void BeltTearingPresenter::stopSendingSimulatedData() -{ - if (m_dataTimer->isActive()) { - m_dataTimer->stop(); - } -} - void BeltTearingPresenter::onNewConnection() { while (m_tcpServer->hasPendingConnections()) { @@ -90,7 +423,7 @@ void BeltTearingPresenter::onNewConnection() // 连接客户端信号 connect(socket, &QTcpSocket::disconnected, this, &BeltTearingPresenter::onClientDisconnected); - std::cout << "Client connected: " << clientId.toStdString() << std::endl; + LOG_INFO("Client connected: %s\n", clientId.toStdString().c_str()); // 发送欢迎消息 QByteArray welcomeMsg = "Welcome to BeltTearing Server!"; @@ -116,164 +449,34 @@ void BeltTearingPresenter::onClientDisconnected() if (!clientId.isEmpty()) { m_clients.remove(clientId); - std::cout << "Client disconnected: " << clientId.toStdString() << std::endl; + LOG_INFO("Client disconnected: %s\n", clientId.toStdString().c_str()); } socket->deleteLater(); } -void BeltTearingPresenter::onSendSimulatedData() -{ - static unsigned int nCount = 0; - nCount++; - // 向所有连接的客户端发送模拟数据 - for (auto it = m_clients.begin(); it != m_clients.end(); ++it) { - QTcpSocket *socket = it.value(); - if (socket->state() == QAbstractSocket::ConnectedState) { - if(nCount % 2 == 0){ - sendSimulatedImage(socket); - }else{ - sendSimulatedTearingData(socket); - } - // 随机选择发送图像或日志记录 - // sendSimulatedImage(socket); - // sendSimulatedLogRecords(socket); - // sendSimulatedTearingData(socket); - } - } -} - -void BeltTearingPresenter::sendSimulatedImage(QTcpSocket *socket) -{ - // 创建一个模拟的图像 - cv::Mat image(480, 640, CV_8UC3, cv::Scalar(128, 128, 128)); - - // 在图像上绘制一些随机内容 - cv::circle(image, cv::Point(320, 240), 50, cv::Scalar(0, 0, 255), -1); - cv::rectangle(image, cv::Rect(100, 100, 200, 150), cv::Scalar(0, 255, 0), 2); - - static unsigned int s_imageCount = 0; - // 在图像上叠加数字 - QString numberText = QString::number(++s_imageCount); // 生成一个随机数 - cv::putText(image, numberText.toStdString(), cv::Point(50, 50), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(255, 255, 255), 2); - - // 编码图像 - std::vector imgBuffer; - std::vector params = {cv::IMWRITE_PNG_COMPRESSION, 3}; - cv::imencode(".png", image, imgBuffer, params); - - QByteArray byteArray(reinterpret_cast(imgBuffer.data()), imgBuffer.size()); - - // 创建数据包 - QByteArray package; - QDataStream stream(&package, QIODevice::WriteOnly); - stream.setByteOrder(QDataStream::BigEndian); - stream << quint8(static_cast(ByteDataType::Image)) << quint32(byteArray.size()); - package.append(byteArray); - package.append("___END___\r\n"); - - // 发送数据 - socket->write(package); - - std::cout << "Sent simulated image data to client" << std::endl; -} - -void BeltTearingPresenter::sendSimulatedLogRecords(QTcpSocket *socket) -{ - // 创建模拟日志记录 - QJsonArray logRecords; - - for (int i = 0; i < 3; i++) { - QJsonObject record; - record["timestamp"] = QDateTime::currentDateTime().toString(Qt::ISODate); - record["level"] = QString("INFO"); - record["message"] = QString("Simulated log message %1").arg(i + 1); - record["source"] = QString("BeltTearingDetector"); - - logRecords.append(record); - } - - // 将日志记录转换为JSON字符串 - QJsonDocument doc(logRecords); - QByteArray message = doc.toJson(QJsonDocument::Compact); - - // 创建数据包 - QByteArray package; - QDataStream stream(&package, QIODevice::WriteOnly); - stream.setByteOrder(QDataStream::BigEndian); - stream << quint8(static_cast(ByteDataType::Text)) << quint32(message.size()); - package.append(message); - package.append("___END___\r\n"); - - // 发送数据 - socket->write(package); - - std::cout << "Sent simulated log records to client" << std::endl; -} - -void BeltTearingPresenter::sendSimulatedTearingData(QTcpSocket *socket) -{ - // 创建模拟撕裂数据 - QJsonArray tearingDataArray; - - // 生成随机数量的撕裂数据(1-3个) - int tearingCount = QRandomGenerator::global()->bounded(1, 4); - - for (int i = 0; i < tearingCount; i++) { - QJsonObject tearingInfo; - - // 随机生成撕裂数据 - int level = QRandomGenerator::global()->bounded(1, 4); // 1-3级 - int tearStatus = QRandomGenerator::global()->bounded(1, 4); // 1-3状态 - - QString statusStr; - switch (tearStatus) { - case 1: statusStr = "新的撕裂"; break; - case 2: statusStr = "进行中"; break; - case 3: statusStr = "已完成"; break; - default: statusStr = "无效撕裂"; break; - } - - // 填充撕裂信息 - tearingInfo.insert("level", QString::number(level)); - tearingInfo.insert("id", QString::number(QRandomGenerator::global()->bounded(1000, 9999))); - tearingInfo.insert("tearStatus", statusStr); - tearingInfo.insert("tearType", QString::number(QRandomGenerator::global()->bounded(1, 5))); - tearingInfo.insert("statLineIdx", QString::number(QRandomGenerator::global()->bounded(1, 100))); - tearingInfo.insert("endLineIdx", QString::number(QRandomGenerator::global()->bounded(1, 100))); - tearingInfo.insert("tearDepth", QString::number(QRandomGenerator::global()->bounded(1, 1000))); - tearingInfo.insert("tearWidth", QString::number(QRandomGenerator::global()->bounded(1, 500))); - tearingInfo.insert("tearLength", QString::number(QRandomGenerator::global()->bounded(1, 1000))); - tearingInfo.insert("roiLeft", QString::number(QRandomGenerator::global()->bounded(1, 200))); - tearingInfo.insert("roiRight", QString::number(QRandomGenerator::global()->bounded(200, 400))); - tearingInfo.insert("roiTop", QString::number(QRandomGenerator::global()->bounded(1, 100))); - tearingInfo.insert("roiBottom", QString::number(QRandomGenerator::global()->bounded(100, 300))); - tearingInfo.insert("imageFile", QString("/Image/Tearing/image_%1__.jpg").arg(QRandomGenerator::global()->bounded(1, 100))); - tearingInfo.insert("older", (QRandomGenerator::global()->bounded(0, 2) == 0) ? "-" : "旧伤"); - - tearingDataArray.append(tearingInfo); - } - - // 将撕裂数据转换为JSON字符串 - QJsonDocument doc(tearingDataArray); - QByteArray message = doc.toJson(QJsonDocument::Compact); - - // 创建数据包 - QByteArray package; - QDataStream stream(&package, QIODevice::WriteOnly); - stream.setByteOrder(QDataStream::BigEndian); - stream << quint8(static_cast(ByteDataType::Text)) << quint32(message.size()); - package.append(message); - package.append("___END___\r\n"); - - // 发送数据 - socket->write(package); - - std::cout << "Sent simulated tearing data to client" << std::endl; -} - QString BeltTearingPresenter::generateClientId(QTcpSocket *socket) { // 使用UUID生成唯一的客户端ID return QUuid::createUuid().toString(); } + +QString BeltTearingPresenter::getVersionString() +{ + return QString(BELT_TEARING_SERVER_VERSION_STRING); +} + +QString BeltTearingPresenter::getProductName() +{ + return QString(BELT_TEARING_SERVER_PRODUCT_NAME); +} + +QString BeltTearingPresenter::getBuildInfo() +{ + return QString("%1 %2 built on %3 %4 for %5") + .arg(BELT_TEARING_SERVER_BUILD_TYPE) + .arg(BELT_TEARING_SERVER_VERSION_STRING) + .arg(BELT_TEARING_SERVER_BUILD_DATE) + .arg(BELT_TEARING_SERVER_BUILD_TIME) + .arg(BELT_TEARING_SERVER_PLATFORM); +} diff --git a/BeltTearingServer/BeltTearingPresenter.h b/BeltTearingServer/BeltTearingPresenter.h index 9dfb32e..782d16e 100644 --- a/BeltTearingServer/BeltTearingPresenter.h +++ b/BeltTearingServer/BeltTearingPresenter.h @@ -7,6 +7,13 @@ #include #include #include +#include "IVrEyeDevice.h" +#include "BeltTearingAlgo.h" +#include "beltTearingDetection_Export.h" +#include "IVrBeltTearingConfig.h" +#include "VZNL_Common.h" +#include "VZNL_Types.h" +#include "VrLog.h" // 假设ByteDataType枚举定义 enum class ByteDataType : quint8 { @@ -14,7 +21,7 @@ enum class ByteDataType : quint8 { Image = 2 }; -class BeltTearingPresenter : public QObject +class BeltTearingPresenter : public QObject, public IVrBeltTearingConfigChangeNotify { Q_OBJECT @@ -22,29 +29,70 @@ public: explicit BeltTearingPresenter(QObject *parent = nullptr); ~BeltTearingPresenter(); + // 配置相关方法 + bool loadConfiguration(const QString& configFilePath); + void applyAlgorithmParameters(const BeltTearingAlgorithmParams& params); + // 初始化TCP服务器 - bool startServer(quint16 port); + bool startServer(quint16 port = 0); // port = 0 表示使用配置文件中的端口 void stopServer(); - // 模拟数据发送 - void startSendingSimulatedData(); - void stopSendingSimulatedData(); + // 相机相关方法 + bool initializeCamera(); + void startCamera(); + void stopCamera(); + + // 获取配置信息 + quint16 getServerPort() const { return m_configResult.serverPort; } + const BeltTearingConfigResult& getConfig() const { return m_configResult; } + + // 获取版本信息 + static QString getVersionString(); + static QString getProductName(); + static QString getBuildInfo(); + + // IVrBeltTearingConfigChangeNotify接口实现 + void OnConfigChanged(const BeltTearingConfigResult& configResult) override; private slots: void onNewConnection(); void onClientDisconnected(); - void onSendSimulatedData(); + void onCameraInitTimer(); private: + // TCP服务器相关 QTcpServer *m_tcpServer; QMap m_clients; - QTimer *m_dataTimer; quint16 m_port; - // 模拟数据发送方法 - void sendSimulatedImage(QTcpSocket *socket); - void sendSimulatedLogRecords(QTcpSocket *socket); - void sendSimulatedTearingData(QTcpSocket *socket); + // 配置相关 + IVrBeltTearingConfig *m_config; + BeltTearingConfigResult m_configResult; + QString m_configFilePath; + + // 相机相关 + IVrEyeDevice *m_eyeDevice; + QTimer *m_cameraInitTimer; + QString m_cameraIP; + bool m_cameraInitialized; + bool m_cameraDetecting; + + // 算法相关 + BeltTearingAlgo *m_beltTearingAlgo; + + // 相机状态回调函数 + static void OnStatusCallback(EVzDeviceWorkStatus eStatus, void* pExtData, unsigned int nDataLength, void* pInfoParam); + + // 点云数据回调函数 + static void OnPointCloudCallback(EVzResultDataType eDataType, SVzLaserLineData* pLaserLinePoint, void* pParam); + + // 处理点云数据 + void processPointCloudData(const SVzLaserLineData* pLaserLine); + + // 发送算法结果 + void sendTearingResults(const std::vector& results); + + // 工具方法 QString generateClientId(QTcpSocket *socket); }; diff --git a/BeltTearingServer/BeltTearingServer.pro b/BeltTearingServer/BeltTearingServer.pro index d98c650..6071414 100644 --- a/BeltTearingServer/BeltTearingServer.pro +++ b/BeltTearingServer/BeltTearingServer.pro @@ -1,5 +1,4 @@ -QT += core gui -QT += serialport +QT += core QT += network greaterThan(QT_MAJOR_VERSION, 4): QT += widgets @@ -7,7 +6,10 @@ greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TEMPLATE = app CONFIG += c++11 console -QMAKE_CXXFLAGS += /utf-8 +# Add /utf-8 flag only for MSVC builds to enforce UTF-8 encoding +win32-msvc { + QMAKE_CXXFLAGS += /utf-8 +} # You can make your code fail to compile if it uses deprecated APIs. # In order to do so, uncomment the following line. @@ -20,12 +22,14 @@ INCLUDEPATH += $$PWD/Utils/Inc INCLUDEPATH += $$PWD/../VrConfig/Inc INCLUDEPATH += $$PWD/../VrEyeDevice/Inc +INCLUDEPATH += $$PWD/../BeltTearingConfig/Inc SOURCES += \ BeltTearingAlgo.cpp \ beltTearingDetection.cpp \ main.cpp \ - BeltTearingPresenter.cpp + BeltTearingPresenter.cpp \ + PathManager.cpp HEADERS += \ SG_baseAlgo_Export.h \ @@ -33,20 +37,25 @@ HEADERS += \ SG_errCode.h \ beltTearingDetection_Export.h \ BeltTearingAlgo.h \ - BeltTearingPresenter.h + BeltTearingPresenter.h \ + PathManager.h \ + Version.h FORMS += win32:CONFIG(debug, debug|release) { - LIBS += -L../VrUtils/debug -lVrUtils - LIBS += -L../VrEyeDevice/debug -lVrEyeDevice + LIBS += -L../BeltTearingConfig/debug -lBeltTearingConfig + LIBS += -L../VrEyeDevice/debug -lVrEyeDevice + LIBS += -L../VrUtils/debug -lVrUtils }else:win32:CONFIG(release, debug|release){ - LIBS += -L../VrUtils/release -lVrUtils - LIBS += -L../VrEyeDevice/release -lVrEyeDevice + LIBS += -L../BeltTearingConfig/release -lBeltTearingConfig + LIBS += -L../VrEyeDevice/release -lVrEyeDevice + LIBS += -L../VrUtils/release -lVrUtils }else:unix:!macx { # Unix/Linux平台库链接(包括交叉编译) # 注意链接顺序:依赖关系从高到低 LIBS += -L../VrUtils -lVrUtils + LIBS += -L../BeltTearingConfig -lBeltTearingConfig LIBS += -L../VrEyeDevice -lVrEyeDevice # 添加系统库依赖 @@ -84,22 +93,10 @@ else:unix:!macx: { LIBS += -L$$PWD/../SDK/OpenCV320/Arm/aarch64 -lopencv_core -lopencv_imgproc -lopencv_highgui } -# 添加libmodbus依赖 + +# 添加Windows系统库依赖 win32 { + LIBS += -lshell32 LIBS += -lws2_32 - LIBS += Advapi32.lib + LIBS += -lAdvapi32 } - -# Default rules for deployment. -unix { - target.path = /usr/lib - # Link real-time library for POSIX shared memory functions - LIBS += -lrt -} -!isEmpty(target.path): INSTALLS += target - - -# Default rules for deployment. -qnx: target.path = /tmp/$${TARGET}/bin -else: unix:!android: target.path = /opt/$${TARGET}/bin -!isEmpty(target.path): INSTALLS += target diff --git a/BeltTearingServer/PathManager.cpp b/BeltTearingServer/PathManager.cpp new file mode 100644 index 0000000..b5de6c4 --- /dev/null +++ b/BeltTearingServer/PathManager.cpp @@ -0,0 +1,59 @@ +#include "PathManager.h" +#include +#include +#include +#include +#include +#include + +QString PathManager::GetConfigFilePath() +{ + // 确保目标目录存在 + EnsureConfigDirectoryExists(); + return GetAppConfigDirectory() + "/config.xml"; +} + + +QString PathManager::GetAppConfigDirectory() +{ + QString configDir = ""; +#ifdef _WIN32 + // Windows系统:使用程序目录 + configDir = GetProgramDirectory() + "/BeltTearingServer/Config"; +#else + // Linux系统:使用用户配置目录 + configDir = GetUserConfigDirectory() + "/../BeltTearingServer/Config"; +#endif + return configDir; +} + +bool PathManager::EnsureConfigDirectoryExists() +{ + QString configDir = GetAppConfigDirectory(); + + if (QDir().exists(configDir)) { + return true; + } + + std::cout << "Creating configuration directory: " << configDir.toStdString() << std::endl; + + bool success = QDir().mkpath(configDir); + if (success) { + std::cout << "Configuration directory created successfully" << std::endl; + } else { + std::cout << "Failed to create configuration directory: " << configDir.toStdString() << std::endl; + } + + return success; +} + +QString PathManager::GetProgramDirectory() +{ + QString exePath = QCoreApplication::applicationFilePath(); + return QFileInfo(exePath).absoluteDir().path(); +} + +QString PathManager::GetUserConfigDirectory() +{ + return QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); +} \ No newline at end of file diff --git a/BeltTearingServer/PathManager.h b/BeltTearingServer/PathManager.h new file mode 100644 index 0000000..c43b9de --- /dev/null +++ b/BeltTearingServer/PathManager.h @@ -0,0 +1,48 @@ +#ifndef PATHMANAGER_H +#define PATHMANAGER_H + +#include + +/** + * @brief 路径管理器类,统一管理皮带撕裂服务器的配置文件路径 + * + * 该类负责根据不同操作系统提供合适的配置文件存储路径: + * - Windows: 程序目录 + * - Linux: 用户配置目录 (~/.config/BeltTearingServer/) + */ +class PathManager +{ +public: + /** + * @brief 获取配置文件(BeltTearingConfig.xml)的完整路径 + * @return 配置文件的完整路径 + */ + static QString GetConfigFilePath(); + +private: + /** + * @brief 确保配置目录存在,如果不存在则创建 + * @return 成功创建或目录已存在返回true,失败返回false + */ + static bool EnsureConfigDirectoryExists(); + + /** + * @brief 获取应用程序配置目录路径 + * @return 配置目录的完整路径 + */ + static QString GetAppConfigDirectory(); + + /** + * @brief 获取程序目录路径 + * @return 程序目录的完整路径 + */ + static QString GetProgramDirectory(); + + /** + * @brief 获取用户配置目录路径(仅Linux系统) + * @return 用户配置目录的完整路径 + */ + static QString GetUserConfigDirectory(); +}; + +#endif // PATHMANAGER_H \ No newline at end of file diff --git a/BeltTearingServer/Version.h b/BeltTearingServer/Version.h new file mode 100644 index 0000000..44f69bf --- /dev/null +++ b/BeltTearingServer/Version.h @@ -0,0 +1,29 @@ +#ifndef VERSION_H +#define VERSION_H + +#define BELT_TEARING_SERVER_VERSION_STRING "1.0.0" +#define BELT_TEARING_SERVER_VERSION_BUILD "1" +#define BELT_TEARING_SERVER_PRODUCT_NAME "BeltTearingServer" +#define BELT_TEARING_SERVER_COMPANY_NAME "VisionRobot" +#define BELT_TEARING_SERVER_COPYRIGHT "Copyright (C) 2024 VisionRobot. All rights reserved." +#define BELT_TEARING_SERVER_DESCRIPTION "Belt Tearing Detection Server with Camera Integration" + +// 构建信息 +#define BELT_TEARING_SERVER_BUILD_DATE __DATE__ +#define BELT_TEARING_SERVER_BUILD_TIME __TIME__ + +#ifdef _WIN32 + #define BELT_TEARING_SERVER_PLATFORM "Windows" +#elif __linux__ + #define BELT_TEARING_SERVER_PLATFORM "Linux" +#else + #define BELT_TEARING_SERVER_PLATFORM "Unknown" +#endif + +#ifdef _DEBUG + #define BELT_TEARING_SERVER_BUILD_TYPE "Debug" +#else + #define BELT_TEARING_SERVER_BUILD_TYPE "Release" +#endif + +#endif // VERSION_H \ No newline at end of file diff --git a/BeltTearingServer/main.cpp b/BeltTearingServer/main.cpp index bebdb6e..e744b22 100644 --- a/BeltTearingServer/main.cpp +++ b/BeltTearingServer/main.cpp @@ -1,25 +1,60 @@ #include #include +#include +#include #include "BeltTearingPresenter.h" +#include "PathManager.h" +#include "version.h" + +#include "VrLog.h" int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); + // 设置应用程序信息 + app.setApplicationName(BELT_TEARING_SERVER_PRODUCT_NAME); + app.setApplicationVersion(BELT_TEARING_SERVER_VERSION_STRING); + app.setOrganizationName(BELT_TEARING_SERVER_COMPANY_NAME); + + // 打印启动横幅 + LOG_DEBUG("===========================================\n"); + LOG_INFO(" %s\n", BELT_TEARING_SERVER_PRODUCT_NAME); + LOG_INFO(" %s\n", BELT_TEARING_SERVER_DESCRIPTION); + LOG_INFO(" %s\n", BELT_TEARING_SERVER_COPYRIGHT); + LOG_DEBUG("===========================================\n"); + // 创建并初始化Presenter BeltTearingPresenter presenter; - // 启动TCP服务器,监听端口12345 - if (!presenter.startServer(12345)) { - std::cout << "Failed to start server" << std::endl; + // 获取配置文件路径 + QString configFilePath = PathManager::GetConfigFilePath(); + + // 加载配置文件 + if (!presenter.loadConfiguration(configFilePath)) { + LOG_WARN("Warning: Failed to load configuration file. Using default settings.\n"); + LOG_INFO("Config file location: %s\n", configFilePath.toStdString().c_str()); + } + + // 启动TCP服务器(使用配置文件中的端口,如果配置加载失败则使用默认端口) + quint16 serverPort = presenter.getServerPort(); + if (serverPort == 0) { + serverPort = 5900; // 默认端口 + } + + if (!presenter.startServer(serverPort)) { + LOG_ERROR("Failed to start server on port %d\n", serverPort); return -1; } - // 开始发送模拟数据 - presenter.startSendingSimulatedData(); + // 启动相机 + presenter.startCamera(); - std::cout << "BeltTearing Server started. Press Ctrl+C to exit." << std::endl; + // 输出系统信息 + LOG_INFO("=== BeltTearing Server Started ===\n"); + LOG_DEBUG("===================================\n"); + LOG_INFO("Server is running. Press Ctrl+C to exit.\n"); return app.exec(); } \ No newline at end of file diff --git a/GrabBagApp/GrabBagApp.pro b/GrabBagApp/GrabBagApp.pro index 065cab2..9f31b5f 100644 --- a/GrabBagApp/GrabBagApp.pro +++ b/GrabBagApp/GrabBagApp.pro @@ -6,6 +6,10 @@ greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TEMPLATE = app CONFIG += c++17 +# Add /utf-8 flag only for MSVC builds to enforce UTF-8 encoding +win32-msvc { + QMAKE_CXXFLAGS += /utf-8 +} # You can make your code fail to compile if it uses deprecated APIs. @@ -91,12 +95,12 @@ win32:CONFIG(debug, debug|release) { }else:unix:!macx { # Unix/Linux平台库链接(包括交叉编译) # 注意链接顺序:依赖关系从高到低 - LIBS += -L../GrabBagConfig -lGrabBagConfig - LIBS += -L../VrUtils -lVrUtils - LIBS += -L../VrEyeDevice -lVrEyeDevice + LIBS += -L../GrabBagConfig -lGrabBagConfig + LIBS += -L../VrEyeDevice -lVrEyeDevice LIBS += -L../Module/ModbusTCPServer -lModbusTCPServer - LIBS += -L../Module/ShareMem -lShareMem - LIBS += -L../VrNets -lVrNets + LIBS += -L../VrNets -lVrModbus + LIBS += -L../Module/ShareMem -lShareMem + LIBS += -L../VrUtils -lVrUtils # 添加系统库依赖 LIBS += -lpthread diff --git a/GrabBagApp/dialogcamera.cpp b/GrabBagApp/dialogcamera.cpp index f314509..fcd7e28 100644 --- a/GrabBagApp/dialogcamera.cpp +++ b/GrabBagApp/dialogcamera.cpp @@ -2,7 +2,6 @@ #include "ui_dialogcamera.h" #include #include "VrLog.h" -#include "StyledMessageBox.h" DialogCamera::DialogCamera(const std::vector>& deviceList, QWidget *parent) : @@ -72,16 +71,16 @@ void DialogCamera::on_camera_selection_changed(int index) void DialogCamera::on_btn_camer_ok_clicked() { if (!m_currentDevice) { - StyledMessageBox::warning(this, "错误", "设备未初始化"); + QMessageBox::warning(this, "错误", "设备未初始化"); return; } // 应用参数配置 if (ApplyCameraParameters()) { - StyledMessageBox::information(this, "成功", "相机参数配置成功!"); + QMessageBox::information(this, "成功", "相机参数配置成功!"); accept(); // 关闭对话框并返回Accepted } else { - StyledMessageBox::warning(this, "失败", "相机参数配置失败,请检查设备连接!"); + QMessageBox::warning(this, "失败", "相机参数配置失败,请检查设备连接!"); } } @@ -133,7 +132,7 @@ void DialogCamera::InitCameraParameters() } } catch (...) { - StyledMessageBox::critical(this, "错误", "读取相机参数时发生异常"); + QMessageBox::critical(this, "错误", "读取相机参数时发生异常"); } } @@ -193,7 +192,7 @@ bool DialogCamera::ApplyCameraParameters() return success; } catch (...) { - StyledMessageBox::critical(this, "错误", "应用相机参数时发生异常"); + QMessageBox::critical(this, "错误", "应用相机参数时发生异常"); return false; } } diff --git a/GrabBagApp/dialogcameralevel.cpp b/GrabBagApp/dialogcameralevel.cpp index a230dc9..ca2e6bb 100644 --- a/GrabBagApp/dialogcameralevel.cpp +++ b/GrabBagApp/dialogcameralevel.cpp @@ -1,841 +1,839 @@ -#include "dialogcameralevel.h" -#include "ui_dialogcameralevel.h" -#include "VrLog.h" -#include "SG_bagPositioning_Export.h" -#include "PathManager.h" -#include -#include -#include -#include -#include -#include "StyledMessageBox.h" - - -#include "LaserDataLoader.h" -#include -#include -#include -#include -#include - -DialogCameraLevel::DialogCameraLevel(QWidget *parent) - : QDialog(parent) - , ui(new Ui::DialogCameraLevel) -{ - ui->setupUi(this); - - // 隐藏标题栏 - setWindowFlags(Qt::FramelessWindowHint); - - // 初始化界面 - initializeCameraCombo(); - - // 初始化结果显示区域 - ui->label_level_result->setText("请选择相机并点击调平按钮\n开始相机调平操作"); - ui->label_level_result->setAlignment(Qt::AlignCenter); -} - -DialogCameraLevel::~DialogCameraLevel() -{ - // 清理扫描数据缓存 - clearScanDataCache(); - - // 确保恢复Presenter的状态回调 - restorePresenterStatusCallback(); - - delete ui; -} - -void DialogCameraLevel::setCameraList(const std::vector>& cameraList, - GrabBagPresenter* presenter) -{ - m_cameraList = cameraList; - m_presenter = presenter; - - // 重新初始化相机选择框 - initializeCameraCombo(); -} - -void DialogCameraLevel::initializeCameraCombo() -{ - ui->combo_camera->clear(); - - if (m_cameraList.empty()) { - ui->combo_camera->setEnabled(false); - ui->label_level_result->setText("无可用相机设备"); - ui->label_level_result->setAlignment(Qt::AlignCenter); - } else { - for (const auto& camera : m_cameraList) { - ui->combo_camera->addItem(QString::fromStdString(camera.first)); - } - ui->combo_camera->setEnabled(true); - - // 检查并显示当前选中相机的标定状态 - checkAndDisplayCalibrationStatus(0); // 默认选中第一个相机 - } -} - -void DialogCameraLevel::on_btn_apply_clicked() -{ - ui->label_level_result->setAlignment(Qt::AlignLeft); - -#ifndef LEVEL_DEBUG_MODE - // 检查是否有可用的相机 - if (m_cameraList.empty()) { - StyledMessageBox::warning(this, "错误", "无可用相机设备!"); - return; - } - - // 获取选中的相机 - int selectedIndex = ui->combo_camera->currentIndex(); - if (selectedIndex < 0 || selectedIndex >= m_cameraList.size()) { - StyledMessageBox::warning(this, "错误", "请选择有效的相机!"); - return; - } -#endif - - // 清空之前的结果显示 - ui->label_level_result->setText("调平计算中,请稍候..."); - - // 显示进度提示 - ui->btn_apply->setEnabled(false); - QApplication::processEvents(); - - try { - // 执行相机调平 - if (performCameraLeveling()) { - // 调平成功,关闭对话框(这会触发析构函数中的回调恢复) - // accept(); - } else { - // 显示失败信息到界面 - ui->label_level_result->setText("调平失败!\n\n请检查:\n1. 相机连接是否正常\n2. 地面扫描数据是否充足\n3. 扫描区域是否有足够的地面"); - } - } catch (const std::exception& e) { - LOG_ERROR("Camera leveling failed with exception: %s\n", e.what()); - StyledMessageBox::critical(this, "错误", QString("调平过程发生异常:%1").arg(e.what())); - } - - // 恢复按钮状态 - ui->btn_apply->setEnabled(true); -} - -void DialogCameraLevel::on_btn_cancel_clicked() -{ - // 直接关闭窗口,不保存任何更改 - reject(); -} - -bool DialogCameraLevel::performCameraLeveling() -{ - try { - // 获取选中的相机索引 - int selectedIndex = ui->combo_camera->currentIndex(); - - // 1. 设置调平状态回调 - setLevelingStatusCallback(); - -#ifdef LEVEL_DEBUG_MODE - // Debug模式:使用文件对话框选择测试数据 - LOG_INFO("=== DEBUG MODE ENABLED ===\n"); - - // 选择debug数据文件 - QString debugDataFile = selectDebugDataFile(); - if (debugDataFile.isEmpty()) { - LOG_INFO("Debug data file selection cancelled\n"); - return false; - } - - // 使用选择的debug数据进行模拟扫描 - if (!loadDebugDataAndSimulateScan(debugDataFile)) { - LOG_ERROR("Failed to load debug data for camera leveling\n"); - return false; - } -#else - // 正常模式:使用真实相机 - if (selectedIndex < 0 || selectedIndex >= m_cameraList.size()) { - LOG_ERROR("Invalid camera index: %d\n", selectedIndex); - return false; - } - - LOG_INFO("Performing camera leveling with camera %d (index %d)\n", selectedIndex + 1, selectedIndex); - - // 2. 清空之前的扫描数据 - clearScanDataCache(); - - // 3. 启动相机扫描地面数据 - if (!startCameraScan(selectedIndex)) { - LOG_ERROR("Failed to start camera scan for leveling\n"); - return false; - } - - // 4. 等待扫描完成(使用状态回调判断) - LOG_INFO("Collecting ground scan data, waiting for swing finish signal...\n"); - int waitTime = 0; - const int maxWaitTime = 10000; // 最大等待10秒 - const int checkInterval = 100; // 每100ms检查一次 - - while (!m_swingFinished && waitTime < maxWaitTime) { - QThread::msleep(checkInterval); - QApplication::processEvents(); // 处理UI事件 - waitTime += checkInterval; - } - - // 5. 停止扫描 - stopCameraScan(selectedIndex); - - // 检查是否通过状态回调收到了扫描完成信号 - if (m_swingFinished) { - LOG_INFO("Camera swing finished signal received, scan completed\n"); - } else if (waitTime >= maxWaitTime) { - LOG_WARNING("Timeout waiting for camera swing finish signal\n"); - } -#endif - - // 5. 检查是否收集到足够的数据 - // 6. 调用调平算法计算 - double planeCalib[9]; - double planeHeight; - double invRMatrix[9]; - - if (!calculatePlaneCalibration(planeCalib, planeHeight, invRMatrix)) { - LOG_ERROR("Failed to calculate plane calibration\n"); - return false; - } - - LOG_INFO("Camera leveling calculation completed\n"); - - // 7. 更新界面显示 - updateLevelingResults(planeCalib, planeHeight, invRMatrix); - - // 8. 保存结果到配置 - // 获取相机索引和名称传递给保存方法 - int cameraIndex = selectedIndex + 1; // 转换为1-based索引 - QString cameraName; - -#ifdef LEVEL_DEBUG_MODE - // Debug模式下使用默认名称 - cameraIndex = 1; - cameraName = QString("Camera_%1").arg(cameraIndex); -#else - // 正常模式下从列表获取名称 - if (selectedIndex >= 0 && selectedIndex < m_cameraList.size()) { - cameraName = QString::fromStdString(m_cameraList[selectedIndex].first); - } else { - cameraName = QString("Camera_%1").arg(cameraIndex); - } -#endif - - if (!saveLevelingResults(planeCalib, planeHeight, invRMatrix, cameraIndex, cameraName)) { - LOG_ERROR("Failed to save leveling results\n"); - return false; - } - - clearScanDataCache(); - LOG_INFO("Camera leveling completed successfully\n"); - - return true; - - } catch (const std::exception& e) { - LOG_ERROR("Exception in performCameraLeveling: %s\n", e.what()); - - return false; - } -} - -void DialogCameraLevel::updateLevelingResults(double planeCalib[9], double planeHeight, double invRMatrix[9]) -{ - // 计算旋转角度的近似值用于显示 - double rotX = atan2(planeCalib[5], planeCalib[8]) * 180.0 / M_PI; - double rotY = atan2(-planeCalib[2], sqrt(planeCalib[5]*planeCalib[5] + planeCalib[8]*planeCalib[8])) * 180.0 / M_PI; - - // 构建显示文本,将矩阵直接显示到页面上 - QString resultText; -#if 0 - // 基本信息 - resultText += QString("旋转角度 X: %1°\n").arg(QString::number(rotX, 'f', 2)); - resultText += QString("旋转角度 Y: %1°\n").arg(QString::number(rotY, 'f', 2)); -#endif - - resultText += QString("地面高度: %1 mm\n").arg(QString::number(planeHeight, 'f', 2)); - - // 调平矩阵 - resultText += QString("调平矩阵:\n"); - for (int i = 0; i < 3; i++) { - resultText += QString("[%1, %2, %3]\n") - .arg(QString::number(planeCalib[i*3], 'f', 4)) - .arg(QString::number(planeCalib[i*3+1], 'f', 4)) - .arg(QString::number(planeCalib[i*3+2], 'f', 4)); - } - - resultText += QString("逆旋转矩阵:\n"); - for (int i = 0; i < 3; i++) { - resultText += QString("[%1, %2, %3]\n") - .arg(QString::number(invRMatrix[i*3], 'f', 4)) - .arg(QString::number(invRMatrix[i*3+1], 'f', 4)) - .arg(QString::number(invRMatrix[i*3+2], 'f', 4)); - } - - // 将结果显示到界面上 - ui->label_level_result->setText(resultText); - ui->label_level_result->setAlignment(Qt::AlignLeft | Qt::AlignTop); -} - -// 启动相机扫描 -bool DialogCameraLevel::startCameraScan(int cameraIndex) -{ - if (cameraIndex < 0 || cameraIndex >= m_cameraList.size()) { - LOG_ERROR("Invalid camera index for scan: %d\n", cameraIndex); - return false; - } - - IVrEyeDevice* camera = m_cameraList[cameraIndex].second; - if (!camera) { - LOG_ERROR("Camera device is null at index: %d\n", cameraIndex); - return false; - } - - // 启动相机检测,使用静态回调函数 - int result = camera->StartDetect(&DialogCameraLevel::StaticDetectionCallback, keResultDataType_Position, this); - if (result != 0) { - LOG_ERROR("Failed to start camera detection: %d\n", result); - return false; - } - - LOG_INFO("Camera scan started successfully for camera index: %d\n", cameraIndex); - return true; -} - -// 停止相机扫描 -bool DialogCameraLevel::stopCameraScan(int cameraIndex) -{ - if (cameraIndex < 0 || cameraIndex >= m_cameraList.size()) { - LOG_ERROR("Invalid camera index for stop scan: %d\n", cameraIndex); - return false; - } - - IVrEyeDevice* camera = m_cameraList[cameraIndex].second; - if (!camera) { - LOG_ERROR("Camera device is null at index: %d\n", cameraIndex); - return false; - } - - int result = camera->StopDetect(); - if (result != 0) { - LOG_WARNING("Failed to stop camera detection, error: %d\n", result); - return false; - } - - LOG_INFO("Camera scan stopped successfully for camera index: %d\n", cameraIndex); - return true; -} - -// 静态检测回调函数 -void DialogCameraLevel::StaticDetectionCallback(EVzResultDataType eDataType, SVzLaserLineData* pLaserLinePoint, void* pUserData) -{ - DialogCameraLevel* pThis = reinterpret_cast(pUserData); - if (pThis && pLaserLinePoint) { - pThis->DetectionCallback(eDataType, pLaserLinePoint); - } -} - -// 静态状态回调函数 -void DialogCameraLevel::StaticStatusCallback(EVzDeviceWorkStatus eStatus, void* pExtData, unsigned int nDataLength, void* pInfoParam) -{ - DialogCameraLevel* pThis = reinterpret_cast(pInfoParam); - if (pThis) { - pThis->StatusCallback(eStatus, pExtData, nDataLength, pInfoParam); - } -} - -// 状态回调函数实例版本 -void DialogCameraLevel::StatusCallback(EVzDeviceWorkStatus eStatus, void* pExtData, unsigned int nDataLength, void* pInfoParam) -{ - LOG_DEBUG("[Leveling Status Callback] received: status=%d\n", (int)eStatus); - - switch (eStatus) { - case EVzDeviceWorkStatus::keDeviceWorkStatus_Device_Swing_Finish: - { - LOG_INFO("[Leveling Status Callback] Camera swing finished, scan completed\n"); - m_swingFinished = true; // 摆动完成即表示扫描完成 - break; - } - default: - LOG_DEBUG("[Leveling Status Callback] Other status: %d\n", (int)eStatus); - break; - } -} - -// 检测数据回调函数实例版本 -void DialogCameraLevel::DetectionCallback(EVzResultDataType eDataType, SVzLaserLineData* pLaserLinePoint) -{ - if (!pLaserLinePoint) { - LOG_WARNING("[Leveling Callback] pLaserLinePoint is null\n"); - return; - } - - if (pLaserLinePoint->nPointCount <= 0) { - LOG_WARNING("[Leveling Callback] Point count is zero or negative: %d\n", pLaserLinePoint->nPointCount); - return; - } - - if (!pLaserLinePoint->p3DPoint) { - LOG_WARNING("[Leveling Callback] p3DPoint is null\n"); - return; - } - - // 转换数据格式:从SVzLaserLineData转换为SVzNL3DLaserLine并存储到缓存 - SVzNL3DLaserLine laser3DLine; - - // 复制基本信息 - laser3DLine.nTimeStamp = pLaserLinePoint->llTimeStamp; - laser3DLine.nPositionCnt = pLaserLinePoint->nPointCount; - - // 分配和复制点云数据 - laser3DLine.p3DPosition = new SVzNL3DPosition[pLaserLinePoint->nPointCount]; - // 复制点云数据 - memcpy(laser3DLine.p3DPosition, pLaserLinePoint->p3DPoint, sizeof(SVzNL3DPosition) * pLaserLinePoint->nPointCount); - - // 将转换后的数据保存到缓存中 - std::lock_guard lock(m_scanDataMutex); - m_scanDataCache.push_back(laser3DLine); -} - -// 调平算法计算 -bool DialogCameraLevel::calculatePlaneCalibration(double planeCalib[9], double& planeHeight, double invRMatrix[9]) -{ - std::lock_guard lock(m_scanDataMutex); - // 检查是否有足够的扫描数据 - if (m_scanDataCache.empty()) { - LOG_ERROR("No scan data available for plane calibration\n"); - return false; - } - - LOG_INFO("Calculating plane calibration from %zu scan lines\n", m_scanDataCache.size()); - - try { - // 调用实际的调平算法 - SSG_planeCalibPara calibResult = sg_getBagBaseCalibPara(m_scanDataCache.data(), static_cast(m_scanDataCache.size())); - - // 将结构体中的数据复制到输出参数 - for (int i = 0; i < 9; i++) { - planeCalib[i] = calibResult.planeCalib[i]; - invRMatrix[i] = calibResult.invRMatrix[i]; - } - planeHeight = calibResult.planeHeight; - - // 计算旋转角度用于日志显示 - double rotAngleX = atan2(planeCalib[5], planeCalib[8]) * 180.0 / M_PI; - double rotAngleY = atan2(-planeCalib[2], sqrt(planeCalib[5]*planeCalib[5] + planeCalib[8]*planeCalib[8])) * 180.0 / M_PI; - - LOG_INFO("Plane calibration calculated: height=%.3f, rotX=%.2f°, rotY=%.2f°\n", - planeHeight, rotAngleX, rotAngleY); - -#ifdef LEVEL_DEBUG_MODE - // 统计点云数据 - int totalPoints = 0; - double avgHeight = 0.0; - for (const auto& scanLine : m_scanDataCache) { - for (int i = 0; i < scanLine.nPositionCnt; i++) { - if (scanLine.p3DPosition[i].pt3D.z > -1000 && scanLine.p3DPosition[i].pt3D.z < 1000) { - avgHeight += scanLine.p3DPosition[i].pt3D.z; - totalPoints++; - } - } - } - - LOG_INFO("=== DEBUG MODE CALIBRATION RESULTS ===\n"); - - LOG_INFO("Algorithm results:\n"); - LOG_INFO(" Calculated plane height: %.3f mm\n", calibResult.planeHeight); - LOG_INFO("Calibration matrix:\n"); - for (int i = 0; i < 3; i++) { - LOG_INFO(" [%.6f, %.6f, %.6f]\n", calibResult.planeCalib[i*3], calibResult.planeCalib[i*3+1], calibResult.planeCalib[i*3+2]); - } - LOG_INFO("Inverse rotation matrix:\n"); - for (int i = 0; i < 3; i++) { - LOG_INFO(" [%.6f, %.6f, %.6f]\n", calibResult.invRMatrix[i*3], calibResult.invRMatrix[i*3+1], calibResult.invRMatrix[i*3+2]); - } - LOG_INFO("=======================================\n"); -#endif - - return true; - - } catch (const std::exception& e) { - LOG_ERROR("Exception in sg_getBagBaseCalibPara: %s\n", e.what()); - return false; - } catch (...) { - LOG_ERROR("Unknown exception in sg_getBagBaseCalibPara\n"); - return false; - } -} - -// 清空扫描数据缓存 -void DialogCameraLevel::clearScanDataCache() -{ - std::lock_guard lock(m_scanDataMutex); - - LOG_DEBUG("Clearing scan data cache, current size: %zu\n", m_scanDataCache.size()); - - // 释放缓存的内存 - for (auto& cachedLine : m_scanDataCache) { - if (cachedLine.p3DPosition) { - delete[] cachedLine.p3DPosition; - cachedLine.p3DPosition = nullptr; - } - } - - // 清空缓存容器 - m_scanDataCache.clear(); - - LOG_DEBUG("Scan data cache cleared successfully\n"); -} - -bool DialogCameraLevel::saveLevelingResults(double planeCalib[9], double planeHeight, double invRMatrix[9], int cameraIndex, const QString& cameraName) -{ - try { - if (!m_presenter) { - LOG_ERROR("Presenter is null, cannot save leveling results\n"); - return false; - } - - // 获取配置对象 - IVrConfig* config = m_presenter->GetConfig(); - if (!config) { - LOG_ERROR("Config is null, cannot save leveling results\n"); - return false; - } - - // 验证传入的相机参数 - if (cameraIndex <= 0) { - LOG_ERROR("Invalid camera index: %d\n", cameraIndex); - return false; - } - - if (cameraName.isEmpty()) { - LOG_ERROR("Camera name is empty\n"); - return false; - } - - // 加载当前配置 - QString configPath = PathManager::GetConfigFilePath(); - LOG_INFO("Config path: %s\n", configPath.toUtf8().constData()); - ConfigResult configResult = config->LoadConfig(configPath.toStdString()); - - // 创建或更新指定相机的调平参数 - VrCameraPlaneCalibParam cameraParam; - cameraParam.cameraIndex = cameraIndex; - cameraParam.cameraName = cameraName.toStdString(); - cameraParam.planeHeight = planeHeight; - cameraParam.isCalibrated = true; - - // 复制校准矩阵 - for (int i = 0; i < 9; i++) { - cameraParam.planeCalib[i] = planeCalib[i]; - cameraParam.invRMatrix[i] = invRMatrix[i]; - } - - // 更新配置中的相机校准参数 - configResult.algorithmParams.planeCalibParam.SetCameraCalibParam(cameraParam); - - // 保存配置 - bool saveResult = config->SaveConfig(configPath.toStdString(), configResult); - if (!saveResult) { - LOG_ERROR("Failed to save config with leveling results\n"); - return false; - } - - LOG_INFO("Leveling results saved successfully for camera %d (%s)\n", cameraIndex, cameraName.toUtf8().constData()); - LOG_INFO("Plane height: %.3f\n", planeHeight); - LOG_INFO("Calibration marked as completed\n"); - - return true; - - } catch (const std::exception& e) { - LOG_ERROR("Exception in saveLevelingResults: %s\n", e.what()); - return false; - } -} - -// 选择debug数据文件 -QString DialogCameraLevel::selectDebugDataFile() -{ - // 获取应用程序目录作为起始目录 - QString appDir = QCoreApplication::applicationDirPath(); - QString testDataDir = appDir + "/TestData"; - - // 如果TestData目录不存在,使用应用程序目录 - if (!QFileInfo::exists(testDataDir)) { - testDataDir = appDir; - } - - // 打开文件选择对话框 - QString fileName = QFileDialog::getOpenFileName( - this, - "选择激光扫描数据文件", - testDataDir, - "文本文件 (*.txt);;所有文件 (*.*)" - ); - - if (fileName.isEmpty()) { - LOG_INFO("No debug data file selected\n"); - return ""; - } - - // 检查文件是否存在 - if (!QFileInfo::exists(fileName)) { - LOG_ERROR("Selected debug data file does not exist: %s\n", fileName.toUtf8().constData()); - StyledMessageBox::warning(this, "文件错误", "选择的文件不存在!"); - return ""; - } - - LOG_INFO("Selected debug data file: %s\n", fileName.toUtf8().constData()); - - return fileName; -} - -// 加载debug数据并模拟扫描过程 -bool DialogCameraLevel::loadDebugDataAndSimulateScan(const QString& filePath) -{ - LOG_INFO("Loading debug data from: %s\n", filePath.toUtf8().constData()); - - try { - // 清空之前的扫描数据 - clearScanDataCache(); - - // 使用LaserDataLoader加载测试数据 - LaserDataLoader dataLoader; - std::vector> debugData; - int lineNum = 0; - float scanSpeed = 0.0f; - int maxTimeStamp = 0; - int clockPerSecond = 0; - - int result = dataLoader.LoadLaserScanData(filePath.toStdString(), debugData, - lineNum, scanSpeed, maxTimeStamp, clockPerSecond); - - if (result != 0) { - LOG_ERROR("Failed to load debug data: %s\n", dataLoader.GetLastError().c_str()); - StyledMessageBox::critical(this, "Debug模式错误", - QString("加载测试数据失败:\n%1").arg(QString::fromStdString(dataLoader.GetLastError()))); - return false; - } - - if (debugData.empty()) { - LOG_ERROR("Debug data is empty\n"); - StyledMessageBox::warning(this, "Debug模式警告", "测试数据文件为空!"); - return false; - } - - LOG_INFO("Loaded %d lines of debug data, starting simulation\n", lineNum); - - // 将加载的数据复制到缓存中(需要深拷贝) - { - std::lock_guard lock(m_scanDataMutex); - m_scanDataCache.reserve(debugData.size()); - - for (const auto& linePair : debugData) { - EVzResultDataType dataType = linePair.first; - const SVzLaserLineData& lineData = linePair.second; - - // 只处理Position类型的数据(相机调平只需要位置信息) - if (dataType == keResultDataType_Position && lineData.p3DPoint) { - SVzNL3DLaserLine copyLine; - copyLine.nTimeStamp = 0; // 从SVzLaserLineData中获取时间戳 - copyLine.nPositionCnt = lineData.nPointCount; - - if (lineData.nPointCount > 0) { - copyLine.p3DPosition = new SVzNL3DPosition[lineData.nPointCount]; - memcpy(copyLine.p3DPosition, lineData.p3DPoint, sizeof(SVzNL3DPosition) * lineData.nPointCount); - } else { - copyLine.p3DPosition = nullptr; - } - - m_scanDataCache.push_back(copyLine); - } - } - } - - // 释放临时加载的数据 - dataLoader.FreeLaserScanData(debugData); - - // 启动模拟扫描过程 - simulateScanProcess(); - - return true; - - } catch (const std::exception& e) { - LOG_ERROR("Exception in loadDebugDataAndSimulateScan: %s\n", e.what()); - StyledMessageBox::critical(this, "Debug模式异常", QString("加载debug数据时发生异常:\n%1").arg(e.what())); - return false; - } -} - -// 模拟扫描过程 -void DialogCameraLevel::simulateScanProcess() -{ - LOG_INFO("Starting scan simulation process\n"); - - // 创建定时器来模拟数据收集过程 - QTimer* simulationTimer = new QTimer(); - simulationTimer->setSingleShot(true); - - // 模拟扫描需要的时间(2秒) - simulationTimer->setInterval(2000); - - // 连接定时器信号 - connect(simulationTimer, &QTimer::timeout, [this, simulationTimer]() { - LOG_INFO("Scan simulation completed\n"); - m_swingFinished = true; // Debug模式下模拟摆动完成 - - // 显示调试信息 - { - std::lock_guard lock(m_scanDataMutex); - LOG_INFO("Debug scan simulation completed with %zu lines\n", m_scanDataCache.size()); - - // 统计点云数据 - int totalPoints = 0; - for (const auto& line : m_scanDataCache) { - totalPoints += line.nPositionCnt; - } - LOG_INFO("Total points in debug data: %d\n", totalPoints); - } - - // 清理定时器 - simulationTimer->deleteLater(); - }); - - // 启动模拟扫描 - simulationTimer->start(); - - LOG_INFO("Simulation timer started, waiting for completion...\n"); -} - -// 相机选择改变的槽函数 -void DialogCameraLevel::on_combo_camera_currentIndexChanged(int index) -{ - if (index >= 0 && index < m_cameraList.size()) { - LOG_INFO("Camera selection changed to index: %d (%s)\n", index, - QString::fromStdString(m_cameraList[index].first).toUtf8().constData()); - checkAndDisplayCalibrationStatus(index); - } else { - LOG_WARNING("Invalid camera index selected: %d\n", index); - ui->label_level_result->setText("无效的相机选择"); - ui->label_level_result->setAlignment(Qt::AlignCenter); - } -} - -// 加载相机标定数据 -bool DialogCameraLevel::loadCameraCalibrationData(int cameraIndex, const QString& cameraName, - double planeCalib[9], double& planeHeight, double invRMatrix[9]) -{ - try { - if (!m_presenter) { - LOG_ERROR("Presenter is null, cannot load calibration data\n"); - return false; - } - - // 获取配置对象 - IVrConfig* config = m_presenter->GetConfig(); - if (!config) { - LOG_ERROR("Config is null, cannot load calibration data\n"); - return false; - } - - // 加载配置文件 - QString configPath = PathManager::GetConfigFilePath(); - ConfigResult configResult = config->LoadConfig(configPath.toStdString()); - - // 获取指定相机的标定参数 - const VrCameraPlaneCalibParam* cameraParam = configResult.algorithmParams.planeCalibParam.GetCameraCalibParam(cameraIndex); - - if (!cameraParam || !cameraParam->isCalibrated) { - LOG_INFO("No calibration data found for camera %d (%s)\n", cameraIndex, cameraName.toUtf8().constData()); - return false; - } - - // 复制标定数据 - for (int i = 0; i < 9; i++) { - planeCalib[i] = cameraParam->planeCalib[i]; - invRMatrix[i] = cameraParam->invRMatrix[i]; - } - planeHeight = cameraParam->planeHeight; - - LOG_INFO("Calibration data loaded successfully for camera %d (%s)\n", cameraIndex, cameraName.toUtf8().constData()); - LOG_INFO("Plane height: %.3f\n", planeHeight); - - return true; - - } catch (const std::exception& e) { - LOG_ERROR("Exception in loadCameraCalibrationData: %s\n", e.what()); - return false; - } -} - -// 检查并显示相机标定状态 -void DialogCameraLevel::checkAndDisplayCalibrationStatus(int cameraIndex) -{ - if (cameraIndex < 0 || cameraIndex >= m_cameraList.size()) { - LOG_WARNING("Invalid camera index for status check: %d\n", cameraIndex); - ui->label_level_result->setText("无效的相机索引"); - ui->label_level_result->setAlignment(Qt::AlignCenter); - return; - } - - QString cameraName = QString::fromStdString(m_cameraList[cameraIndex].first); - int configCameraIndex = cameraIndex + 1; // 转换为1-based索引 - - // 尝试加载该相机的标定数据 - double planeCalib[9]; - double planeHeight; - double invRMatrix[9]; - - if (loadCameraCalibrationData(configCameraIndex, cameraName, planeCalib, planeHeight, invRMatrix)) { - // 有标定数据,显示标定结果 - 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, showing instruction\n", cameraName.toUtf8().constData()); - ui->label_level_result->setText(QString("请选择相机 \"%1\" 并点击调平按钮\n开始相机调平操作").arg(cameraName)); - ui->label_level_result->setAlignment(Qt::AlignCenter); - } -} - -// 设置调平时的状态回调 -void DialogCameraLevel::setLevelingStatusCallback() -{ - if (!m_presenter) { - LOG_ERROR("Presenter is null, cannot set leveling status callback\n"); - return; - } - - // 为所有相机设置调平状态回调 - m_presenter->SetCameraStatusCallback(&DialogCameraLevel::StaticStatusCallback, this); - - // 重置状态标志 - m_swingFinished = false; - m_callbackRestored = false; // 重置恢复标志,允许稍后恢复 - - LOG_INFO("Leveling status callback set for all cameras\n"); -} - -// 恢复Presenter的状态回调 -void DialogCameraLevel::restorePresenterStatusCallback() -{ - // 检查是否已经恢复过回调,避免重复调用 - if (m_callbackRestored.exchange(true)) { - LOG_DEBUG("Presenter status callback already restored, skipping\n"); - return; - } - - if (!m_presenter) { - LOG_ERROR("Presenter is null, cannot restore status callback\n"); - return; - } - - // 恢复Presenter的状态回调 - 使用GrabBagPresenter的静态回调 - m_presenter->SetCameraStatusCallback(&GrabBagPresenter::_StaticCameraNotify, m_presenter); - - LOG_INFO("Presenter status callback restored for all cameras\n"); -} - +#include "dialogcameralevel.h" +#include "ui_dialogcameralevel.h" +#include "IVrUtils.h" +#include "SG_bagPositioning_Export.h" +#include "PathManager.h" +#include +#include +#include +#include +#include + +#include "LaserDataLoader.h" +#include +#include +#include +#include +#include + +DialogCameraLevel::DialogCameraLevel(QWidget *parent) + : QDialog(parent) + , ui(new Ui::DialogCameraLevel) +{ + ui->setupUi(this); + + // 隐藏标题栏 + setWindowFlags(Qt::FramelessWindowHint); + + // 初始化界面 + initializeCameraCombo(); + + // 初始化结果显示区域 + ui->label_level_result->setText("请选择相机并点击调平按钮\n开始相机调平操作"); + ui->label_level_result->setAlignment(Qt::AlignCenter); +} + +DialogCameraLevel::~DialogCameraLevel() +{ + // 清理扫描数据缓存 + clearScanDataCache(); + + // 确保恢复Presenter的状态回调 + restorePresenterStatusCallback(); + + delete ui; +} + +void DialogCameraLevel::setCameraList(const std::vector>& cameraList, + GrabBagPresenter* presenter) +{ + m_cameraList = cameraList; + m_presenter = presenter; + + // 重新初始化相机选择框 + initializeCameraCombo(); +} + +void DialogCameraLevel::initializeCameraCombo() +{ + ui->combo_camera->clear(); + + if (m_cameraList.empty()) { + ui->combo_camera->setEnabled(false); + ui->label_level_result->setText("无可用相机设备"); + ui->label_level_result->setAlignment(Qt::AlignCenter); + } else { + for (const auto& camera : m_cameraList) { + ui->combo_camera->addItem(QString::fromStdString(camera.first)); + } + ui->combo_camera->setEnabled(true); + + // 检查并显示当前选中相机的标定状态 + checkAndDisplayCalibrationStatus(0); // 默认选中第一个相机 + } +} + +void DialogCameraLevel::on_btn_apply_clicked() +{ + ui->label_level_result->setAlignment(Qt::AlignLeft); + +#ifndef LEVEL_DEBUG_MODE + // 检查是否有可用的相机 + if (m_cameraList.empty()) { + QMessageBox::warning(this, "错误", "无可用相机设备!"); + return; + } + + // 获取选中的相机 + int selectedIndex = ui->combo_camera->currentIndex(); + if (selectedIndex < 0 || selectedIndex >= m_cameraList.size()) { + QMessageBox::warning(this, "错误", "请选择有效的相机!"); + return; + } +#endif + + // 清空之前的结果显示 + ui->label_level_result->setText("调平计算中,请稍候..."); + + // 显示进度提示 + ui->btn_apply->setEnabled(false); + QApplication::processEvents(); + + try { + // 执行相机调平 + if (performCameraLeveling()) { + // 调平成功,关闭对话框(这会触发析构函数中的回调恢复) + // accept(); + } else { + // 显示失败信息到界面 + ui->label_level_result->setText("调平失败!\n\n请检查:\n1. 相机连接是否正常\n2. 地面扫描数据是否充足\n3. 扫描区域是否有足够的地面"); + } + } catch (const std::exception& e) { + LOG_ERROR("Camera leveling failed with exception: %s\n", e.what()); + QMessageBox::critical(this, "错误", QString("调平过程发生异常:%1").arg(e.what())); + } + + // 恢复按钮状态 + ui->btn_apply->setEnabled(true); +} + +void DialogCameraLevel::on_btn_cancel_clicked() +{ + // 直接关闭窗口,不保存任何更改 + reject(); +} + +bool DialogCameraLevel::performCameraLeveling() +{ + try { + // 获取选中的相机索引 + int selectedIndex = ui->combo_camera->currentIndex(); + + // 1. 设置调平状态回调 + setLevelingStatusCallback(); + +#ifdef LEVEL_DEBUG_MODE + // Debug模式:使用文件对话框选择测试数据 + LOG_INFO("=== DEBUG MODE ENABLED ===\n"); + + // 选择debug数据文件 + QString debugDataFile = selectDebugDataFile(); + if (debugDataFile.isEmpty()) { + LOG_INFO("Debug data file selection cancelled\n"); + return false; + } + + // 使用选择的debug数据进行模拟扫描 + if (!loadDebugDataAndSimulateScan(debugDataFile)) { + LOG_ERROR("Failed to load debug data for camera leveling\n"); + return false; + } +#else + // 正常模式:使用真实相机 + if (selectedIndex < 0 || selectedIndex >= m_cameraList.size()) { + LOG_ERROR("Invalid camera index: %d\n", selectedIndex); + return false; + } + + LOG_INFO("Performing camera leveling with camera %d (index %d)\n", selectedIndex + 1, selectedIndex); + + // 2. 清空之前的扫描数据 + clearScanDataCache(); + + // 3. 启动相机扫描地面数据 + if (!startCameraScan(selectedIndex)) { + LOG_ERROR("Failed to start camera scan for leveling\n"); + return false; + } + + // 4. 等待扫描完成(使用状态回调判断) + LOG_INFO("Collecting ground scan data, waiting for swing finish signal...\n"); + int waitTime = 0; + const int maxWaitTime = 10000; // 最大等待10秒 + const int checkInterval = 100; // 每100ms检查一次 + + while (!m_swingFinished && waitTime < maxWaitTime) { + QThread::msleep(checkInterval); + QApplication::processEvents(); // 处理UI事件 + waitTime += checkInterval; + } + + // 5. 停止扫描 + stopCameraScan(selectedIndex); + + // 检查是否通过状态回调收到了扫描完成信号 + if (m_swingFinished) { + LOG_INFO("Camera swing finished signal received, scan completed\n"); + } else if (waitTime >= maxWaitTime) { + LOG_WARNING("Timeout waiting for camera swing finish signal\n"); + } +#endif + + // 5. 检查是否收集到足够的数据 + // 6. 调用调平算法计算 + double planeCalib[9]; + double planeHeight; + double invRMatrix[9]; + + if (!calculatePlaneCalibration(planeCalib, planeHeight, invRMatrix)) { + LOG_ERROR("Failed to calculate plane calibration\n"); + return false; + } + + LOG_INFO("Camera leveling calculation completed\n"); + + // 7. 更新界面显示 + updateLevelingResults(planeCalib, planeHeight, invRMatrix); + + // 8. 保存结果到配置 + // 获取相机索引和名称传递给保存方法 + int cameraIndex = selectedIndex + 1; // 转换为1-based索引 + QString cameraName; + +#ifdef LEVEL_DEBUG_MODE + // Debug模式下使用默认名称 + cameraIndex = 1; + cameraName = QString("Camera_%1").arg(cameraIndex); +#else + // 正常模式下从列表获取名称 + if (selectedIndex >= 0 && selectedIndex < m_cameraList.size()) { + cameraName = QString::fromStdString(m_cameraList[selectedIndex].first); + } else { + cameraName = QString("Camera_%1").arg(cameraIndex); + } +#endif + + if (!saveLevelingResults(planeCalib, planeHeight, invRMatrix, cameraIndex, cameraName)) { + LOG_ERROR("Failed to save leveling results\n"); + return false; + } + + clearScanDataCache(); + LOG_INFO("Camera leveling completed successfully\n"); + + return true; + + } catch (const std::exception& e) { + LOG_ERROR("Exception in performCameraLeveling: %s\n", e.what()); + + return false; + } +} + +void DialogCameraLevel::updateLevelingResults(double planeCalib[9], double planeHeight, double invRMatrix[9]) +{ + // 计算旋转角度的近似值用于显示 + double rotX = atan2(planeCalib[5], planeCalib[8]) * 180.0 / M_PI; + double rotY = atan2(-planeCalib[2], sqrt(planeCalib[5]*planeCalib[5] + planeCalib[8]*planeCalib[8])) * 180.0 / M_PI; + + // 构建显示文本,将矩阵直接显示到页面上 + QString resultText; +#if 0 + // 基本信息 + resultText += QString("旋转角度 X: %1°\n").arg(QString::number(rotX, 'f', 2)); + resultText += QString("旋转角度 Y: %1°\n").arg(QString::number(rotY, 'f', 2)); +#endif + + resultText += QString("地面高度: %1 mm\n").arg(QString::number(planeHeight, 'f', 2)); + + // 调平矩阵 + resultText += QString("调平矩阵:\n"); + for (int i = 0; i < 3; i++) { + resultText += QString("[%1, %2, %3]\n") + .arg(QString::number(planeCalib[i*3], 'f', 4)) + .arg(QString::number(planeCalib[i*3+1], 'f', 4)) + .arg(QString::number(planeCalib[i*3+2], 'f', 4)); + } + + resultText += QString("逆旋转矩阵:\n"); + for (int i = 0; i < 3; i++) { + resultText += QString("[%1, %2, %3]\n") + .arg(QString::number(invRMatrix[i*3], 'f', 4)) + .arg(QString::number(invRMatrix[i*3+1], 'f', 4)) + .arg(QString::number(invRMatrix[i*3+2], 'f', 4)); + } + + // 将结果显示到界面上 + ui->label_level_result->setText(resultText); + ui->label_level_result->setAlignment(Qt::AlignLeft | Qt::AlignTop); +} + +// 启动相机扫描 +bool DialogCameraLevel::startCameraScan(int cameraIndex) +{ + if (cameraIndex < 0 || cameraIndex >= m_cameraList.size()) { + LOG_ERROR("Invalid camera index for scan: %d\n", cameraIndex); + return false; + } + + IVrEyeDevice* camera = m_cameraList[cameraIndex].second; + if (!camera) { + LOG_ERROR("Camera device is null at index: %d\n", cameraIndex); + return false; + } + + // 启动相机检测,使用静态回调函数 + int result = camera->StartDetect(&DialogCameraLevel::StaticDetectionCallback, keResultDataType_Position, this); + if (result != 0) { + LOG_ERROR("Failed to start camera detection: %d\n", result); + return false; + } + + LOG_INFO("Camera scan started successfully for camera index: %d\n", cameraIndex); + return true; +} + +// 停止相机扫描 +bool DialogCameraLevel::stopCameraScan(int cameraIndex) +{ + if (cameraIndex < 0 || cameraIndex >= m_cameraList.size()) { + LOG_ERROR("Invalid camera index for stop scan: %d\n", cameraIndex); + return false; + } + + IVrEyeDevice* camera = m_cameraList[cameraIndex].second; + if (!camera) { + LOG_ERROR("Camera device is null at index: %d\n", cameraIndex); + return false; + } + + int result = camera->StopDetect(); + if (result != 0) { + LOG_WARNING("Failed to stop camera detection, error: %d\n", result); + return false; + } + + LOG_INFO("Camera scan stopped successfully for camera index: %d\n", cameraIndex); + return true; +} + +// 静态检测回调函数 +void DialogCameraLevel::StaticDetectionCallback(EVzResultDataType eDataType, SVzLaserLineData* pLaserLinePoint, void* pUserData) +{ + DialogCameraLevel* pThis = reinterpret_cast(pUserData); + if (pThis && pLaserLinePoint) { + pThis->DetectionCallback(eDataType, pLaserLinePoint); + } +} + +// 静态状态回调函数 +void DialogCameraLevel::StaticStatusCallback(EVzDeviceWorkStatus eStatus, void* pExtData, unsigned int nDataLength, void* pInfoParam) +{ + DialogCameraLevel* pThis = reinterpret_cast(pInfoParam); + if (pThis) { + pThis->StatusCallback(eStatus, pExtData, nDataLength, pInfoParam); + } +} + +// 状态回调函数实例版本 +void DialogCameraLevel::StatusCallback(EVzDeviceWorkStatus eStatus, void* pExtData, unsigned int nDataLength, void* pInfoParam) +{ + LOG_DEBUG("[Leveling Status Callback] received: status=%d\n", (int)eStatus); + + switch (eStatus) { + case EVzDeviceWorkStatus::keDeviceWorkStatus_Device_Swing_Finish: + { + LOG_INFO("[Leveling Status Callback] Camera swing finished, scan completed\n"); + m_swingFinished = true; // 摆动完成即表示扫描完成 + break; + } + default: + LOG_DEBUG("[Leveling Status Callback] Other status: %d\n", (int)eStatus); + break; + } +} + +// 检测数据回调函数实例版本 +void DialogCameraLevel::DetectionCallback(EVzResultDataType eDataType, SVzLaserLineData* pLaserLinePoint) +{ + if (!pLaserLinePoint) { + LOG_WARNING("[Leveling Callback] pLaserLinePoint is null\n"); + return; + } + + if (pLaserLinePoint->nPointCount <= 0) { + LOG_WARNING("[Leveling Callback] Point count is zero or negative: %d\n", pLaserLinePoint->nPointCount); + return; + } + + if (!pLaserLinePoint->p3DPoint) { + LOG_WARNING("[Leveling Callback] p3DPoint is null\n"); + return; + } + + // 转换数据格式:从SVzLaserLineData转换为SVzNL3DLaserLine并存储到缓存 + SVzNL3DLaserLine laser3DLine; + + // 复制基本信息 + laser3DLine.nTimeStamp = pLaserLinePoint->llTimeStamp; + laser3DLine.nPositionCnt = pLaserLinePoint->nPointCount; + + // 分配和复制点云数据 + laser3DLine.p3DPosition = new SVzNL3DPosition[pLaserLinePoint->nPointCount]; + // 复制点云数据 + memcpy(laser3DLine.p3DPosition, pLaserLinePoint->p3DPoint, sizeof(SVzNL3DPosition) * pLaserLinePoint->nPointCount); + + // 将转换后的数据保存到缓存中 + std::lock_guard lock(m_scanDataMutex); + m_scanDataCache.push_back(laser3DLine); +} + +// 调平算法计算 +bool DialogCameraLevel::calculatePlaneCalibration(double planeCalib[9], double& planeHeight, double invRMatrix[9]) +{ + std::lock_guard lock(m_scanDataMutex); + // 检查是否有足够的扫描数据 + if (m_scanDataCache.empty()) { + LOG_ERROR("No scan data available for plane calibration\n"); + return false; + } + + LOG_INFO("Calculating plane calibration from %zu scan lines\n", m_scanDataCache.size()); + + try { + // 调用实际的调平算法 + SSG_planeCalibPara calibResult = sg_getBagBaseCalibPara(m_scanDataCache.data(), static_cast(m_scanDataCache.size())); + + // 将结构体中的数据复制到输出参数 + for (int i = 0; i < 9; i++) { + planeCalib[i] = calibResult.planeCalib[i]; + invRMatrix[i] = calibResult.invRMatrix[i]; + } + planeHeight = calibResult.planeHeight; + + // 计算旋转角度用于日志显示 + double rotAngleX = atan2(planeCalib[5], planeCalib[8]) * 180.0 / M_PI; + double rotAngleY = atan2(-planeCalib[2], sqrt(planeCalib[5]*planeCalib[5] + planeCalib[8]*planeCalib[8])) * 180.0 / M_PI; + + LOG_INFO("Plane calibration calculated: height=%.3f, rotX=%.2f°, rotY=%.2f°\n", + planeHeight, rotAngleX, rotAngleY); + +#ifdef LEVEL_DEBUG_MODE + // 统计点云数据 + int totalPoints = 0; + double avgHeight = 0.0; + for (const auto& scanLine : m_scanDataCache) { + for (int i = 0; i < scanLine.nPositionCnt; i++) { + if (scanLine.p3DPosition[i].pt3D.z > -1000 && scanLine.p3DPosition[i].pt3D.z < 1000) { + avgHeight += scanLine.p3DPosition[i].pt3D.z; + totalPoints++; + } + } + } + + LOG_INFO("=== DEBUG MODE CALIBRATION RESULTS ===\n"); + + LOG_INFO("Algorithm results:\n"); + LOG_INFO(" Calculated plane height: %.3f mm\n", calibResult.planeHeight); + LOG_INFO("Calibration matrix:\n"); + for (int i = 0; i < 3; i++) { + LOG_INFO(" [%.6f, %.6f, %.6f]\n", calibResult.planeCalib[i*3], calibResult.planeCalib[i*3+1], calibResult.planeCalib[i*3+2]); + } + LOG_INFO("Inverse rotation matrix:\n"); + for (int i = 0; i < 3; i++) { + LOG_INFO(" [%.6f, %.6f, %.6f]\n", calibResult.invRMatrix[i*3], calibResult.invRMatrix[i*3+1], calibResult.invRMatrix[i*3+2]); + } + LOG_INFO("=======================================\n"); +#endif + + return true; + + } catch (const std::exception& e) { + LOG_ERROR("Exception in sg_getBagBaseCalibPara: %s\n", e.what()); + return false; + } catch (...) { + LOG_ERROR("Unknown exception in sg_getBagBaseCalibPara\n"); + return false; + } +} + +// 清空扫描数据缓存 +void DialogCameraLevel::clearScanDataCache() +{ + std::lock_guard lock(m_scanDataMutex); + + LOG_DEBUG("Clearing scan data cache, current size: %zu\n", m_scanDataCache.size()); + + // 释放缓存的内存 + for (auto& cachedLine : m_scanDataCache) { + if (cachedLine.p3DPosition) { + delete[] cachedLine.p3DPosition; + cachedLine.p3DPosition = nullptr; + } + } + + // 清空缓存容器 + m_scanDataCache.clear(); + + LOG_DEBUG("Scan data cache cleared successfully\n"); +} + +bool DialogCameraLevel::saveLevelingResults(double planeCalib[9], double planeHeight, double invRMatrix[9], int cameraIndex, const QString& cameraName) +{ + try { + if (!m_presenter) { + LOG_ERROR("Presenter is null, cannot save leveling results\n"); + return false; + } + + // 获取配置对象 + IVrConfig* config = m_presenter->GetConfig(); + if (!config) { + LOG_ERROR("Config is null, cannot save leveling results\n"); + return false; + } + + // 验证传入的相机参数 + if (cameraIndex <= 0) { + LOG_ERROR("Invalid camera index: %d\n", cameraIndex); + return false; + } + + if (cameraName.isEmpty()) { + LOG_ERROR("Camera name is empty\n"); + return false; + } + + // 加载当前配置 + QString configPath = PathManager::GetConfigFilePath(); + LOG_INFO("Config path: %s\n", configPath.toUtf8().constData()); + ConfigResult configResult = config->LoadConfig(configPath.toStdString()); + + // 创建或更新指定相机的调平参数 + VrCameraPlaneCalibParam cameraParam; + cameraParam.cameraIndex = cameraIndex; + cameraParam.cameraName = cameraName.toStdString(); + cameraParam.planeHeight = planeHeight; + cameraParam.isCalibrated = true; + + // 复制校准矩阵 + for (int i = 0; i < 9; i++) { + cameraParam.planeCalib[i] = planeCalib[i]; + cameraParam.invRMatrix[i] = invRMatrix[i]; + } + + // 更新配置中的相机校准参数 + configResult.algorithmParams.planeCalibParam.SetCameraCalibParam(cameraParam); + + // 保存配置 + bool saveResult = config->SaveConfig(configPath.toStdString(), configResult); + if (!saveResult) { + LOG_ERROR("Failed to save config with leveling results\n"); + return false; + } + + LOG_INFO("Leveling results saved successfully for camera %d (%s)\n", cameraIndex, cameraName.toUtf8().constData()); + LOG_INFO("Plane height: %.3f\n", planeHeight); + LOG_INFO("Calibration marked as completed\n"); + + return true; + + } catch (const std::exception& e) { + LOG_ERROR("Exception in saveLevelingResults: %s\n", e.what()); + return false; + } +} + +// 选择debug数据文件 +QString DialogCameraLevel::selectDebugDataFile() +{ + // 获取应用程序目录作为起始目录 + QString appDir = QCoreApplication::applicationDirPath(); + QString testDataDir = appDir + "/TestData"; + + // 如果TestData目录不存在,使用应用程序目录 + if (!QFileInfo::exists(testDataDir)) { + testDataDir = appDir; + } + + // 打开文件选择对话框 + QString fileName = QFileDialog::getOpenFileName( + this, + "选择激光扫描数据文件", + testDataDir, + "文本文件 (*.txt);;所有文件 (*.*)" + ); + + if (fileName.isEmpty()) { + LOG_INFO("No debug data file selected\n"); + return ""; + } + + // 检查文件是否存在 + if (!QFileInfo::exists(fileName)) { + LOG_ERROR("Selected debug data file does not exist: %s\n", fileName.toUtf8().constData()); + QMessageBox::warning(this, "文件错误", "选择的文件不存在!"); + return ""; + } + + LOG_INFO("Selected debug data file: %s\n", fileName.toUtf8().constData()); + + return fileName; +} + +// 加载debug数据并模拟扫描过程 +bool DialogCameraLevel::loadDebugDataAndSimulateScan(const QString& filePath) +{ + LOG_INFO("Loading debug data from: %s\n", filePath.toUtf8().constData()); + + try { + // 清空之前的扫描数据 + clearScanDataCache(); + + // 使用LaserDataLoader加载测试数据 + LaserDataLoader dataLoader; + std::vector> debugData; + int lineNum = 0; + float scanSpeed = 0.0f; + int maxTimeStamp = 0; + int clockPerSecond = 0; + + int result = dataLoader.LoadLaserScanData(filePath.toStdString(), debugData, + lineNum, scanSpeed, maxTimeStamp, clockPerSecond); + + if (result != 0) { + LOG_ERROR("Failed to load debug data: %s\n", dataLoader.GetLastError().c_str()); + QMessageBox::critical(this, "Debug模式错误", + QString("加载测试数据失败:\n%1").arg(QString::fromStdString(dataLoader.GetLastError()))); + return false; + } + + if (debugData.empty()) { + LOG_ERROR("Debug data is empty\n"); + QMessageBox::warning(this, "Debug模式警告", "测试数据文件为空!"); + return false; + } + + LOG_INFO("Loaded %d lines of debug data, starting simulation\n", lineNum); + + // 将加载的数据复制到缓存中(需要深拷贝) + { + std::lock_guard lock(m_scanDataMutex); + m_scanDataCache.reserve(debugData.size()); + + for (const auto& linePair : debugData) { + EVzResultDataType dataType = linePair.first; + const SVzLaserLineData& lineData = linePair.second; + + // 只处理Position类型的数据(相机调平只需要位置信息) + if (dataType == keResultDataType_Position && lineData.p3DPoint) { + SVzNL3DLaserLine copyLine; + copyLine.nTimeStamp = 0; // 从SVzLaserLineData中获取时间戳 + copyLine.nPositionCnt = lineData.nPointCount; + + if (lineData.nPointCount > 0) { + copyLine.p3DPosition = new SVzNL3DPosition[lineData.nPointCount]; + memcpy(copyLine.p3DPosition, lineData.p3DPoint, sizeof(SVzNL3DPosition) * lineData.nPointCount); + } else { + copyLine.p3DPosition = nullptr; + } + + m_scanDataCache.push_back(copyLine); + } + } + } + + // 释放临时加载的数据 + dataLoader.FreeLaserScanData(debugData); + + // 启动模拟扫描过程 + simulateScanProcess(); + + return true; + + } catch (const std::exception& e) { + LOG_ERROR("Exception in loadDebugDataAndSimulateScan: %s\n", e.what()); + QMessageBox::critical(this, "Debug模式异常", QString("加载debug数据时发生异常:\n%1").arg(e.what())); + return false; + } +} + +// 模拟扫描过程 +void DialogCameraLevel::simulateScanProcess() +{ + LOG_INFO("Starting scan simulation process\n"); + + // 创建定时器来模拟数据收集过程 + QTimer* simulationTimer = new QTimer(); + simulationTimer->setSingleShot(true); + + // 模拟扫描需要的时间(2秒) + simulationTimer->setInterval(2000); + + // 连接定时器信号 + connect(simulationTimer, &QTimer::timeout, [this, simulationTimer]() { + LOG_INFO("Scan simulation completed\n"); + m_swingFinished = true; // Debug模式下模拟摆动完成 + + // 显示调试信息 + { + std::lock_guard lock(m_scanDataMutex); + LOG_INFO("Debug scan simulation completed with %zu lines\n", m_scanDataCache.size()); + + // 统计点云数据 + int totalPoints = 0; + for (const auto& line : m_scanDataCache) { + totalPoints += line.nPositionCnt; + } + LOG_INFO("Total points in debug data: %d\n", totalPoints); + } + + // 清理定时器 + simulationTimer->deleteLater(); + }); + + // 启动模拟扫描 + simulationTimer->start(); + + LOG_INFO("Simulation timer started, waiting for completion...\n"); +} + +// 相机选择改变的槽函数 +void DialogCameraLevel::on_combo_camera_currentIndexChanged(int index) +{ + if (index >= 0 && index < m_cameraList.size()) { + LOG_INFO("Camera selection changed to index: %d (%s)\n", index, + QString::fromStdString(m_cameraList[index].first).toUtf8().constData()); + checkAndDisplayCalibrationStatus(index); + } else { + LOG_WARNING("Invalid camera index selected: %d\n", index); + ui->label_level_result->setText("无效的相机选择"); + ui->label_level_result->setAlignment(Qt::AlignCenter); + } +} + +// 加载相机标定数据 +bool DialogCameraLevel::loadCameraCalibrationData(int cameraIndex, const QString& cameraName, + double planeCalib[9], double& planeHeight, double invRMatrix[9]) +{ + try { + if (!m_presenter) { + LOG_ERROR("Presenter is null, cannot load calibration data\n"); + return false; + } + + // 获取配置对象 + IVrConfig* config = m_presenter->GetConfig(); + if (!config) { + LOG_ERROR("Config is null, cannot load calibration data\n"); + return false; + } + + // 加载配置文件 + QString configPath = PathManager::GetConfigFilePath(); + ConfigResult configResult = config->LoadConfig(configPath.toStdString()); + + // 获取指定相机的标定参数 + const VrCameraPlaneCalibParam* cameraParam = configResult.algorithmParams.planeCalibParam.GetCameraCalibParam(cameraIndex); + + if (!cameraParam || !cameraParam->isCalibrated) { + LOG_INFO("No calibration data found for camera %d (%s)\n", cameraIndex, cameraName.toUtf8().constData()); + return false; + } + + // 复制标定数据 + for (int i = 0; i < 9; i++) { + planeCalib[i] = cameraParam->planeCalib[i]; + invRMatrix[i] = cameraParam->invRMatrix[i]; + } + planeHeight = cameraParam->planeHeight; + + LOG_INFO("Calibration data loaded successfully for camera %d (%s)\n", cameraIndex, cameraName.toUtf8().constData()); + LOG_INFO("Plane height: %.3f\n", planeHeight); + + return true; + + } catch (const std::exception& e) { + LOG_ERROR("Exception in loadCameraCalibrationData: %s\n", e.what()); + return false; + } +} + +// 检查并显示相机标定状态 +void DialogCameraLevel::checkAndDisplayCalibrationStatus(int cameraIndex) +{ + if (cameraIndex < 0 || cameraIndex >= m_cameraList.size()) { + LOG_WARNING("Invalid camera index for status check: %d\n", cameraIndex); + ui->label_level_result->setText("无效的相机索引"); + ui->label_level_result->setAlignment(Qt::AlignCenter); + return; + } + + QString cameraName = QString::fromStdString(m_cameraList[cameraIndex].first); + int configCameraIndex = cameraIndex + 1; // 转换为1-based索引 + + // 尝试加载该相机的标定数据 + double planeCalib[9]; + double planeHeight; + double invRMatrix[9]; + + if (loadCameraCalibrationData(configCameraIndex, cameraName, planeCalib, planeHeight, invRMatrix)) { + // 有标定数据,显示标定结果 + 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, showing instruction\n", cameraName.toUtf8().constData()); + ui->label_level_result->setText(QString("请选择相机 \"%1\" 并点击调平按钮\n开始相机调平操作").arg(cameraName)); + ui->label_level_result->setAlignment(Qt::AlignCenter); + } +} + +// 设置调平时的状态回调 +void DialogCameraLevel::setLevelingStatusCallback() +{ + if (!m_presenter) { + LOG_ERROR("Presenter is null, cannot set leveling status callback\n"); + return; + } + + // 为所有相机设置调平状态回调 + m_presenter->SetCameraStatusCallback(&DialogCameraLevel::StaticStatusCallback, this); + + // 重置状态标志 + m_swingFinished = false; + m_callbackRestored = false; // 重置恢复标志,允许稍后恢复 + + LOG_INFO("Leveling status callback set for all cameras\n"); +} + +// 恢复Presenter的状态回调 +void DialogCameraLevel::restorePresenterStatusCallback() +{ + // 检查是否已经恢复过回调,避免重复调用 + if (m_callbackRestored.exchange(true)) { + LOG_DEBUG("Presenter status callback already restored, skipping\n"); + return; + } + + if (!m_presenter) { + LOG_ERROR("Presenter is null, cannot restore status callback\n"); + return; + } + + // 恢复Presenter的状态回调 - 使用GrabBagPresenter的静态回调 + m_presenter->SetCameraStatusCallback(&GrabBagPresenter::_StaticCameraNotify, m_presenter); + + LOG_INFO("Presenter status callback restored for all cameras\n"); +} + diff --git a/GrabBagApp/dialogconfig.cpp b/GrabBagApp/dialogconfig.cpp index eb55989..c3a6937 100644 --- a/GrabBagApp/dialogconfig.cpp +++ b/GrabBagApp/dialogconfig.cpp @@ -9,7 +9,6 @@ #include #include "VrLog.h" #include "PathManager.h" -#include "StyledMessageBox.h" DialogConfig::DialogConfig(IVrConfig* devConfig, QWidget *parent) : QDialog(parent), @@ -71,7 +70,7 @@ void DialogConfig::LoadConfigToUI() } catch (const std::exception& e) { LOG_ERROR("Failed to load configuration: %s\n", e.what()); - StyledMessageBox::critical(this, "错误", "加载配置文件失败!"); + QMessageBox::critical(this, "错误", "加载配置文件失败!"); } } @@ -127,10 +126,10 @@ bool DialogConfig::SaveConfigFromUI() void DialogConfig::on_btn_apply_clicked() { if (SaveConfigFromUI()) { - StyledMessageBox::information(this, "成功", "配置保存成功!"); + QMessageBox::information(this, "成功", "配置保存成功!"); accept(); // 关闭对话框并返回Accepted } else { - StyledMessageBox::warning(this, "失败", "配置保存失败,请检查文件权限!"); + QMessageBox::warning(this, "失败", "配置保存失败,请检查文件权限!"); } } diff --git a/GrabBagConfig/GrabBagConfig.pro b/GrabBagConfig/GrabBagConfig.pro index 6df2643..791bd04 100644 --- a/GrabBagConfig/GrabBagConfig.pro +++ b/GrabBagConfig/GrabBagConfig.pro @@ -28,7 +28,7 @@ win32:CONFIG(debug, debug|release) { }else:win32:CONFIG(release, debug|release){ LIBS += -L../VrUtils/release -lVrUtils }else:unix:!macx { - # Unix/Linux平台库链接(包括交叉编译) + # Unix/Linux平台库链接(including cross-compilation) LIBS += -L../VrUtils -lVrUtils } @@ -36,4 +36,4 @@ win32:CONFIG(debug, debug|release) { unix { target.path = /usr/lib } -!isEmpty(target.path): INSTALLS += target +!isEmpty(target.path): INSTALLS += target \ No newline at end of file diff --git a/GrabBagConfig/Inc/IVrConfig.h b/GrabBagConfig/Inc/IVrConfig.h index 801bb09..4803789 100644 --- a/GrabBagConfig/Inc/IVrConfig.h +++ b/GrabBagConfig/Inc/IVrConfig.h @@ -1,339 +1,339 @@ -#ifndef IVRCONFIG_H -#define IVRCONFIG_H - -#include -#include -#include -#include - -/** - * @brief 项目类型枚举 - */ -enum class ProjectType -{ - GrabBag = 0, // 抓包 - DirectBag = 1, // 带方向的编织袋 -}; - -/** - * @brief 项目类型字符串转换函数 - */ -inline std::string ProjectTypeToString(ProjectType type) -{ - switch (type) { - case ProjectType::GrabBag: - return "GrabBag"; - case ProjectType::DirectBag: - return "DirectBag"; - default: - return "Unknown"; - } -} - -/** - * @brief 字符串转项目类型函数 - */ -inline ProjectType StringToProjectType(const std::string& str) -{ - if (str == "GrabBag" || str == "0") { - return ProjectType::GrabBag; - } else if (str == "DirectBag" || str == "1") { - return ProjectType::DirectBag; - } else { - return ProjectType::GrabBag; // 默认返回抓包 - } -} - -struct DeviceInfo -{ - std::string name; - std::string ip; -}; - -/** - * @brief 串口配置信息 - */ -struct SerialConfig -{ -#ifdef _WIN32 - std::string portName = "COM6"; // 串口名称 -#else - std::string portName = "/dev/ttyS3"; // 串口名称 -#endif - int baudRate = 115200; // 波特率 - int dataBits = 8; // 数据位 - int stopBits = 1; // 停止位 - int parity = 0; // 校验位 (0-无校验, 1-奇校验, 2-偶校验) - int flowControl = 0; // 流控制 (0-无, 1-硬件, 2-软件) - bool enabled = true; // 是否启用串口通信 -}; - -/** - * @brief 编织袋参数 - */ -struct VrBagParam -{ - double bagL = 650.0; // 长 - double bagW = 450.0; // 宽 - double bagH = 160.0; // 高 -}; - -/** - * @brief 垛参数 - */ -struct VrPileParam -{ - double pileL = 1300.0; // 垛长 - double pileW = 900.0; // 垛宽 - double pileH = 800.0; // 垛高 -}; - -/** - * @brief 离群点滤波参数 - */ -struct VrOutlierFilterParam -{ - double continuityTh = 20.0; // 连续性阈值 - int outlierTh = 5; // 离群点判断阈值 -}; - -/** - * @brief 角点参数 - */ -struct VrCornerParam -{ - double minEndingGap = 20.0; // 最小结束间隙 - double minEndingGap_z = 20.0; // z方向最小结束间隙 - double scale = 15.0; // 计算方向角的窗口比例因子 - double cornerTh = 30.0; // 角点阈值 - double jumpCornerTh_1 = 60.0; // 判断角点是否为跳跃的第一阈值 - double jumpCornerTh_2 = 15.0; // 判断角点是否为跳跃的第二阈值 -}; - -/** - * @brief 斜率参数 - */ -struct VrSlopeParam -{ - double LSlopeZWin = 10.0; // 计算L型Slope特征高度计算的窗口长度 - double validSlopeH = 10.0; // 有效斜率高度 - double minLJumpH = 20.0; // 最小L跳跃高度 - double minEndingGap = 20.0; // 最小结束间隙 -}; - -/** - * @brief V特征参数 - */ -struct VrVFeatureParam -{ - double valleyMinH = 10.0; // 山谷最小高度 - double valleyMaxW = 80.0; // 山谷最大宽度 -}; - -/** - * @brief 树生长参数 - */ -struct VrTreeGrowParam -{ - double yDeviation_max = 20.0; // 生长时允许的最大Y偏差 - double zDeviation_max = 80.0; // 生长时允许的最大Z偏差 - int maxLineSkipNum = 5; // 生长时允许跳过的最大线条数 - double maxSkipDistance = 20.0; // 最大跳跃距离 - double minLTypeTreeLen = 50.0; // L型树的最小长度 - double minVTypeTreeLen = 50.0; // V型树的最小长度 -}; - -/** - * @brief HSV颜色比较参数 - */ -struct VrHsvCmpParam -{ - double hueTh = 15.0; // 色度阈值,小于门限为同一颜色 - double saturateTh = 120.0; // 色饱和度阈值 - double FBVldPtRatioTh = 0.075; // 正反两面有效颜色点的比例门限 - bool frontVldPtGreater = true; // true:有效颜色比例高的点的是正面;false:有效颜色比例高的点的是反面 - bool front_upVldPtGreater = false; // true:有效颜色比例高的点的是正面朝上;false:有效颜色比例高的点的是正面朝下 - bool back_upVldPtGreater = true; // true:有效颜色比例高的点的是反面朝上;false:有效颜色比例高的点的是反面朝下 -}; - -/** - * @brief RGB颜色模式 - */ -struct VrRgbColorPattern -{ - int r = 36; // 红色分量 - int g = 165; // 绿色分量 - int b = 208; // 蓝色分量 -}; - -/** - * @brief 颜色模板参数 - */ -#define RGN_HIST_SIZE 16 -struct VrColorTemplateParam -{ - double frontColorTemplate[RGN_HIST_SIZE] = {0.0}; // 正面颜色模板 - double backColorTemplate[RGN_HIST_SIZE] = {0.0}; // 反面颜色模板 -}; - -/** - * @brief 单个相机的平面校准参数 - */ -struct VrCameraPlaneCalibParam -{ - int cameraIndex = 1; // 相机索引(1-based) - std::string cameraName = ""; // 相机名称 - double planeCalib[9] = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0}; // 旋转矩阵,将数据调平(默认单位矩阵) - double planeHeight = -1.0; // 参考平面的高度,用于去除地面数据 - double invRMatrix[9] = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0}; // 逆旋转矩阵,回到原坐标系(默认单位矩阵) - bool isCalibrated = false; // 是否已经校准 -}; - -/** - * @brief 平面校准参数(支持多相机) - */ -struct VrPlaneCalibParam -{ - std::vector cameraCalibParams; // 各个相机的校准参数 - - // 获取指定相机的校准参数 - VrCameraPlaneCalibParam* GetCameraCalibParam(int cameraIndex) { - for (auto& param : cameraCalibParams) { - if (param.cameraIndex == cameraIndex) { - return ¶m; - } - } - return nullptr; - } - - // 获取指定相机的校准参数(const版本) - const VrCameraPlaneCalibParam* GetCameraCalibParam(int cameraIndex) const { - for (const auto& param : cameraCalibParams) { - if (param.cameraIndex == cameraIndex) { - return ¶m; - } - } - return nullptr; - } - - // 设置或更新指定相机的校准参数 - void SetCameraCalibParam(const VrCameraPlaneCalibParam& param) { - for (auto& existingParam : cameraCalibParams) { - if (existingParam.cameraIndex == param.cameraIndex) { - existingParam = param; - return; - } - } - // 如果不存在,则添加新的 - cameraCalibParams.push_back(param); - } - - // 移除指定相机的校准参数 - void RemoveCameraCalibParam(int cameraIndex) { - cameraCalibParams.erase( - std::remove_if(cameraCalibParams.begin(), cameraCalibParams.end(), - [cameraIndex](const VrCameraPlaneCalibParam& param) { - return param.cameraIndex == cameraIndex; - }), - cameraCalibParams.end()); - } -}; - -/** - * @brief 调试参数 - */ -struct VrDebugParam -{ - bool enableDebug = false; // 是否开启调试模式 - bool savePointCloud = false; // 是否保存点云数据 - bool saveDebugImage = false; // 是否保存调试图像 - bool printDetailLog = false; // 是否打印详细日志 - std::string debugOutputPath = ""; // 调试输出路径 -}; - -/** - * @brief 算法参数配置结构 - */ -struct VrAlgorithmParams -{ - VrBagParam bagParam; // 编织袋参数 - VrPileParam pileParam; // 垛参数 - VrOutlierFilterParam filterParam; // 滤波参数 - VrCornerParam cornerParam; // 角点特征参数 - VrSlopeParam slopeParam; // 斜率参数 - VrVFeatureParam valleyParam; // 山谷参数 - VrTreeGrowParam growParam; // 增长参数 - VrPlaneCalibParam planeCalibParam; // 平面校准参数 - VrHsvCmpParam hsvCmpParam; // HSV颜色比较参数 - VrRgbColorPattern rgbColorPattern; // RGB颜色模式 - VrColorTemplateParam colorTemplateParam; // 颜色模板参数 -}; - -/** - * @brief 配置加载结果 - */ -struct ConfigResult -{ - std::vector cameraList; - std::vector deviceList; - VrAlgorithmParams algorithmParams; // 算法参数 - VrDebugParam debugParam; // 调试参数 - SerialConfig serialConfig; // 串口配置 - ProjectType projectType; // 项目类型 -}; - -/** - * @brief 配置改变通知接口 - */ -class IVrConfigChangeNotify -{ -public: - virtual ~IVrConfigChangeNotify() {} - - /** - * @brief 配置数据改变通知 - * @param configResult 新的配置数据 - */ - virtual void OnConfigChanged(const ConfigResult& configResult) = 0; -}; - -/** - * @brief VrConfig接口类 - */ -class IVrConfig -{ -public: - /** - * @brief 虚析构函数 - */ - virtual ~IVrConfig() {} - - /** - * @brief 创建实例 - * @return 实例 - */ - static bool CreateInstance(IVrConfig** ppVrConfig); - - /** - * @brief 加载配置文件 - * @param filePath 配置文件路径 - * @return 加载的配置结果 - */ - virtual ConfigResult LoadConfig(const std::string& filePath) = 0; - - /** - * @brief 保存配置文件 - * @param filePath 配置文件路径 - * @param configResult 配置结果 - * @return 是否保存成功 - */ - virtual bool SaveConfig(const std::string& filePath, ConfigResult& configResult) = 0; - - /** - * @brief 设置配置改变通知回调 - * @param notify 通知接口指针 - */ - virtual void SetConfigChangeNotify(IVrConfigChangeNotify* notify) = 0; -}; - -#endif // IVRCONFIG_H +#ifndef IVRCONFIG_H +#define IVRCONFIG_H + +#include +#include +#include +#include + +/** + * @brief 项目类型枚举 + */ +enum class ProjectType +{ + GrabBag = 0, // 抓包 + DirectBag = 1, // 带方向的编织袋 +}; + +/** + * @brief 项目类型字符串转换函数 + */ +inline std::string ProjectTypeToString(ProjectType type) +{ + switch (type) { + case ProjectType::GrabBag: + return "GrabBag"; + case ProjectType::DirectBag: + return "DirectBag"; + default: + return "Unknown"; + } +} + +/** + * @brief 字符串转项目类型函数 + */ +inline ProjectType StringToProjectType(const std::string& str) +{ + if (str == "GrabBag" || str == "0") { + return ProjectType::GrabBag; + } else if (str == "DirectBag" || str == "1") { + return ProjectType::DirectBag; + } else { + return ProjectType::GrabBag; // 默认返回抓包 + } +} + +struct DeviceInfo +{ + std::string name; + std::string ip; +}; + +/** + * @brief 串口配置信息 + */ +struct SerialConfig +{ +#ifdef _WIN32 + std::string portName = "COM6"; // 串口名称 +#else + std::string portName = "/dev/ttyS3"; // 串口名称 +#endif + int baudRate = 115200; // 波特率 + int dataBits = 8; // 数据位 + int stopBits = 1; // 停止位 + int parity = 0; // 校验位 (0-无校验, 1-奇校验, 2-偶校验) + int flowControl = 0; // 流控制 (0-无, 1-硬件, 2-软件) + bool enabled = true; // 是否启用串口通信 +}; + +/** + * @brief 编织袋参数 + */ +struct VrBagParam +{ + double bagL = 650.0; // 长 + double bagW = 450.0; // 宽 + double bagH = 160.0; // 高 +}; + +/** + * @brief 垛参数 + */ +struct VrPileParam +{ + double pileL = 1300.0; // 垛长 + double pileW = 900.0; // 垛宽 + double pileH = 800.0; // 垛高 +}; + +/** + * @brief 离群点滤波参数 + */ +struct VrOutlierFilterParam +{ + double continuityTh = 20.0; // 连续性阈值 + int outlierTh = 5; // 离群点判断阈值 +}; + +/** + * @brief 角点参数 + */ +struct VrCornerParam +{ + double minEndingGap = 20.0; // 最小结束间隙 + double minEndingGap_z = 20.0; // z方向最小结束间隙 + double scale = 15.0; // 计算方向角的窗口比例因子 + double cornerTh = 30.0; // 角点阈值 + double jumpCornerTh_1 = 60.0; // 判断角点是否为跳跃的第一阈值 + double jumpCornerTh_2 = 15.0; // 判断角点是否为跳跃的第二阈值 +}; + +/** + * @brief 斜率参数 + */ +struct VrSlopeParam +{ + double LSlopeZWin = 10.0; // 计算L型Slope特征高度计算的窗口长度 + double validSlopeH = 10.0; // 有效斜率高度 + double minLJumpH = 20.0; // 最小L跳跃高度 + double minEndingGap = 20.0; // 最小结束间隙 +}; + +/** + * @brief V特征参数 + */ +struct VrVFeatureParam +{ + double valleyMinH = 10.0; // 山谷最小高度 + double valleyMaxW = 80.0; // 山谷最大宽度 +}; + +/** + * @brief 树生长参数 + */ +struct VrTreeGrowParam +{ + double yDeviation_max = 20.0; // 生长时允许的最大Y偏差 + double zDeviation_max = 80.0; // 生长时允许的最大Z偏差 + int maxLineSkipNum = 5; // 生长时允许跳过的最大线条数 + double maxSkipDistance = 20.0; // 最大跳跃距离 + double minLTypeTreeLen = 50.0; // L型树的最小长度 + double minVTypeTreeLen = 50.0; // V型树的最小长度 +}; + +/** + * @brief HSV颜色比较参数 + */ +struct VrHsvCmpParam +{ + double hueTh = 15.0; // 色度阈值,小于门限为同一颜色 + double saturateTh = 120.0; // 色饱和度阈值 + double FBVldPtRatioTh = 0.075; // 正反两面有效颜色点的比例门限 + bool frontVldPtGreater = true; // true:有效颜色比例高的点的是正面;false:有效颜色比例高的点的是反面 + bool front_upVldPtGreater = false; // true:有效颜色比例高的点的是正面朝上;false:有效颜色比例高的点的是正面朝下 + bool back_upVldPtGreater = true; // true:有效颜色比例高的点的是反面朝上;false:有效颜色比例高的点的是反面朝下 +}; + +/** + * @brief RGB颜色模式 + */ +struct VrRgbColorPattern +{ + int r = 36; // 红色分量 + int g = 165; // 绿色分量 + int b = 208; // 蓝色分量 +}; + +/** + * @brief 颜色模板参数 + */ +#define RGN_HIST_SIZE 16 +struct VrColorTemplateParam +{ + double frontColorTemplate[RGN_HIST_SIZE] = {0.0}; // 正面颜色模板 + double backColorTemplate[RGN_HIST_SIZE] = {0.0}; // 反面颜色模板 +}; + +/** + * @brief 单个相机的平面校准参数 + */ +struct VrCameraPlaneCalibParam +{ + int cameraIndex = 1; // 相机索引(1-based) + std::string cameraName = ""; // 相机名称 + double planeCalib[9] = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0}; // 旋转矩阵,将数据调平(默认单位矩阵) + double planeHeight = -1.0; // 参考平面的高度,用于去除地面数据 + double invRMatrix[9] = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0}; // 逆旋转矩阵,回到原坐标系(默认单位矩阵) + bool isCalibrated = false; // 是否已经校准 +}; + +/** + * @brief 平面校准参数(支持多相机) + */ +struct VrPlaneCalibParam +{ + std::vector cameraCalibParams; // 各个相机的校准参数 + + // 获取指定相机的校准参数 + VrCameraPlaneCalibParam* GetCameraCalibParam(int cameraIndex) { + for (auto& param : cameraCalibParams) { + if (param.cameraIndex == cameraIndex) { + return ¶m; + } + } + return nullptr; + } + + // 获取指定相机的校准参数(const版本) + const VrCameraPlaneCalibParam* GetCameraCalibParam(int cameraIndex) const { + for (const auto& param : cameraCalibParams) { + if (param.cameraIndex == cameraIndex) { + return ¶m; + } + } + return nullptr; + } + + // 设置或更新指定相机的校准参数 + void SetCameraCalibParam(const VrCameraPlaneCalibParam& param) { + for (auto& existingParam : cameraCalibParams) { + if (existingParam.cameraIndex == param.cameraIndex) { + existingParam = param; + return; + } + } + // 如果不存在,则添加新的 + cameraCalibParams.push_back(param); + } + + // 移除指定相机的校准参数 + void RemoveCameraCalibParam(int cameraIndex) { + cameraCalibParams.erase( + std::remove_if(cameraCalibParams.begin(), cameraCalibParams.end(), + [cameraIndex](const VrCameraPlaneCalibParam& param) { + return param.cameraIndex == cameraIndex; + }), + cameraCalibParams.end()); + } +}; + +/** + * @brief 调试参数 + */ +struct VrDebugParam +{ + bool enableDebug = false; // 是否开启调试模式 + bool savePointCloud = false; // 是否保存点云数据 + bool saveDebugImage = false; // 是否保存调试图像 + bool printDetailLog = false; // 是否打印详细日志 + std::string debugOutputPath = ""; // 调试输出路径 +}; + +/** + * @brief 算法参数配置结构 + */ +struct VrAlgorithmParams +{ + VrBagParam bagParam; // 编织袋参数 + VrPileParam pileParam; // 垛参数 + VrOutlierFilterParam filterParam; // 滤波参数 + VrCornerParam cornerParam; // 角点特征参数 + VrSlopeParam slopeParam; // 斜率参数 + VrVFeatureParam valleyParam; // 山谷参数 + VrTreeGrowParam growParam; // 增长参数 + VrPlaneCalibParam planeCalibParam; // 平面校准参数 + VrHsvCmpParam hsvCmpParam; // HSV颜色比较参数 + VrRgbColorPattern rgbColorPattern; // RGB颜色模式 + VrColorTemplateParam colorTemplateParam; // 颜色模板参数 +}; + +/** + * @brief 配置加载结果 + */ +struct ConfigResult +{ + std::vector cameraList; + std::vector deviceList; + VrAlgorithmParams algorithmParams; // 算法参数 + VrDebugParam debugParam; // 调试参数 + SerialConfig serialConfig; // 串口配置 + ProjectType projectType; // 项目类型 +}; + +/** + * @brief 配置改变通知接口 + */ +class IVrConfigChangeNotify +{ +public: + virtual ~IVrConfigChangeNotify() {} + + /** + * @brief 配置数据改变通知 + * @param configResult 新的配置数据 + */ + virtual void OnConfigChanged(const ConfigResult& configResult) = 0; +}; + +/** + * @brief VrConfig接口类 + */ +class IVrConfig +{ +public: + /** + * @brief 虚析构函数 + */ + virtual ~IVrConfig() {} + + /** + * @brief 创建实例 + * @return 实例 + */ + static bool CreateInstance(IVrConfig** ppVrConfig); + + /** + * @brief 加载配置文件 + * @param filePath 配置文件路径 + * @return 加载的配置结果 + */ + virtual ConfigResult LoadConfig(const std::string& filePath) = 0; + + /** + * @brief 保存配置文件 + * @param filePath 配置文件路径 + * @param configResult 配置结果 + * @return 是否保存成功 + */ + virtual bool SaveConfig(const std::string& filePath, ConfigResult& configResult) = 0; + + /** + * @brief 设置配置改变通知回调 + * @param notify 通知接口指针 + */ + virtual void SetConfigChangeNotify(IVrConfigChangeNotify* notify) = 0; +}; + +#endif // IVRCONFIG_H diff --git a/GrabBagPrj/pkg_belttearingserver.sh b/GrabBagPrj/pkg_belttearingserver.sh index 3af9111..8547b09 100644 --- a/GrabBagPrj/pkg_belttearingserver.sh +++ b/GrabBagPrj/pkg_belttearingserver.sh @@ -4,9 +4,37 @@ PKG_NAME="BeltTearingServer" PKG_ARCH="arm64" -# 默认版本信息 -PKG_VERSION="1.0.0" -BUILD_NUMBER="1" +# 从Version.h文件中读取版本信息 +VERSION_FILE="../BeltTearingServer/Version.h" + +if [ -f "${VERSION_FILE}" ]; then + # 读取版本号(从 BELT_TEARING_SERVER_VERSION_STRING 中提取) + PKG_VERSION=$(grep '#define BELT_TEARING_SERVER_VERSION_STRING' ${VERSION_FILE} | sed 's/.*"\(.*\)".*/\1/') + # 读取构建号(从 BELT_TEARING_SERVER_VERSION_BUILD 中提取) + BUILD_NUMBER=$(grep '#define BELT_TEARING_SERVER_VERSION_BUILD' ${VERSION_FILE} | sed 's/.*"\(.*\)".*/\1/') + + echo "从 ${VERSION_FILE} 读取版本信息:" + echo " 版本号: ${PKG_VERSION}" + echo " 构建号: ${BUILD_NUMBER}" + + # 如果读取失败,使用默认值 + if [ -z "${PKG_VERSION}" ]; then + PKG_VERSION="1.0.0" + echo "警告: 无法读取版本号,使用默认值: ${PKG_VERSION}" + fi + + if [ -z "${BUILD_NUMBER}" ]; then + BUILD_NUMBER="1" + echo "警告: 无法读取构建号,使用默认值: ${BUILD_NUMBER}" + fi +else + # Version.h文件不存在时的默认值 + PKG_VERSION="1.0.0" + BUILD_NUMBER="1" + echo "警告: ${VERSION_FILE} 文件不存在,使用默认版本信息" + echo " 版本号: ${PKG_VERSION}" + echo " 构建号: ${BUILD_NUMBER}" +fi PKG_PATH=$HOME/BeltTearingServerPkg CODE_PATH=../ @@ -23,12 +51,32 @@ fi echo "创建打包目录结构..." mkdir -p ${PKG_PATH}/DEBIAN -mkdir -p ${PKG_PATH}/opt/onvm +mkdir -p ${PKG_PATH}/usr/lib +mkdir -p ${PKG_PATH}/etc/profile.d +mkdir -p ${PKG_PATH}/opt/belttearingserver -# 复制 BeltTearingServer/onvm 目录下的所有内容到 /opt -BELT_TEARING_SERVER_PATH=${CODE_PATH}/GrabBagPrj/BeltTearingServer/onvm + +echo "复制 Qt 运行时环境..." +#QT depend +QT_PKG_PATH=/opt/firefly_qt5.15_arm64_20.04 +cp -rfd ${QT_PKG_PATH}/ext ${PKG_PATH}/opt/firefly_qt5.15 +cp ${QT_PKG_PATH}/target_qtEnv.sh ${PKG_PATH}/etc/profile.d/ + +echo "复制依赖库文件..." +#depend +cp -a ${CODE_PATH}/SDK/OpenCV320/Arm/aarch64/*opencv_core*.so* ${PKG_PATH}/usr/lib/ +cp -a ${CODE_PATH}/SDK/OpenCV320/Arm/aarch64/*opencv_imgproc*.so* ${PKG_PATH}/usr/lib/ +cp -a ${CODE_PATH}/SDK/OpenCV320/Arm/aarch64/*opencv_highgui*.so* ${PKG_PATH}/usr/lib/ + +cp ${CODE_PATH}/SDK/bagPosition/Arm/aarch64/*.so ${PKG_PATH}/usr/lib/ +cp ${CODE_PATH}/SDK/VzNLSDK/Arm/aarch64/*.so ${PKG_PATH}/usr/lib/ + + +# 复制 BeltTearingServer 目录下的所有内容到 /opt echo "复制 BeltTearingServer 内容到 /opt/..." -cp -r ${BELT_TEARING_SERVER_PATH}/* ${PKG_PATH}/opt/onvm + +#APP +cp ${CODE_PATH}/GrabBagPrj/buildarm/BeltTearingServer/BeltTearingServer ${PKG_PATH}/opt/belttearingserver/ echo "生成 control 文件..." #control @@ -49,16 +97,16 @@ cat > ${POSTINST_PATH} << 'EOF' echo "配置 BeltTearingServer 应用程序..." -# 安装并启用 onvm.service -if [ -f /opt/onvm/onvm.service ]; then - echo "安装 onvm.service..." - cp /opt/onvm/onvm.service /etc/systemd/system/ +# 安装并启用 belttearingserver.service +if [ -f /opt/belttearingserver/belttearingserver.service ]; then + echo "安装 belttearingserver.service..." + cp /opt/belttearingserver/belttearingserver.service /etc/systemd/system/ systemctl daemon-reload - systemctl enable onvm.service - systemctl start onvm.service - echo "onvm.service 已安装并设置为开机自启" + systemctl enable belttearingserver.service + systemctl start belttearingserver.service + echo "belttearingserver.service 已安装并设置为开机自启" else - echo "错误: 未找到 onvm.service 文件" + echo "错误: 未找到 belttearingserver.service 文件" fi EOF @@ -73,14 +121,17 @@ cat > ${POSTRM_PATH} << 'EOF' echo "卸载 BeltTearingServer 应用程序..." -# 停止并禁用 onvm.service -systemctl stop onvm.service 2>/dev/null || true -systemctl disable onvm.service 2>/dev/null || true -rm -f /etc/systemd/system/onvm.service +# 停止并禁用 belttearingserver.service +systemctl stop belttearingserver.service 2>/dev/null || true +systemctl disable belttearingserver.service 2>/dev/null || true +rm -f /etc/systemd/system/belttearingserver.service # 重新加载systemd systemctl daemon-reload +# 清理 /opt/belttearingserver 目录 +rm -rf /opt/belttearingserver + echo "BeltTearingServer 应用程序卸载完成!" EOF diff --git a/GrabBagPrj/pkg_app.sh b/GrabBagPrj/pkg_grabbagapp.sh similarity index 100% rename from GrabBagPrj/pkg_app.sh rename to GrabBagPrj/pkg_grabbagapp.sh diff --git a/Module/ModbusTCPServer/ModbusTCPServer.pro b/Module/ModbusTCPServer/ModbusTCPServer.pro index d867966..f8889b5 100644 --- a/Module/ModbusTCPServer/ModbusTCPServer.pro +++ b/Module/ModbusTCPServer/ModbusTCPServer.pro @@ -1,7 +1,10 @@ QT -= gui CONFIG += c++17 console -QMAKE_CXXFLAGS += /utf-8 +# Add /utf-8 flag only for MSVC builds to enforce UTF-8 encoding +win32-msvc { + QMAKE_CXXFLAGS += /utf-8 +} # The following define makes your compiler emit warnings if you use # any Qt feature that has been marked deprecated (the exact warnings diff --git a/VrNets/VrModbus.pro b/VrNets/VrModbus.pro index ac167be..a8089ed 100644 --- a/VrNets/VrModbus.pro +++ b/VrNets/VrModbus.pro @@ -7,6 +7,11 @@ DEFINES += DLLBUILD CONFIG += c++17 +# Add /utf-8 flag only for MSVC builds to enforce UTF-8 encoding +win32-msvc { + QMAKE_CXXFLAGS += /utf-8 +} + # You can make your code fail to compile if it uses deprecated APIs. # In order to do so, uncomment the following line. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 @@ -34,4 +39,4 @@ SOURCES += \ unix { target.path = $$[QT_INSTALL_PLUGINS]/generic } -!isEmpty(target.path): INSTALLS += target +!isEmpty(target.path): INSTALLS += target \ No newline at end of file diff --git a/VrUtils/Inc/IVrUtils.h b/VrUtils/Inc/IVrUtils.h index 33166b6..efcb071 100644 --- a/VrUtils/Inc/IVrUtils.h +++ b/VrUtils/Inc/IVrUtils.h @@ -15,7 +15,11 @@ #include "../crc/checksum.h" #include "../ini/SimpleIni.h" -// getoptֻWindowsƽ̨°ƽ̨ʹϵͳԴgetopt +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +// getopt只在Windows平台下包含,其他平台使用系统自带的getopt #ifdef _WIN32 #include "getopt.h" #pragma comment(lib, "VrUtils.lib") diff --git a/VrUtils/Inc/VrLog.h b/VrUtils/Inc/VrLog.h index 719c8b7..5f9659e 100644 --- a/VrUtils/Inc/VrLog.h +++ b/VrUtils/Inc/VrLog.h @@ -31,6 +31,7 @@ #define LOG_DEBUG(...) VrLogUtils::EchoLog(KELOGLEVEL_Debug, logfilename(__FILE__), __LINE__, "APPD", ##__VA_ARGS__) #define LOG_INFO(...) VrLogUtils::EchoLog(KELOGLEVEL_Info, logfilename(__FILE__), __LINE__, "APPI", ##__VA_ARGS__) #define LOG_WARNING(...) VrLogUtils::EchoLog(KELOGLEVEL_Warning, logfilename(__FILE__), __LINE__, "APPW", ##__VA_ARGS__) +#define LOG_WARN(...) VrLogUtils::EchoLog(KELOGLEVEL_Warning, logfilename(__FILE__), __LINE__, "APPW", ##__VA_ARGS__) #define LOG_ERROR(...) VrLogUtils::EchoLog(KELOGLEVEL_Error, logfilename(__FILE__), __LINE__, "APPE", ##__VA_ARGS__) #define LOG_ERRO(...) VrLogUtils::EchoLog(KELOGLEVEL_Error, logfilename(__FILE__), __LINE__, "APPE", ##__VA_ARGS__) #define LOG_ERR(...) VrLogUtils::EchoLog(KELOGLEVEL_Error, logfilename(__FILE__), __LINE__, "APPE", ##__VA_ARGS__)