此页面由 AI 自动翻译。查看英文原版

本页目录

  • 自动校准选项
  • 使用动态校准库 (DCL)
  • 示例
  • 场景指南
  • PerformanceMode 调优
  • 限制和注意事项
  • 故障排除
  • 另请参阅

DynamicCalibration - 技术实现

Supported on:RVC2RVC4
DynamicCalibration 是内置于 DepthAI 3.0自校准工作流,可在温度变化、物理冲击或长期漂移导致工厂校准降级时恢复和维持立体精度。
本页将介绍将动态校准集成到您的项目的技术实现步骤。如果您想了解更多关于动态校准的通用信息,请访问 此页面.

主要功能

  • 恢复深度性能 — 使视差图恢复到最佳视觉质量。
  • 无需目标 — 在自然场景中运行;只需移动摄像头即可捕捉多样的视图。
  • 快速执行 — 通常在几秒钟内完成。
  • 健康监控 — 随时运行诊断,无需刷新新校准。

自动校准选项

有两种方法可以在 DynamicCalibration 之上运行自动校准:
  • 使用 AutoCalibration Host Node 在管道内进行显式节点级控制。
  • 使用 DEPTHAI_AUTOCALIBRATION 在部署时启用,而无需更改管道代码。
  • 使用 Pipeline.setAutoCalibrationMode() 的直接设置器来启用自动校准,而无需使用 AutoCalibration Host Node。

使用动态校准库 (DCL)

本节将对如何在 DepthAI 中使用 DynamicCalibration 节点进行动态校准工作流进行简单的、高层次的表示。
动态校准接收一个 DynamicCalibrationControl 消息作为输入,其中包含一个命令(例如 ApplyCalibrationCalibrate... 有关所有可用命令,请参阅消息定义)。该节点在三个输出队列之一中返回输出:具体取决于发送了哪个输入命令。在接下来的段落中,我们将探讨如何实际将 DCL 集成到您的代码中。

初始化 DynamicCalibration 节点

DynamicCalibration 节点需要来自同一设备的两个同步摄像头流。设置方法如下:
Python
1import depthai as dai
2
3# 初始化管道
4pipeline = dai.Pipeline()
5
6# 创建摄像头节点
7cam_left = pipeline.create(dai.node.Camera).build(dai.CameraBoardSocket.CAM_B)
8cam_right = pipeline.create(dai.node.Camera).build(dai.CameraBoardSocket.CAM_C)
9
10# 请求全分辨率 NV12 输出
11left_out = cam_left.requestFullResolutionOutput()
12right_out = cam_right.requestFullResolutionOutput()
13
14# 初始化 DynamicCalibration 节点
15dyn_calib = pipeline.create(dai.node.DynamicCalibration)
16
17# 将摄像头链接到 DynamicCalibration
18left_out.link(dyn_calib.left)
19right_out.link(dyn_calib.right)
20
21device = pipeline.getDefaultDevice()
22calibration = device.readCalibration()
23device.setCalibration(calibration)
24
25pipeline.start()
26while pipeline.isRunning():
27    ...

向节点发送命令

节点通过输入/输出消息队列进行通信。 DynamicCalibration 节点有几个队列,但最重要的控制队列是 inputControl
Python
1# 初始化命令输入队列
2command_input = dyn_calib.inputControl.createInputQueue()
3# 发送命令的示例
4command_input.send(dai.DynamicCalibrationControl.startCalibration())
可用命令
  • StartCalibration() - 启动校准过程。
  • StopCalibration() - 停止校准过程。
  • Calibrate(force=False) - 基于加载的数据计算新的校准。
    • force - 对加载的数据没有限制
  • CalibrationQuality(force=False) - 评估当前校准的质量。
    • force - 对加载的数据没有限制
  • LoadImage() - 从设备加载一张图像。
  • ComputeCalibrationMetrics(calibration) - 计算校准指标,如 dataQuality 和 calibrationConfidence。
  • ApplyCalibration(calibration) - 将校准应用于设备。
  • SetPerformanceMode(performanceMode) - 发送将要使用的性能模式。
  • ResetData() - 删除所有先前加载的数据。

