GrabBag/App/TunnelChannel/TunnelChannelApp/Presenter/Src/TunnelChannelPresenter.cpp

932 lines
33 KiB
C++
Raw Normal View History

#include "TunnelChannelPresenter.h"
#include "VrLog.h"
#include "VrTimeUtils.h"
#include "VrError.h"
#include "PointCloudImageUtils.h"
#include "PathManager.h"
#include <QCoreApplication>
#include <QDir>
#include <QFileInfo>
#include <algorithm>
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<DeviceInfo> 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<int>(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<std::pair<EVzResultDataType, SVzLaserLineData>>& 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<std::vector<SVzNL3DPosition>> 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<IYTunnelChannelStatus>();
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<IYTunnelChannelStatus>();
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<IYTunnelChannelStatus>();
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<IYTunnelChannelStatus>();
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<std::mutex> 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<std::chrono::milliseconds>(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<std::mutex> 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<int>(status));
// 通过信号转发到主线程处理(跨线程安全)
emit sigHikStatusChanged(static_cast<int>(status));
}
void TunnelChannelPresenter::OnHikExceptionReceived(EHikExceptionType exceptionType)
{
LOG_WARN("TunnelChannelPresenter::OnHikExceptionReceived() - Exception: %d\n",
static_cast<int>(exceptionType));
// 通过信号转发到主线程处理(跨线程安全)
emit sigHikException(static_cast<int>(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<IYTunnelChannelStatus>();
if (pStatusCallback) {
pStatusCallback->OnCamera2StatusChanged(isConnected);
}
}
void TunnelChannelPresenter::NotifyRobotConnectionStatus(bool isConnected)
{
auto* pStatusCallback = GetStatusCallback<IYTunnelChannelStatus>();
if (pStatusCallback) {
pStatusCallback->OnRobotConnectionChanged(isConnected);
}
}
void TunnelChannelPresenter::NotifySerialConnectionStatus(bool isConnected)
{
auto* pStatusCallback = GetStatusCallback<IYTunnelChannelStatus>();
if (pStatusCallback) {
pStatusCallback->OnSerialConnectionChanged(isConnected);
}
}
// ============ 相机调平接口实现 ============
bool TunnelChannelPresenter::CalculatePlaneCalibration(
const std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& 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<IYTunnelChannelStatus>();
if (pStatusCallback) {
pStatusCallback->OnHikImageUpdated(image);
}
}
void TunnelChannelPresenter::onHikStatusChangedInMainThread(int status)
{
EHikDeviceStatus hikStatus = static_cast<EHikDeviceStatus>(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<EHikExceptionType>(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;
}