fix: capture full image without changing detection ROI

This commit is contained in:
Ben 2026-08-26 10:51:39 +08:00
parent 46f025327b
commit d054ea7017
2 changed files with 134 additions and 100 deletions

View File

@ -13,6 +13,10 @@
#include "VZNL_DetectConfig.h" #include "VZNL_DetectConfig.h"
#include "VZNL_EyeConfig.h" #include "VZNL_EyeConfig.h"
#include "VZNL_SwingMotor.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 #endif
/** /**
@ -106,98 +110,92 @@ struct VizumCamera::Impl {
#ifdef ENABLE_VIZUM_CAMERA #ifdef ENABLE_VIZUM_CAMERA
namespace { namespace {
class DetectRoiRestoreGuard std::mutex& OutputRoiModeMutex()
{
static std::mutex mutex;
return mutex;
}
class OutputRoiModeRestoreGuard
{ {
public: public:
DetectRoiRestoreGuard(VZNLHANDLE device, OutputRoiModeRestoreGuard()
const SVzNLROIRect& leftROI,
const SVzNLROIRect& rightROI)
: m_device(device), m_leftROI(leftROI), m_rightROI(rightROI)
{ {
VzNL_SetOutputROIImage(VzFalse);
LOG_INFO("[VizumCamera] SDK image output switched to full-frame mode; detection ROI unchanged");
} }
bool RestoreNow() void RestoreNow()
{ {
if (m_restored) if (m_restored)
return true; return;
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;
}
VzNL_SetOutputROIImage(VzTrue);
m_restored = true; m_restored = true;
LOG_INFO("[VizumCamera] Detection ROI restored: left=[{}, {}, {}, {}], " LOG_INFO("[VizumCamera] SDK image output restored to configured-ROI mode");
"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() ~OutputRoiModeRestoreGuard()
{ {
if (!m_restored && !RestoreNow()) RestoreNow();
LOG_ERROR("[VizumCamera] Emergency detection ROI restoration failed");
} }
DetectRoiRestoreGuard(const DetectRoiRestoreGuard&) = delete; OutputRoiModeRestoreGuard(const OutputRoiModeRestoreGuard&) = delete;
DetectRoiRestoreGuard& operator=(const DetectRoiRestoreGuard&) = delete; OutputRoiModeRestoreGuard& operator=(const OutputRoiModeRestoreGuard&) = delete;
private: private:
VZNLHANDLE m_device = nullptr;
SVzNLROIRect m_leftROI{};
SVzNLROIRect m_rightROI{};
bool m_restored = false; bool m_restored = false;
}; };
bool NormalizeToDeviceFrame(SLImage& image, uint32_t frameWidth, uint32_t frameHeight) class SdkImageReleaseGuard
{ {
if (image.empty() || frameWidth == 0 || frameHeight == 0 public:
|| image.originX < 0 || image.originY < 0 || image.channels <= 0) { SdkImageReleaseGuard(SVzNLImageData*& image, const char* label)
return false; : m_image(image), m_label(label)
{
} }
const uint64_t right = static_cast<uint64_t>(image.originX) + image.width; bool ReleaseNow()
const uint64_t bottom = static_cast<uint64_t>(image.originY) + image.height; {
if (right > frameWidth || bottom > frameHeight) if (m_attempted)
return false; return m_succeeded;
if (image.originX == 0 && image.originY == 0 m_attempted = true;
&& image.width == frameWidth && image.height == frameHeight) { if (!m_image) {
return true; 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; ~SdkImageReleaseGuard()
const size_t pixelBytes = static_cast<size_t>(image.channels) * bytesPerChannel; {
const size_t sourceRowBytes = static_cast<size_t>(image.width) * pixelBytes; ReleaseNow();
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<size_t>(frameWidth) * frameHeight * pixelBytes, 0);
const size_t targetRowBytes = static_cast<size_t>(frameWidth) * pixelBytes;
const size_t targetXBytes = static_cast<size_t>(image.originX) * pixelBytes;
for (uint32_t row = 0; row < image.height; ++row) {
const size_t sourceOffset = static_cast<size_t>(row) * sourceRowBytes;
const size_t targetOffset = (static_cast<size_t>(image.originY) + row) * targetRowBytes
+ targetXBytes;
std::memcpy(fullImage.data.data() + targetOffset,
image.data.data() + sourceOffset, sourceRowBytes);
} }
image = std::move(fullImage); SdkImageReleaseGuard(const SdkImageReleaseGuard&) = delete;
return true; 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) 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: " LOG_INFO("[VizumCamera] Device status callback registered; scan completion events: "
"Device_Auto_Stop or Device_Swing_Finish"); "Device_Auto_Stop or Device_Swing_Finish");
// OpenDevice 之后立即设置图像输出格式(在此之前设置对本次连接无效) // 输出格式和 ROI 图开关均为 SDK 全局状态,和所有 GetEyeImage 调用共用同一把锁。
VzNL_SetOutputImageFormat(keVzNLImageType_BGR888); {
LOG_INFO("[VizumCamera] Output image format set to BGR888"); const std::lock_guard<std::mutex> 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()) { if (!UpdateDeviceInfo()) {
@ -850,6 +853,9 @@ bool VizumCamera::CaptureAndCacheData(CaptureType captureType)
{ {
LOG_INFO("[VizumCamera] CaptureAndCacheData called, captureType={}", static_cast<int>(captureType)); LOG_INFO("[VizumCamera] CaptureAndCacheData called, captureType={}", static_cast<int>(captureType));
#ifdef ENABLE_VIZUM_CAMERA #ifdef ENABLE_VIZUM_CAMERA
std::unique_lock<std::mutex> outputModeLock(OutputRoiModeMutex(), std::defer_lock);
if (captureType & CaptureType::Image)
outputModeLock.lock();
const std::lock_guard<std::mutex> deviceLock(m_deviceAccessMutex); const std::lock_guard<std::mutex> deviceLock(m_deviceAccessMutex);
if (!m_deviceHandle) { if (!m_deviceHandle) {
LOG_ERROR("[VizumCamera] CaptureAndCacheData failed: Device handle is null"); LOG_ERROR("[VizumCamera] CaptureAndCacheData failed: Device handle is null");
@ -872,6 +878,8 @@ bool VizumCamera::CaptureAndCacheData(CaptureType captureType)
SVzNLImageData* pLeftImage = nullptr; SVzNLImageData* pLeftImage = nullptr;
SVzNLImageData* pRightImage = nullptr; SVzNLImageData* pRightImage = nullptr;
// 每次普通取图都显式自愈到配置 ROI 输出,避免继承异常退出留下的全图模式。
VzNL_SetOutputROIImage(VzTrue);
int result = VzNL_GetEyeImage(m_deviceHandle, &pLeftImage, &pRightImage, 3000); int result = VzNL_GetEyeImage(m_deviceHandle, &pLeftImage, &pRightImage, 3000);
if (result == 0) { if (result == 0) {
if (pLeftImage) { if (pLeftImage) {
@ -889,6 +897,10 @@ bool VizumCamera::CaptureAndCacheData(CaptureType captureType)
} }
} }
// 图像读取结束即可释放 SDK 全局输出锁;点云扫描只保留设备锁。
if (outputModeLock.owns_lock())
outputModeLock.unlock();
// 采集点云(通过激光扫描) // 采集点云(通过激光扫描)
// 按照 VZNLCameraAdapter::scan 的逻辑,使用自动检测模式采集 // 按照 VZNLCameraAdapter::scan 的逻辑,使用自动检测模式采集
if (captureType & CaptureType::PointCloud) { if (captureType & CaptureType::PointCloud) {
@ -965,6 +977,7 @@ bool VizumCamera::CaptureFullFrameLeftImage(SLImage& image, std::string& frameId
image.clear(); image.clear();
frameId.clear(); frameId.clear();
#ifdef ENABLE_VIZUM_CAMERA #ifdef ENABLE_VIZUM_CAMERA
const std::lock_guard<std::mutex> outputModeLock(OutputRoiModeMutex());
const std::lock_guard<std::mutex> deviceLock(m_deviceAccessMutex); const std::lock_guard<std::mutex> deviceLock(m_deviceAccessMutex);
if (!m_isConnected || !m_deviceHandle) { if (!m_isConnected || !m_deviceHandle) {
LOG_ERROR("[VizumCamera] Full-frame capture failed: device not connected"); LOG_ERROR("[VizumCamera] Full-frame capture failed: device not connected");
@ -993,24 +1006,12 @@ bool VizumCamera::CaptureFullFrameLeftImage(SLImage& image, std::string& frameId
return false; return false;
} }
DetectRoiRestoreGuard restoreGuard(m_deviceHandle, oldLeftROI, oldRightROI); OutputRoiModeRestoreGuard outputModeGuard;
const SVzNLROIRect fullLeftROI{0, static_cast<int>(resolution.nFrameWidth),
0, static_cast<int>(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* leftImage = nullptr;
SVzNLImageData* rightImage = nullptr; SVzNLImageData* rightImage = nullptr;
SdkImageReleaseGuard leftImageGuard(leftImage, "left");
SdkImageReleaseGuard rightImageGuard(rightImage, "right");
ret = VzNL_GetEyeImage(m_deviceHandle, &leftImage, &rightImage, 3000); ret = VzNL_GetEyeImage(m_deviceHandle, &leftImage, &rightImage, 3000);
bool converted = false; bool converted = false;
if (ret == 0 && leftImage) { 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: {})", LOG_ERROR("[VizumCamera] Full-frame image acquisition failed: {} (error code: {})",
errorMsg, ret); errorMsg, ret);
} }
if (leftImage) const bool leftReleased = leftImageGuard.ReleaseNow();
VzNL_ReleaseImage(&leftImage); const bool rightReleased = rightImageGuard.ReleaseNow();
if (rightImage) const bool imagesReleased = leftReleased && rightReleased;
VzNL_ReleaseImage(&rightImage);
const bool normalized = converted const bool exactFullFrame = converted
&& NormalizeToDeviceFrame(image, resolution.nFrameWidth, resolution.nFrameHeight); && image.originX == 0 && image.originY == 0
if (converted && !normalized) { && image.width == resolution.nFrameWidth
LOG_ERROR("[VizumCamera] SDK full-frame image cannot be placed in device frame: " && image.height == resolution.nFrameHeight;
"image={}x{} origin=({}, {}), device={}x{}", 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, image.width, image.height, image.originX, image.originY,
resolution.nFrameWidth, resolution.nFrameHeight); resolution.nFrameWidth, resolution.nFrameHeight);
} }
const bool restored = restoreGuard.RestoreNow(); outputModeGuard.RestoreNow();
if (!normalized || !restored) {
SVzNLROIRect currentLeftROI{};
SVzNLROIRect currentRightROI{};
ret = VzNL_GetConfigDetectROI(m_deviceHandle, &currentLeftROI, &currentRightROI);
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(); image.clear();
return false; return false;
} }
const uint64_t id = ++m_frameCounter; const uint64_t id = ++m_frameCounter;
frameId = "VZ_FULL_" + std::to_string(id); 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); "output={}x{} origin=(0, 0)", frameId, image.width, image.height);
return true; return true;
#else #else

View File

@ -128,11 +128,11 @@ public:
bool GetRightImage(SLImage& image); bool GetRightImage(SLImage& image);
/** /**
* @brief 采集全幅左目图像并在返回前恢复原检测 ROI * @brief 不修改检测 ROI,仅切换 SDK 输出模式采集全幅左目图像
* @param[out] image 原点归一到(0,0)的设备全分辨率图像 * @param[out] image SDK 原始返回的设备全分辨率图像(原点必须为(0,0))
* @param[out] frameId 本次采集帧编号 * @param[out] frameId 本次采集帧编号
* @return 采集、全幅归一化及左右检测 ROI 恢复是否全部成功 * @return 采集、SDK 图像释放、精确全幅校验、输出模式恢复及检测 ROI 未变化校验是否全部成功
* @note 保持当前 CalibROI 状态不变;任何失败路径都会尝试恢复原左右 ROI * @note 保持当前 CalibROI 和检测 ROI 不变;任何失败路径都会恢复 ROI 图输出模式
*/ */
bool CaptureFullFrameLeftImage(SLImage& image, std::string& frameId); bool CaptureFullFrameLeftImage(SLImage& image, std::string& frameId);