从节点接收数据

该节点提供多个输出队列:
Python
1# 用于接收新校准的队列
2calibration_output = dyn_calib.calibrationOutput.createOutputQueue()
3# 用于接收覆盖率的队列
4coverage_output = dyn_calib.coverageOutput.createOutputQueue()
5# 用于检查校准质量的队列
6quality_output = dyn_calib.qualityOutput.createOutputQueue()
7# 用于检查校准指标的队列
8metrics_output = dyn_calib.metricsOutput.createOutputQueue()
请参阅 DynamicCalibrationResultCoverageDataCalibrationQualityCalibrationMetrics 的参考文档,了解输出的确切数据结构。

读取覆盖率数据

当手动加载图像(使用 LoadImage 命令)或在连续校准期间(运行 StartCalibration 命令后)加载图像时,将通过 coverageOutput 接收覆盖率数据。手动加载图像
Python
1# 加载单张图像
2command_input.send(dai.DynamicCalibrationControl.loadImage())
3
4# 加载后获取覆盖率
5coverage = coverage_output.get()
6print(f"Coverage = {coverage.meanCoverage}")
校准期间连续收集
Python
1command_input.send(dai.DynamicCalibrationControl.startCalibration())
2
3while pipeline.isRunning():
4    # 阻塞读取
5    coverage = coverage_output.get()
6    print(f"Coverage = {coverage.meanCoverage}")
7
8    # 非阻塞读取
9    coverage = coverage_output.tryGet()
10    if coverage:
11        print(f"Coverage = {coverage.meanCoverage}")

读取校准数据

校准结果可以从以下方式获得:
  • dai.DynamicCalibrationControl.startCalibration() — 开始收集数据并尝试校准。
  • dai.DynamicCalibrationControl.calibrate(force=False) — 使用现有加载的数据进行校准(必须提前使用 LoadImage 命令加载图像,如下例所示)。
校准数据将作为 DynamicCalibrationResult 消息类型返回。手动加载图像
Python
1# 加载单张图像
2command_inputsend.(dai.DynamicCalibrationControl.loadImage())
3
4# 发送校准命令
5command_input.send(dai.DynamicCalibrationControl.calibrate(force=False))
6
7# 加载后获取校准数据
8calibration = calibration_output.get()
9print(f"Calibration = {calibration.info}")
连续收集
Python
1# 开始收集数据并尝试校准
2command_input.send(dai.DynamicCalibrationControl.startCalibration())
3
4while pipeline.isRunning():
5    # 阻塞读取
6    calibration = calibration_output.get()
7    print(f"Calibration = {calibration.info}")
8
9    # 非阻塞读取
10    calibration = calibration_output.tryGet()
11    if calibration:
12        print(f"Calibration = {calibration.info}")

性能模式

使用以下命令设置性能模式:
Python
1# 设置性能模式
2dynCalibInputControl.send(dai.DynamicCalibrationControl.setPerformanceMode(dai.node.DynamicCalibration.OPTIMIZE_PERFORMANCE))
性能模式设置了校准所需的数据量。
Python
1dai.node.DynamicCalibration.PerformanceMode.OPTIMIZE_PERFORMANCE  # 最严格的模式
2dai.node.DynamicCalibration.PerformanceMode.DEFAULT               # 不那么严格,但通常足够
3dai.node.DynamicCalibration.PerformanceMode.OPTIMIZE_SPEED        # 优化速度而非性能
4dai.node.DynamicCalibration.PerformanceMode.STATIC_SCENERY        # 不严格
5dai.node.DynamicCalibration.PerformanceMode.SKIP_CHECKS           # 跳过所有内部检查

示例

动态校准交互式可视化工具

使用以下命令,您可以克隆并运行校准集成。
Command Line
1git clone https://github.com/luxonis/depthai-core.git
2cd depthai-core/
3python3 -m venv venv
4source venv/bin/activate
5python3 examples/python/install_requirements.py
6python3 examples/python/DynamicCalibration/calibration_integration.py

校准质量检查

Python

