932 lines
33 KiB
C++
932 lines
33 KiB
C++
#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;
|
||
}
|