#include "TunnelChannelPresenter.h" #include "VrLog.h" #include "VrTimeUtils.h" #include "VrError.h" #include "PointCloudImageUtils.h" #include "PathManager.h" #include #include #include #include TunnelChannelPresenter::TunnelChannelPresenter(QObject *parent) : BasePresenter(parent) , m_pConfigManager(nullptr) , m_pHikDevice(nullptr) , m_bHikConnected(false) , m_pHikReconnectTimer(nullptr) , m_pDetectPresenter(nullptr) { // 连接跨线程信号槽(使用QueuedConnection确保在主线程执行) connect(this, &TunnelChannelPresenter::sigHikImageUpdated, this, &TunnelChannelPresenter::onHikImageUpdatedInMainThread, Qt::QueuedConnection); connect(this, &TunnelChannelPresenter::sigHikStatusChanged, this, &TunnelChannelPresenter::onHikStatusChangedInMainThread, Qt::QueuedConnection); connect(this, &TunnelChannelPresenter::sigHikException, this, &TunnelChannelPresenter::onHikExceptionInMainThread, Qt::QueuedConnection); LOG_INFO("TunnelChannelPresenter created\n"); } TunnelChannelPresenter::~TunnelChannelPresenter() { // 停止海康预览并清理设备 if (m_pHikDevice) { m_pHikDevice->StopPreview(); m_pHikDevice->Logout(); m_pHikDevice->CleanupSDK(); delete m_pHikDevice; m_pHikDevice = nullptr; } // 停止海康重连定时器 if (m_pHikReconnectTimer) { m_pHikReconnectTimer->stop(); delete m_pHikReconnectTimer; m_pHikReconnectTimer = nullptr; } // 清理配置管理器 if (m_pConfigManager) { delete m_pConfigManager; m_pConfigManager = nullptr; } // 清理检测处理器 if (m_pDetectPresenter) { delete m_pDetectPresenter; m_pDetectPresenter = nullptr; } LOG_INFO("TunnelChannelPresenter destroyed\n"); } int TunnelChannelPresenter::InitApp() { LOG_INFO("TunnelChannelPresenter::InitApp() - Starting initialization\n"); // 1. 初始化配置管理器 m_pConfigManager = new ConfigManager(); if (!m_pConfigManager->Initialize()) { LOG_ERR("Failed to initialize ConfigManager\n"); return ERR_CODE(DEV_OPEN_ERR); } // 设置配置变化通知 m_pConfigManager->SetConfigChangeNotify(this); // 2. 初始化海康设备 int hikResult = InitHikDevice(); if (hikResult != 0) { LOG_WARN("Failed to initialize HikDevice, error: %d. Hik camera will not be available.\n", hikResult); // 海康SDK初始化失败不阻止程序运行 } // 3. 初始化3D相机(使用BasePresenter的InitCamera) ConfigResult config = m_pConfigManager->GetCurrentConfig(); std::vector cameraList = config.cameraList; // 设置调试参数到基类 SetDebugParam(config.debugParam); // 如果没有配置相机,添加一个空配置让设备自动搜索第一个相机 if (cameraList.empty()) { DeviceInfo autoSearchCamera; autoSearchCamera.name = "相机"; autoSearchCamera.ip = ""; // 空IP会触发自动搜索 cameraList.push_back(autoSearchCamera); LOG_INFO("No camera configured, will auto search first available camera\n"); } // 不需要RGB,需要摆动 int cameraResult = InitCamera(cameraList, false, true); if (cameraResult != 0) { LOG_WARN("Failed to initialize 3D cameras, error: %d\n", cameraResult); } // 通知相机数量 OnCameraCountChanged(static_cast(cameraList.size())); // 4. 初始化海康相机 if (m_pHikDevice) { HikCameraConfig hikConfig; bool hasHikConfig = false; if (!config.hikCameraList.empty() && !config.hikCameraList[0].ip.empty()) { // 使用配置文件中的海康相机配置 hikConfig = config.hikCameraList[0]; hasHikConfig = hikConfig.enabled; LOG_INFO("Using configured Hik camera: %s\n", hikConfig.ip.c_str()); } else { // 没有配置IP,提示用户配置 LOG_WARN("No Hik camera IP configured\n"); OnStatusUpdate("未配置2D相机IP"); } int connectResult = ConnectHikCamera(hikConfig); if (connectResult == 0) { m_bHikConnected = true; NotifyHikCameraStatus(true); LOG_INFO("Hik camera connected successfully\n"); // 关闭OSD显示(通道名称和时间) int osdResult = m_pHikDevice->ConfigureOSD(false, false); if (osdResult != 0) { LOG_WARN("Failed to configure OSD, error: %d\n", osdResult); } // 开始实时预览(根据是否设置窗口句柄选择渲染方式) if (m_hHikDisplayWnd) { StartHikPreviewEx(m_hHikDisplayWnd); // 硬件渲染,低延迟 } else { StartHikPreview(); // 软件解码回调 } } else { LOG_WARN("Failed to connect Hik camera, will retry later\n"); NotifyHikCameraStatus(false); m_bHikConnected = false; } } // 5. 创建海康相机重连定时器 m_pHikReconnectTimer = new QTimer(this); m_pHikReconnectTimer->setInterval(5000); // 5秒重试一次 connect(m_pHikReconnectTimer, &QTimer::timeout, this, &TunnelChannelPresenter::OnHikReconnectTimer); // 如果海康相机未连接,启动重连定时器 if (m_pHikDevice && !m_bHikConnected) { m_pHikReconnectTimer->start(); LOG_INFO("Hik camera reconnect timer started (interval: 5000ms)\n"); } // 6. 更新工作状态 CheckAndUpdateWorkStatus(); LOG_INFO("TunnelChannelPresenter::InitApp() - Initialization completed\n"); return SUCCESS; } int TunnelChannelPresenter::InitAlgoParams() { LOG_INFO("TunnelChannelPresenter::InitAlgoParams()\n"); // 获取算法版本号 const char* algoVersion = wd_ChannelSPaceMeasureVersion(); LOG_INFO("ChannelSpaceMeasure algorithm version: %s\n", algoVersion ? algoVersion : "unknown"); // 尝试从配置文件加载算法参数 if (m_pConfigManager) { ConfigResult config = m_pConfigManager->GetCurrentConfig(); const ChannelSpaceAlgoParam& param = config.algorithmParams.channelSpaceParam; // 角点检测参数 m_cornerParam.minEndingGap = param.minEndingGap; m_cornerParam.minEndingGap_z = param.minEndingGap_z; m_cornerParam.scale = param.scale; m_cornerParam.cornerTh = param.cornerTh; m_cornerParam.jumpCornerTh_1 = param.jumpCornerTh_1; m_cornerParam.jumpCornerTh_2 = param.jumpCornerTh_2; // 离群点过滤参数 m_outlierFilterParam.continuityTh = param.continuityTh; m_outlierFilterParam.outlierTh = param.outlierTh; // 树生长参数 m_treeGrowParam.yDeviation_max = param.yDeviationMax; m_treeGrowParam.zDeviation_max = param.zDeviationMax; m_treeGrowParam.maxLineSkipNum = param.maxLineSkipNum; m_treeGrowParam.maxSkipDistance = param.maxSkipDistance; m_treeGrowParam.minLTypeTreeLen = param.minLTypeTreeLen; m_treeGrowParam.minVTypeTreeLen = param.minVTypeTreeLen; // 通道参数 m_channelParam.channelWidthRng.min = param.channelWidthMin; m_channelParam.channelWidthRng.max = param.channelWidthMax; m_channelParam.channleSpaceRng.min = param.channelSpaceMin; m_channelParam.channleSpaceRng.max = param.channelSpaceMax; // 扫描方向 m_bHorizonScan = param.horizonScan; LOG_INFO("Algorithm parameters loaded from config file\n"); } else { // 使用默认值(参考 HC_chanelSpaceMeasure_test.cpp) m_cornerParam.minEndingGap = 20.0; m_cornerParam.minEndingGap_z = 50.0; m_cornerParam.scale = 15.0; m_cornerParam.cornerTh = 45.0; m_cornerParam.jumpCornerTh_1 = 15.0; m_cornerParam.jumpCornerTh_2 = 60.0; m_outlierFilterParam.continuityTh = 20.0; m_outlierFilterParam.outlierTh = 5.0; m_treeGrowParam.yDeviation_max = 20.0; m_treeGrowParam.zDeviation_max = 50.0; m_treeGrowParam.maxLineSkipNum = 10; m_treeGrowParam.maxSkipDistance = 20.0; m_treeGrowParam.minLTypeTreeLen = 100.0; m_treeGrowParam.minVTypeTreeLen = 100.0; m_channelParam.channelWidthRng.min = 5.0; m_channelParam.channelWidthRng.max = 30.0; m_channelParam.channleSpaceRng.min = 300.0; m_channelParam.channleSpaceRng.max = 800.0; m_bHorizonScan = true; LOG_INFO("Using default algorithm parameters\n"); } LOG_INFO("TunnelChannelPresenter::InitAlgoParams() - Algorithm parameters initialized\n"); return SUCCESS; } int TunnelChannelPresenter::ProcessAlgoDetection(std::vector>& detectionDataCache) { LOG_INFO("TunnelChannelPresenter::ProcessAlgoDetection() - Processing %zu data items\n", detectionDataCache.size()); if (detectionDataCache.empty()) { LOG_WARN("Detection data cache is empty\n"); return ERR_CODE(DEV_RESULT_EMPTY); } // 创建检测结果 TunnelDetectionResult result; result.cameraIndex = m_currentCameraIndex; result.errorCode = 0; // 0表示成功 // ========== 使用 CloudUtils 转换点云数据为算法输入格式 ========== std::vector> scanLines; LaserDataLoader loader; int convertResult = loader.ConvertToSVzNL3DPosition(detectionDataCache, scanLines); if (convertResult != SUCCESS) { LOG_WARN("Failed to convert point cloud data, error: %d\n", convertResult); } LOG_INFO("Converted %zu scan lines for algorithm processing\n", scanLines.size()); // ========== 打印算法参数 ========== LOG_INFO("========== Algorithm Parameters ==========\n"); LOG_INFO("[CornerParam] minEndingGap=%.2f, minEndingGap_z=%.2f, scale=%.2f\n", m_cornerParam.minEndingGap, m_cornerParam.minEndingGap_z, m_cornerParam.scale); LOG_INFO("[CornerParam] cornerTh=%.2f, jumpCornerTh_1=%.2f, jumpCornerTh_2=%.2f\n", m_cornerParam.cornerTh, m_cornerParam.jumpCornerTh_1, m_cornerParam.jumpCornerTh_2); LOG_INFO("[FilterParam] continuityTh=%.2f, outlierTh=%.2f\n", m_outlierFilterParam.continuityTh, m_outlierFilterParam.outlierTh); LOG_INFO("[TreeGrowParam] yDeviation_max=%.2f, zDeviation_max=%.2f, maxLineSkipNum=%d\n", m_treeGrowParam.yDeviation_max, m_treeGrowParam.zDeviation_max, m_treeGrowParam.maxLineSkipNum); LOG_INFO("[TreeGrowParam] maxSkipDistance=%.2f, minLTypeTreeLen=%.2f, minVTypeTreeLen=%.2f\n", m_treeGrowParam.maxSkipDistance, m_treeGrowParam.minLTypeTreeLen, m_treeGrowParam.minVTypeTreeLen); LOG_INFO("[ChannelParam] channelWidthRng=[%.2f, %.2f], channleSpaceRng=[%.2f, %.2f]\n", m_channelParam.channelWidthRng.min, m_channelParam.channelWidthRng.max, m_channelParam.channleSpaceRng.min, m_channelParam.channleSpaceRng.max); LOG_INFO("[ScanDirection] isHorizonScan=%s\n", m_bHorizonScan ? "true" : "false"); LOG_INFO("===========================================\n"); // ========== 调用通道间距测量算法 ========== int errCode = 0; SSX_channelInfo channelInfo = {0}; if (!scanLines.empty()) { channelInfo = sx_channelSpaceMeasure( scanLines, m_bHorizonScan, m_cornerParam, m_outlierFilterParam, m_treeGrowParam, m_channelParam, &errCode ); if (errCode != 0) { LOG_WARN("Channel space measure algorithm returned error code: %d\n", errCode); result.errorCode = errCode; result.detectionType = 1; // 异常 result.alarmMessage = "算法检测失败,错误码:" + std::to_string(errCode); } else { LOG_INFO("Channel space measure result: space=%.2f, width[0]=%.2f, width[1]=%.2f, depth[0]=%.2f, depth[1]=%.2f\n", channelInfo.channelSpace, channelInfo.channelWidth[0], channelInfo.channelWidth[1], channelInfo.channelDepth[0], channelInfo.channelDepth[1]); // 填充检测结果 result.channelSpace = channelInfo.channelSpace; result.channelWidth[0] = channelInfo.channelWidth[0]; result.channelWidth[1] = channelInfo.channelWidth[1]; result.channelDepth[0] = channelInfo.channelDepth[0]; result.channelDepth[1] = channelInfo.channelDepth[1]; result.detectionType = 0; // 正常 result.confidence = 1.0; // 置信度100% } } else { LOG_WARN("No valid scan lines for algorithm\n"); result.errorCode = ERR_CODE(DEV_RESULT_EMPTY); result.detectionType = 1; result.alarmMessage = "无有效扫描数据"; } // ========== 生成特征标记图像(使用算法处理后的scanLines) ========== // 算法处理后,scanLines中的nPointIdx被修改为特征标记 // 使用 GenerateChannelSpaceImage 根据 nPointIdx 着色 QImage channelImage; if (!scanLines.empty()) { channelImage = PointCloudImageUtils::GenerateChannelSpaceImage(scanLines); if (!channelImage.isNull()) { result.image = channelImage; LOG_INFO("Generated channel space image with feature markers\n"); } else { LOG_WARN("Failed to generate channel space image\n"); } } else { // 如果没有scanLines,使用深度图作为备用 QImage depthImage; int imageResult = PointCloudImageUtils::GenerateDepthImage(detectionDataCache, depthImage); if (imageResult == 0 && !depthImage.isNull()) { result.image = depthImage; } else { LOG_WARN("Failed to generate depth image\n"); } } // 获取海康相机当前帧 result.hikImage = GetCurrentHikFrame(); // 通知UI更新检测结果 auto* pStatusCallback = GetStatusCallback(); if (pStatusCallback) { pStatusCallback->OnDetectionResult(result); } LOG_INFO("TunnelChannelPresenter::ProcessAlgoDetection() - Detection completed\n"); return SUCCESS; } void TunnelChannelPresenter::OnCameraStatusChanged(int cameraIndex, bool isConnected) { LOG_INFO("TunnelChannelPresenter::OnCameraStatusChanged() - Camera %d: %s\n", cameraIndex, isConnected ? "Connected" : "Disconnected"); auto* pStatusCallback = GetStatusCallback(); if (pStatusCallback) { if (cameraIndex == 1) { pStatusCallback->OnCamera1StatusChanged(isConnected); } else if (cameraIndex == 2) { pStatusCallback->OnCamera2StatusChanged(isConnected); } } CheckAndUpdateWorkStatus(); } void TunnelChannelPresenter::OnWorkStatusChanged(WorkStatus status) { LOG_INFO("TunnelChannelPresenter::OnWorkStatusChanged() - Status: %s\n", WorkStatusToString(status).c_str()); auto* pStatusCallback = GetStatusCallback(); if (pStatusCallback) { pStatusCallback->OnWorkStatusChanged(status); } } void TunnelChannelPresenter::OnCameraCountChanged(int count) { LOG_INFO("TunnelChannelPresenter::OnCameraCountChanged() - Count: %d\n", count); } void TunnelChannelPresenter::OnStatusUpdate(const std::string& statusMessage) { auto* pStatusCallback = GetStatusCallback(); if (pStatusCallback) { pStatusCallback->OnStatusUpdate(statusMessage); } } void TunnelChannelPresenter::OnConfigChanged(const ConfigResult& configResult) { LOG_INFO("TunnelChannelPresenter::OnConfigChanged()\n"); // 更新基类调试参数 SetDebugParam(configResult.debugParam); } // ============ 海康相机相关实现 ============ int TunnelChannelPresenter::InitHikDevice() { LOG_INFO("TunnelChannelPresenter::InitHikDevice()\n"); // 创建海康设备对象 int result = IHikDevice::CreateObject(&m_pHikDevice); if (result != 0 || !m_pHikDevice) { LOG_ERR("Failed to create HikDevice object\n"); return ERR_CODE(DATA_ERR_MEM); } // 初始化海康SDK result = m_pHikDevice->InitSDK(); if (result != 0) { LOG_ERR("Failed to initialize HikDevice SDK, error: %d\n", result); delete m_pHikDevice; m_pHikDevice = nullptr; return result; } // 设置帧数据回调 m_pHikDevice->SetDecodedFrameCallback( [this](unsigned char* pRGBData, int dataSize, const HikFrameInfo& frameInfo, void* pUser) { this->OnHikFrameReceived(pRGBData, dataSize, frameInfo); }, this ); // 设置状态回调 m_pHikDevice->SetStatusCallback( [this](EHikDeviceStatus status, void* pUser) { this->OnHikDeviceStatusChanged(status); }, this ); // 设置异常回调 m_pHikDevice->SetExceptionCallback( [this](EHikExceptionType exceptionType, void* pUser) { this->OnHikExceptionReceived(exceptionType); }, this ); LOG_INFO("TunnelChannelPresenter::InitHikDevice() - HikDevice initialized successfully\n"); return SUCCESS; } int TunnelChannelPresenter::ConnectHikCamera(const HikCameraConfig& config) { LOG_INFO("TunnelChannelPresenter::ConnectHikCamera() - IP: %s, Port: %d\n", config.ip.c_str(), config.port); if (!m_pHikDevice) { LOG_ERR("HikDevice not initialized\n"); return ERR_CODE(DEV_NO_OPEN); } // 转换配置 HikDeviceConfig deviceConfig; deviceConfig.ip = config.ip; deviceConfig.port = config.port; deviceConfig.username = config.username; deviceConfig.password = config.password; deviceConfig.channelNo = config.channelNo; deviceConfig.streamType = config.streamType; deviceConfig.enabled = config.enabled; // 登录相机 int result = m_pHikDevice->Login(deviceConfig); if (result != 0) { if (result == ERR_CODE(DEV_UNACTIVATE)) { LOG_ERR("2D相机未激活,请先激活设备后再连接 (IP: %s)\n", config.ip.c_str()); OnStatusUpdate("2D相机未激活,请先激活设备"); } else { LOG_ERR("Failed to login Hik camera, error: %d \n", result); } return result; } m_bHikConnected = true; LOG_INFO("TunnelChannelPresenter::ConnectHikCamera() - Connected successfully\n"); return SUCCESS; } void TunnelChannelPresenter::DisconnectHikCamera() { if (m_pHikDevice) { m_pHikDevice->StopPreview(); m_pHikDevice->Logout(); m_bHikConnected = false; LOG_INFO("TunnelChannelPresenter::DisconnectHikCamera() - Disconnected\n"); } } int TunnelChannelPresenter::StartHikPreview() { LOG_INFO("TunnelChannelPresenter::StartHikPreview()\n"); if (!m_pHikDevice) { LOG_ERR("HikDevice not initialized\n"); return ERR_CODE(DEV_NO_OPEN); } if (!m_pHikDevice->IsLoggedIn()) { LOG_ERR("Hik camera not logged in\n"); return ERR_CODE(DEV_NO_OPEN); } int result = m_pHikDevice->StartPreview(); if (result != 0) { LOG_ERR("Failed to start Hik preview, error: %d\n", result); return result; } LOG_INFO("TunnelChannelPresenter::StartHikPreview() - Preview started\n"); return SUCCESS; } int TunnelChannelPresenter::StartHikPreviewEx(void* hWnd) { LOG_INFO("TunnelChannelPresenter::StartHikPreviewEx() - hWnd: %p\n", hWnd); if (!m_pHikDevice) { LOG_ERR("HikDevice not initialized\n"); return ERR_CODE(DEV_NO_OPEN); } if (!m_pHikDevice->IsLoggedIn()) { LOG_ERR("Hik camera not logged in\n"); return ERR_CODE(DEV_NO_OPEN); } int result = m_pHikDevice->StartPreviewEx(hWnd); if (result != 0) { LOG_ERR("Failed to start Hik preview (hardware), error: %d\n", result); return result; } LOG_INFO("TunnelChannelPresenter::StartHikPreviewEx() - Hardware preview started\n"); return SUCCESS; } void TunnelChannelPresenter::StopHikPreview() { if (m_pHikDevice) { m_pHikDevice->StopPreview(); LOG_INFO("TunnelChannelPresenter::StopHikPreview() - Preview stopped\n"); } } QImage TunnelChannelPresenter::GetCurrentHikFrame() const { std::lock_guard lock(m_hikFrameMutex); return m_currentHikFrame; } bool TunnelChannelPresenter::IsHikCameraConnected() const { return m_bHikConnected && m_pHikDevice && m_pHikDevice->IsLoggedIn(); } // ============ 海康相机回调处理 ============ void TunnelChannelPresenter::OnHikFrameReceived(unsigned char* pRGBData, int dataSize, const HikFrameInfo& frameInfo) { if (!pRGBData || dataSize <= 0) { return; } // 帧率限制:避免过度刷新UI auto now = std::chrono::steady_clock::now(); auto elapsed = std::chrono::duration_cast(now - m_lastHikFrameTime).count(); if (elapsed < HIK_FRAME_INTERVAL_MS) { return; // 跳过此帧 } m_lastHikFrameTime = now; int width = frameInfo.width; int height = frameInfo.height; int bytesPerLine = width * 4; // RGB32 格式: 每像素4字节 // 优化: 使用外部数据构造临时QImage,然后一次性拷贝 // 避免多次memcpy,只进行一次深拷贝 QImage tempImage(pRGBData, width, height, bytesPerLine, QImage::Format_RGB32); QImage image = tempImage.copy(); // 只进行一次深拷贝 // 更新当前帧(使用移动语义,避免不必要的拷贝) { std::lock_guard lock(m_hikFrameMutex); m_currentHikFrame = std::move(image); } // 通过信号通知UI更新(QImage隐式共享,不会实际拷贝) emit sigHikImageUpdated(m_currentHikFrame); } void TunnelChannelPresenter::OnHikDeviceStatusChanged(EHikDeviceStatus status) { LOG_INFO("TunnelChannelPresenter::OnHikDeviceStatusChanged() - Status: %d\n", static_cast(status)); // 通过信号转发到主线程处理(跨线程安全) emit sigHikStatusChanged(static_cast(status)); } void TunnelChannelPresenter::OnHikExceptionReceived(EHikExceptionType exceptionType) { LOG_WARN("TunnelChannelPresenter::OnHikExceptionReceived() - Exception: %d\n", static_cast(exceptionType)); // 通过信号转发到主线程处理(跨线程安全) emit sigHikException(static_cast(exceptionType)); } void TunnelChannelPresenter::OnHikReconnectTimer() { if (m_bHikConnected) { return; // 已连接,不需要重连 } LOG_INFO("TunnelChannelPresenter::OnHikReconnectTimer() - Attempting to reconnect Hik camera\n"); ConfigResult config = m_pConfigManager->GetCurrentConfig(); if (config.hikCameraList.empty()) { return; } const HikCameraConfig& hikConfig = config.hikCameraList[0]; if (!hikConfig.enabled) { return; } // 尝试重新连接 int result = ConnectHikCamera(hikConfig); if (result == 0) { m_bHikConnected = true; NotifyHikCameraStatus(true); m_pHikReconnectTimer->stop(); // 关闭OSD显示(通道名称和时间) m_pHikDevice->ConfigureOSD(false, false); // 重新开始预览(根据是否设置窗口句柄选择渲染方式) if (m_hHikDisplayWnd) { StartHikPreviewEx(m_hHikDisplayWnd); } else { StartHikPreview(); } } } void TunnelChannelPresenter::CheckAndUpdateWorkStatus() { bool hasCameraConnected = m_bCameraConnected || m_bHikConnected; if (hasCameraConnected) { SetWorkStatus(WorkStatus::Ready); } else { // 如果没有任何相机连接,保持初始化状态 if (GetCurrentWorkStatus() != WorkStatus::InitIng) { SetWorkStatus(WorkStatus::Ready); } } } void TunnelChannelPresenter::NotifyHikCameraStatus(bool isConnected) { // 2D海康相机使用相机2的状态更新接口 auto* pStatusCallback = GetStatusCallback(); if (pStatusCallback) { pStatusCallback->OnCamera2StatusChanged(isConnected); } } void TunnelChannelPresenter::NotifyRobotConnectionStatus(bool isConnected) { auto* pStatusCallback = GetStatusCallback(); if (pStatusCallback) { pStatusCallback->OnRobotConnectionChanged(isConnected); } } void TunnelChannelPresenter::NotifySerialConnectionStatus(bool isConnected) { auto* pStatusCallback = GetStatusCallback(); if (pStatusCallback) { pStatusCallback->OnSerialConnectionChanged(isConnected); } } // ============ 相机调平接口实现 ============ bool TunnelChannelPresenter::CalculatePlaneCalibration( const std::vector>& scanData, double planeCalib[9], double& planeHeight, double invRMatrix[9]) { // TODO: 实现平面调平计算 // 暂时返回单位矩阵 for (int i = 0; i < 9; i++) { planeCalib[i] = (i % 4 == 0) ? 1.0 : 0.0; invRMatrix[i] = (i % 4 == 0) ? 1.0 : 0.0; } planeHeight = 0.0; return true; } bool TunnelChannelPresenter::SaveLevelingResults( double planeCalib[9], double planeHeight, double invRMatrix[9], int cameraIndex, const QString& cameraName) { // TODO: 保存调平结果到配置文件 LOG_INFO("SaveLevelingResults for camera %d (%s)\n", cameraIndex, cameraName.toStdString().c_str()); return true; } bool TunnelChannelPresenter::LoadLevelingResults( int cameraIndex, const QString& cameraName, double planeCalib[9], double& planeHeight, double invRMatrix[9]) { // TODO: 从配置文件加载调平结果 LOG_INFO("LoadLevelingResults for camera %d (%s)\n", cameraIndex, cameraName.toStdString().c_str()); // 默认返回单位矩阵 for (int i = 0; i < 9; i++) { planeCalib[i] = (i % 4 == 0) ? 1.0 : 0.0; invRMatrix[i] = (i % 4 == 0) ? 1.0 : 0.0; } planeHeight = 0.0; return true; } // ============ 跨线程槽函数实现(在主线程执行) ============ void TunnelChannelPresenter::onHikImageUpdatedInMainThread(const QImage& image) { // 通知UI更新(在主线程中安全调用) auto* pStatusCallback = GetStatusCallback(); if (pStatusCallback) { pStatusCallback->OnHikImageUpdated(image); } } void TunnelChannelPresenter::onHikStatusChangedInMainThread(int status) { EHikDeviceStatus hikStatus = static_cast(status); switch (hikStatus) { case EHikDeviceStatus::Connected: m_bHikConnected = true; NotifyHikCameraStatus(true); break; case EHikDeviceStatus::Disconnected: case EHikDeviceStatus::Error: m_bHikConnected = false; NotifyHikCameraStatus(false); // 启动重连定时器(在主线程中直接操作QTimer) if (m_pHikReconnectTimer && !m_pHikReconnectTimer->isActive()) { m_pHikReconnectTimer->start(); LOG_INFO("Hik camera reconnect timer started due to status change\n"); } break; case EHikDeviceStatus::Reconnecting: LOG_INFO("Hik camera is reconnecting...\n"); break; case EHikDeviceStatus::Previewing: LOG_INFO("Hik camera is previewing\n"); break; } CheckAndUpdateWorkStatus(); } void TunnelChannelPresenter::onHikExceptionInMainThread(int exceptionType) { EHikExceptionType hikException = static_cast(exceptionType); switch (hikException) { case EHikExceptionType::PreviewException: // 预览异常,停止预览 StopHikPreview(); m_bHikConnected = false; NotifyHikCameraStatus(false); // 启动重连定时器(在主线程中直接操作QTimer) if (m_pHikReconnectTimer && !m_pHikReconnectTimer->isActive()) { m_pHikReconnectTimer->start(); LOG_INFO("Hik camera reconnect timer started due to exception\n"); } break; case EHikExceptionType::Reconnect: LOG_INFO("Hik camera preview reconnecting...\n"); break; default: break; } } // ============ 算法参数配置接口实现 ============ void TunnelChannelPresenter::GetAlgoParams(SSG_cornerParam& cornerParam, SSG_outlierFilterParam& filterParam, SSG_treeGrowParam& treeParam, SSX_channelParam& channelParam, bool& horizonScan) const { cornerParam = m_cornerParam; filterParam = m_outlierFilterParam; treeParam = m_treeGrowParam; channelParam = m_channelParam; horizonScan = m_bHorizonScan; } void TunnelChannelPresenter::SetAlgoParams(const SSG_cornerParam& cornerParam, const SSG_outlierFilterParam& filterParam, const SSG_treeGrowParam& treeParam, const SSX_channelParam& channelParam, bool horizonScan) { m_cornerParam = cornerParam; m_outlierFilterParam = filterParam; m_treeGrowParam = treeParam; m_channelParam = channelParam; m_bHorizonScan = horizonScan; LOG_INFO("Algorithm parameters updated:\n"); LOG_INFO(" Corner: minEndingGap=%.2f, minEndingGap_z=%.2f, scale=%.2f, cornerTh=%.2f\n", m_cornerParam.minEndingGap, m_cornerParam.minEndingGap_z, m_cornerParam.scale, m_cornerParam.cornerTh); LOG_INFO(" Filter: continuityTh=%.2f, outlierTh=%.2f\n", m_outlierFilterParam.continuityTh, m_outlierFilterParam.outlierTh); LOG_INFO(" Tree: yDeviation_max=%.2f, zDeviation_max=%.2f, maxLineSkipNum=%d\n", m_treeGrowParam.yDeviation_max, m_treeGrowParam.zDeviation_max, m_treeGrowParam.maxLineSkipNum); LOG_INFO(" Channel: widthRng=[%.2f, %.2f], spaceRng=[%.2f, %.2f]\n", m_channelParam.channelWidthRng.min, m_channelParam.channelWidthRng.max, m_channelParam.channleSpaceRng.min, m_channelParam.channleSpaceRng.max); LOG_INFO(" HorizonScan: %s\n", m_bHorizonScan ? "true" : "false"); } bool TunnelChannelPresenter::SaveAlgoParamsToConfig() { if (!m_pConfigManager) { LOG_ERR("ConfigManager not initialized\n"); return false; } // 获取当前配置 ConfigResult config = m_pConfigManager->GetCurrentConfig(); // 更新通道间距算法参数 ChannelSpaceAlgoParam& param = config.algorithmParams.channelSpaceParam; // 角点检测参数 param.minEndingGap = m_cornerParam.minEndingGap; param.minEndingGap_z = m_cornerParam.minEndingGap_z; param.scale = m_cornerParam.scale; param.cornerTh = m_cornerParam.cornerTh; param.jumpCornerTh_1 = m_cornerParam.jumpCornerTh_1; param.jumpCornerTh_2 = m_cornerParam.jumpCornerTh_2; // 离群点过滤参数 param.continuityTh = m_outlierFilterParam.continuityTh; param.outlierTh = m_outlierFilterParam.outlierTh; // 树生长参数 param.yDeviationMax = m_treeGrowParam.yDeviation_max; param.zDeviationMax = m_treeGrowParam.zDeviation_max; param.maxLineSkipNum = m_treeGrowParam.maxLineSkipNum; param.maxSkipDistance = m_treeGrowParam.maxSkipDistance; param.minLTypeTreeLen = m_treeGrowParam.minLTypeTreeLen; param.minVTypeTreeLen = m_treeGrowParam.minVTypeTreeLen; // 通道参数 param.channelWidthMin = m_channelParam.channelWidthRng.min; param.channelWidthMax = m_channelParam.channelWidthRng.max; param.channelSpaceMin = m_channelParam.channleSpaceRng.min; param.channelSpaceMax = m_channelParam.channleSpaceRng.max; // 扫描方向 param.horizonScan = m_bHorizonScan; // 更新配置管理器的算法参数 m_pConfigManager->UpdateAlgorithmParams(config.algorithmParams); // 使用ConfigManager保存的配置文件路径 std::string configPath = m_pConfigManager->GetConfigFilePath(); if (configPath.empty()) { // 如果配置管理器没有路径,使用PathManager获取 configPath = PathManager::GetInstance().GetConfigFilePath().toStdString(); } bool result = m_pConfigManager->SaveConfigToFile(configPath); if (result) { LOG_INFO("Algorithm parameters saved to config file: %s\n", configPath.c_str()); } else { LOG_ERR("Failed to save algorithm parameters to config file: %s\n", configPath.c_str()); } return result; }