请按照 Github 上的 README 来运行此示例。
Python
GitHub
1import depthai as dai
2import numpy as np
3import time
4import cv2
5
6# ---------- Pipeline definition ----------
7with dai.Pipeline() as pipeline:
8    # Create camera nodes
9    monoLeft  = pipeline.create(dai.node.Camera).build(dai.CameraBoardSocket.CAM_B)
10    monoRight = pipeline.create(dai.node.Camera).build(dai.CameraBoardSocket.CAM_C)
11
12    # Request full resolution NV12 outputs
13    monoLeftOut  = monoLeft.requestFullResolutionOutput()
14    monoRightOut = monoRight.requestFullResolutionOutput()
15
16    # Initialize the DynamicCalibration node
17    dynCalib = pipeline.create(dai.node.DynamicCalibration)
18
19    # Link the cameras to the DynamicCalibration
20    monoLeftOut.link(dynCalib.left)
21    monoRightOut.link(dynCalib.right)
22
23    stereo = pipeline.create(dai.node.StereoDepth)
24    monoLeftOut.link(stereo.left)
25    monoRightOut.link(stereo.right)
26
27    # Queues
28    syncedLeftQueue  = stereo.syncedLeft.createOutputQueue()
29    syncedRightQueue = stereo.syncedRight.createOutputQueue()
30    disparityQueue = stereo.disparity.createOutputQueue()
31
32    # Initialize the command output queues for coverage and calibration quality
33    dynCalibCoverageQueue = dynCalib.coverageOutput.createOutputQueue()
34    dynCalibQualityQueue = dynCalib.qualityOutput.createOutputQueue()
35
36    # Initialize the command input queue
37    dynCalibInputControl = dynCalib.inputControl.createInputQueue()
38
39    device = pipeline.getDefaultDevice()
40    device.setCalibration(device.readCalibration())
41
42    # Setup the colormap for visualization
43    colorMap = cv2.applyColorMap(np.arange(256, dtype=np.uint8), cv2.COLORMAP_JET)
44    colorMap[0] = [0, 0, 0]  # to make zero-disparity pixels black
45    maxDisparity = 1
46
47    pipeline.start()
48    time.sleep(1) # wait for auto exposure to settle
49
50    while pipeline.isRunning():
51        leftSynced  = syncedLeftQueue.get()
52        rightSynced = syncedRightQueue.get()
53        disparity = disparityQueue.get()
54
55        cv2.imshow("left", leftSynced.getCvFrame())
56        cv2.imshow("right", rightSynced.getCvFrame())
57
58        # --- Disparity visualization ---
59        npDisparity = disparity.getFrame()
60        curMax = float(np.max(npDisparity))
61        if curMax > 0:
62            maxDisparity = max(maxDisparity, curMax)
63        normalized = (npDisparity / (maxDisparity if maxDisparity > 0 else 1.0) * 255.0).astype(np.uint8)
64        colorizedDisparity = cv2.applyColorMap(normalized, cv2.COLORMAP_JET)
65        colorizedDisparity[normalized == 0] = (0, 0, 0)
66        cv2.imshow("disparity", colorizedDisparity)
67
68        # --- Load one frame into calibration & read coverage
69        dynCalibInputControl.send(dai.DynamicCalibrationControl.loadImage())
70        coverage = dynCalibCoverageQueue.get()
71        if coverage is not None:
72            print(f"2D Spatial Coverage = {coverage.meanCoverage} / 100 [%]")
73            print(f"Data Acquired       = {coverage.dataAcquired} / 100 [%]")
74
75        # --- Request a quality evaluation & read result
76        dynCalibInputControl.send(dai.DynamicCalibrationControl.calibrationQuality(False))
77        dynQualityResult = dynCalibQualityQueue.get()
78        if dynQualityResult is not None:
79            print(f"Dynamic calibration status: {dynQualityResult.info}")
80
81            # If the calibration is successfully returned apply it to the device
82            if dynQualityResult.qualityData:
83                q = dynQualityResult.qualityData
84                print("Successfully evaluated Quality")
85                rotDiff = float(np.sqrt(q.rotationChange[0]**2 +
86                                        q.rotationChange[1]**2 +
87                                        q.rotationChange[2]**2))
88                print(f"Rotation difference: || r_current - r_new || = {rotDiff:.2f} deg")
89                print(f"Mean Sampson error achievable = {q.sampsonErrorNew:.3f} px")
90                print(f"Mean Sampson error current    = {q.sampsonErrorCurrent:.3f} px")
91                print(
92                    "Theoretical Depth Error Difference "
93                    f"@1m:{q.depthErrorDifference[0]:.2f}%, "
94                    f"2m:{q.depthErrorDifference[1]:.2f}%, "
95                    f"5m:{q.depthErrorDifference[2]:.2f}%, "
96                    f"10m:{q.depthErrorDifference[3]:.2f}%"
97                )
98                # Reset temporary accumulators before the next cycle
99                dynCalibInputControl.send(dai.DynamicCalibrationControl.resetData())
100
101        key = cv2.waitKey(1)
102        if key == ord('q'):
103            pipeline.stop()
104            break

