#include "VizumCamera.h" #include "utils/Logger.h" #include #include #include #include #include #ifdef ENABLE_VIZUM_CAMERA #include "VZNL_Common.h" #include "VZNL_Types.h" #include "VZNL_DetectLaser.h" #include "VZNL_DetectConfig.h" #include "VZNL_EyeConfig.h" #include "VZNL_SwingMotor.h" #endif /** * @file VizumCamera.cpp * @brief 伟景智能 Vizum 结构光相机封装实现 */ namespace StructuredLight { #ifdef ENABLE_VIZUM_CAMERA struct VizumCameraCallbackHooks { static void AutoOutputLaserLine(int dataType, void* laserLinePoint, VizumCamera* camera) { if (!camera) { return; } camera->HandleAutoOutputLaserLine(dataType, laserLinePoint); } static void DeviceStatusChange(int status, VizumCamera* camera) { if (!camera) { return; } camera->HandleDeviceStatusChange(status); } }; #endif // 静态成员初始化 std::atomic VizumCamera::s_sdkInitialized(false); std::mutex VizumCamera::s_sdkMutex; struct VizumCamera::Impl { #ifdef ENABLE_VIZUM_CAMERA VZNLHANDLE deviceHandle = nullptr; SVzNLEyeCBInfo vzDeviceInfo{}; #else void* deviceHandle = nullptr; #endif std::string ipAddress; SLDeviceInfo deviceInfo; std::atomic isConnected{false}; std::atomic isAsyncCapturing{false}; FrameCallback frameCallback = nullptr; CaptureType captureType = CaptureType::All; mutable std::mutex mutex; mutable std::mutex deviceAccessMutex; std::atomic frameCounter{0}; struct CachedFrameData { std::string frameId; SLImage leftImage; SLImage rightImage; PointCloud pointCloud; DepthMap depthMap; uint64_t timestamp = 0; } cachedFrame; std::vector> accumulatedScanLines; std::vector> accumulatedScanLines2D; // 与扫描线一一对应的双目2D坐标 std::vector accumulatedLineMeta; // 每线 encodeNo/timestamp std::mutex scanLineMutex; std::atomic autoDetectRunning{false}; std::atomic isPaused{true}; std::atomic scanCompletionStatus{0}; PointCloud accumulatedCloud; }; #define m_deviceHandle m_impl->deviceHandle #define m_vzDeviceInfo m_impl->vzDeviceInfo #define m_ipAddress m_impl->ipAddress #define m_deviceInfo m_impl->deviceInfo #define m_isConnected m_impl->isConnected #define m_isAsyncCapturing m_impl->isAsyncCapturing #define m_frameCallback m_impl->frameCallback #define m_captureType m_impl->captureType #define m_mutex m_impl->mutex #define m_deviceAccessMutex m_impl->deviceAccessMutex #define m_frameCounter m_impl->frameCounter #define m_cachedFrame m_impl->cachedFrame #define m_accumulatedScanLines m_impl->accumulatedScanLines #define m_accumulatedScanLines2D m_impl->accumulatedScanLines2D #define m_accumulatedLineMeta m_impl->accumulatedLineMeta #define m_scanLineMutex m_impl->scanLineMutex #define m_autoDetectRunning m_impl->autoDetectRunning #define m_isPaused m_impl->isPaused #define m_scanCompletionStatus m_impl->scanCompletionStatus #define m_accumulatedCloud m_impl->accumulatedCloud #ifdef ENABLE_VIZUM_CAMERA namespace { class DetectRoiRestoreGuard { public: DetectRoiRestoreGuard(VZNLHANDLE device, const SVzNLROIRect& leftROI, const SVzNLROIRect& rightROI) : m_device(device), m_leftROI(leftROI), m_rightROI(rightROI) { } bool RestoreNow() { if (m_restored) return true; const int ret = VzNL_ConfigDetectROI(m_device, &m_leftROI, &m_rightROI); if (ret != 0) { char errorMsg[256] = {0}; VzNL_GetErrorInfo(ret, errorMsg); LOG_ERROR("[VizumCamera] Failed to restore detection ROI: {} (error code: {})", errorMsg, ret); return false; } m_restored = true; LOG_INFO("[VizumCamera] Detection ROI restored: left=[{}, {}, {}, {}], " "right=[{}, {}, {}, {}]", m_leftROI.left, m_leftROI.right, m_leftROI.top, m_leftROI.bottom, m_rightROI.left, m_rightROI.right, m_rightROI.top, m_rightROI.bottom); return true; } ~DetectRoiRestoreGuard() { if (!m_restored && !RestoreNow()) LOG_ERROR("[VizumCamera] Emergency detection ROI restoration failed"); } DetectRoiRestoreGuard(const DetectRoiRestoreGuard&) = delete; DetectRoiRestoreGuard& operator=(const DetectRoiRestoreGuard&) = delete; private: VZNLHANDLE m_device = nullptr; SVzNLROIRect m_leftROI{}; SVzNLROIRect m_rightROI{}; bool m_restored = false; }; bool NormalizeToDeviceFrame(SLImage& image, uint32_t frameWidth, uint32_t frameHeight) { if (image.empty() || frameWidth == 0 || frameHeight == 0 || image.originX < 0 || image.originY < 0 || image.channels <= 0) { return false; } const uint64_t right = static_cast(image.originX) + image.width; const uint64_t bottom = static_cast(image.originY) + image.height; if (right > frameWidth || bottom > frameHeight) return false; if (image.originX == 0 && image.originY == 0 && image.width == frameWidth && image.height == frameHeight) { return true; } const size_t bytesPerChannel = image.bitDepth > 8 ? 2u : 1u; const size_t pixelBytes = static_cast(image.channels) * bytesPerChannel; const size_t sourceRowBytes = static_cast(image.width) * pixelBytes; if (image.data.size() < sourceRowBytes * image.height) return false; SLImage fullImage; fullImage.width = frameWidth; fullImage.height = frameHeight; fullImage.originX = 0; fullImage.originY = 0; fullImage.channels = image.channels; fullImage.bitDepth = image.bitDepth; fullImage.data.assign(static_cast(frameWidth) * frameHeight * pixelBytes, 0); const size_t targetRowBytes = static_cast(frameWidth) * pixelBytes; const size_t targetXBytes = static_cast(image.originX) * pixelBytes; for (uint32_t row = 0; row < image.height; ++row) { const size_t sourceOffset = static_cast(row) * sourceRowBytes; const size_t targetOffset = (static_cast(image.originY) + row) * targetRowBytes + targetXBytes; std::memcpy(fullImage.data.data() + targetOffset, image.data.data() + sourceOffset, sourceRowBytes); } image = std::move(fullImage); return true; } const char* DeviceStatusName(int status) { switch (static_cast(status)) { case keDeviceWorkStatusUnknown: return "Unknown"; case keDeviceWorkStatus_Eye_Comming: return "Eye_Comming"; case keDeviceWorkStatus_Eye_Reconnect: return "Eye_Reconnect"; case keDeviceWorkStatus_Working: return "Working"; case keDeviceWorkStatus_Offline: return "Offline"; case keDeviceWorkStatus_Device_Swing_Finish: return "Device_Swing_Finish"; case keDeviceWorkStatus_Device_Auto_Stop: return "Device_Auto_Stop"; case keDeviceWorkStatus_Device_Swing_Running: return "Device_Swing_Running"; default: return "Other"; } } void AutoOutputLaserLineExCB(EVzResultDataType eDataType, SVzLaserLineData* pLaserLinePoint, void* pParam) { VizumCameraCallbackHooks::AutoOutputLaserLine( static_cast(eDataType), pLaserLinePoint, static_cast(pParam)); } void DeviceStatusCB(EVzDeviceWorkStatus eStatus, void* pParam) { VizumCameraCallbackHooks::DeviceStatusChange( static_cast(eStatus), static_cast(pParam)); } } // namespace #endif void VizumCamera::HandleAutoOutputLaserLine(int dataType, void* laserLinePoint) { #ifdef ENABLE_VIZUM_CAMERA static bool firstCallback = true; if (firstCallback) { firstCallback = false; LOG_INFO("[VizumCamera] Received first callback, eDataType={}", dataType); } if (dataType == static_cast(keResultDataType_PointXYZRGBA)) { ConvertLaserLineDataToPointCloud(laserLinePoint); } else { LOG_WARN("[VizumCamera] Callback received with unexpected data type: {}", dataType); } #else (void)dataType; (void)laserLinePoint; #endif } void VizumCamera::HandleDeviceStatusChange(int status) { #ifdef ENABLE_VIZUM_CAMERA const char* statusName = DeviceStatusName(status); LOG_INFO("[VizumCamera] Device status changed: {} ({})", statusName, status); const bool completesScan = status == static_cast(keDeviceWorkStatus_Device_Swing_Finish) || status == static_cast(keDeviceWorkStatus_Device_Auto_Stop); if (completesScan) { if (!m_autoDetectRunning.load()) { LOG_INFO("[VizumCamera] Completion status {} ({}) received outside an active " "scan wait; ignored", statusName, status); return; } size_t scanLines = 0; size_t validPoints = 0; { std::lock_guard lock(m_scanLineMutex); scanLines = m_accumulatedScanLines.size(); validPoints = m_accumulatedCloud.points.size(); } m_scanCompletionStatus = status; LOG_INFO("[VizumCamera] Scan completion status received: {} ({}); " "releasing scan wait, scan lines={}, valid points={}", statusName, status, scanLines, validPoints); m_isPaused = true; return; } if (status == static_cast(keDeviceWorkStatus_Offline)) { LOG_WARN("[VizumCamera] Camera went offline"); } else if (status == static_cast(keDeviceWorkStatus_Device_Swing_Running)) { LOG_INFO("[VizumCamera] Camera swing motor started running"); } else if (status == static_cast(keDeviceWorkStatus_Eye_Reconnect)) { LOG_INFO("[VizumCamera] Camera reconnected"); } #else (void)status; #endif } void VizumCamera::ConvertLaserLineDataToPointCloud(void* laserLinePoint) { #ifdef ENABLE_VIZUM_CAMERA SVzLaserLineData* pLaserLinePoint = static_cast(laserLinePoint); if (pLaserLinePoint == nullptr) { return; } int nPoints = pLaserLinePoint->nPointCount; // 检查点数量和指针有效性 if (nPoints <= 0 || pLaserLinePoint->p3DPoint == nullptr) { return; } // 将 p3DPoint 转换为 SVzNLPointXYZRGBA* 类型 SVzNLPointXYZRGBA* pPoints = static_cast(pLaserLinePoint->p3DPoint); // keResultDataType_PointXYZRGBA 对应的 2D 数据类型为 SVzNL2DLRPoint(左右目像素坐标) SVzNL2DLRPoint* pPoints2D = static_cast(pLaserLinePoint->p2DPoint); // 使用锁保护对累积点云的访问 std::lock_guard lock(m_scanLineMutex); // 预先分配内存 m_accumulatedCloud.points.reserve(m_accumulatedCloud.points.size() + nPoints); // 同时保存扫描线数据 std::vector scanLine; scanLine.reserve(nPoints); std::vector scanLine2D; scanLine2D.reserve(nPoints); int validCount = 0; for (int i = 0; i < nPoints; ++i) { SVzNLPointXYZRGBA& point = pPoints[i]; // 只将有效点添加到显示用的点云 if (point.x != 0.0f || point.y != 0.0f || point.z != 0.0f) { Point3D pt; pt.x = point.x; pt.y = point.y; pt.z = point.z; m_accumulatedCloud.points.push_back(pt); validCount++; } // 保存所有点到扫描线数据(包括无效点),保持网格格式 Point3D scanPt; scanPt.x = point.x; scanPt.y = point.y; scanPt.z = point.z; scanLine.push_back(scanPt); // 同步保存双目 2D 像素坐标(无 2D 数据时补零占位,保持与 3D 点对齐) PixelLR px; if (pPoints2D != nullptr) { px.leftX = pPoints2D[i].sLeft.x; px.leftY = pPoints2D[i].sLeft.y; px.rightX = pPoints2D[i].sRight.x; px.rightY = pPoints2D[i].sRight.y; } scanLine2D.push_back(px); } // 逐线统计只留 TRACE(SPDLOG_ACTIVE_LEVEL=DEBUG 下编译期剔除,不再刷爆日志); // 每次扫描的汇总信息由采集结束处的 "Organized grid built" 一条日志给出 LOG_TRACE("[VizumCamera] 收到扫描线: {} 个点, 有效点: {}", nPoints, validCount); // 将扫描线添加到累积的扫描线数据中 if (!scanLine.empty()) { m_accumulatedScanLines.push_back(std::move(scanLine)); m_accumulatedScanLines2D.push_back(std::move(scanLine2D)); ScanLineInfo meta; meta.pointCount = (size_t)nPoints; meta.timestamp = pLaserLinePoint->llTimeStamp; meta.encodeNo = pLaserLinePoint->nEncodeNo; m_accumulatedLineMeta.push_back(meta); LOG_TRACE("[VizumCamera] 累积扫描线数量: {}", m_accumulatedScanLines.size()); } #else (void)laserLinePoint; #endif } // 获取累积的扫描线数据 std::vector> VizumCamera::GetAccumulatedScanLines() { std::lock_guard lock(m_scanLineMutex); return m_accumulatedScanLines; } // 清除累积的扫描线数据 void VizumCamera::ClearAccumulatedScanLines() { std::lock_guard lock(m_scanLineMutex); m_accumulatedScanLines.clear(); m_accumulatedScanLines2D.clear(); m_accumulatedLineMeta.clear(); m_accumulatedCloud.clear(); } // ============================================================================ // 构造与析构 // ============================================================================ VizumCamera::VizumCamera() : m_impl(std::make_unique()) { #ifdef ENABLE_VIZUM_CAMERA memset(&m_vzDeviceInfo, 0, sizeof(m_vzDeviceInfo)); #endif } VizumCamera::~VizumCamera() { Disconnect(); } // ============================================================================ // SDK初始化/销毁 // ============================================================================ bool VizumCamera::InitSDK() { #ifdef ENABLE_VIZUM_CAMERA std::lock_guard lock(s_sdkMutex); if (s_sdkInitialized) { return true; } // 与VznlSDKManager::InitializeSDK保持一致 SVzNLConfigParam configParam; configParam.nDeviceTimeOut = 0; int result = VzNL_Init(&configParam); if (result != 0) { char errorMsg[256] = {0}; VzNL_GetErrorInfo(result, errorMsg); LOG_ERROR("[VizumCamera] SDK initialization failed: {}", errorMsg); return false; } // SDK 日志:只记警告及以上、只写文件(VizumLog/) // Information 级会逐帧刷屏,现场排查需要更详细时临时调回 VzNL_SetLogLevel(keNLLogLevel_Warning, keNLLogType_File); s_sdkInitialized = true; LOG_INFO("[VizumCamera] SDK initialized successfully"); return true; #else LOG_ERROR("[VizumCamera] Vizum camera support not enabled"); return false; #endif } void VizumCamera::DestroySDK() { #ifdef ENABLE_VIZUM_CAMERA std::lock_guard lock(s_sdkMutex); if (s_sdkInitialized) { VzNL_Destroy(); s_sdkInitialized = false; LOG_INFO("[VizumCamera] SDK destroyed"); } #endif } // ============================================================================ // 基本操作实现 // ============================================================================ std::string VizumCamera::GetSDKVersion() const { #ifdef ENABLE_VIZUM_CAMERA if (m_deviceHandle) { SVzNLVersionInfo versionInfo; memset(&versionInfo, 0, sizeof(versionInfo)); if (VzNL_GetVersion(m_deviceHandle, &versionInfo) == 0) { return std::string(versionInfo.szSDKVersion); } } return "Unknown"; #else return "Vizum SDK not enabled"; #endif } bool VizumCamera::SearchCameras(std::vector& deviceList) { #ifdef ENABLE_VIZUM_CAMERA deviceList.clear(); // 确保SDK已初始化 if (!s_sdkInitialized) { if (!InitSDK()) { return false; } } // 搜索设备 - 使用与成功实现相同的标志 int result = VzNL_ResearchDevice(keSearchDeviceFlag_EthLaserRobotEye); if (result != 0) { char errorMsg[256] = {0}; VzNL_GetErrorInfo(result, errorMsg); LOG_ERROR("[VizumCamera] Device search failed: {}", errorMsg); return false; } // 获取设备数量 int deviceCount = 0; result = VzNL_GetEyeCBDeviceInfo(nullptr, &deviceCount); if (deviceCount <= 0) { LOG_INFO("[VizumCamera] No devices found"); return false; } // 获取设备信息 std::vector vzDevices(deviceCount); result = VzNL_GetEyeCBDeviceInfo(vzDevices.data(), &deviceCount); if (result != 0) { char errorMsg[256] = {0}; VzNL_GetErrorInfo(result, errorMsg); LOG_ERROR("[VizumCamera] Failed to get device info: {}", errorMsg); return false; } // 转换为通用设备信息,并绑定未绑定的设备 for (int i = 0; i < deviceCount; ++i) { // 检查设备是否有效,如果无效则尝试绑定(与成功实现一致) if (vzDevices[i].bValidDevice == VzFalse) { LOG_INFO("[VizumCamera] Device {} is not bound, attempting to bind...", std::string(vzDevices[i].byServerIP)); // 获取设备配置信息 SVzNLEthernetEyeConfigInfo ethConfigInfo; int configResult = VzNL_GetEthernetEyeConfigInfo(&vzDevices[i], ðConfigInfo); if (configResult != 0) { char errorMsg[256] = {0}; VzNL_GetErrorInfo(configResult, errorMsg); LOG_WARN("[VizumCamera] Failed to get ethernet eye config: {}", errorMsg); } // 绑定设备 int bindResult = VzNL_BindEthernetEye(&vzDevices[i]); if (bindResult != 0) { char errorMsg[256] = {0}; VzNL_GetErrorInfo(bindResult, errorMsg); LOG_WARN("[VizumCamera] Failed to bind ethernet eye: {}", errorMsg); continue; // 绑定失败,跳过此设备 } LOG_INFO("[VizumCamera] Device bound successfully"); } SLDeviceInfo info; info.ipAddress = std::string(vzDevices[i].byServerIP); info.serialNumber = std::string(vzDevices[i].szGUID); info.model = std::string(vzDevices[i].szDeviceName); info.alias = std::string(vzDevices[i].szDesc); // 宽高暂时设置为默认值,连接后会更新 info.width = 1280; info.height = 960; deviceList.push_back(info); } LOG_INFO("[VizumCamera] Found {} device(s)", deviceCount); return !deviceList.empty(); #else LOG_ERROR("[VizumCamera] Vizum camera support not enabled"); return false; #endif } bool VizumCamera::Connect(const std::string& ipAddress) { #ifdef ENABLE_VIZUM_CAMERA std::unique_lock lock(m_mutex); if (m_isConnected) { if (m_ipAddress == ipAddress) { return true; // 已连接到相同地址 } lock.unlock(); Disconnect(); // 断开之前的连接 lock.lock(); } // 确保SDK已初始化 if (!s_sdkInitialized) { if (!InitSDK()) { return false; } } m_ipAddress = ipAddress; // 搜索设备 - 使用与成功实现相同的标志 int result = VzNL_ResearchDevice(keSearchDeviceFlag_EthLaserRobotEye); if (result != 0) { LOG_ERROR("[VizumCamera] Device search failed during connect"); m_ipAddress.clear(); return false; } // 获取设备列表 int deviceCount = 0; VzNL_GetEyeCBDeviceInfo(nullptr, &deviceCount); if (deviceCount <= 0) { LOG_ERROR("[VizumCamera] No devices found"); m_ipAddress.clear(); return false; } std::vector vzDevices(deviceCount); VzNL_GetEyeCBDeviceInfo(vzDevices.data(), &deviceCount); // 查找匹配的设备 bool found = false; for (int i = 0; i < deviceCount; ++i) { if (std::string(vzDevices[i].byServerIP) == ipAddress) { m_vzDeviceInfo = vzDevices[i]; found = true; // 检查设备是否有效,如果无效则尝试绑定(与成功实现一致) if (m_vzDeviceInfo.bValidDevice == VzFalse) { LOG_INFO("[VizumCamera] Device is not bound, attempting to bind..."); // 获取设备配置信息 SVzNLEthernetEyeConfigInfo ethConfigInfo; int configResult = VzNL_GetEthernetEyeConfigInfo(&m_vzDeviceInfo, ðConfigInfo); if (configResult != 0) { char errorMsg[256] = {0}; VzNL_GetErrorInfo(configResult, errorMsg); LOG_WARN("[VizumCamera] Failed to get ethernet eye config: {}", errorMsg); } // 绑定设备 int bindResult = VzNL_BindEthernetEye(&m_vzDeviceInfo); if (bindResult != 0) { char errorMsg[256] = {0}; VzNL_GetErrorInfo(bindResult, errorMsg); LOG_ERROR("[VizumCamera] Failed to bind ethernet eye: {}", errorMsg); m_ipAddress.clear(); return false; } LOG_INFO("[VizumCamera] Device bound successfully"); } break; } } if (!found) { LOG_ERROR("[VizumCamera] Device with IP {} not found", ipAddress); m_ipAddress.clear(); return false; } // 打开设备(与VznlCamera::OpenCamera保持一致,不设置额外参数) SVzNLOpenDeviceParam openParam{}; // 注意:正确示例中不设置任何参数,使用默认值 int errorCode = 0; m_deviceHandle = VzNL_OpenDevice(&m_vzDeviceInfo, &openParam, &errorCode); if (!m_deviceHandle || errorCode != 0) { char errorMsg[256] = {0}; VzNL_GetErrorInfo(errorCode, errorMsg); LOG_ERROR("[VizumCamera] Failed to open device: {}", errorMsg); m_ipAddress.clear(); return false; } // 设置设备状态回调。静态扫描以 Device_Auto_Stop 作为官方完成信号,注册失败不可静默继续。 const int statusNotifyResult = VzNL_SetDeviceStatusNotify(m_deviceHandle, &DeviceStatusCB, this); if (statusNotifyResult != 0) { char statusErrorMsg[256] = {0}; VzNL_GetErrorInfo(statusNotifyResult, statusErrorMsg); LOG_ERROR("[VizumCamera] Failed to register device status callback: {} (error code: {})", statusErrorMsg, statusNotifyResult); VzNL_CloseDevice(m_deviceHandle); m_deviceHandle = nullptr; m_ipAddress.clear(); return false; } LOG_INFO("[VizumCamera] Device status callback registered; scan completion events: " "Device_Auto_Stop or Device_Swing_Finish"); // OpenDevice 之后立即设置图像输出格式(在此之前设置对本次连接无效) VzNL_SetOutputImageFormat(keVzNLImageType_BGR888); LOG_INFO("[VizumCamera] Output image format set to BGR888"); // 更新设备信息 if (!UpdateDeviceInfo()) { VzNL_CloseDevice(m_deviceHandle); m_deviceHandle = nullptr; m_ipAddress.clear(); return false; } // 初始化激光检测(非致命,与 HandEyeKit 保持一致) int laserResult = VzNL_BeginDetectLaser(m_deviceHandle); if (laserResult != 0) { char errorMsg[256] = {0}; VzNL_GetErrorInfo(laserResult, errorMsg); LOG_WARN("[VizumCamera] BeginDetectLaser failed (non-fatal): {}", errorMsg); } else { LOG_INFO("[VizumCamera] Laser detection initialized"); } // 切到 Master 主动模式 — 避免设备停留在外触发模式导致无法取图/设参数(非致命) { const int trigResult = VzNL_SetTriggerMode(m_deviceHandle, keEyeTriggerMode_Master); if (trigResult != 0) { char errorMsg[256] = {0}; VzNL_GetErrorInfo(trigResult, errorMsg); LOG_WARN("[VizumCamera] SetTriggerMode(Master) failed (non-fatal): {}", errorMsg); } else { LOG_INFO("[VizumCamera] Trigger mode set to Master"); } } // 忽略外部触发使能信号(非致命) { const int ignResult = VzNL_IgnoreTriggerExtSignal(m_deviceHandle, VzTrue); if (ignResult != 0) { char errorMsg[256] = {0}; VzNL_GetErrorInfo(ignResult, errorMsg); LOG_WARN("[VizumCamera] IgnoreTriggerExtSignal failed (non-fatal): {}", errorMsg); } else { LOG_INFO("[VizumCamera] External trigger signal ignored"); } } m_isConnected = true; LOG_INFO("[VizumCamera] Connected to camera at {}", ipAddress); return true; #else LOG_ERROR("[VizumCamera] Vizum camera support not enabled"); return false; #endif } void VizumCamera::Disconnect() { #ifdef ENABLE_VIZUM_CAMERA std::lock_guard lock(m_mutex); const std::lock_guard deviceLock(m_deviceAccessMutex); if (m_isAsyncCapturing) { StopAsyncCapture(); } if (m_deviceHandle) { // 结束激光检测(与VZNLCameraAdapter保持一致) VzNL_EndDetectLaser(m_deviceHandle); VzNL_StopCapture(m_deviceHandle); VzNL_CloseDevice(m_deviceHandle); m_deviceHandle = nullptr; } if (s_sdkInitialized) { VzNL_Destroy(); s_sdkInitialized = false; } m_isConnected = false; m_ipAddress.clear(); m_deviceInfo = SLDeviceInfo(); memset(&m_vzDeviceInfo, 0, sizeof(m_vzDeviceInfo)); LOG_INFO("[VizumCamera] Disconnected"); #endif } bool VizumCamera::IsConnected() const { return m_isConnected; } bool VizumCamera::GetDeviceInfo(SLDeviceInfo& info) { if (!m_isConnected) { return false; } info = m_deviceInfo; return true; } bool VizumCamera::UpdateDeviceInfo() { #ifdef ENABLE_VIZUM_CAMERA if (!m_deviceHandle) { return false; } // 获取分辨率 SVzVideoResolution resolution; if (VzNL_GetResolution(m_deviceHandle, &resolution) == 0) { m_deviceInfo.width = resolution.nFrameWidth; m_deviceInfo.height = resolution.nFrameHeight; } else { m_deviceInfo.width = 1280; m_deviceInfo.height = 960; } m_deviceInfo.ipAddress = m_ipAddress; m_deviceInfo.serialNumber = std::string(m_vzDeviceInfo.szGUID); m_deviceInfo.model = std::string(m_vzDeviceInfo.szDeviceName); m_deviceInfo.alias = std::string(m_vzDeviceInfo.szDesc); return true; #else return false; #endif } // ============================================================================ // 同步采集实现 // ============================================================================ bool VizumCamera::TriggerCapture(std::string& frameId, CaptureType captureType) { LOG_INFO("[VizumCamera] TriggerCapture called, captureType={}", static_cast(captureType)); #ifdef ENABLE_VIZUM_CAMERA if (!m_isConnected || !m_deviceHandle) { LOG_ERROR("[VizumCamera] TriggerCapture failed: Camera not connected or handle invalid"); return false; } // 生成帧ID m_frameCounter++; frameId = "VZ_" + std::to_string(m_frameCounter); // 执行采集并缓存数据 if (!CaptureAndCacheData(captureType)) { LOG_ERROR("[VizumCamera] CaptureAndCacheData failed"); return false; } m_cachedFrame.frameId = frameId; LOG_INFO("[VizumCamera] TriggerCapture success, frameId={}", frameId); return true; #else return false; #endif } bool VizumCamera::CaptureAndCacheData(CaptureType captureType) { LOG_INFO("[VizumCamera] CaptureAndCacheData called, captureType={}", static_cast(captureType)); #ifdef ENABLE_VIZUM_CAMERA const std::lock_guard deviceLock(m_deviceAccessMutex); if (!m_deviceHandle) { LOG_ERROR("[VizumCamera] CaptureAndCacheData failed: Device handle is null"); return false; } // 清空缓存 m_cachedFrame.leftImage.clear(); m_cachedFrame.rightImage.clear(); m_cachedFrame.pointCloud.clear(); m_cachedFrame.depthMap.clear(); // 获取当前时间戳 auto now = std::chrono::system_clock::now(); m_cachedFrame.timestamp = std::chrono::duration_cast( now.time_since_epoch()).count(); // 采集图像 if (captureType & CaptureType::Image) { SVzNLImageData* pLeftImage = nullptr; SVzNLImageData* pRightImage = nullptr; int result = VzNL_GetEyeImage(m_deviceHandle, &pLeftImage, &pRightImage, 3000); if (result == 0) { if (pLeftImage) { ConvertVzImageToSLImage(pLeftImage, m_cachedFrame.leftImage); VzNL_ReleaseImage(&pLeftImage); } if (pRightImage) { ConvertVzImageToSLImage(pRightImage, m_cachedFrame.rightImage); VzNL_ReleaseImage(&pRightImage); } } else { char errorMsg[256] = {0}; VzNL_GetErrorInfo(result, errorMsg); LOG_ERROR("[VizumCamera] Failed to get image: {}", errorMsg); } } // 采集点云(通过激光扫描) // 按照 VZNLCameraAdapter::scan 的逻辑,使用自动检测模式采集 if (captureType & CaptureType::PointCloud) { LOG_INFO("[VizumCamera] CaptureAndCacheData: Requesting point cloud via CapturePointCloudWithAutoDetect..."); PointCloud pc; // 使用默认超时或根据需要调整 if (CapturePointCloudWithAutoDetect(pc, 3000)) { m_cachedFrame.pointCloud = std::move(pc); LOG_INFO("[VizumCamera] CaptureAndCacheData: Successfully captured {} points", m_cachedFrame.pointCloud.points.size()); } else { LOG_WARN("[VizumCamera] CaptureAndCacheData: Failed to capture point cloud"); } } return true; #else return false; #endif } #ifdef ENABLE_VIZUM_CAMERA bool VizumCamera::ConvertVzImageToSLImage(void* vzImagePtr, SLImage& slImage) { SVzNLImageData* vzImage = static_cast(vzImagePtr); if (!vzImage || !vzImage->pBuffer) { return false; } slImage.width = vzImage->nWidth; slImage.height = vzImage->nHeight; slImage.originX = vzImage->ptOriPos.x; slImage.originY = vzImage->ptOriPos.y; slImage.channels = vzImage->nChannels; slImage.bitDepth = vzImage->byBitDepth; size_t dataSize = vzImage->nWidth * vzImage->nHeight * vzImage->nChannels; if (vzImage->byBitDepth == 16) { dataSize *= 2; } slImage.data.resize(dataSize); memcpy(slImage.data.data(), vzImage->pBuffer, dataSize); return true; } #endif bool VizumCamera::GetImage(const std::string& frameId, SLImage& image) { if (m_cachedFrame.frameId != frameId) { return false; } // 返回左目图像作为主图像 image = m_cachedFrame.leftImage; return !image.empty(); } bool VizumCamera::GetLeftImage(SLImage& image) { image = m_cachedFrame.leftImage; return !image.empty(); } bool VizumCamera::GetRightImage(SLImage& image) { image = m_cachedFrame.rightImage; return !image.empty(); } bool VizumCamera::CaptureFullFrameLeftImage(SLImage& image, std::string& frameId) { image.clear(); frameId.clear(); #ifdef ENABLE_VIZUM_CAMERA const std::lock_guard deviceLock(m_deviceAccessMutex); if (!m_isConnected || !m_deviceHandle) { LOG_ERROR("[VizumCamera] Full-frame capture failed: device not connected"); return false; } SVzNLROIRect oldLeftROI{}; SVzNLROIRect oldRightROI{}; int ret = VzNL_GetConfigDetectROI(m_deviceHandle, &oldLeftROI, &oldRightROI); if (ret != 0) { char errorMsg[256] = {0}; VzNL_GetErrorInfo(ret, errorMsg); LOG_ERROR("[VizumCamera] Full-frame capture cannot read detection ROI: {} " "(error code: {})", errorMsg, ret); return false; } SVzVideoResolution resolution{}; ret = VzNL_GetResolution(m_deviceHandle, &resolution); if (ret != 0 || resolution.nFrameWidth == 0 || resolution.nFrameHeight == 0) { char errorMsg[256] = {0}; VzNL_GetErrorInfo(ret, errorMsg); LOG_ERROR("[VizumCamera] Full-frame capture cannot read device resolution: {} " "(error code: {}, size={}x{})", errorMsg, ret, resolution.nFrameWidth, resolution.nFrameHeight); return false; } DetectRoiRestoreGuard restoreGuard(m_deviceHandle, oldLeftROI, oldRightROI); const SVzNLROIRect fullLeftROI{0, static_cast(resolution.nFrameWidth), 0, static_cast(resolution.nFrameHeight)}; const SVzNLROIRect fullRightROI = fullLeftROI; ret = VzNL_ConfigDetectROI(m_deviceHandle, &fullLeftROI, &fullRightROI); if (ret != 0) { char errorMsg[256] = {0}; VzNL_GetErrorInfo(ret, errorMsg); LOG_ERROR("[VizumCamera] Failed to apply temporary full-frame ROI: {} " "(error code: {}, size={}x{})", errorMsg, ret, resolution.nFrameWidth, resolution.nFrameHeight); return false; } LOG_INFO("[VizumCamera] Temporary full-frame ROI applied: {}x{}; CalibROI unchanged", resolution.nFrameWidth, resolution.nFrameHeight); SVzNLImageData* leftImage = nullptr; SVzNLImageData* rightImage = nullptr; ret = VzNL_GetEyeImage(m_deviceHandle, &leftImage, &rightImage, 3000); bool converted = false; if (ret == 0 && leftImage) { converted = ConvertVzImageToSLImage(leftImage, image); LOG_INFO("[VizumCamera] Full-frame SDK image received: size={}x{}, origin=({}, {})", leftImage->nWidth, leftImage->nHeight, leftImage->ptOriPos.x, leftImage->ptOriPos.y); } else { char errorMsg[256] = {0}; VzNL_GetErrorInfo(ret, errorMsg); LOG_ERROR("[VizumCamera] Full-frame image acquisition failed: {} (error code: {})", errorMsg, ret); } if (leftImage) VzNL_ReleaseImage(&leftImage); if (rightImage) VzNL_ReleaseImage(&rightImage); const bool normalized = converted && NormalizeToDeviceFrame(image, resolution.nFrameWidth, resolution.nFrameHeight); if (converted && !normalized) { LOG_ERROR("[VizumCamera] SDK full-frame image cannot be placed in device frame: " "image={}x{} origin=({}, {}), device={}x{}", image.width, image.height, image.originX, image.originY, resolution.nFrameWidth, resolution.nFrameHeight); } const bool restored = restoreGuard.RestoreNow(); if (!normalized || !restored) { image.clear(); return false; } const uint64_t id = ++m_frameCounter; frameId = "VZ_FULL_" + std::to_string(id); LOG_INFO("[VizumCamera] Full-frame capture completed and ROI restored: frameId={}, " "output={}x{} origin=(0, 0)", frameId, image.width, image.height); return true; #else return false; #endif } bool VizumCamera::CapturePointCloudWithAutoDetect(PointCloud& pointCloud, int timeoutMs) { LOG_INFO("[VizumCamera] CapturePointCloudWithAutoDetect called, timeoutMs={}", timeoutMs); LOG_INFO("[VizumCamera] Input pointCloud state: scanLines.size()={}, points.size()={}", pointCloud.scanLines.size(), pointCloud.points.size()); #ifdef ENABLE_VIZUM_CAMERA LOG_INFO("[VizumCamera] ENABLE_VIZUM_CAMERA is defined"); LOG_INFO("[VizumCamera] m_isConnected={}, m_deviceHandle={}", m_isConnected, (m_deviceHandle ? "valid" : "null")); if (!m_isConnected || !m_deviceHandle) { LOG_ERROR("[VizumCamera] Camera not connected, returning false"); return false; } LOG_INFO("[VizumCamera] ========================================"); LOG_INFO("[VizumCamera] Starting point cloud capture (AutoDetect mode)..."); LOG_INFO("[VizumCamera] ========================================"); int result = 0; char errorMsg[256] = {0}; // 0. 检查并配置摆镜扫描(关键步骤!) LOG_INFO("[VizumCamera] Step 0: Checking and configuring swing motor..."); // 检查是否支持摆镜 int errorCode = 0; VzBool supportSwing = VzNL_IsSupportSwingMotor(m_deviceHandle, &errorCode); if (supportSwing == VzTrue) { LOG_INFO("[VizumCamera] Swing motor is supported"); // 检查摆镜是否已启用 VzBool swingEnabled = VzNL_IsEnableSwingMotor(m_deviceHandle, &errorCode); if (swingEnabled != VzTrue) { // 启用摆镜 LOG_INFO("[VizumCamera] Enabling swing motor..."); result = VzNL_EnableSwingMotor(m_deviceHandle, VzTrue); if (result != 0) { VzNL_GetErrorInfo(result, errorMsg); LOG_WARN("[VizumCamera] Failed to enable swing motor: {} (error code: {})", errorMsg, result); // 继续尝试,可能摆镜已经手动启用 } else { LOG_INFO("[VizumCamera] Swing motor enabled successfully"); } } else { LOG_INFO("[VizumCamera] Swing motor is already enabled"); } // 获取并记录摆镜配置信息 int startAngle = 0, endAngle = 0; if (VzNL_GetSwingMotorAngle(m_deviceHandle, &startAngle, &endAngle) == 0) { LOG_INFO("[VizumCamera] Swing motor angle range: {} to {}", startAngle, endAngle); } int angleSpeed = 0; if (VzNL_GetSwingAngleSpeed(m_deviceHandle, &angleSpeed) == 0) { LOG_INFO("[VizumCamera] Swing motor angle speed: {}", angleSpeed); } // 获取当前角度 float curAngle = VzNL_GetSwingCurAngle(m_deviceHandle, &errorCode); if (errorCode == 0) { LOG_INFO("[VizumCamera] Current swing angle: {:.2f}", curAngle); } } else { LOG_WARN("[VizumCamera] Swing motor is NOT supported, point cloud may not be captured correctly"); } // 设置点云处理模式为固定步长模式(摆镜扫描推荐) // 注意:参考实现中没有设置此模式,如果遇到问题可以尝试注释掉 /* LOG_INFO("[VizumCamera] Setting point cloud processing mode to FixedStep..."); result = VzNL_SetPointCloudProcMode(m_deviceHandle, kePointCloudProcMode_FixedStep); if (result != 0) { VzNL_GetErrorInfo(result, errorMsg); LOG_WARN("[VizumCamera] Failed to set point cloud proc mode: {} (trying Speed mode...)", errorMsg); // 尝试速度模式作为备选 result = VzNL_SetPointCloudProcMode(m_deviceHandle, kePointCloudProcMode_Speed); if (result == 0) { LOG_INFO("[VizumCamera] Set point cloud proc mode to Speed successfully"); } } else { LOG_INFO("[VizumCamera] Set point cloud proc mode to FixedStep successfully"); } */ // 获取当前点云处理模式 EVzPointCloudProcMode procMode = kePointCloudProcMode_Invalid; if (VzNL_GetPointCloudProcMode(m_deviceHandle, &procMode) == 0) { const char* modeStr = "Unknown"; switch(procMode) { case kePointCloudProcMode_Speed: modeStr = "Speed"; break; case kePointCloudProcMode_Encoder: modeStr = "Encoder"; break; case kePointCloudProcMode_FixedStep: modeStr = "FixedStep"; break; default: break; } LOG_INFO("[VizumCamera] Current point cloud proc mode: {}", modeStr); } // 1. 清空累积的点云和扫描线数据(与VZNLCameraAdapter::scan一致) { std::lock_guard lock(m_scanLineMutex); m_accumulatedScanLines.clear(); m_accumulatedScanLines2D.clear(); m_accumulatedLineMeta.clear(); m_accumulatedCloud.clear(); } LOG_INFO("[VizumCamera] Step 1: Cleared accumulated data"); // 2. 重新初始化激光检测(关键:参考示例代码,每次扫描前重新初始化) // 示例代码中 StopAutoDetect 会调用 EndDetectLaser,所以这里需要 BeginDetectLaser LOG_INFO("[VizumCamera] Step 1.5: Re-initializing laser detection..."); VzNL_EndDetectLaser(m_deviceHandle); // 确保之前的已结束 result = VzNL_BeginDetectLaser(m_deviceHandle); if (result != 0) { VzNL_GetErrorInfo(result, errorMsg); LOG_WARN("[VizumCamera] Failed to begin detect laser: {}", errorMsg); // 如果失败,可能是因为已经处于检测状态,尝试继续 } else { LOG_INFO("[VizumCamera] Laser detection initialized successfully"); } // 2.5 启用激光点填充:SDK 对每条激光线输出定长点序列,无测量处填充占位点, // 使扫描线构成真正列对齐的 W×H 栅格(供组织化点云保存/栅格聚类使用) result = VzNL_EnableFillLaserPoint(m_deviceHandle, VzTrue); if (result != 0) { VzNL_GetErrorInfo(result, errorMsg); LOG_WARN("[VizumCamera] VzNL_EnableFillLaserPoint failed: {} (error code: {})", errorMsg, result); } else { LOG_INFO("[VizumCamera] Fill laser point mode enabled (rasterized scan lines)"); } // 3. 启动自动检测模式(与VznlCamera::StartAutoDetect一致) m_scanCompletionStatus = 0; m_isPaused = false; m_autoDetectRunning = true; LOG_INFO("[VizumCamera] Step 2: Starting AutoDetectEx..."); result = VzNL_StartAutoDetectEx(m_deviceHandle, keResultDataType_PointXYZRGBA, keFlipType_None, &AutoOutputLaserLineExCB, this); if (result != 0) { m_autoDetectRunning = false; VzNL_GetErrorInfo(result, errorMsg); LOG_ERROR("[VizumCamera] Failed to start auto detect: {} (error code: {})", errorMsg, result); // 尝试清理 VzNL_EndDetectLaser(m_deviceHandle); return false; } LOG_INFO("[VizumCamera] AutoDetectEx started successfully"); // 稍微等待一下,确保状态更新 std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 检查自动检测状态 VzBool isDetecting = VzNL_IsAutoDetecting(m_deviceHandle, &errorCode); if (errorCode == 0) { LOG_INFO("[VizumCamera] IsAutoDetecting after start: {}", isDetecting == VzTrue ? "Yes" : "No"); } else { char detectingErrorMsg[256] = {0}; VzNL_GetErrorInfo(errorCode, detectingErrorMsg); LOG_WARN("[VizumCamera] Failed to query IsAutoDetecting after start: {} " "(error code: {})", detectingErrorMsg, errorCode); } // 3. 等待扫描完成 // 策略:等待以下任一条件满足: // 1. 收到 SDK 扫描完成信号 Device_Auto_Stop 或 Device_Swing_Finish // 2. 等待超时 // 5秒后仍无数据时持续告警,但保留等待,避免慢启动时误判失败。 int maxWaitTime = (timeoutMs > 0) ? timeoutMs : 30000; LOG_INFO("[VizumCamera] Step 3: Waiting up to {}ms for scan to finish...", maxWaitTime); LOG_INFO("[VizumCamera] NOTE: The camera will collect point cloud data while the swing motor is running."); LOG_INFO("[VizumCamera] If no data is received, please check if the swing motor is enabled in the camera software."); auto startTime = std::chrono::steady_clock::now(); int lastLoggedPoints = 0; int logInterval = 1000; // 每秒输出一次状态 int lastLogTime = 0; int noDataTime = 0; // 无数据的持续时间 const int noDataWarningThreshold = 5000; // 5秒无数据发出警告 bool dataReceived = false; while (true) { // 检查是否暂停(扫描完成) if (m_isPaused) { // 获取当前点云状态 int currentPoints = 0; { std::lock_guard lock(m_scanLineMutex); currentPoints = static_cast(m_accumulatedCloud.points.size()); } if (currentPoints > 0) { LOG_INFO("[VizumCamera] Scan finished with {} points.", currentPoints); break; } else { auto now = std::chrono::steady_clock::now(); int elapsed = static_cast(std::chrono::duration_cast(now - startTime).count()); // 如果时间太短且没有点云,可能是虚假的结束信号(例如刚开始时的残留信号),忽略并继续 if (elapsed < 2000) { // 2秒内结束且无数据 const int completionStatus = m_scanCompletionStatus.load(); LOG_WARN("[VizumCamera] Received completion status {} ({}) too early " "({}ms) without data. Ignoring...", DeviceStatusName(completionStatus), completionStatus, elapsed); m_scanCompletionStatus = 0; m_isPaused = false; } else { LOG_WARN("[VizumCamera] Scan finished but no points received after {}ms.", elapsed); break; } } } std::this_thread::sleep_for(std::chrono::milliseconds(100)); auto now = std::chrono::steady_clock::now(); int elapsed = static_cast(std::chrono::duration_cast(now - startTime).count()); // 获取当前点云状态 int currentPoints = 0; int scanLines = 0; { std::lock_guard lock(m_scanLineMutex); currentPoints = static_cast(m_accumulatedCloud.points.size()); scanLines = static_cast(m_accumulatedScanLines.size()); } // 检测是否收到数据 if (currentPoints > 0 && !dataReceived) { dataReceived = true; LOG_INFO("[VizumCamera] First data received! Points: {}", currentPoints); } // 每秒输出一次当前状态 if (elapsed - lastLogTime >= logInterval) { lastLogTime = elapsed; if (currentPoints != lastLoggedPoints) { LOG_INFO("[VizumCamera] Scanning... elapsed: {}ms, scan lines: {}, points: {} (new: {})", elapsed, scanLines, currentPoints, currentPoints - lastLoggedPoints); lastLoggedPoints = currentPoints; noDataTime = 0; // 重置无数据计时 } else { noDataTime += logInterval; LOG_INFO("[VizumCamera] Waiting... elapsed: {}ms, no new points for {}ms (total: {})", elapsed, noDataTime, currentPoints); // 如果超过阈值没有数据,输出警告 if (noDataTime >= noDataWarningThreshold && !dataReceived) { LOG_WARN("[VizumCamera] No data received for {}ms. Please check:", noDataTime); LOG_WARN("[VizumCamera] 1. Is the swing motor enabled and running in the camera software?"); LOG_WARN("[VizumCamera] 2. Is the laser line visible on the calibration board?"); LOG_WARN("[VizumCamera] 3. Are the camera parameters (exposure, ROI) configured correctly?"); } } // 检查摆镜状态 float curAngle = VzNL_GetSwingCurAngle(m_deviceHandle, &errorCode); if (errorCode == 0) { LOG_INFO("[VizumCamera] Current swing angle: {:.2f}", curAngle); } } if (elapsed > maxWaitTime) { LOG_WARN("[VizumCamera] Scan timed out after {}ms", maxWaitTime); break; } } // 从此处起 StopAutoDetect 触发的 Auto_Stop 仅属于清理动作,不能改写本次完成原因。 m_autoDetectRunning = false; int totalDuration = static_cast(std::chrono::duration_cast( std::chrono::steady_clock::now() - startTime).count()); const int completionStatus = m_scanCompletionStatus.load(); if (m_isPaused) { LOG_INFO("[VizumCamera] Scan finished by completion status {} ({}). Duration: {}ms", DeviceStatusName(completionStatus), completionStatus, totalDuration); } else { int detectingError = 0; const VzBool stillDetecting = VzNL_IsAutoDetecting(m_deviceHandle, &detectingError); char detectingErrorMsg[256] = {0}; if (detectingError != 0) { VzNL_GetErrorInfo(detectingError, detectingErrorMsg); } size_t timeoutScanLines = 0; size_t timeoutValidPoints = 0; { std::lock_guard lock(m_scanLineMutex); timeoutScanLines = m_accumulatedScanLines.size(); timeoutValidPoints = m_accumulatedCloud.points.size(); } LOG_WARN("[VizumCamera] Timeout diagnostics: duration={}ms, autoDetecting={}, " "detectError={} ({}), completion status={} ({}), scan lines={}, valid points={}", totalDuration, stillDetecting == VzTrue ? "Yes" : "No", detectingError, detectingError == 0 ? "none" : detectingErrorMsg, DeviceStatusName(completionStatus), completionStatus, timeoutScanLines, timeoutValidPoints); } // 输出扫描过程中的状态 { std::lock_guard lock(m_scanLineMutex); LOG_INFO("[VizumCamera] After waiting: 扫描线 {} 条, 点数: {}", m_accumulatedScanLines.size(), m_accumulatedCloud.points.size()); } // 4. 停止自动检测(与VznlCamera::StopAutoDetect一致) LOG_INFO("[VizumCamera] Step 4: Stopping AutoDetect..."); result = VzNL_StopAutoDetect(m_deviceHandle); if (result != 0) { char errorMsg[256] = {0}; VzNL_GetErrorInfo(result, errorMsg); LOG_WARN("[VizumCamera] Failed to stop auto detect: {}", errorMsg); } // 4.1 结束激光检测(关键:与示例代码一致,每次扫描后结束检测) LOG_INFO("[VizumCamera] Step 4.1: Ending laser detection..."); VzNL_EndDetectLaser(m_deviceHandle); // 5. 获取累积的点云数据 { std::lock_guard lock(m_scanLineMutex); // 如果有扫描线数据,优先构建带结构的扫描线点云(用于VZNL格式保存) if (!m_accumulatedScanLines.empty()) { // 每条扫描线=一行(回调已保全宽、无效点为 (0,0,0) 占位),补齐到最大列宽 → 规整组织化栅格。 // 这样保存/识别可走"栅格化"链路(去壁面+网格聚类分离粘着圆柱)。 const size_t H = m_accumulatedScanLines.size(); size_t W = 0; for (const auto& line : m_accumulatedScanLines) { if (line.size() > W) W = line.size(); } LOG_INFO("[VizumCamera] Step 5: Building organized grid {}x{} from {} scan lines", W, H, H); pointCloud.clear(); pointCloud.width = static_cast(W); pointCloud.height = static_cast(H); pointCloud.isOrganized = true; pointCloud.points.assign(W * H, Point3D{}); // (0,0,0) = 无效占位 pointCloud.pixels.assign(W * H, PixelLR{}); // 双目2D坐标,零占位 pointCloud.scanLines.reserve(H); for (size_t r = 0; r < H; ++r) { const auto& line = m_accumulatedScanLines[r]; ScanLineInfo info; info.startIndex = r * W; info.pointCount = line.size(); if (r < m_accumulatedLineMeta.size()) { info.timestamp = m_accumulatedLineMeta[r].timestamp; info.encodeNo = m_accumulatedLineMeta[r].encodeNo; } pointCloud.scanLines.push_back(info); for (size_t c = 0; c < line.size() && c < W; ++c) { pointCloud.points[r * W + c] = line[c]; } if (r < m_accumulatedScanLines2D.size()) { const auto& line2D = m_accumulatedScanLines2D[r]; for (size_t c = 0; c < line2D.size() && c < W; ++c) { pointCloud.pixels[r * W + c] = line2D[c]; } } } // 记录摆镜角速度(供保存元信息用,查询失败保持 0) int angleSpeed = 0; if (VzNL_GetSwingAngleSpeed(m_deviceHandle, &angleSpeed) == 0) { pointCloud.scanSpeed = static_cast(angleSpeed); } LOG_INFO("[VizumCamera] Organized grid built: {}x{} ({} cells), {} scan lines, total {} points", W, H, W * H, pointCloud.scanLines.size(), pointCloud.points.size()); } else { // 如果没有扫描线数据(异常情况),回退到仅使用有效点 LOG_INFO("[VizumCamera] Step 5: Building point cloud from {} valid points (No scanlines)", m_accumulatedCloud.points.size()); pointCloud = std::move(m_accumulatedCloud); pointCloud.width = static_cast(pointCloud.points.size()); pointCloud.height = 1; pointCloud.isOrganized = false; } // 清空累积数据 m_accumulatedScanLines.clear(); m_accumulatedScanLines2D.clear(); m_accumulatedLineMeta.clear(); m_accumulatedCloud.clear(); } // 在返回前再次验证点云状态 LOG_INFO("[VizumCamera] Before return: pointCloud.scanLines.size()={}, points.size()={}", pointCloud.scanLines.size(), pointCloud.points.size()); LOG_INFO("[VizumCamera] ========================================"); LOG_INFO("[VizumCamera] Capture completed: {} points", pointCloud.points.size()); LOG_INFO("[VizumCamera] ========================================"); return !pointCloud.empty(); #else LOG_WARN("[VizumCamera] ENABLE_VIZUM_CAMERA is NOT defined, returning false"); (void)pointCloud; (void)timeoutMs; return false; #endif } bool VizumCamera::GetPointCloud(const std::string& frameId, PointCloud& cloud) { if (m_cachedFrame.frameId != frameId) { return false; } cloud = m_cachedFrame.pointCloud; return !cloud.empty(); } bool VizumCamera::GetPointCloudRaw(const std::string& frameId, float* buffer, size_t bufferSize) { if (m_cachedFrame.frameId != frameId || !buffer) { return false; } const auto& cloud = m_cachedFrame.pointCloud; size_t requiredSize = cloud.points.size() * 3 * sizeof(float); if (bufferSize < requiredSize) { return false; } for (size_t i = 0; i < cloud.points.size(); ++i) { buffer[i * 3 + 0] = cloud.points[i].x; buffer[i * 3 + 1] = cloud.points[i].y; buffer[i * 3 + 2] = cloud.points[i].z; } return true; } bool VizumCamera::GetPointCloudVector(const std::string& frameId, std::vector& data) { if (m_cachedFrame.frameId != frameId) { return false; } const auto& cloud = m_cachedFrame.pointCloud; data.resize(cloud.points.size() * 3); for (size_t i = 0; i < cloud.points.size(); ++i) { data[i * 3 + 0] = cloud.points[i].x; data[i * 3 + 1] = cloud.points[i].y; data[i * 3 + 2] = cloud.points[i].z; } return true; } bool VizumCamera::GetDepthMap(const std::string& frameId, DepthMap& depthMap) { if (m_cachedFrame.frameId != frameId) { return false; } depthMap = m_cachedFrame.depthMap; return !depthMap.empty(); } bool VizumCamera::GetDepthMapRaw(const std::string& frameId, float* buffer, size_t bufferSize) { if (m_cachedFrame.frameId != frameId || !buffer) { return false; } const auto& depth = m_cachedFrame.depthMap; size_t requiredSize = depth.data.size() * sizeof(float); if (bufferSize < requiredSize) { return false; } memcpy(buffer, depth.data.data(), requiredSize); return true; } bool VizumCamera::CaptureFrame(FrameData& frameData, CaptureType captureType) { std::string frameId; if (!TriggerCapture(frameId, captureType)) { return false; } frameData.frameId = frameId; frameData.timestamp = m_cachedFrame.timestamp; if (captureType & CaptureType::Image) { frameData.image = m_cachedFrame.leftImage; } if (captureType & CaptureType::PointCloud) { frameData.pointCloud = m_cachedFrame.pointCloud; } if (captureType & CaptureType::DepthMap) { frameData.depthMap = m_cachedFrame.depthMap; } return true; } // ============================================================================ // 异步采集实现(预留) // ============================================================================ bool VizumCamera::StartAsyncCapture(FrameCallback callback, CaptureType captureType) { (void)callback; (void)captureType; return false; // 暂不实现 } void VizumCamera::StopAsyncCapture() { // 暂不实现 } bool VizumCamera::IsAsyncCapturing() const { return false; } // ============================================================================ // 相机参数实现 // ============================================================================ bool VizumCamera::GetCameraMatrix(CameraMatrix& matrix) { #ifdef ENABLE_VIZUM_CAMERA if (!m_isConnected || !m_deviceHandle) { return false; } // 获取左目内参 (3x3 + 5x1 畸变参数) double calibData[14] = {0}; int result = VzNL_QueryLeftEyeCalibData(m_deviceHandle, calibData); if (result != 0) { char errorMsg[256] = {0}; VzNL_GetErrorInfo(result, errorMsg); LOG_ERROR("[VizumCamera] Failed to get camera matrix: {}", errorMsg); return false; } // calibData格式: [fx, fy, cx, cy, k1, k2, p1, p2, k3, ...] // 内参矩阵: [fx, 0, cx; 0, fy, cy; 0, 0, 1] matrix.fx = static_cast(calibData[0]); matrix.fy = static_cast(calibData[1]); matrix.cx = static_cast(calibData[2]); matrix.cy = static_cast(calibData[3]); return true; #else return false; #endif } bool VizumCamera::GetCameraMatrixVector(std::vector& matrixVec) { CameraMatrix matrix; if (!GetCameraMatrix(matrix)) { return false; } matrixVec = matrix.toVector(); return true; } bool VizumCamera::GetDistortionCoeffs(DistortionCoeffs& coeffs) { #ifdef ENABLE_VIZUM_CAMERA if (!m_isConnected || !m_deviceHandle) { return false; } // 获取左目标定数据 double calibData[14] = {0}; int result = VzNL_QueryLeftEyeCalibData(m_deviceHandle, calibData); if (result != 0) { return false; } // calibData格式: [fx, fy, cx, cy, k1, k2, p1, p2, k3, ...] coeffs.k1 = static_cast(calibData[4]); coeffs.k2 = static_cast(calibData[5]); coeffs.p1 = static_cast(calibData[6]); coeffs.p2 = static_cast(calibData[7]); coeffs.k3 = static_cast(calibData[8]); return true; #else return false; #endif } bool VizumCamera::GetDistortionCoeffsVector(std::vector& coeffsVec) { DistortionCoeffs coeffs; if (!GetDistortionCoeffs(coeffs)) { return false; } coeffsVec = coeffs.toVector(); return true; } // ============================================================================ // 配置接口实现 // ============================================================================ bool VizumCamera::GetIntParam(const std::string& name, int& value) { (void)name; (void)value; // TODO: 根据需要实现 return false; } bool VizumCamera::SetIntParam(const std::string& name, int value) { (void)name; (void)value; // TODO: 根据需要实现 return false; } bool VizumCamera::GetFloatParam(const std::string& name, float& value) { #ifdef ENABLE_VIZUM_CAMERA const std::lock_guard deviceLock(m_deviceAccessMutex); if (!m_isConnected || !m_deviceHandle) { return false; } if (name == "ExposureTime" || name == "Exposure" || name == "exposure") { EVzNLExposeMode exposeMode = keVzNLExposeMode_Fix; uint32_t exposeUs = 0; const int result = VzNL_GetConfigEyeExpose(m_deviceHandle, &exposeMode, &exposeUs); if (result != 0) { return false; } value = static_cast(exposeUs); return true; } if (name == "Gain" || name == "gain") { unsigned short rawGain = 0; int result = VzNL_GetCameraGain(m_deviceHandle, keEyeSensorType_Left, &rawGain); if (result != 0) { result = VzNL_GetCameraGain(m_deviceHandle, keEyeSensorType_Right, &rawGain); } if (result != 0) { return false; } value = static_cast(rawGain); return true; } return false; #else (void)name; (void)value; return false; #endif } bool VizumCamera::SetFloatParam(const std::string& name, float value) { #ifdef ENABLE_VIZUM_CAMERA const std::lock_guard deviceLock(m_deviceAccessMutex); if (!m_isConnected || !m_deviceHandle) { return false; } if (name == "ExposureTime" || name == "Exposure" || name == "exposure") { unsigned int minVal = 1; unsigned int maxVal = 65535; VzNL_QueryParamRange(m_deviceHandle, keDeviceParamType_Expose, &minVal, &maxVal); LOG_INFO("[VizumCamera] SetExposure: requested={:.1f}us, range=[{}, {}]", value, minVal, maxVal); const float bounded = std::clamp(value, static_cast(minVal), static_cast(maxVal)); const uint32_t exposeUs = static_cast(bounded + 0.5f); const int result = VzNL_ConfigEyeExpose(m_deviceHandle, keVzNLExposeMode_Fix, exposeUs); if (result != 0) { char errMsg[256] = {0}; VzNL_GetErrorInfo(result, errMsg); LOG_ERROR("[VizumCamera] VzNL_ConfigEyeExpose failed: code={} msg={}", result, errMsg); } else { LOG_INFO("[VizumCamera] VzNL_ConfigEyeExpose OK: {}us", exposeUs); } return result == 0; } if (name == "Gain" || name == "gain") { unsigned int minVal = 0; unsigned int maxVal = 255; VzNL_QueryParamRange(m_deviceHandle, keDeviceParamType_Gain, &minVal, &maxVal); LOG_INFO("[VizumCamera] SetGain: requested={:.2f}, range=[{}, {}]", value, minVal, maxVal); const float bounded = std::clamp(value, static_cast(minVal), static_cast(maxVal)); const unsigned short rawGain = static_cast(bounded + 0.5f); const int leftResult = VzNL_SetCameraGain(m_deviceHandle, keEyeSensorType_Left, rawGain); const int rightResult = VzNL_SetCameraGain(m_deviceHandle, keEyeSensorType_Right, rawGain); if (leftResult != 0 && rightResult != 0) { char errMsgL[256] = {0}, errMsgR[256] = {0}; VzNL_GetErrorInfo(leftResult, errMsgL); VzNL_GetErrorInfo(rightResult, errMsgR); LOG_ERROR("[VizumCamera] VzNL_SetCameraGain both failed: L={}({}) R={}({})", leftResult, errMsgL, rightResult, errMsgR); } else { LOG_INFO("[VizumCamera] VzNL_SetCameraGain OK: gain={} L={} R={}", rawGain, leftResult, rightResult); } return leftResult == 0 || rightResult == 0; } return false; #else (void)name; (void)value; return false; #endif } bool VizumCamera::GetStringParam(const std::string& name, std::string& value) { (void)name; (void)value; // TODO: 根据需要实现 return false; } bool VizumCamera::SetStringParam(const std::string& name, const std::string& value) { (void)name; (void)value; // TODO: 根据需要实现 return false; } bool VizumCamera::GetConfig(std::string& configJson) { (void)configJson; // TODO: 根据需要实现 return false; } bool VizumCamera::SetConfig(const std::string& configJson) { (void)configJson; // TODO: 根据需要实现 return false; } bool VizumCamera::GetDetectROI(ROIRect& leftROI, ROIRect& rightROI) { #ifdef ENABLE_VIZUM_CAMERA const std::lock_guard deviceLock(m_deviceAccessMutex); if (!m_isConnected || !m_deviceHandle) { LOG_WARN("[VizumCamera] GetDetectROI failed: device not connected"); return false; } SVzNLROIRect sdkLeft{}; SVzNLROIRect sdkRight{}; const int ret = VzNL_GetConfigDetectROI(m_deviceHandle, &sdkLeft, &sdkRight); if (ret != 0) { char errorMsg[256] = {0}; VzNL_GetErrorInfo(ret, errorMsg); LOG_ERROR("[VizumCamera] VzNL_GetConfigDetectROI failed: {} (error code: {})", errorMsg, ret); return false; } leftROI = {sdkLeft.left, sdkLeft.right, sdkLeft.top, sdkLeft.bottom}; rightROI = {sdkRight.left, sdkRight.right, sdkRight.top, sdkRight.bottom}; LOG_INFO("[VizumCamera] Detect ROI read only: left=[{}, {}, {}, {}], " "right=[{}, {}, {}, {}]", leftROI.left, leftROI.right, leftROI.top, leftROI.bottom, rightROI.left, rightROI.right, rightROI.top, rightROI.bottom); return true; #else (void)leftROI; (void)rightROI; return false; #endif } bool VizumCamera::SetCalibROIEnabled(bool enable) { #ifdef ENABLE_VIZUM_CAMERA const std::lock_guard deviceLock(m_deviceAccessMutex); if (!m_isConnected || !m_deviceHandle) { LOG_WARN("[VizumCamera] SetCalibROIEnabled({}) failed: device not connected", enable); return false; } const int ret = VzNL_EnableCalibROI(m_deviceHandle, enable ? VzTrue : VzFalse); if (ret != 0) { LOG_ERROR("[VizumCamera] VzNL_EnableCalibROI({}) failed, error={}", enable, ret); return false; } LOG_INFO("[VizumCamera] CalibROI (rectified image output) {}", enable ? "enabled" : "disabled"); return true; #else (void)enable; return false; #endif } SLCameraPtr CreateVizumCamera() { return std::make_unique(); } #undef m_deviceHandle #undef m_vzDeviceInfo #undef m_ipAddress #undef m_deviceInfo #undef m_isConnected #undef m_isAsyncCapturing #undef m_frameCallback #undef m_captureType #undef m_mutex #undef m_deviceAccessMutex #undef m_frameCounter #undef m_cachedFrame #undef m_accumulatedScanLines #undef m_scanLineMutex #undef m_autoDetectRunning #undef m_isPaused #undef m_scanCompletionStatus #undef m_accumulatedCloud } // namespace StructuredLight