本页目录

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

DynamicCalibration - 技术实现

Supported on:RVC2RVC4
DynamicCalibration 是内置于 DepthAI 3.0 中的自校准工作流程,可在温度变化、物理冲击或长期漂移降低出厂校准时恢复并保持立体精度。
在本页中,我们列出了在您项目中集成 DynamicCalibration 的技术实施步骤。如果您想了解 DynamicCalibration 的更多一般信息,请访问此页面

主要功能

  • 恢复深度性能——将视差图恢复至最佳视觉质量。
  • 无需目标标靶——在自然场景中操作;只需移动相机以捕捉不同视角。
  • 快速执行——通常可在数秒内完成。
  • 健康监控——随时运行诊断,无需刷写新校准。

自动校准选项

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

动态校准库(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    ...

向节点发送命令

DepthAI中的节点通过输入/输出消息队列进行通信。 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) - 计算校准指标,如数据质量和校准置信度。
  • 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 的参考文档,了解输出的具体数据结构。

读取覆盖数据

覆盖数据通过 coverageOutput 接收,触发时机包括手动加载图像(使用 LoadImage 命令)或连续校准过程中(运行 StartCalibration 命令后)。手动图像加载
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_input.send(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           # 跳过所有内部检查

示例

Dynamic Calibration Interactive Visualizer

通过以下命令,您可以克隆并运行标定集成。
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

Calibration Quality Check

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 的示例:
1#include <chrono>
2#include <iostream>
3#include <opencv2/opencv.hpp>
4#include <thread>
5
6#include "depthai/depthai.hpp"
7
8namespace {
9void printMetrics(const dai::CalibrationQuality::Data& q) {
10    for(const auto& [socketPair, rotation] : q.pairwiseRotationDifference) {
11        std::cout << "Pairwise rotation difference " << static_cast<int>(socketPair.first) << " -> " << static_cast<int>(socketPair.second) << " = [";
12        for(std::size_t i = 0; i < rotation.size(); ++i) {
13            if(i > 0) std::cout << ", ";
14            std::cout << rotation[i];
15        }
16        std::cout << "] deg" << std::endl;
17    }
18    std::cout << "Mean Sampson error achievable = " << q.sampsonErrorNew << " px" << std::endl;
19    std::cout << "Mean Sampson error current    = " << q.sampsonErrorCurrent << " px" << std::endl;
20}
21}  // namespace
22
23int main() {
24    auto device = std::make_shared<dai::Device>();
25
26    // ---------- Pipeline definition ----------
27    dai::Pipeline pipeline(device);
28
29    auto monoLeft = pipeline.create<dai::node::Camera>()->build(dai::CameraBoardSocket::CAM_B);
30    auto monoRight = pipeline.create<dai::node::Camera>()->build(dai::CameraBoardSocket::CAM_C);
31
32    auto* leftOut = monoLeft->requestFullResolutionOutput();
33    auto* rightOut = monoRight->requestFullResolutionOutput();
34
35    // Dynamic-calibration node
36    auto dynCalib = pipeline.create<dai::node::DynamicCalibration>();
37    leftOut->link(dynCalib->left);
38    rightOut->link(dynCalib->right);
39
40    auto stereo = pipeline.create<dai::node::StereoDepth>();
41    leftOut->link(stereo->left);
42    rightOut->link(stereo->right);
43
44    // In-pipeline host queues
45    auto leftSyncedQueue = stereo->syncedLeft.createOutputQueue();
46    auto rightSyncedQueue = stereo->syncedRight.createOutputQueue();
47    auto disparityQueue = stereo->disparity.createOutputQueue();
48
49    auto dynCoverageOutQ = dynCalib->coverageOutput.createOutputQueue();
50    auto dynCalibOutQ = dynCalib->calibrationOutput.createOutputQueue();
51    auto dynCalibInputControl = dynCalib->inputControl.createInputQueue();
52
53    device->setCalibration(device->getCalibration());
54
55    pipeline.start();
56    std::this_thread::sleep_for(std::chrono::seconds(1));  // wait for autoexposure to settle
57
58    using DCC = dai::DynamicCalibrationControl;
59
60    while(pipeline.isRunning()) {
61        auto leftSynced = leftSyncedQueue->get<dai::ImgFrame>();
62        auto rightSynced = rightSyncedQueue->get<dai::ImgFrame>();
63        auto disparity = disparityQueue->get<dai::ImgFrame>();
64
65        cv::imshow("left", leftSynced->getCvFrame());
66        cv::imshow("right", rightSynced->getCvFrame());
67
68        // --- Load one frame pair into the calibration pipeline
69        dynCalibInputControl->send(DCC::loadImage());
70
71        // Wait for coverage info
72        auto coverageMsg = dynCoverageOutQ->get<dai::CoverageData>();
73        if(coverageMsg) {
74            std::cout << "2D Spatial Coverage = " << coverageMsg->meanCoverage << " / 100 [%]" << std::endl;
75            std::cout << "Data Acquired       = " << coverageMsg->dataAcquired << " / 100 [%]" << std::endl;
76        }
77
78        // Run one-shot calibration and inspect the returned metrics.
79        dynCalibInputControl->send(DCC::calibrate(false));
80
81        // Wait for calibration result
82        auto dynCalibrationResult = dynCalibOutQ->get<dai::DynamicCalibrationResult>();
83        if(dynCalibrationResult) {
84            std::cout << "Dynamic calibration status: " << dynCalibrationResult->info << std::endl;
85
86            if(dynCalibrationResult->calibrationData) {
87                std::cout << "Successfully evaluated metrics from calibration output." << std::endl;
88                printMetrics(dynCalibrationResult->calibrationData->calibrationDifference);
89                dynCalibInputControl->send(DCC::resetData());
90            }
91        } else {
92            std::cout << "Dynamic calibration: no result received." << std::endl;
93        }
94
95        int key = cv::waitKey(1);
96        if(key == 'q') break;
97    }
98
99    return 0;
100}

Dynamic Calibration

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.getCalibration())
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.PerformanceMode.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            if q.pairwiseRotationDifference:
107                pairwiseRotation = next(iter(q.pairwiseRotationDifference.values()))
108                rotDiff = float(np.sqrt(sum(axis * axis for axis in pairwiseRotation)))
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
113            # Reset accumulators and continue periodic calibration
114            dynCalibInputControl.send(
115                dai.DynamicCalibrationControl.resetData()
116            )
117            dynCalibInputControl.send(
118                dai.DynamicCalibrationControl.startCalibration()
119            )
120
121        key = cv2.waitKey(1)
122        if key == ord('q'):
123            pipeline.stop()
124            break

C++

来自我们的 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->getCalibration());
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                if(!q.pairwiseRotationDifference.empty()) {
103                    const auto& pairwiseRotation = q.pairwiseRotationDifference.begin()->second;
104                    float rotDiff = 0.0f;
105                    for(const auto axis : pairwiseRotation) rotDiff += axis * axis;
106                    rotDiff = std::sqrt(rotDiff);
107                    std::cout << "Rotation difference: " << rotDiff << " deg\n";
108                }
109                std::cout << "Mean Sampson error achievable = " << q.sampsonErrorNew << " px\n";
110                std::cout << "Mean Sampson error current    = " << q.sampsonErrorCurrent << " px\n";
111
112                // Reset and start a new round if desired
113                dynCalibInputControl->send(DCC::startCalibration());
114            }
115        }
116
117        int key = cv::waitKey(1);
118        if(key == 'q') break;
119    }
120
121    return 0;
122}
更多信息请参阅示例的 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 质量报告场景覆盖不足移动相机以捕获更丰富的纹理
运行时错误:"The calibration on the device is too old to perform DynamicCalibration, full re-calibration required!"设备标定过于陈旧,动态重新标定无法提供任何好处。需要更新的设备

另请参阅