C++

来自我们 Github 的示例:Github
1#include <chrono>
2#include <cmath>
3#include <iomanip>
4#include <iostream>
5#include <opencv2/opencv.hpp>
6#include <thread>
7
8#include "depthai/depthai.hpp"
9
10int main() {
11    auto device = std::make_shared<dai::Device>();
12
13    // ---------- Pipeline definition ----------
14    dai::Pipeline pipeline(device);
15
16    auto monoLeft = pipeline.create<dai::node::Camera>()->build(dai::CameraBoardSocket::CAM_B);
17    auto monoRight = pipeline.create<dai::node::Camera>()->build(dai::CameraBoardSocket::CAM_C);
18
19    auto* leftOut = monoLeft->requestFullResolutionOutput();
20    auto* rightOut = monoRight->requestFullResolutionOutput();
21
22    // Dynamic-calibration node
23    auto dynCalib = pipeline.create<dai::node::DynamicCalibration>();
24    leftOut->link(dynCalib->left);
25    rightOut->link(dynCalib->right);
26
27    auto stereo = pipeline.create<dai::node::StereoDepth>();
28    leftOut->link(stereo->left);
29    rightOut->link(stereo->right);
30
31    // In-pipeline host queues
32    auto leftSyncedQueue = stereo->syncedLeft.createOutputQueue();
33    auto rightSyncedQueue = stereo->syncedRight.createOutputQueue();
34    auto disparityQueue = stereo->disparity.createOutputQueue();
35
36    auto dynQualityOutQ = dynCalib->qualityOutput.createOutputQueue();
37    auto dynCoverageOutQ = dynCalib->coverageOutput.createOutputQueue();
38    auto dynCalibInputControl = dynCalib->inputControl.createInputQueue();
39
40    device->setCalibration(device->readCalibration());
41
42    pipeline.start();
43    std::this_thread::sleep_for(std::chrono::seconds(1));  // wait for autoexposure to settle
44
45    using DCC = dai::DynamicCalibrationControl;
46
47    while(pipeline.isRunning()) {
48        auto leftSynced = leftSyncedQueue->get<dai::ImgFrame>();
49        auto rightSynced = rightSyncedQueue->get<dai::ImgFrame>();
50        auto disparity = disparityQueue->get<dai::ImgFrame>();
51
52        cv::imshow("left", leftSynced->getCvFrame());
53        cv::imshow("right", rightSynced->getCvFrame());
54
55        // --- Load one frame pair into the calibration pipeline
56        dynCalibInputControl->send(DCC::loadImage());
57
58        // Wait for coverage info
59        auto coverageMsg = dynCoverageOutQ->get<dai::CoverageData>();
60        if(coverageMsg) {
61            std::cout << "2D Spatial Coverage = " << coverageMsg->meanCoverage << " / 100 [%]" << std::endl;
62            std::cout << "Data Acquired       = " << coverageMsg->dataAcquired << " / 100 [%]" << std::endl;
63        }
64
65        // Request a calibration quality evaluation (non-forced)
66        dynCalibInputControl->send(DCC::calibrationQuality(false));
67
68        // Wait for calibration result
69        auto dynCalibrationResult = dynQualityOutQ->get<dai::CalibrationQuality>();
70        if(dynCalibrationResult) {
71            std::cout << "Dynamic calibration status: " << dynCalibrationResult->info << std::endl;
72
73            if(dynCalibrationResult->qualityData) {
74                std::cout << "Successfully evaluated Quality." << std::endl;
75
76                const auto& q = *dynCalibrationResult->qualityData;
77
78                // --- Rotation difference magnitude (degrees) ---
79                float rotDiff = std::sqrt(q.rotationChange[0] * q.rotationChange[0] + q.rotationChange[1] * q.rotationChange[1]
80                                          + q.rotationChange[2] * q.rotationChange[2]);
81                std::cout << "Rotation difference: || r_current - r_new || = " << rotDiff << " deg" << std::endl;
82
83                // --- Sampson error (px) ---
84                std::cout << "Mean Sampson error achievable = " << q.sampsonErrorNew << " px" << std::endl;
85                std::cout << "Mean Sampson error current    = " << q.sampsonErrorCurrent << " px" << std::endl;
86
87                // --- Depth error difference (%) at 1/2/5/10 m ---
88                std::cout << "Theoretical Depth Error Difference " << "@1m:" << std::fixed << std::setprecision(2) << q.depthErrorDifference[0] << "%, "
89                          << "2m:" << q.depthErrorDifference[1] << "%, " << "5m:" << q.depthErrorDifference[2] << "%, " << "10m:" << q.depthErrorDifference[3]
90                          << "%" << std::endl;
91
92                // (Optional) Trigger a calibration step if desired:
93                // dynCalibInputControl->send(DCC::calibrate(true));
94
95                // Reset temporary data after reading metrics
96                dynCalibInputControl->send(DCC::resetData());
97            }
98        } else {
99            std::cout << "Dynamic calibration: no result received." << std::endl;
100        }
101
102        int key = cv::waitKey(1);
103        if(key == 'q') break;
104    }
105
106    return 0;
107}

