diff --git a/App/App.pro b/App/App.pro index 638682b..6b5579b 100644 --- a/App/App.pro +++ b/App/App.pro @@ -1,7 +1,7 @@ TEMPLATE = subdirs # 拆包项目 -SUBDIRS += ./GrabBag/GrabBag.pro +# SUBDIRS += ./GrabBag/GrabBag.pro # 撕裂项目 # SUBDIRS += ./BeltTearing/BeltTearing.pro @@ -10,7 +10,7 @@ SUBDIRS += ./GrabBag/GrabBag.pro # SUBDIRS += ./LapWeld/LapWeld.pro #工件定位 -SUBDIRS += ./Workpiece/Workpiece.pro +# SUBDIRS += ./Workpiece/Workpiece.pro # 颗粒尺寸检测 # SUBDIRS += ./ParticleSize/ParticleSize.pro diff --git a/App/BinocularMark/BinocularMarkApp/BinocularMarkPresenter.cpp b/App/BinocularMark/BinocularMarkApp/BinocularMarkPresenter.cpp index 59af38d..0f80ca1 100644 --- a/App/BinocularMark/BinocularMarkApp/BinocularMarkPresenter.cpp +++ b/App/BinocularMark/BinocularMarkApp/BinocularMarkPresenter.cpp @@ -10,6 +10,9 @@ #include #include #include +#include +#include "VrTimeUtils.h" +#include "PathManager.h" BinocularMarkPresenter::BinocularMarkPresenter(QObject *parent) : QObject(parent) @@ -20,6 +23,8 @@ BinocularMarkPresenter::BinocularMarkPresenter(QObject *parent) , m_bAutoStartDetection(false) , m_bIsDetecting(false) , m_bSendContinuousResult(false) + , m_bSendContinuousImage(false) + , m_bIsProcessingImage(false) , m_bLeftImageReady(false) , m_bRightImageReady(false) , m_nServerPort(5901) @@ -56,7 +61,7 @@ BinocularMarkPresenter::BinocularMarkPresenter(QObject *parent) m_markInfo.dictType = 1; // DICT_6x6 // 初始化Board配置(默认值) - m_boardInfo.totalBoardNum = 1; // 默认1个board + m_boardInfo.totalBoardNum = 10; // 默认1个board m_boardInfo.boardIdInterval = 8; // 间隔8 m_boardInfo.boardChaucoIDNum = 4; // 3x3有4个charuco @@ -185,21 +190,47 @@ int BinocularMarkPresenter::tryConnectCameras() return ERR_CODE(APP_ERR_EXEC); } - LOG_INFO("Found %zu cameras\n", deviceList.size()); - - // 打开左相机 - ret = m_pLeftCamera->OpenDeviceByIndex(m_nLeftCameraIndex); - if (ret != SUCCESS) + LOG_INFO("Found %zu cameras:\n", deviceList.size()); + for (size_t i = 0; i < deviceList.size(); i++) { - LOG_ERROR("Failed to open left camera (index: %u)\n", m_nLeftCameraIndex); - delete m_pLeftCamera; - delete m_pRightCamera; - m_pLeftCamera = nullptr; - m_pRightCamera = nullptr; - return ERR_CODE(APP_ERR_EXEC); + LOG_INFO(" [%zu] SN: %s, Model: %s, Name: %s\n", + i, + deviceList[i].serialNumber.c_str(), + deviceList[i].modelName.c_str(), + deviceList[i].displayName.c_str()); } - LOG_INFO("Left camera opened (index: %u)\n", m_nLeftCameraIndex); + // 打开左相机(优先使用序列号) + if (!m_strLeftCameraSerial.empty()) + { + LOG_INFO("Opening left camera by serial number: %s\n", m_strLeftCameraSerial.c_str()); + ret = m_pLeftCamera->OpenDevice(m_strLeftCameraSerial); + if (ret != SUCCESS) + { + LOG_ERROR("Failed to open left camera by serial number: %s\n", m_strLeftCameraSerial.c_str()); + delete m_pLeftCamera; + delete m_pRightCamera; + m_pLeftCamera = nullptr; + m_pRightCamera = nullptr; + return ERR_CODE(APP_ERR_EXEC); + } + LOG_INFO("Left camera opened successfully (SN: %s)\n", m_strLeftCameraSerial.c_str()); + } + else + { + LOG_INFO("Opening left camera by index: %u\n", m_nLeftCameraIndex); + ret = m_pLeftCamera->OpenDeviceByIndex(m_nLeftCameraIndex); + if (ret != SUCCESS) + { + LOG_ERROR("Failed to open left camera (index: %u)\n", m_nLeftCameraIndex); + delete m_pLeftCamera; + delete m_pRightCamera; + m_pLeftCamera = nullptr; + m_pRightCamera = nullptr; + return ERR_CODE(APP_ERR_EXEC); + } + LOG_INFO("Left camera opened successfully (index: %u)\n", m_nLeftCameraIndex); + } // 初始化右相机SDK(每个设备对象需要独立初始化) ret = m_pRightCamera->InitSDK(); @@ -214,20 +245,39 @@ int BinocularMarkPresenter::tryConnectCameras() return ERR_CODE(APP_ERR_EXEC); } - // 打开右相机 - ret = m_pRightCamera->OpenDeviceByIndex(m_nRightCameraIndex); - if (ret != SUCCESS) + // 打开右相机(优先使用序列号) + if (!m_strRightCameraSerial.empty()) { - LOG_ERROR("Failed to open right camera (index: %u)\n", m_nRightCameraIndex); - m_pLeftCamera->CloseDevice(); - delete m_pLeftCamera; - delete m_pRightCamera; - m_pLeftCamera = nullptr; - m_pRightCamera = nullptr; - return ERR_CODE(APP_ERR_EXEC); + LOG_INFO("Opening right camera by serial number: %s\n", m_strRightCameraSerial.c_str()); + ret = m_pRightCamera->OpenDevice(m_strRightCameraSerial); + if (ret != SUCCESS) + { + LOG_ERROR("Failed to open right camera by serial number: %s\n", m_strRightCameraSerial.c_str()); + m_pLeftCamera->CloseDevice(); + delete m_pLeftCamera; + delete m_pRightCamera; + m_pLeftCamera = nullptr; + m_pRightCamera = nullptr; + return ERR_CODE(APP_ERR_EXEC); + } + LOG_INFO("Right camera opened successfully (SN: %s)\n", m_strRightCameraSerial.c_str()); + } + else + { + LOG_INFO("Opening right camera by index: %u\n", m_nRightCameraIndex); + ret = m_pRightCamera->OpenDeviceByIndex(m_nRightCameraIndex); + if (ret != SUCCESS) + { + LOG_ERROR("Failed to open right camera (index: %u)\n", m_nRightCameraIndex); + m_pLeftCamera->CloseDevice(); + delete m_pLeftCamera; + delete m_pRightCamera; + m_pLeftCamera = nullptr; + m_pRightCamera = nullptr; + return ERR_CODE(APP_ERR_EXEC); + } + LOG_INFO("Right camera opened successfully (index: %u)\n", m_nRightCameraIndex); } - - LOG_INFO("Right camera opened (index: %u)\n", m_nRightCameraIndex); // 设置左相机参数 m_pLeftCamera->SetExposureTime(m_fExposureTime); @@ -239,35 +289,7 @@ int BinocularMarkPresenter::tryConnectCameras() m_pRightCamera->SetGain(m_fGain); m_pRightCamera->SetTriggerMode(false); // 连续采集模式 - // 启动相机采集(使 CaptureImage 可用) - ret = m_pLeftCamera->StartAcquisition(); - if (ret != SUCCESS) - { - LOG_ERROR("Failed to start left camera acquisition\n"); - m_pLeftCamera->CloseDevice(); - m_pRightCamera->CloseDevice(); - delete m_pLeftCamera; - delete m_pRightCamera; - m_pLeftCamera = nullptr; - m_pRightCamera = nullptr; - return ERR_CODE(APP_ERR_EXEC); - } - - ret = m_pRightCamera->StartAcquisition(); - if (ret != SUCCESS) - { - LOG_ERROR("Failed to start right camera acquisition\n"); - m_pLeftCamera->StopAcquisition(); - m_pLeftCamera->CloseDevice(); - m_pRightCamera->CloseDevice(); - delete m_pLeftCamera; - delete m_pRightCamera; - m_pLeftCamera = nullptr; - m_pRightCamera = nullptr; - return ERR_CODE(APP_ERR_EXEC); - } - - LOG_INFO("Cameras connected and acquisition started successfully\n"); + LOG_INFO("Cameras connected successfully\n"); return SUCCESS; } @@ -335,7 +357,7 @@ int BinocularMarkPresenter::startDetection() return ERR_CODE(APP_ERR_EXEC); } - // 注册左相机图像回调(使用lambda捕获this指针) + // 1. 注册左相机图像回调(使用lambda捕获this指针) int ret = m_pLeftCamera->RegisterImageCallback( [this](const GalaxyImageData& imageData) { this->leftCameraCallback(imageData); @@ -347,7 +369,7 @@ int BinocularMarkPresenter::startDetection() return ERR_CODE(APP_ERR_EXEC); } - // 注册右相机图像回调 + // 2. 注册右相机图像回调 ret = m_pRightCamera->RegisterImageCallback( [this](const GalaxyImageData& imageData) { this->rightCameraCallback(imageData); @@ -360,13 +382,34 @@ int BinocularMarkPresenter::startDetection() return ERR_CODE(APP_ERR_EXEC); } + // 3. 启动左相机采集 + ret = m_pLeftCamera->StartAcquisition(); + if (ret != SUCCESS) + { + LOG_ERROR("Failed to start left camera acquisition\n"); + m_pLeftCamera->UnregisterImageCallback(); + m_pRightCamera->UnregisterImageCallback(); + return ERR_CODE(APP_ERR_EXEC); + } + + // 4. 启动右相机采集 + ret = m_pRightCamera->StartAcquisition(); + if (ret != SUCCESS) + { + LOG_ERROR("Failed to start right camera acquisition\n"); + m_pLeftCamera->StopAcquisition(); + m_pLeftCamera->UnregisterImageCallback(); + m_pRightCamera->UnregisterImageCallback(); + return ERR_CODE(APP_ERR_EXEC); + } + m_bIsDetecting.store(true); m_bAutoStartDetection.store(false); // 清除自动启动标志 - // 启动采集线程 + // 5. 启动采集线程 m_captureThread = std::thread(&BinocularMarkPresenter::captureThreadFunc, this); - LOG_INFO("Detection started\n"); + LOG_INFO("Detection started (callbacks registered and cameras acquisition started)\n"); return SUCCESS; } @@ -378,7 +421,23 @@ void BinocularMarkPresenter::stopDetection() m_bIsDetecting.store(false); m_bAutoStartDetection.store(false); // 清除自动启动标志(用户主动停止) - // 取消注册图像回调(保持相机采集状态,以便 CaptureImage 可用) + // 1. 等待线程结束 + if (m_captureThread.joinable()) + { + m_captureThread.join(); + } + + // 2. 停止相机采集 + if (m_pLeftCamera != nullptr) + { + m_pLeftCamera->StopAcquisition(); + } + if (m_pRightCamera != nullptr) + { + m_pRightCamera->StopAcquisition(); + } + + // 3. 取消注册图像回调 if (m_pLeftCamera != nullptr) { m_pLeftCamera->UnregisterImageCallback(); @@ -388,13 +447,7 @@ void BinocularMarkPresenter::stopDetection() m_pRightCamera->UnregisterImageCallback(); } - // 等待线程结束 - if (m_captureThread.joinable()) - { - m_captureThread.join(); - } - - LOG_INFO("Detection stopped\n"); + LOG_INFO("Detection stopped (cameras acquisition stopped and callbacks unregistered)\n"); } void BinocularMarkPresenter::leftCameraCallback(const GalaxyImageData& imageData) @@ -487,6 +540,23 @@ void BinocularMarkPresenter::captureThreadFunc() if (leftReady && rightReady) { + // 帧率限制:限制为5fps(200ms间隔) + auto now = std::chrono::steady_clock::now(); + auto elapsed = std::chrono::duration_cast(now - m_lastImageSendTime).count(); + if (elapsed < 200) { + LOG_DEBUG("Drop frame: frame rate limit (elapsed: %lld ms)\n", elapsed); + // 清除就绪标志 + { + QMutexLocker locker(&m_imageMutex); + m_bLeftImageReady = false; + m_bRightImageReady = false; + } + continue; + } + + // 更新发送时间 + m_lastImageSendTime = now; + // 处理图像并进行检测 processImages(); @@ -500,7 +570,7 @@ void BinocularMarkPresenter::captureThreadFunc() else { // 等待图像 - std::this_thread::sleep_for(std::chrono::milliseconds(10)); + std::this_thread::sleep_for(std::chrono::milliseconds(2)); } } @@ -509,19 +579,23 @@ void BinocularMarkPresenter::captureThreadFunc() void BinocularMarkPresenter::processImages() { - // 直接在互斥锁中转换为 cv::Mat(深拷贝),避免多余的临时变量和二次拷贝 + // 在互斥锁中转换为 cv::Mat(深拷贝) cv::Mat leftImage, rightImage; { QMutexLocker locker(&m_imageMutex); - - // 直接转换为 cv::Mat(imageDataToMat 会执行深拷贝) leftImage = imageDataToMat(m_leftImageData); rightImage = imageDataToMat(m_rightImageData); } + // 如果启用持续图像流,直接发送图像,不做检测 + if (m_bSendContinuousImage) { + emit detectionResult(std::vector(), leftImage, rightImage, 0); + LOG_DEBUG("Sent continuous image stream\n"); + return; + } + // 调用算法进行检测 std::vector marks; - // 使用新的算法接口 wd_BQ_getCharuco3DMark( leftImage, @@ -576,10 +650,17 @@ cv::Mat BinocularMarkPresenter::imageDataToMat(const GalaxyImageData& imageData) bool BinocularMarkPresenter::loadConfiguration(const QString& configFilePath) { + m_calibrationFilePath = PathManager::GetInstance().GetAppConfigDirectory() + "/StereoCamera.xml"; + QFile file(configFilePath); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { LOG_WARN("Failed to open config file: %s\n", configFilePath.toStdString().c_str()); + // 即使配置文件不存在,也要加载标定文件 + if (!loadCalibration(m_calibrationFilePath)) + { + LOG_WARN("Failed to load calibration file: %s (will use default values)\n", m_calibrationFilePath.toStdString().c_str()); + } return false; } @@ -588,8 +669,7 @@ bool BinocularMarkPresenter::loadConfiguration(const QString& configFilePath) int errorLine, errorColumn; if (!doc.setContent(&file, &errorMsg, &errorLine, &errorColumn)) { - LOG_ERROR("Failed to parse config XML: %s (Line: %d, Column: %d)\n", - errorMsg.toStdString().c_str(), errorLine, errorColumn); + LOG_ERROR("Failed to parse config XML: %s (Line: %d, Column: %d)\n", errorMsg.toStdString().c_str(), errorLine, errorColumn); file.close(); return false; } @@ -625,22 +705,36 @@ bool BinocularMarkPresenter::loadConfiguration(const QString& configFilePath) while (!camera.isNull()) { int index = camera.attribute("index", "0").toInt(); + QString serialNumber = camera.attribute("serialNumber", ""); double exposureTime = camera.attribute("exposureTime", "10000.0").toDouble(); double gain = camera.attribute("gain", "1.0").toDouble(); - LOG_DEBUG(" Camera[%d]: exposureTime=%.2f, gain=%.2f\n", index, exposureTime, gain); + LOG_DEBUG(" Camera[%d]: serialNumber=%s, exposureTime=%.2f, gain=%.2f\n", + index, serialNumber.toStdString().c_str(), exposureTime, gain); if (index == 0) { m_nLeftCameraIndex = 0; + m_strLeftCameraSerial = serialNumber.toStdString(); m_fExposureTime = exposureTime; m_fGain = gain; - LOG_DEBUG(" -> 设置为左相机\n"); + LOG_DEBUG(" -> 设置为左相机"); + if (!m_strLeftCameraSerial.empty()) + { + LOG_DEBUG(" (序列号: %s)", m_strLeftCameraSerial.c_str()); + } + LOG_DEBUG("\n"); } else if (index == 1) { m_nRightCameraIndex = 1; - LOG_DEBUG(" -> 设置为右相机\n"); + m_strRightCameraSerial = serialNumber.toStdString(); + LOG_DEBUG(" -> 设置为右相机"); + if (!m_strRightCameraSerial.empty()) + { + LOG_DEBUG(" (序列号: %s)", m_strRightCameraSerial.c_str()); + } + LOG_DEBUG("\n"); // 假设左右相机使用相同的曝光和增益 } @@ -704,27 +798,25 @@ bool BinocularMarkPresenter::loadConfiguration(const QString& configFilePath) LOG_DEBUG(" [AlgorithmParams] 未找到,使用默认值\n"); } - // 读取标定文件路径(必须配置) + // 读取标定文件路径(可选,如果没有则使用默认路径) QDomElement calibFileElem = root.firstChildElement("CalibrationFile"); - if (calibFileElem.isNull()) + QString calibFile; + + if (!calibFileElem.isNull()) { - LOG_ERROR("Missing element in config file!\n"); - LOG_ERROR("Please add: \n"); - return false; + calibFile = calibFileElem.attribute("path", ""); + LOG_DEBUG(" [CalibrationFile] path (原始) = %s\n", calibFile.toStdString().c_str()); } - QString calibFile = calibFileElem.attribute("path", ""); - LOG_DEBUG(" [CalibrationFile] path (原始) = %s\n", calibFile.toStdString().c_str()); - + // 如果没有配置或配置为空,使用默认路径 if (calibFile.isEmpty()) { - LOG_ERROR("CalibrationFile path is empty in config file!\n"); - LOG_ERROR("Please set path attribute: \n"); - return false; + QFileInfo configFileInfo(configFilePath); + calibFile = configFileInfo.dir().absoluteFilePath("StereoCamera.xml"); + LOG_DEBUG(" [CalibrationFile] 使用默认路径: %s\n", calibFile.toStdString().c_str()); } - // 如果是相对路径,则相对于配置文件所在目录 - if (QFileInfo(calibFile).isRelative()) + else if (QFileInfo(calibFile).isRelative()) { QFileInfo configFileInfo(configFilePath); calibFile = configFileInfo.dir().absoluteFilePath(calibFile); @@ -733,11 +825,13 @@ bool BinocularMarkPresenter::loadConfiguration(const QString& configFilePath) LOG_DEBUG("========== 配置文件读取完成 ==========\n\n"); - // 加载标定文件 + // 保存标定文件路径 + m_calibrationFilePath = calibFile; + + // 加载标定文件(如果文件不存在,只警告不返回失败) if (!loadCalibration(calibFile)) { - LOG_ERROR("Failed to load calibration file: %s\n", calibFile.toStdString().c_str()); - return false; + LOG_WARN("Failed to load calibration file: %s (will use default values)\n", calibFile.toStdString().c_str()); } LOG_INFO("Configuration loaded from %s\n", configFilePath.toStdString().c_str()); @@ -977,6 +1071,7 @@ void BinocularMarkPresenter::handleSingleDetection(const TCPClient* pClient) memset(&leftImageData, 0, sizeof(GalaxyImageData)); memset(&rightImageData, 0, sizeof(GalaxyImageData)); + // 顺序取图 int ret1 = m_pLeftCamera->CaptureImage(leftImageData, 5000); int ret2 = m_pRightCamera->CaptureImage(rightImageData, 5000); @@ -988,10 +1083,17 @@ void BinocularMarkPresenter::handleSingleDetection(const TCPClient* pClient) return; } - cv::Mat leftImage = imageDataToMat(leftImageData); - cv::Mat rightImage = imageDataToMat(rightImageData); - LOG_DEBUG("Image captured: left=%dx%d, right=%dx%d\n", - leftImage.cols, leftImage.rows, rightImage.cols, rightImage.rows); + // 并行转换图像 + auto leftFuture = std::async(std::launch::async, [&]() { + return imageDataToMat(leftImageData); + }); + auto rightFuture = std::async(std::launch::async, [&]() { + return imageDataToMat(rightImageData); + }); + + cv::Mat leftImage = leftFuture.get(); + cv::Mat rightImage = rightFuture.get(); + LOG_DEBUG("Image captured: left=%dx%d, right=%dx%d\n", leftImage.cols, leftImage.rows, rightImage.cols, rightImage.rows); delete[] leftImageData.pData; delete[] rightImageData.pData; @@ -1001,7 +1103,7 @@ void BinocularMarkPresenter::handleSingleDetection(const TCPClient* pClient) m_cameraMatrixR, m_distCoeffsR, m_R1, m_R2, m_P1, m_P2, m_Q, m_markInfo, m_boardInfo, m_disparityOffset, marks); - int errCode = marks.empty() ? -1 : 0; + int errCode = marks.empty() ? ERR_CODE(DEV_RESULT_EMPTY) : 0; emit singleDetectionResult(pClient, marks, leftImage, rightImage, errCode); LOG_INFO("Single detection completed, detected %zu marks\n", marks.size()); } @@ -1016,13 +1118,14 @@ void BinocularMarkPresenter::handleSingleImage(const TCPClient* pClient) return; } + CVrTimeUtils oTimeUtils; GalaxyImageData leftImageData, rightImageData; memset(&leftImageData, 0, sizeof(GalaxyImageData)); memset(&rightImageData, 0, sizeof(GalaxyImageData)); - + + // 顺序取图 int ret1 = m_pLeftCamera->CaptureImage(leftImageData, 5000); int ret2 = m_pRightCamera->CaptureImage(rightImageData, 5000); - if (ret1 != 0 || ret2 != 0) { LOG_WARN("Failed to capture images: left=%d, right=%d\n", ret1, ret2); if (leftImageData.pData) delete[] leftImageData.pData; @@ -1031,17 +1134,24 @@ void BinocularMarkPresenter::handleSingleImage(const TCPClient* pClient) return; } - cv::Mat leftImage = imageDataToMat(leftImageData); - cv::Mat rightImage = imageDataToMat(rightImageData); - LOG_DEBUG("Image captured: left=%dx%d, right=%dx%d\n", - leftImage.cols, leftImage.rows, rightImage.cols, rightImage.rows); + // 并行转换图像 + auto leftFuture = std::async(std::launch::async, [&]() { + return imageDataToMat(leftImageData); + }); + auto rightFuture = std::async(std::launch::async, [&]() { + return imageDataToMat(rightImageData); + }); + + cv::Mat leftImage = leftFuture.get(); + cv::Mat rightImage = rightFuture.get(); delete[] leftImageData.pData; delete[] rightImageData.pData; - + + double timeCost = oTimeUtils.GetElapsedTimeInMilliSec(); emit singleImageResult(pClient, leftImage, rightImage); - LOG_INFO("Single image sent, left=%dx%d, right=%dx%d\n", - leftImage.cols, leftImage.rows, rightImage.cols, rightImage.rows); + LOG_DEBUG("Image captured: left=%dx%d, right=%dx%d, getimage:%.3f ms time:%.3f ms\n", + leftImage.cols, leftImage.rows, rightImage.cols, rightImage.rows, timeCost, oTimeUtils.GetElapsedTimeInMilliSec()); } void BinocularMarkPresenter::handleStartWork() @@ -1074,6 +1184,37 @@ void BinocularMarkPresenter::handleStopWork() } } +void BinocularMarkPresenter::handleStartContinuousImage() +{ + LOG_INFO("Handle start continuous image request\n"); + + if (!m_bIsDetecting) { + int ret = startDetection(); + if (ret != 0) { + LOG_ERROR("Failed to start detection, error code: %d\n", ret); + return; + } + LOG_INFO("Detection thread started\n"); + } + + m_bSendContinuousImage = true; + LOG_INFO("Continuous image sending enabled\n"); +} + +void BinocularMarkPresenter::handleStopContinuousImage() +{ + LOG_INFO("Handle stop continuous image request\n"); + + m_bSendContinuousImage = false; + LOG_INFO("Continuous image sending disabled\n"); + + // 停止持续图像流时,无条件停止检测线程 + if (m_bIsDetecting) { + stopDetection(); + LOG_INFO("Detection thread stopped\n"); + } +} + void BinocularMarkPresenter::handleSetExposureTime(double exposureTime) { LOG_INFO("Handle set exposure time: %.2f\n", exposureTime); @@ -1105,3 +1246,154 @@ void BinocularMarkPresenter::handleSetGain(double gain) LOG_INFO("Gain updated successfully\n"); } + +void BinocularMarkPresenter::handleSetLeftExposureTime(double exposureTime) +{ + LOG_INFO("Handle set left camera exposure time: %.2f\n", exposureTime); + + if (m_pLeftCamera) { + m_pLeftCamera->SetExposureTime(exposureTime); + LOG_INFO("Left camera exposure time updated successfully\n"); + } else { + LOG_WARN("Left camera not initialized\n"); + } +} + +void BinocularMarkPresenter::handleSetRightExposureTime(double exposureTime) +{ + LOG_INFO("Handle set right camera exposure time: %.2f\n", exposureTime); + + if (m_pRightCamera) { + m_pRightCamera->SetExposureTime(exposureTime); + LOG_INFO("Right camera exposure time updated successfully\n"); + } else { + LOG_WARN("Right camera not initialized\n"); + } +} + +void BinocularMarkPresenter::handleSetLeftGain(double gain) +{ + LOG_INFO("Handle set left camera gain: %.2f\n", gain); + + if (m_pLeftCamera) { + m_pLeftCamera->SetGain(gain); + LOG_INFO("Left camera gain updated successfully\n"); + } else { + LOG_WARN("Left camera not initialized\n"); + } +} + +void BinocularMarkPresenter::handleSetRightGain(double gain) +{ + LOG_INFO("Handle set right camera gain: %.2f\n", gain); + + if (m_pRightCamera) { + m_pRightCamera->SetGain(gain); + LOG_INFO("Right camera gain updated successfully\n"); + } else { + LOG_WARN("Right camera not initialized\n"); + } +} + +void BinocularMarkPresenter::handleGetCameraInfo(const TCPClient* pClient, const QString& camera) +{ + LOG_INFO("Handle get camera info: %s\n", camera.toStdString().c_str()); + + IGalaxyDevice* pCamera = nullptr; + if (camera == "left") { + pCamera = m_pLeftCamera; + } else if (camera == "right") { + pCamera = m_pRightCamera; + } + + if (!pCamera) { + LOG_WARN("Camera not initialized: %s\n", camera.toStdString().c_str()); + emit cameraInfoResult(pClient, camera, "", "", "", 0.0, 0.0); + return; + } + + // 获取相机信息 + GalaxyDeviceInfo deviceInfo; + int ret = pCamera->GetDeviceInfo(deviceInfo); + if (ret != 0) { + LOG_WARN("Failed to get camera info: %s\n", camera.toStdString().c_str()); + emit cameraInfoResult(pClient, camera, "", "", "", 0.0, 0.0); + return; + } + + // 获取当前曝光时间和增益 + double exposureTime = 0.0; + double gain = 0.0; + pCamera->GetExposureTime(exposureTime); + pCamera->GetGain(gain); + + // 发送相机信息 + emit cameraInfoResult(pClient, + camera, + QString::fromStdString(deviceInfo.serialNumber), + QString::fromStdString(deviceInfo.modelName), + QString::fromStdString(deviceInfo.displayName), + exposureTime, + gain); + + LOG_INFO("Camera info sent: %s, SN=%s, Model=%s, Exposure=%.2f, Gain=%.2f\n", + camera.toStdString().c_str(), + deviceInfo.serialNumber.c_str(), + deviceInfo.modelName.c_str(), + exposureTime, + gain); +} + +void BinocularMarkPresenter::handleGetCalibration(const TCPClient* pClient) +{ + LOG_INFO("Handle get calibration request\n"); + + if (m_calibrationFilePath.isEmpty()) { + LOG_WARN("Calibration file path not set\n"); + emit calibrationMatrixResult(pClient, ""); + return; + } + + QFile file(m_calibrationFilePath); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + LOG_WARN("Failed to open calibration file: %s\n", m_calibrationFilePath.toStdString().c_str()); + emit calibrationMatrixResult(pClient, ""); + return; + } + + QTextStream in(&file); + QString calibrationXml = in.readAll(); + file.close(); + + emit calibrationMatrixResult(pClient, calibrationXml); + LOG_INFO("Calibration matrix sent from: %s\n", m_calibrationFilePath.toStdString().c_str()); +} + +void BinocularMarkPresenter::handleSetCalibration(const TCPClient* pClient, const QString& calibrationXml) +{ + LOG_INFO("Handle set calibration request : %s\n", m_calibrationFilePath.toStdString().c_str()); + + if (m_calibrationFilePath.isEmpty()) { + LOG_ERROR("Calibration file path not set\n"); + return; + } + + QFile file(m_calibrationFilePath); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + LOG_ERROR("Failed to open calibration file for writing: %s\n", m_calibrationFilePath.toStdString().c_str()); + return; + } + + QTextStream out(&file); + out << calibrationXml; + file.close(); + + LOG_INFO("Calibration matrix saved to: %s\n", m_calibrationFilePath.toStdString().c_str()); + + // 重新加载标定文件 + if (loadCalibration(m_calibrationFilePath)) { + LOG_INFO("Calibration reloaded successfully\n"); + } else { + LOG_ERROR("Failed to reload calibration\n"); + } +} diff --git a/App/BinocularMark/BinocularMarkApp/BinocularMarkPresenter.h b/App/BinocularMark/BinocularMarkApp/BinocularMarkPresenter.h index e63c1b8..3990685 100644 --- a/App/BinocularMark/BinocularMarkApp/BinocularMarkPresenter.h +++ b/App/BinocularMark/BinocularMarkApp/BinocularMarkPresenter.h @@ -131,17 +131,71 @@ public slots: void handleStopWork(); /** - * @brief 处理设置曝光时间请求 + * @brief 处理开始持续图像流请求 + */ + void handleStartContinuousImage(); + + /** + * @brief 处理停止持续图像流请求 + */ + void handleStopContinuousImage(); + + /** + * @brief 处理设置曝光时间请求(同时设置左右相机) * @param exposureTime 曝光时间 */ void handleSetExposureTime(double exposureTime); /** - * @brief 处理设置增益请求 + * @brief 处理设置增益请求(同时设置左右相机) * @param gain 增益 */ void handleSetGain(double gain); + /** + * @brief 处理设置左相机曝光时间请求 + * @param exposureTime 曝光时间 + */ + void handleSetLeftExposureTime(double exposureTime); + + /** + * @brief 处理设置右相机曝光时间请求 + * @param exposureTime 曝光时间 + */ + void handleSetRightExposureTime(double exposureTime); + + /** + * @brief 处理设置左相机增益请求 + * @param gain 增益 + */ + void handleSetLeftGain(double gain); + + /** + * @brief 处理设置右相机增益请求 + * @param gain 增益 + */ + void handleSetRightGain(double gain); + + /** + * @brief 处理获取相机信息请求 + * @param pClient 客户端指针 + * @param camera 相机标识("left"或"right") + */ + void handleGetCameraInfo(const TCPClient* pClient, const QString& camera); + + /** + * @brief 处理获取标定矩阵请求 + * @param pClient 客户端指针 + */ + void handleGetCalibration(const TCPClient* pClient); + + /** + * @brief 处理设置标定矩阵请求 + * @param pClient 客户端指针 + * @param calibrationXml 标定矩阵XML内容 + */ + void handleSetCalibration(const TCPClient* pClient, const QString& calibrationXml); + signals: /** * @brief 检测结果信号 @@ -171,6 +225,31 @@ signals: cv::Mat leftImage, cv::Mat rightImage); + /** + * @brief 相机信息结果信号 + * @param pClient 客户端指针 + * @param camera 相机标识("left"或"right") + * @param serialNumber 序列号 + * @param modelName 型号 + * @param displayName 显示名称 + * @param exposureTime 曝光时间 + * @param gain 增益 + */ + void cameraInfoResult(const TCPClient* pClient, + const QString& camera, + const QString& serialNumber, + const QString& modelName, + const QString& displayName, + double exposureTime, + double gain); + + /** + * @brief 标定矩阵结果信号 + * @param pClient 客户端指针 + * @param calibrationXml 标定矩阵XML字符串 + */ + void calibrationMatrixResult(const TCPClient* pClient, const QString& calibrationXml); + /** * @brief 相机连接状态变化信号 * @param connected true-已连接,false-未连接 @@ -228,6 +307,9 @@ private: // 采集控制 std::atomic m_bIsDetecting; // 是否正在检测 std::atomic m_bSendContinuousResult; // 是否发送持续检测结果 + std::atomic m_bSendContinuousImage; // 是否发送持续图像流 + std::atomic m_bIsProcessingImage; // 是否正在处理图像(用于丢帧) + std::chrono::steady_clock::time_point m_lastImageSendTime; // 上次发送图像时间(用于限制帧率) std::thread m_captureThread; // 采集线程 // 图像缓存 @@ -241,6 +323,8 @@ private: quint16 m_nServerPort; // TCP服务器端口 unsigned int m_nLeftCameraIndex; // 左相机索引 unsigned int m_nRightCameraIndex; // 右相机索引 + std::string m_strLeftCameraSerial; // 左相机序列号(优先使用) + std::string m_strRightCameraSerial; // 右相机序列号(优先使用) // 相机参数 double m_fExposureTime; // 曝光时间 @@ -261,6 +345,9 @@ private: SWD_BQ_CharucoMarkInfo m_markInfo; // Mark板配置 SWD_BQ_MarkBoardInfo m_boardInfo; // Board配置 double m_disparityOffset; // 视差偏移 + + // 标定文件路径 + QString m_calibrationFilePath; // 标定文件完整路径 }; #endif // BINOCULARMARKPRESENTER_H diff --git a/App/BinocularMark/BinocularMarkApp/BinocularMarkTcpProtocol.cpp b/App/BinocularMark/BinocularMarkApp/BinocularMarkTcpProtocol.cpp index a4166c8..a854f91 100644 --- a/App/BinocularMark/BinocularMarkApp/BinocularMarkTcpProtocol.cpp +++ b/App/BinocularMark/BinocularMarkApp/BinocularMarkTcpProtocol.cpp @@ -5,6 +5,7 @@ #include #include #include +#include // 静态实例指针 BinocularMarkTcpProtocol* BinocularMarkTcpProtocol::s_pInstance = nullptr; @@ -15,6 +16,7 @@ BinocularMarkTcpProtocol::BinocularMarkTcpProtocol(QObject *parent) , m_pHeartbeatTimer(nullptr) , m_nHeartbeatInterval(30) , m_nTcpPort(5901) + , m_bIsProcessingFrame(false) { s_pInstance = this; @@ -185,13 +187,13 @@ QByteArray BinocularMarkTcpProtocol::buildFrame(const QByteArray& jsonData) // 帧头(8字节) frame.append(FRAME_HEADER, FRAME_HEADER_SIZE); - // 写入数据长度(8位字符串格式) + // 写入数据长度(8位字符串格式,64位无符号整数) quint64 dataLength = jsonData.size(); char lengthStr[9]; // 8位数字 + '\0' #ifdef _WIN32 - sprintf_s(lengthStr, "%08u", dataLength); + sprintf_s(lengthStr, 9, "%08llu", dataLength); #else - sprintf(lengthStr, "%08u", dataLength); + snprintf(lengthStr, 9, "%08llu", dataLength); #endif frame.append(lengthStr, FRAME_LENGTH_SIZE); @@ -293,12 +295,18 @@ MarkMessageType BinocularMarkTcpProtocol::parseMessageType(const QString& msgTyp return MarkMessageType::CMD_START_WORK; else if (msgTypeStr == "cmd_stop_work") return MarkMessageType::CMD_STOP_WORK; + else if (msgTypeStr == "cmd_start_continuous_image") + return MarkMessageType::CMD_START_CONTINUOUS_IMAGE; + else if (msgTypeStr == "cmd_stop_continuous_image") + return MarkMessageType::CMD_STOP_CONTINUOUS_IMAGE; else if (msgTypeStr == "cmd_set_calibration") return MarkMessageType::CMD_SET_CALIBRATION; else if (msgTypeStr == "cmd_set_exposure_time") return MarkMessageType::CMD_SET_EXPOSURE_TIME; else if (msgTypeStr == "cmd_set_gain") return MarkMessageType::CMD_SET_GAIN; + else if (msgTypeStr == "cmd_get_camera_info") + return MarkMessageType::CMD_GET_CAMERA_INFO; else if (msgTypeStr == "cmd_response") return MarkMessageType::CMD_RESPONSE; else @@ -356,10 +364,22 @@ void BinocularMarkTcpProtocol::handleJsonMessage(const TCPClient* pClient, const handleStopWorkCommand(pClient, jsonObj); break; + case MarkMessageType::CMD_START_CONTINUOUS_IMAGE: + handleStartContinuousImageCommand(pClient, jsonObj); + break; + + case MarkMessageType::CMD_STOP_CONTINUOUS_IMAGE: + handleStopContinuousImageCommand(pClient, jsonObj); + break; + case MarkMessageType::CMD_SET_CALIBRATION: handleSetCalibrationCommand(pClient, jsonObj); break; + case MarkMessageType::CMD_GET_CALIBRATION: + handleGetCalibrationCommand(pClient, jsonObj); + break; + case MarkMessageType::CMD_SET_EXPOSURE_TIME: handleSetExposureTimeCommand(pClient, jsonObj); break; @@ -368,6 +388,10 @@ void BinocularMarkTcpProtocol::handleJsonMessage(const TCPClient* pClient, const handleSetGainCommand(pClient, jsonObj); break; + case MarkMessageType::CMD_GET_CAMERA_INFO: + handleGetCameraInfoCommand(pClient, jsonObj); + break; + case MarkMessageType::HEARTBEAT_ACK: // 心跳应答,不做处理 break; @@ -429,6 +453,24 @@ void BinocularMarkTcpProtocol::handleStopWorkCommand(const TCPClient* pClient, c sendCommandResponse(pClient, "stop_work", true, 0, "OK"); } +void BinocularMarkTcpProtocol::handleStartContinuousImageCommand(const TCPClient* pClient, const QJsonObject& jsonObj) +{ + // 开始持续图像流 + emit startContinuousImageRequested(); + + // 发送命令应答 + sendCommandResponse(pClient, "start_continuous_image", true, 0, "OK"); +} + +void BinocularMarkTcpProtocol::handleStopContinuousImageCommand(const TCPClient* pClient, const QJsonObject& jsonObj) +{ + // 停止持续图像流 + emit stopContinuousImageRequested(); + + // 发送命令应答 + sendCommandResponse(pClient, "stop_continuous_image", true, 0, "OK"); +} + void BinocularMarkTcpProtocol::handleSetCalibrationCommand(const TCPClient* pClient, const QJsonObject& jsonObj) { QString calibrationXml = jsonObj["calibration_xml"].toString(); @@ -438,23 +480,34 @@ void BinocularMarkTcpProtocol::handleSetCalibrationCommand(const TCPClient* pCli return; } - // 保存到StereoCamera.xml文件 - QFile file("StereoCamera.xml"); - if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { - QString errorMsg = QString("Failed to open file: %1").arg(file.errorString()); - sendCommandResponse(pClient, "set_calibration", false, -2, errorMsg.toStdString().c_str()); - LOG_ERROR("Failed to open StereoCamera.xml: %s\n", file.errorString().toStdString().c_str()); - return; - } + // 发送信号给Presenter处理 + emit setCalibrationRequested(pClient, calibrationXml); - QTextStream out(&file); - out << calibrationXml; - file.close(); - - LOG_INFO("Calibration matrix saved to StereoCamera.xml\n"); + // 发送命令应答 sendCommandResponse(pClient, "set_calibration", true, 0, "OK"); } +void BinocularMarkTcpProtocol::handleGetCalibrationCommand(const TCPClient* pClient, const QJsonObject& jsonObj) +{ + emit getCalibrationRequested(pClient); +} + +void BinocularMarkTcpProtocol::sendCalibrationMatrixResponse(const TCPClient* pClient, const QString& calibrationXml) +{ + QJsonObject resultObj; + resultObj["msg_type"] = "calibration_matrix_response"; + resultObj["timestamp"] = QDateTime::currentMSecsSinceEpoch(); + resultObj["calibration_xml"] = calibrationXml; + + QJsonDocument doc(resultObj); + QByteArray jsonData = doc.toJson(QJsonDocument::Compact); + QByteArray frameData = buildFrame(jsonData); + + if (m_pTcpServer != nullptr) { + m_pTcpServer->SendData(pClient, frameData.data(), frameData.size()); + } +} + void BinocularMarkTcpProtocol::handleSetExposureTimeCommand(const TCPClient* pClient, const QJsonObject& jsonObj) { double exposureTime = jsonObj["exposure_time"].toDouble(); @@ -464,9 +517,20 @@ void BinocularMarkTcpProtocol::handleSetExposureTimeCommand(const TCPClient* pCl return; } - emit setExposureTimeRequested(exposureTime); + // 检查是否指定了相机 + QString camera = jsonObj["camera"].toString(); + if (camera == "left") { + emit setLeftExposureTimeRequested(exposureTime); + LOG_INFO("Left camera exposure time set: %.2f\n", exposureTime); + } else if (camera == "right") { + emit setRightExposureTimeRequested(exposureTime); + LOG_INFO("Right camera exposure time set: %.2f\n", exposureTime); + } else { + // 未指定相机,同时设置左右相机 + emit setExposureTimeRequested(exposureTime); + LOG_INFO("Both cameras exposure time set: %.2f\n", exposureTime); + } - LOG_INFO("Exposure time set: %.2f\n", exposureTime); sendCommandResponse(pClient, "set_exposure_time", true, 0, "OK"); } @@ -479,9 +543,20 @@ void BinocularMarkTcpProtocol::handleSetGainCommand(const TCPClient* pClient, co return; } - emit setGainRequested(gain); + // 检查是否指定了相机 + QString camera = jsonObj["camera"].toString(); + if (camera == "left") { + emit setLeftGainRequested(gain); + LOG_INFO("Left camera gain set: %.2f\n", gain); + } else if (camera == "right") { + emit setRightGainRequested(gain); + LOG_INFO("Right camera gain set: %.2f\n", gain); + } else { + // 未指定相机,同时设置左右相机 + emit setGainRequested(gain); + LOG_INFO("Both cameras gain set: %.2f\n", gain); + } - LOG_INFO("Gain set: %.2f\n", gain); sendCommandResponse(pClient, "set_gain", true, 0, "OK"); } @@ -527,6 +602,15 @@ void BinocularMarkTcpProtocol::sendMarkResult(const std::vector leftFuture; + std::future rightFuture; + if (!leftImage.empty()) { - resultObj["left_image"] = imageToBase64(leftImage); + leftFuture = std::async(std::launch::async, [this, leftImage]() { + return imageToBase64(leftImage); + }); } if (!rightImage.empty()) { - resultObj["right_image"] = imageToBase64(rightImage); + rightFuture = std::async(std::launch::async, [this, rightImage]() { + return imageToBase64(rightImage); + }); } -#endif + + // 获取编码结果 + if (!leftImage.empty()) + { + resultObj["left_image"] = leftFuture.get(); + } + if (!rightImage.empty()) + { + resultObj["right_image"] = rightFuture.get(); + } + QJsonDocument doc(resultObj); QByteArray jsonData = doc.toJson(QJsonDocument::Compact); QByteArray frameData = buildFrame(jsonData); @@ -567,6 +667,9 @@ void BinocularMarkTcpProtocol::sendMarkResult(const std::vectorSendAllData(frameData.data(), frameData.size()); } + // 处理完成,清除标志位 + m_bIsProcessingFrame = false; + LOG_INFO("Sent mark result, mark_count: %zu, error_code: %d\n", marks.size(), errorCode); } @@ -624,14 +727,33 @@ void BinocularMarkTcpProtocol::sendImageData(const TCPClient* pClient, const cv: resultObj["msg_type"] = "image_data"; resultObj["timestamp"] = QDateTime::currentMSecsSinceEpoch(); - // 添加图像(Base64编码) - if (!leftImage.empty()) + // 并行编码图像 + std::future leftFuture; + std::future rightFuture; + bool hasLeft = !leftImage.empty(); + bool hasRight = !rightImage.empty(); + + if (hasLeft) { - resultObj["left_image"] = imageToBase64(leftImage); + leftFuture = std::async(std::launch::async, [this, &leftImage]() { + return imageToBase64(leftImage); + }); } - if (!rightImage.empty()) + if (hasRight) { - resultObj["right_image"] = imageToBase64(rightImage); + rightFuture = std::async(std::launch::async, [this, &rightImage]() { + return imageToBase64(rightImage); + }); + } + + // 获取编码结果 + if (hasLeft) + { + resultObj["left_image"] = leftFuture.get(); + } + if (hasRight) + { + resultObj["right_image"] = rightFuture.get(); } QJsonDocument doc(resultObj); @@ -649,9 +771,10 @@ void BinocularMarkTcpProtocol::sendImageData(const TCPClient* pClient, const cv: QString BinocularMarkTcpProtocol::imageToBase64(const cv::Mat& image) { - // 将cv::Mat编码为JPEG格式 + // 将cv::Mat编码为JPEG格式,质量设置为70以减少数据量 std::vector buf; - cv::imencode(".jpg", image, buf); + std::vector params = {cv::IMWRITE_JPEG_QUALITY, 70}; + cv::imencode(".jpg", image, buf, params); // 转换为Base64 QByteArray ba(reinterpret_cast(buf.data()), buf.size()); @@ -662,3 +785,53 @@ QString BinocularMarkTcpProtocol::generateClientId(const TCPClient* pClient) { return QString::number(reinterpret_cast(pClient)); } + +void BinocularMarkTcpProtocol::handleGetCameraInfoCommand(const TCPClient* pClient, const QJsonObject& jsonObj) +{ + QString camera = jsonObj["camera"].toString(); + + if (camera != "left" && camera != "right") { + sendCommandResponse(pClient, "get_camera_info", false, -1, "Invalid camera parameter (must be 'left' or 'right')"); + return; + } + + // 触发获取相机信息请求 + emit getCameraInfoRequested(pClient, camera); + + LOG_INFO("Camera info requested: %s\n", camera.toStdString().c_str()); +} + +void BinocularMarkTcpProtocol::sendCameraInfoResponse(const TCPClient* pClient, + const QString& camera, + const QString& serialNumber, + const QString& modelName, + const QString& displayName, + double exposureTime, + double gain) +{ + QJsonObject responseObj; + responseObj["msg_type"] = "camera_info_response"; + responseObj["camera"] = camera; + responseObj["serial_number"] = serialNumber; + responseObj["model_name"] = modelName; + responseObj["display_name"] = displayName; + responseObj["exposure_time"] = exposureTime; + responseObj["gain"] = gain; + responseObj["timestamp"] = QDateTime::currentMSecsSinceEpoch(); + + QJsonDocument doc(responseObj); + QByteArray jsonData = doc.toJson(QJsonDocument::Compact); + QByteArray frameData = buildFrame(jsonData); + + if (m_pTcpServer != nullptr && pClient != nullptr) + { + m_pTcpServer->SendData(pClient, frameData.data(), frameData.size()); + } + + LOG_INFO("Sent camera info response: %s, SN=%s, Model=%s, Exposure=%.2f, Gain=%.2f\n", + camera.toStdString().c_str(), + serialNumber.toStdString().c_str(), + modelName.toStdString().c_str(), + exposureTime, + gain); +} diff --git a/App/BinocularMark/BinocularMarkApp/BinocularMarkTcpProtocol.h b/App/BinocularMark/BinocularMarkApp/BinocularMarkTcpProtocol.h index eb1f88d..cda8bb4 100644 --- a/App/BinocularMark/BinocularMarkApp/BinocularMarkTcpProtocol.h +++ b/App/BinocularMark/BinocularMarkApp/BinocularMarkTcpProtocol.h @@ -36,9 +36,13 @@ enum class MarkMessageType IMAGE_DATA, // 图像数据 CMD_START_WORK, // 开始持续工作命令 CMD_STOP_WORK, // 停止持续工作命令 + CMD_START_CONTINUOUS_IMAGE, // 开始持续图像流命令 + CMD_STOP_CONTINUOUS_IMAGE, // 停止持续图像流命令 CMD_SET_CALIBRATION, // 设置标定矩阵命令 + CMD_GET_CALIBRATION, // 获取标定矩阵命令 CMD_SET_EXPOSURE_TIME, // 设置曝光时间命令 CMD_SET_GAIN, // 设置增益命令 + CMD_GET_CAMERA_INFO, // 获取相机信息命令 CMD_RESPONSE = 200, // 命令应答 }; @@ -78,6 +82,101 @@ public: */ void stopHeartbeat(); +signals: + /** + * @brief 触发检测信号 + */ + void triggerDetection(); + + /** + * @brief 单次检测请求信号 + * @param pClient 请求的客户端 + */ + void singleDetectionRequested(const TCPClient* pClient); + + /** + * @brief 单次图像请求信号 + * @param pClient 请求的客户端 + */ + void singleImageRequested(const TCPClient* pClient); + + /** + * @brief 开始持续工作请求信号 + */ + void startWorkRequested(); + + /** + * @brief 停止持续工作请求信号 + */ + void stopWorkRequested(); + + /** + * @brief 开始持续图像流请求信号 + */ + void startContinuousImageRequested(); + + /** + * @brief 停止持续图像流请求信号 + */ + void stopContinuousImageRequested(); + + /** + * @brief 设置曝光时间请求信号(同时设置左右相机) + * @param exposureTime 曝光时间 + */ + void setExposureTimeRequested(double exposureTime); + + /** + * @brief 设置增益请求信号(同时设置左右相机) + * @param gain 增益 + */ + void setGainRequested(double gain); + + /** + * @brief 设置左相机曝光时间请求信号 + * @param exposureTime 曝光时间 + */ + void setLeftExposureTimeRequested(double exposureTime); + + /** + * @brief 设置右相机曝光时间请求信号 + * @param exposureTime 曝光时间 + */ + void setRightExposureTimeRequested(double exposureTime); + + /** + * @brief 设置左相机增益请求信号 + * @param gain 增益 + */ + void setLeftGainRequested(double gain); + + /** + * @brief 设置右相机增益请求信号 + * @param gain 增益 + */ + void setRightGainRequested(double gain); + + /** + * @brief 获取相机信息请求信号 + * @param pClient 客户端指针 + * @param camera 相机标识("left"或"right") + */ + void getCameraInfoRequested(const TCPClient* pClient, const QString& camera); + + /** + * @brief 获取标定矩阵请求信号 + * @param pClient 客户端指针 + */ + void getCalibrationRequested(const TCPClient* pClient); + + /** + * @brief 设置标定矩阵请求信号 + * @param pClient 客户端指针 + * @param calibrationXml 标定矩阵XML内容 + */ + void setCalibrationRequested(const TCPClient* pClient, const QString& calibrationXml); + +public slots: /** * @brief 发送标记检测结果(持续工作模式) * @param marks 检测到的3D标记列表 @@ -112,45 +211,30 @@ public: */ void sendImageData(const TCPClient* pClient, const cv::Mat& leftImage, const cv::Mat& rightImage); -signals: /** - * @brief 触发检测信号 - */ - void triggerDetection(); - - /** - * @brief 单次检测请求信号 - * @param pClient 请求的客户端 - */ - void singleDetectionRequested(const TCPClient* pClient); - - /** - * @brief 单次图像请求信号 - * @param pClient 请求的客户端 - */ - void singleImageRequested(const TCPClient* pClient); - - /** - * @brief 开始持续工作请求信号 - */ - void startWorkRequested(); - - /** - * @brief 停止持续工作请求信号 - */ - void stopWorkRequested(); - - /** - * @brief 设置曝光时间请求信号 + * @brief 发送相机信息响应 + * @param pClient 客户端指针 + * @param camera 相机标识("left"或"right") + * @param serialNumber 序列号 + * @param modelName 型号 + * @param displayName 显示名称 * @param exposureTime 曝光时间 - */ - void setExposureTimeRequested(double exposureTime); - - /** - * @brief 设置增益请求信号 * @param gain 增益 */ - void setGainRequested(double gain); + void sendCameraInfoResponse(const TCPClient* pClient, + const QString& camera, + const QString& serialNumber, + const QString& modelName, + const QString& displayName, + double exposureTime, + double gain); + + /** + * @brief 发送标定矩阵响应 + * @param pClient 客户端指针 + * @param calibrationXml 标定矩阵XML字符串 + */ + void sendCalibrationMatrixResponse(const TCPClient* pClient, const QString& calibrationXml); private slots: /** @@ -249,6 +333,20 @@ private: */ void handleStopWorkCommand(const TCPClient* pClient, const QJsonObject& jsonObj); + /** + * @brief 处理开始持续图像流命令 + * @param pClient 客户端指针 + * @param jsonObj JSON对象 + */ + void handleStartContinuousImageCommand(const TCPClient* pClient, const QJsonObject& jsonObj); + + /** + * @brief 处理停止持续图像流命令 + * @param pClient 客户端指针 + * @param jsonObj JSON对象 + */ + void handleStopContinuousImageCommand(const TCPClient* pClient, const QJsonObject& jsonObj); + /** * @brief 处理设置标定矩阵命令 * @param pClient 客户端指针 @@ -256,6 +354,13 @@ private: */ void handleSetCalibrationCommand(const TCPClient* pClient, const QJsonObject& jsonObj); + /** + * @brief 处理获取标定矩阵命令 + * @param pClient 客户端指针 + * @param jsonObj JSON对象 + */ + void handleGetCalibrationCommand(const TCPClient* pClient, const QJsonObject& jsonObj); + /** * @brief 处理设置曝光时间命令 * @param pClient 客户端指针 @@ -270,6 +375,13 @@ private: */ void handleSetGainCommand(const TCPClient* pClient, const QJsonObject& jsonObj); + /** + * @brief 处理获取相机信息命令 + * @param pClient 客户端指针 + * @param jsonObj JSON对象 + */ + void handleGetCameraInfoCommand(const TCPClient* pClient, const QJsonObject& jsonObj); + /** * @brief 发送心跳应答 * @param pClient 客户端指针 @@ -306,6 +418,7 @@ private: QTimer* m_pHeartbeatTimer; // 心跳定时器 int m_nHeartbeatInterval; // 心跳间隔(秒) quint16 m_nTcpPort; // TCP服务器端口 + std::atomic m_bIsProcessingFrame; // 是否正在处理帧(用于丢帧) // 客户端数据缓冲区(用于处理粘包) QMap m_clientBuffers; // 客户端ID -> 数据缓冲区 diff --git a/App/BinocularMark/BinocularMarkApp/Version.h b/App/BinocularMark/BinocularMarkApp/Version.h index 78478f6..4e3edfb 100644 --- a/App/BinocularMark/BinocularMarkApp/Version.h +++ b/App/BinocularMark/BinocularMarkApp/Version.h @@ -8,7 +8,7 @@ #define BINOCULAR_MARK_COMPANY_NAME "VisionTech" #define BINOCULAR_MARK_COPYRIGHT "Copyright (C) 2025" #define BINOCULAR_MARK_VERSION_STRING "1.0.0" -#define BINOCULAR_MARK_VERSION_BUILD "1" +#define BINOCULAR_MARK_VERSION_BUILD "3" #endif // VERSION_H diff --git a/App/BinocularMark/BinocularMarkApp/config/config.xml b/App/BinocularMark/BinocularMarkApp/config/config.xml index 17ceb5f..5d93fcf 100644 --- a/App/BinocularMark/BinocularMarkApp/config/config.xml +++ b/App/BinocularMark/BinocularMarkApp/config/config.xml @@ -3,10 +3,20 @@ - + - - + +