From d054ea7017e87dc6fc81ba6c1229ba25daeeca23 Mon Sep 17 00:00:00 2001 From: Ben <987807597@qq.com> Date: Wed, 26 Aug 2026 10:51:39 +0800 Subject: [PATCH] fix: capture full image without changing detection ROI --- src/VizumCamera.cpp | 226 +++++++++++++++++++++++++------------------- src/VizumCamera.h | 8 +- 2 files changed, 134 insertions(+), 100 deletions(-) diff --git a/src/VizumCamera.cpp b/src/VizumCamera.cpp index 0269cd5..f079ee6 100755 --- a/src/VizumCamera.cpp +++ b/src/VizumCamera.cpp @@ -13,6 +13,10 @@ #include "VZNL_DetectConfig.h" #include "VZNL_EyeConfig.h" #include "VZNL_SwingMotor.h" + +// The bundled VZNL_Common.h omits this declaration, while the bundled +// VzNLDetect import library exports the API (newer official headers declare it). +VZNLAPI void VzNL_SetOutputROIImage(VzBool bOutputROI); #endif /** @@ -106,98 +110,92 @@ struct VizumCamera::Impl { #ifdef ENABLE_VIZUM_CAMERA namespace { -class DetectRoiRestoreGuard +std::mutex& OutputRoiModeMutex() +{ + static std::mutex mutex; + return mutex; +} + +class OutputRoiModeRestoreGuard { public: - DetectRoiRestoreGuard(VZNLHANDLE device, - const SVzNLROIRect& leftROI, - const SVzNLROIRect& rightROI) - : m_device(device), m_leftROI(leftROI), m_rightROI(rightROI) + OutputRoiModeRestoreGuard() { + VzNL_SetOutputROIImage(VzFalse); + LOG_INFO("[VizumCamera] SDK image output switched to full-frame mode; detection ROI unchanged"); } - bool RestoreNow() + void 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; - } + return; + VzNL_SetOutputROIImage(VzTrue); 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; + LOG_INFO("[VizumCamera] SDK image output restored to configured-ROI mode"); } - ~DetectRoiRestoreGuard() + ~OutputRoiModeRestoreGuard() { - if (!m_restored && !RestoreNow()) - LOG_ERROR("[VizumCamera] Emergency detection ROI restoration failed"); + RestoreNow(); } - DetectRoiRestoreGuard(const DetectRoiRestoreGuard&) = delete; - DetectRoiRestoreGuard& operator=(const DetectRoiRestoreGuard&) = delete; + OutputRoiModeRestoreGuard(const OutputRoiModeRestoreGuard&) = delete; + OutputRoiModeRestoreGuard& operator=(const OutputRoiModeRestoreGuard&) = 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) +class SdkImageReleaseGuard { - if (image.empty() || frameWidth == 0 || frameHeight == 0 - || image.originX < 0 || image.originY < 0 || image.channels <= 0) { - return false; +public: + SdkImageReleaseGuard(SVzNLImageData*& image, const char* label) + : m_image(image), m_label(label) + { } - 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; + bool ReleaseNow() + { + if (m_attempted) + return m_succeeded; - if (image.originX == 0 && image.originY == 0 - && image.width == frameWidth && image.height == frameHeight) { - return true; + m_attempted = true; + if (!m_image) { + m_succeeded = true; + return true; + } + + const int ret = VzNL_ReleaseImage(&m_image); + m_succeeded = ret == 0; + if (!m_succeeded) { + char errorMsg[256] = {0}; + VzNL_GetErrorInfo(ret, errorMsg); + LOG_ERROR("[VizumCamera] Failed to release {} SDK image: {} (error code: {})", + m_label, errorMsg, ret); + } + return m_succeeded; } - 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); + ~SdkImageReleaseGuard() + { + ReleaseNow(); } - image = std::move(fullImage); - return true; + SdkImageReleaseGuard(const SdkImageReleaseGuard&) = delete; + SdkImageReleaseGuard& operator=(const SdkImageReleaseGuard&) = delete; + +private: + SVzNLImageData*& m_image; + const char* m_label = nullptr; + bool m_attempted = false; + bool m_succeeded = false; +}; + +bool SameSdkRoi(const SVzNLROIRect& lhs, const SVzNLROIRect& rhs) +{ + return lhs.left == rhs.left && lhs.right == rhs.right + && lhs.top == rhs.top && lhs.bottom == rhs.bottom; } const char* DeviceStatusName(int status) @@ -686,9 +684,14 @@ bool VizumCamera::Connect(const std::string& ipAddress) 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"); + // 输出格式和 ROI 图开关均为 SDK 全局状态,和所有 GetEyeImage 调用共用同一把锁。 + { + const std::lock_guard outputModeLock(OutputRoiModeMutex()); + VzNL_SetOutputImageFormat(keVzNLImageType_BGR888); + LOG_INFO("[VizumCamera] Output image format set to BGR888"); + VzNL_SetOutputROIImage(VzTrue); + LOG_INFO("[VizumCamera] SDK image output initialized to configured-ROI mode"); + } // 更新设备信息 if (!UpdateDeviceInfo()) { @@ -850,6 +853,9 @@ bool VizumCamera::CaptureAndCacheData(CaptureType captureType) { LOG_INFO("[VizumCamera] CaptureAndCacheData called, captureType={}", static_cast(captureType)); #ifdef ENABLE_VIZUM_CAMERA + std::unique_lock outputModeLock(OutputRoiModeMutex(), std::defer_lock); + if (captureType & CaptureType::Image) + outputModeLock.lock(); const std::lock_guard deviceLock(m_deviceAccessMutex); if (!m_deviceHandle) { LOG_ERROR("[VizumCamera] CaptureAndCacheData failed: Device handle is null"); @@ -871,7 +877,9 @@ bool VizumCamera::CaptureAndCacheData(CaptureType captureType) if (captureType & CaptureType::Image) { SVzNLImageData* pLeftImage = nullptr; SVzNLImageData* pRightImage = nullptr; - + + // 每次普通取图都显式自愈到配置 ROI 输出,避免继承异常退出留下的全图模式。 + VzNL_SetOutputROIImage(VzTrue); int result = VzNL_GetEyeImage(m_deviceHandle, &pLeftImage, &pRightImage, 3000); if (result == 0) { if (pLeftImage) { @@ -888,6 +896,10 @@ bool VizumCamera::CaptureAndCacheData(CaptureType captureType) LOG_ERROR("[VizumCamera] Failed to get image: {}", errorMsg); } } + + // 图像读取结束即可释放 SDK 全局输出锁;点云扫描只保留设备锁。 + if (outputModeLock.owns_lock()) + outputModeLock.unlock(); // 采集点云(通过激光扫描) // 按照 VZNLCameraAdapter::scan 的逻辑,使用自动检测模式采集 @@ -965,6 +977,7 @@ bool VizumCamera::CaptureFullFrameLeftImage(SLImage& image, std::string& frameId image.clear(); frameId.clear(); #ifdef ENABLE_VIZUM_CAMERA + const std::lock_guard outputModeLock(OutputRoiModeMutex()); const std::lock_guard deviceLock(m_deviceAccessMutex); if (!m_isConnected || !m_deviceHandle) { LOG_ERROR("[VizumCamera] Full-frame capture failed: device not connected"); @@ -993,24 +1006,12 @@ bool VizumCamera::CaptureFullFrameLeftImage(SLImage& image, std::string& frameId 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); + OutputRoiModeRestoreGuard outputModeGuard; SVzNLImageData* leftImage = nullptr; SVzNLImageData* rightImage = nullptr; + SdkImageReleaseGuard leftImageGuard(leftImage, "left"); + SdkImageReleaseGuard rightImageGuard(rightImage, "right"); ret = VzNL_GetEyeImage(m_deviceHandle, &leftImage, &rightImage, 3000); bool converted = false; if (ret == 0 && leftImage) { @@ -1024,29 +1025,62 @@ bool VizumCamera::CaptureFullFrameLeftImage(SLImage& image, std::string& frameId LOG_ERROR("[VizumCamera] Full-frame image acquisition failed: {} (error code: {})", errorMsg, ret); } - if (leftImage) - VzNL_ReleaseImage(&leftImage); - if (rightImage) - VzNL_ReleaseImage(&rightImage); + const bool leftReleased = leftImageGuard.ReleaseNow(); + const bool rightReleased = rightImageGuard.ReleaseNow(); + const bool imagesReleased = leftReleased && rightReleased; - 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{}", + const bool exactFullFrame = converted + && image.originX == 0 && image.originY == 0 + && image.width == resolution.nFrameWidth + && image.height == resolution.nFrameHeight; + if (converted && !exactFullFrame) { + LOG_ERROR("[VizumCamera] SDK output-mode switch did not return an exact full frame; " + "capture rejected without zero-padding: 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) { + outputModeGuard.RestoreNow(); + + SVzNLROIRect currentLeftROI{}; + SVzNLROIRect currentRightROI{}; + ret = VzNL_GetConfigDetectROI(m_deviceHandle, ¤tLeftROI, ¤tRightROI); + const bool roiUnchanged = ret == 0 + && SameSdkRoi(currentLeftROI, oldLeftROI) + && SameSdkRoi(currentRightROI, oldRightROI); + if (ret != 0) { + char errorMsg[256] = {0}; + VzNL_GetErrorInfo(ret, errorMsg); + LOG_ERROR("[VizumCamera] Failed to query detection ROI after full-frame capture: {} " + "(error code: {})", errorMsg, ret); + } else if (!roiUnchanged) { + LOG_ERROR("[VizumCamera] Detection ROI changed during output-only full-frame capture; " + "before left=[{}, {}, {}, {}], right=[{}, {}, {}, {}]; " + "after left=[{}, {}, {}, {}], right=[{}, {}, {}, {}]", + oldLeftROI.left, oldLeftROI.right, oldLeftROI.top, oldLeftROI.bottom, + oldRightROI.left, oldRightROI.right, oldRightROI.top, oldRightROI.bottom, + currentLeftROI.left, currentLeftROI.right, + currentLeftROI.top, currentLeftROI.bottom, + currentRightROI.left, currentRightROI.right, + currentRightROI.top, currentRightROI.bottom); + } else { + LOG_INFO("[VizumCamera] Detection ROI verified unchanged after full-frame capture: " + "left=[{}, {}, {}, {}], right=[{}, {}, {}, {}]", + currentLeftROI.left, currentLeftROI.right, + currentLeftROI.top, currentLeftROI.bottom, + currentRightROI.left, currentRightROI.right, + currentRightROI.top, currentRightROI.bottom); + } + + if (!exactFullFrame || !imagesReleased || !roiUnchanged) { 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={}, " + LOG_INFO("[VizumCamera] Full-frame capture completed without writing detection ROI: frameId={}, " "output={}x{} origin=(0, 0)", frameId, image.width, image.height); return true; #else diff --git a/src/VizumCamera.h b/src/VizumCamera.h index 18028fd..90c45ee 100755 --- a/src/VizumCamera.h +++ b/src/VizumCamera.h @@ -128,11 +128,11 @@ public: bool GetRightImage(SLImage& image); /** - * @brief 采集全幅左目图像并在返回前恢复原检测 ROI - * @param[out] image 原点归一到(0,0)的设备全分辨率图像 + * @brief 不修改检测 ROI,仅切换 SDK 输出模式采集全幅左目图像 + * @param[out] image SDK 原始返回的设备全分辨率图像(原点必须为(0,0)) * @param[out] frameId 本次采集帧编号 - * @return 采集、全幅归一化及左右检测 ROI 恢复是否全部成功 - * @note 保持当前 CalibROI 状态不变;任何失败路径都会尝试恢复原左右 ROI + * @return 采集、SDK 图像释放、精确全幅校验、输出模式恢复及检测 ROI 未变化校验是否全部成功 + * @note 保持当前 CalibROI 和检测 ROI 不变;任何失败路径都会恢复 ROI 图输出模式 */ bool CaptureFullFrameLeftImage(SLImage& image, std::string& frameId);