动态校准

Python

请按照 Github 上的 README 来运行此示例。
Python
GitHub
1import depthai as dai
2import numpy as np
3import time
4import cv2
5
6# ---------- Pipeline definition ----------
7with dai.Pipeline() as pipeline:
8    # Cameras
9    monoLeft  = pipeline.create(dai.node.Camera).build(dai.CameraBoardSocket.CAM_B)
10    monoRight = pipeline.create(dai.node.Camera).build(dai.CameraBoardSocket.CAM_C)
11
12    # Full-res NV12 outputs
13    monoLeftOut  = monoLeft.requestFullResolutionOutput()
14    monoRightOut = monoRight.requestFullResolutionOutput()
15
16    # Initialize the DynamicCalibration node
17    dynCalib = pipeline.create(dai.node.DynamicCalibration)
18
19    # Link the cameras to the DynamicCalibration
20    monoLeftOut.link(dynCalib.left)
21    monoRightOut.link(dynCalib.right)
22
23    # Stereo (for disparity + synced previews)
24    stereo = pipeline.create(dai.node.StereoDepth)
25    monoLeftOut.link(stereo.left)
26    monoRightOut.link(stereo.right)
27
28    # Output queues
29    syncedLeftQueue  = stereo.syncedLeft.createOutputQueue()
30    syncedRightQueue = stereo.syncedRight.createOutputQueue()
31    disparityQueue   = stereo.disparity.createOutputQueue()
32
33    # Initialize the command output queues for calibration and coverage
34    dynCalibCalibrationQueue = dynCalib.calibrationOutput.createOutputQueue()
35    dynCalibCoverageQueue    = dynCalib.coverageOutput.createOutputQueue()
36
37    # Initialize the command input queue
38    dynCalibInputControl = dynCalib.inputControl.createInputQueue()
39
40    device = pipeline.getDefaultDevice()
41    device.setCalibration(device.readCalibration())
42
43    # Setup the colormap for visualization
44    colorMap = cv2.applyColorMap(np.arange(256, dtype=np.uint8), cv2.COLORMAP_JET)
45    colorMap[0] = [0, 0, 0]  # to make zero-disparity pixels black
46    maxDisparity = 1.0
47
48    pipeline.start()
49    time.sleep(1) # wait for auto exposure to settle
50
51    # Set performance mode
52    dynCalibInputControl.send(
53        dai.DynamicCalibrationControl.setPerformanceMode(
54            dai.DynamicCalibrationControl.OPTIMIZE_PERFORMANCE
55        )
56    )
57
58    # Start periodic calibration
59    dynCalibInputControl.send(
60        dai.DynamicCalibrationControl.startCalibration()
61    )
62
63    while pipeline.isRunning():
64        leftSynced  = syncedLeftQueue.get()
65        rightSynced = syncedRightQueue.get()
66        disparity = disparityQueue.get()
67
68        cv2.imshow("left", leftSynced.getCvFrame())
69        cv2.imshow("right", rightSynced.getCvFrame())
70
71        # --- Disparity visualization ---
72        npDisparity = disparity.getFrame()
73        curMax = float(np.max(npDisparity))
74        if curMax > 0:
75            maxDisparity = max(maxDisparity, curMax)
76
77        # Normalize to [0,255] and colorize; keep zero-disparity as black
78        denom = maxDisparity if maxDisparity > 0 else 1.0
79        normalized = (npDisparity / denom * 255.0).astype(np.uint8)
80        colorizedDisparity = cv2.applyColorMap(normalized, cv2.COLORMAP_JET)
81        colorizedDisparity[normalized == 0] = (0, 0, 0)
82        cv2.imshow("disparity", colorizedDisparity)
83
84        # --- Coverage (non-blocking) ---
85        coverage = dynCalibCoverageQueue.tryGet()
86        if coverage is not None:
87            print(f"2D Spatial Coverage = {coverage.meanCoverage} / 100 [%]")
88            print(f"Data Acquired       = {coverage.dataAcquired} / 100 [%]")
89
90        # --- Calibration result (non-blocking) ---
91        dynCalibrationResult = dynCalibCalibrationQueue.tryGet()
92        calibrationData = dynCalibrationResult.calibrationData if dynCalibrationResult is not None else None
93
94        if dynCalibrationResult is not None:
95            print(f"Dynamic calibration status: {dynCalibrationResult.info}")
96
97        # --- Apply calibration if available, print quality deltas, then reset+continue ---
98        if calibrationData:
99            print("Successfully calibrated")
100            # Apply to device
101            dynCalibInputControl.send(
102                dai.DynamicCalibrationControl.applyCalibration(calibrationData.newCalibration)
103            )
104
105            q = calibrationData.calibrationDifference
106            rotDiff = float(np.sqrt(q.rotationChange[0]**2 +
107                                    q.rotationChange[1]**2 +
108                                    q.rotationChange[2]**2))
109            print(f"Rotation difference: || r_current - r_new || = {rotDiff:.2f} deg")
110            print(f"Mean Sampson error achievable = {q.sampsonErrorNew:.3f} px")
111            print(f"Mean Sampson error current    = {q.sampsonErrorCurrent:.3f} px")
112            print("Theoretical Depth Error Difference "
113                  f"@1m:{q.depthErrorDifference[0]:.2f}%, "
114                  f"2m:{q.depthErrorDifference[1]:.2f}%, "
115                  f"5m:{q.depthErrorDifference[2]:.2f}%, "
116                  f"10m:{q.depthErrorDifference[3]:.2f}%")
117
118            # Reset accumulators and continue periodic calibration
119            dynCalibInputControl.send(
120                dai.DynamicCalibrationControl.resetData()
121            )
122            dynCalibInputControl.send(
123                dai.DynamicCalibrationControl.startCalibration()
124            )
125
126        key = cv2.waitKey(1)
127        if key == ord('q'):
128            pipeline.stop()
129            break

C++

来自我们 Github 的示例:Github
1// examples/cpp/DynamicCalibration/calibrate.cpp
2#include <algorithm>
3#include <chrono>
4#include <cmath>
5#include <iomanip>
6#include <iostream>
7#include <opencv2/opencv.hpp>
8#include <thread>
9
10#include "depthai/depthai.hpp"
11
12int main() {
13    auto device = std::make_shared<dai::Device>();
14
15    // ---------- Pipeline definition ----------
16    dai::Pipeline pipeline(device);
17
18    auto monoLeft = pipeline.create<dai::node::Camera>()->build(dai::CameraBoardSocket::CAM_B);
19    auto monoRight = pipeline.create<dai::node::Camera>()->build(dai::CameraBoardSocket::CAM_C);
20
21    auto* leftOut = monoLeft->requestFullResolutionOutput();
22    auto* rightOut = monoRight->requestFullResolutionOutput();
23
24    // Dynamic-calibration node
25    auto dynCalib = pipeline.create<dai::node::DynamicCalibration>();
26    leftOut->link(dynCalib->left);
27    rightOut->link(dynCalib->right);
28
29    auto stereo = pipeline.create<dai::node::StereoDepth>();
30    leftOut->link(stereo->left);
31    rightOut->link(stereo->right);
32
33    // In-pipeline host queues
34    auto leftSyncedQueue = stereo->syncedLeft.createOutputQueue();
35    auto rightSyncedQueue = stereo->syncedRight.createOutputQueue();
36    auto disparityQueue = stereo->disparity.createOutputQueue();
37
38    auto dynCalibOutQ = dynCalib->calibrationOutput.createOutputQueue();
39    auto dynCoverageOutQ = dynCalib->coverageOutput.createOutputQueue();
40
41    auto dynCalibInputControl = dynCalib->inputControl.createInputQueue();
42
43    device->setCalibration(device->readCalibration());
44
45    pipeline.start();
46    std::this_thread::sleep_for(std::chrono::seconds(1));  // wait for autoexposure to settle
47
48    using DCC = dai::DynamicCalibrationControl;
49    // Optionally set performance mode:
50    dynCalibInputControl->send(DCC::setPerformanceMode(DCC::PerformanceMode::OPTIMIZE_PERFORMANCE));
51
52    // Start calibration (optimize performance)
53    dynCalibInputControl->send(DCC::startCalibration());
54
55    double maxDisparity = 1.0;
56    while(pipeline.isRunning()) {
57        auto leftSynced = leftSyncedQueue->get<dai::ImgFrame>();
58        auto rightSynced = rightSyncedQueue->get<dai::ImgFrame>();
59        auto disparity = disparityQueue->get<dai::ImgFrame>();
60
61        cv::imshow("left", leftSynced->getCvFrame());
62        cv::imshow("right", rightSynced->getCvFrame());
63
64        cv::Mat npDisparity = disparity->getFrame();
65
66        double minVal = 0.0, curMax = 0.0;
67        cv::minMaxLoc(npDisparity, &minVal, &curMax);
68        maxDisparity = std::max(maxDisparity, curMax);
69
70        // Normalize the disparity image to an 8-bit scale.
71        cv::Mat normalized;
72        npDisparity.convertTo(normalized, CV_8UC1, 255.0 / (maxDisparity > 0 ? maxDisparity : 1.0));
73
74        cv::Mat colorizedDisparity;
75        cv::applyColorMap(normalized, colorizedDisparity, cv::COLORMAP_JET);
76
77        // Set pixels with zero disparity to black.
78        colorizedDisparity.setTo(cv::Scalar(0, 0, 0), normalized == 0);
79
80        cv::imshow("disparity", colorizedDisparity);
81
82        // Coverage (non-blocking)
83        if(auto coverageMsg = dynCoverageOutQ->tryGet<dai::CoverageData>()) {
84            std::cout << "2D Spatial Coverage = " << coverageMsg->meanCoverage << "  / 100 [%]\n";
85            std::cout << "Data Acquired       = " << coverageMsg->dataAcquired << "  / 100 [%]\n";
86        }
87
88        // Calibration result (non-blocking)
89        if(auto dynCalibrationResult = dynCalibOutQ->tryGet<dai::DynamicCalibrationResult>()) {
90            std::cout << "Dynamic calibration status: " << dynCalibrationResult->info << std::endl;
91
92            if(dynCalibrationResult->calibrationData) {
93                std::cout << "Successfully calibrated." << std::endl;
94
95                // Apply the produced calibration
96                const auto& newCalib = dynCalibrationResult->calibrationData->newCalibration;
97                dynCalibInputControl->send(DCC::applyCalibration(newCalib));
98
99                // Print quality deltas
100                const auto& q = dynCalibrationResult->calibrationData->calibrationDifference;
101
102                float rotDiff = std::sqrt(q.rotationChange[0] * q.rotationChange[0] + q.rotationChange[1] * q.rotationChange[1]
103                                          + q.rotationChange[2] * q.rotationChange[2]);
104                std::cout << "Rotation difference: " << rotDiff << " deg\n";
105                std::cout << "Mean Sampson error achievable = " << q.sampsonErrorNew << " px\n";
106                std::cout << "Mean Sampson error current    = " << q.sampsonErrorCurrent << " px\n";
107                std::cout << "Theoretical Depth Error Difference " << "@1m:" << std::fixed << std::setprecision(2) << q.depthErrorDifference[0] << "%, "
108                          << "2m:" << q.depthErrorDifference[1] << "%, " << "5m:" << q.depthErrorDifference[2] << "%, " << "10m:" << q.depthErrorDifference[3]
109                          << "%\n";
110
111                // Reset and start a new round if desired
112                dynCalibInputControl->send(DCC::startCalibration());
113            }
114        }
115
116        int key = cv::waitKey(1);
117        if(key == 'q') break;
118    }
119
120    return 0;
121}
有关更多信息,请遵循示例的 README。

场景指南

良好的校准场景有助于算法更轻松地检测、匹配和跟踪特征。建议的特征:
  • 包含不同深度的纹理对象。
  • 避免空白墙壁或无特征的表面。
  • 缓慢移动相机以覆盖整个视场;避免突然的动作
建议原始图像 vs. 特征覆盖(绿色部分)
确保丰富的纹理和视觉细节 - 丰富的纹理、边缘和均匀分布在整个视场中的对象可创建理想的校准条件
🚫避免平坦或无特征的表面 - 缺乏纹理表面或视觉上可区分的对象提供的可用特征很少
🚫避免反光和透明表面 - 反光和透明表面会产生错误的 3D 特征
🚫避免黑暗场景 - 低对比度、阴影和光线不足的场景产生的可检测特征很少
🚫避免重复图案 - 许多图案区域看起来太相似而无法区分

PerformanceMode 调优

模式何时使用
DEFAULT准确性与速度的平衡。
STATIC_SCENERY相机固定,场景稳定。
OPTIMIZE_SPEED最快的校准,精度降低。
OPTIMIZE_PERFORMANCE在特征丰富的场景中实现最大精度。
SKIP_CHECKS自动化流水线,忽略内部检查以保证场景质量。
通过结合合适的场景和正确的 PerformanceMode,用户可以显著提高校准的可靠性和深度估计的质量。

限制和注意事项

  • 支持的设备 — 动态校准适用于:
    • 所有立体 OAK Series 2 相机(不包括 FFC)
    • 所有立体 OAK Series 4 相机
  • DepthAI 版本 — 需要 DepthAI 3.0 或更高版本。
  • 重新校准的参数 — 仅更新**外参;**内参保持不变。
  • 操作系统支持 — 支持 Linux、MacOS 和 Windows。
  • 绝对深度规格 — DCL 改进了相对深度感知;绝对精度可能仍与原始出厂规格略有不同。

故障排除

症状可能的原因修复方法
重投影误差高板配置文件中的模型名称或 HFOV 不正确验证板 JSON 和相机规格
DCL “成功”后深度仍然不正确左右相机颠倒交换插槽或更新板配置并重新校准
nullopt 质量报告场景覆盖不足将相机移至能捕捉更丰富纹理的位置
运行时错误:“设备上的校准太旧,无法执行动态校准,需要完全重新校准!”设备校准太旧,动态重新校准无法提供任何优势。需要新设备

另请参阅