# DynamicCalibration - 技术实现

DynamicCalibration 是内置于 DepthAI 3.0 中的自校准工作流程，可在温度变化、物理冲击或长期漂移降低出厂校准时恢复并保持立体精度。

在本页中，我们列出了在您项目中集成 DynamicCalibration 的技术实施步骤。如果您想了解 DynamicCalibration
的更多一般信息，请访问[此页面](https://docs.luxonis.com/hardware/platform/depth/dynamic-calibration.md)。

### 主要功能

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

## 自动校准选项

有两种方法可以在 DynamicCalibration 之上运行自动校准：

 * 使用[AutoCalibration
   主机节点](https://docs.luxonis.com/software-v3/depthai/depthai-components/host_nodes/auto_calibration.md)在管道内进行显式的节点级控制。
 * 使用 DEPTHAI_AUTOCALIBRATION 在部署时启用，无需更改管道代码。
 * 使用 Pipeline.setAutoCalibrationMode() 的直接设置器来启用自动校准，无需使用 AutoCalibration 主机节点。

> 从
> `DepthAI 3.6`
> 开始，自动校准在
> `ON_START`
> 模式下默认启用。使用
> `OFF`
> 可禁用它。 如果您的管道已包含
> `DynamicCalibration`
> 节点或
> `AutoCalibration`
> 节点，则
> `AutoCalibrationMode`
> 不适用。

## 动态校准库（DCL）的使用

本节是对如何在 DepthAI 中使用 DynamicCalibration 节点进行动态校准工作流程的简单高层次表示。

动态校准以包含命令的[DynamicCalibrationControl](https://docs.luxonis.com/software-v3/depthai/depthai-components/messages/dynamic_calibration_control.md)消息作为输入（例如
ApplyCalibration、Calibrate……有关所有可用命令，请参阅消息定义）。节点根据发送的输入命令，在以下三个输出队列之一返回输出：

 * calibrationOutput 的消息类型为
   [DynamicCalibrationResult](https://docs.luxonis.com/software-v3/depthai/depthai-components/messages/dynamic_calibration_result.md)
 * qualityOutput 的消息类型为
   [CalibrationQuality](https://docs.luxonis.com/software-v3/depthai/depthai-components/messages/calibration_quality.md)
 * coverageOutput 的消息类型为 [CoverageData](https://docs.luxonis.com/software-v3/depthai/depthai-components/messages/coverage_data.md)
 * metricsOutput 的消息类型为
   [CalibrationMetrics](https://docs.luxonis.com/software-v3/depthai/depthai-components/messages/calibration_metrics.md)

取决于发送了哪个输入命令。

在接下来的段落中，我们将探讨如何将 DCL 实际集成到您的代码中。

#### 初始化 DynamicCalibration 节点

DynamicCalibration 节点需要来自同一设备的两个同步相机流。设置方法如下：

```python
import depthai as dai

# 初始化管道
pipeline = dai.Pipeline()

# 创建相机节点
cam_left = pipeline.create(dai.node.Camera).build(dai.CameraBoardSocket.CAM_B)
cam_right = pipeline.create(dai.node.Camera).build(dai.CameraBoardSocket.CAM_C)

# 请求全分辨率 NV12 输出
left_out = cam_left.requestFullResolutionOutput()
right_out = cam_right.requestFullResolutionOutput()

# 初始化 DynamicCalibration 节点
dyn_calib = pipeline.create(dai.node.DynamicCalibration)

# 将相机链接到 DynamicCalibration
left_out.link(dyn_calib.left)
right_out.link(dyn_calib.right)

device = pipeline.getDefaultDevice()
calibration = device.readCalibration()
device.setCalibration(calibration)

pipeline.start()
while pipeline.isRunning():
    ...
```

#### 向节点发送命令

DepthAI中的节点通过输入/输出消息队列进行通信。 DynamicCalibration节点有多个队列，但控制最重要的队列是inputControl队列。

```python
# 初始化命令输入队列
command_input = dyn_calib.inputControl.createInputQueue()
# 示例命令：发送开始校准
command_input.send(dai.DynamicCalibrationControl.startCalibration())
```

可用命令

 * StartCalibration() - 开始校准过程。
 * StopCalibration() - 停止校准过程。
 * Calibrate(force=False) - 基于已加载的数据计算新的校准。
   * force - 不对已加载数据施加限制
 * CalibrationQuality(force=False) - 评估当前校准的质量。
   * force - 不对已加载数据施加限制
 * LoadImage() - 从设备加载一张图像。
 * ComputeCalibrationMetrics(calibration) - 计算校准指标，如数据质量和校准置信度。
 * ApplyCalibration(calibration) - 将校准应用到设备。
 * SetPerformanceMode(performanceMode) - 发送将要使用的性能模式。
 * ResetData() - 移除所有之前加载的数据。

#### 从节点接收数据

该节点提供多个输出队列：

 * coverageOutput →
   覆盖统计（[CoverageData](https://docs.luxonis.com/software-v3/depthai/depthai-components/messages/coverage_data.md)）
 * calibrationOutput →
   校准结果（[DynamicCalibrationResult](https://docs.luxonis.com/software-v3/depthai/depthai-components/messages/dynamic_calibration_result.md)）
 * qualityOutput →
   校准质量检查（[CalibrationQuality](https://docs.luxonis.com/software-v3/depthai/depthai-components/messages/calibration_quality.md)）
 * metricsOutput →
   校准统计与数据质量（[CalibrationMetrics](https://docs.luxonis.com/software-v3/depthai/depthai-components/messages/calibration_metrics.md)）

```python
# 用于接收新校准的队列
calibration_output = dyn_calib.calibrationOutput.createOutputQueue()
# 用于接收覆盖数据的队列
coverage_output = dyn_calib.coverageOutput.createOutputQueue()
# 用于检查校准质量的队列
quality_output = dyn_calib.qualityOutput.createOutputQueue()
# 用于检查校准指标的队列
metrics_output = dyn_calib.metricsOutput.createOutputQueue()
```

请查阅 DynamicCalibrationResult、CoverageData、CalibrationQuality 和 CalibrationMetrics 的参考文档，了解输出的具体数据结构。

#### 读取覆盖数据

覆盖数据通过 coverageOutput 接收，触发时机包括手动加载图像（使用 LoadImage 命令）或连续校准过程中（运行 StartCalibration 命令后）。

手动图像加载

```python
# 加载单张图像
command_input.send(dai.DynamicCalibrationControl.loadImage())

# 加载后获取覆盖数据
coverage = coverage_output.get()
print(f"Coverage = {coverage.meanCoverage}")
```

校准期间持续采集

```python
command_input.send(dai.DynamicCalibrationControl.startCalibration())

while pipeline.isRunning():
    # 阻塞读取
    coverage = coverage_output.get()
    print(f"Coverage = {coverage.meanCoverage}")

    # 非阻塞读取
    coverage = coverage_output.tryGet()
    if coverage:
        print(f"Coverage = {coverage.meanCoverage}")
```

#### 读取校准数据

校准结果可通过以下方式获取：

 * dai.DynamicCalibrationControl.startCalibration() — 开始数据收集并尝试校准。
 * dai.DynamicCalibrationControl.calibrate(force=False) — 使用已有的已加载数据进行校准（如以下示例所示，需先用 LoadImage 命令加载图像）。

校准数据将以
[DynamicCalibrationResult](https://docs.luxonis.com/software-v3/depthai/depthai-components/messages/dynamic_calibration_result.md)
消息类型返回。

手动图像加载

```python
# 加载单张图像
command_input.send(dai.DynamicCalibrationControl.loadImage())

# 发送校准命令
command_input.send(dai.DynamicCalibrationControl.calibrate(force=False))

# 加载后获取校准结果
calibration = calibration_output.get()
print(f"Calibration = {calibration.info}")
```

持续采集

```python
# 开始数据收集并尝试校准
command_input.send(dai.DynamicCalibrationControl.startCalibration())

while pipeline.isRunning():
    # 阻塞读取
    calibration = calibration_output.get()
    print(f"Calibration = {calibration.info}")

    # 非阻塞读取
    calibration = calibration_output.tryGet()
    if calibration:
        print(f"Calibration = {calibration.info}")
```

#### 性能模式

设置性能模式如下：

```python
# 设置性能模式
dynCalibInputControl.send(dai.DynamicCalibrationControl.setPerformanceMode(dai.node.DynamicCalibration.OPTIMIZE_PERFORMANCE))
```

性能模式设置校准所需的数据量。

```python
dai.node.DynamicCalibration.PerformanceMode.OPTIMIZE_PERFORMANCE  # 最严格的模式
dai.node.DynamicCalibration.PerformanceMode.DEFAULT               # 较宽松但通常足够
dai.node.DynamicCalibration.PerformanceMode.OPTIMIZE_SPEED        # 优化速度而非性能
dai.node.DynamicCalibration.PerformanceMode.STATIC_SCENERY        # 不严格
dai.node.DynamicCalibration.PerformanceMode.SKIP_CHECKS           # 跳过所有内部检查
```

## 示例

#### Dynamic Calibration Interactive Visualizer

通过以下命令，您可以克隆并运行标定集成。

```bash
git clone https://github.com/luxonis/depthai-core.git
cd depthai-core/
python3 -m venv venv
source venv/bin/activate
python3 examples/python/install_requirements.py
python3 examples/python/DynamicCalibration/calibration_integration.py
```

#### Calibration Quality Check

#### Python

按照 [Github 上的 README](https://github.com/luxonis/depthai-core/blob/main/examples/python/DynamicCalibration/README.md) 运行此示例。

```python
import depthai as dai
import numpy as np
import time
import cv2

# ---------- Pipeline definition ----------
with dai.Pipeline() as pipeline:
    # Create camera nodes
    monoLeft  = pipeline.create(dai.node.Camera).build(dai.CameraBoardSocket.CAM_B)
    monoRight = pipeline.create(dai.node.Camera).build(dai.CameraBoardSocket.CAM_C)

    # Request full resolution NV12 outputs
    monoLeftOut  = monoLeft.requestFullResolutionOutput()
    monoRightOut = monoRight.requestFullResolutionOutput()

    # Initialize the DynamicCalibration node
    dynCalib = pipeline.create(dai.node.DynamicCalibration)

    # Link the cameras to the DynamicCalibration
    monoLeftOut.link(dynCalib.left)
    monoRightOut.link(dynCalib.right)

    stereo = pipeline.create(dai.node.StereoDepth)
    monoLeftOut.link(stereo.left)
    monoRightOut.link(stereo.right)

    # Queues
    syncedLeftQueue  = stereo.syncedLeft.createOutputQueue()
    syncedRightQueue = stereo.syncedRight.createOutputQueue()
    disparityQueue = stereo.disparity.createOutputQueue()

    # Initialize the command output queues for coverage and calibration quality
    dynCalibCoverageQueue = dynCalib.coverageOutput.createOutputQueue()
    dynCalibQualityQueue = dynCalib.qualityOutput.createOutputQueue()

    # Initialize the command input queue
    dynCalibInputControl = dynCalib.inputControl.createInputQueue()

    device = pipeline.getDefaultDevice()
    device.setCalibration(device.readCalibration())

    # Setup the colormap for visualization
    colorMap = cv2.applyColorMap(np.arange(256, dtype=np.uint8), cv2.COLORMAP_JET)
    colorMap[0] = [0, 0, 0]  # to make zero-disparity pixels black
    maxDisparity = 1

    pipeline.start()
    time.sleep(1) # wait for auto exposure to settle

    while pipeline.isRunning():
        leftSynced  = syncedLeftQueue.get()
        rightSynced = syncedRightQueue.get()
        disparity = disparityQueue.get()

        cv2.imshow("left", leftSynced.getCvFrame())
        cv2.imshow("right", rightSynced.getCvFrame())

        # --- Disparity visualization ---
        npDisparity = disparity.getFrame()
        curMax = float(np.max(npDisparity))
        if curMax > 0:
            maxDisparity = max(maxDisparity, curMax)
        normalized = (npDisparity / (maxDisparity if maxDisparity > 0 else 1.0) * 255.0).astype(np.uint8)
        colorizedDisparity = cv2.applyColorMap(normalized, cv2.COLORMAP_JET)
        colorizedDisparity[normalized == 0] = (0, 0, 0)
        cv2.imshow("disparity", colorizedDisparity)

        # --- Load one frame into calibration & read coverage
        dynCalibInputControl.send(dai.DynamicCalibrationControl.loadImage())
        coverage = dynCalibCoverageQueue.get()
        if coverage is not None:
            print(f"2D Spatial Coverage = {coverage.meanCoverage} / 100 [%]")
            print(f"Data Acquired       = {coverage.dataAcquired} / 100 [%]")

        # --- Request a quality evaluation & read result
        dynCalibInputControl.send(dai.DynamicCalibrationControl.calibrationQuality(False))
        dynQualityResult = dynCalibQualityQueue.get()
        if dynQualityResult is not None:
            print(f"Dynamic calibration status: {dynQualityResult.info}")

            # If the calibration is successfully returned apply it to the device
            if dynQualityResult.qualityData:
                q = dynQualityResult.qualityData
                print("Successfully evaluated Quality")
                rotDiff = float(np.sqrt(q.rotationChange[0]**2 +
                                        q.rotationChange[1]**2 +
                                        q.rotationChange[2]**2))
                print(f"Rotation difference: || r_current - r_new || = {rotDiff:.2f} deg")
                print(f"Mean Sampson error achievable = {q.sampsonErrorNew:.3f} px")
                print(f"Mean Sampson error current    = {q.sampsonErrorCurrent:.3f} px")
                print(
                    "Theoretical Depth Error Difference "
                    f"@1m:{q.depthErrorDifference[0]:.2f}%, "
                    f"2m:{q.depthErrorDifference[1]:.2f}%, "
                    f"5m:{q.depthErrorDifference[2]:.2f}%, "
                    f"10m:{q.depthErrorDifference[3]:.2f}%"
                )
                # Reset temporary accumulators before the next cycle
                dynCalibInputControl.send(dai.DynamicCalibrationControl.resetData())

        key = cv2.waitKey(1)
        if key == ord('q'):
            pipeline.stop()
            break
```

#### C++

来自我们的 [Github](https://github.com/luxonis/depthai-core/blob/main/examples/cpp/DynamicCalibration/calibration_quality_dynamic.cpp)
的示例：

```cpp
#include <chrono>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <opencv2/opencv.hpp>
#include <thread>

#include "depthai/depthai.hpp"

int main() {
    auto device = std::make_shared<dai::Device>();

    // ---------- Pipeline definition ----------
    dai::Pipeline pipeline(device);

    auto monoLeft = pipeline.create<dai::node::Camera>()->build(dai::CameraBoardSocket::CAM_B);
    auto monoRight = pipeline.create<dai::node::Camera>()->build(dai::CameraBoardSocket::CAM_C);

    auto* leftOut = monoLeft->requestFullResolutionOutput();
    auto* rightOut = monoRight->requestFullResolutionOutput();

    // Dynamic-calibration node
    auto dynCalib = pipeline.create<dai::node::DynamicCalibration>();
    leftOut->link(dynCalib->left);
    rightOut->link(dynCalib->right);

    auto stereo = pipeline.create<dai::node::StereoDepth>();
    leftOut->link(stereo->left);
    rightOut->link(stereo->right);

    // In-pipeline host queues
    auto leftSyncedQueue = stereo->syncedLeft.createOutputQueue();
    auto rightSyncedQueue = stereo->syncedRight.createOutputQueue();
    auto disparityQueue = stereo->disparity.createOutputQueue();

    auto dynQualityOutQ = dynCalib->qualityOutput.createOutputQueue();
    auto dynCoverageOutQ = dynCalib->coverageOutput.createOutputQueue();
    auto dynCalibInputControl = dynCalib->inputControl.createInputQueue();

    device->setCalibration(device->readCalibration());

    pipeline.start();
    std::this_thread::sleep_for(std::chrono::seconds(1));  // wait for autoexposure to settle

    using DCC = dai::DynamicCalibrationControl;

    while(pipeline.isRunning()) {
        auto leftSynced = leftSyncedQueue->get<dai::ImgFrame>();
        auto rightSynced = rightSyncedQueue->get<dai::ImgFrame>();
        auto disparity = disparityQueue->get<dai::ImgFrame>();

        cv::imshow("left", leftSynced->getCvFrame());
        cv::imshow("right", rightSynced->getCvFrame());

        // --- Load one frame pair into the calibration pipeline
        dynCalibInputControl->send(DCC::loadImage());

        // Wait for coverage info
        auto coverageMsg = dynCoverageOutQ->get<dai::CoverageData>();
        if(coverageMsg) {
            std::cout << "2D Spatial Coverage = " << coverageMsg->meanCoverage << " / 100 [%]" << std::endl;
            std::cout << "Data Acquired       = " << coverageMsg->dataAcquired << " / 100 [%]" << std::endl;
        }

        // Request a calibration quality evaluation (non-forced)
        dynCalibInputControl->send(DCC::calibrationQuality(false));

        // Wait for calibration result
        auto dynCalibrationResult = dynQualityOutQ->get<dai::CalibrationQuality>();
        if(dynCalibrationResult) {
            std::cout << "Dynamic calibration status: " << dynCalibrationResult->info << std::endl;

            if(dynCalibrationResult->qualityData) {
                std::cout << "Successfully evaluated Quality." << std::endl;

                const auto& q = *dynCalibrationResult->qualityData;

                // --- Rotation difference magnitude (degrees) ---
                float rotDiff = std::sqrt(q.rotationChange[0] * q.rotationChange[0] + q.rotationChange[1] * q.rotationChange[1]
                                          + q.rotationChange[2] * q.rotationChange[2]);
                std::cout << "Rotation difference: || r_current - r_new || = " << rotDiff << " deg" << std::endl;

                // --- Sampson error (px) ---
                std::cout << "Mean Sampson error achievable = " << q.sampsonErrorNew << " px" << std::endl;
                std::cout << "Mean Sampson error current    = " << q.sampsonErrorCurrent << " px" << std::endl;

                // --- Depth error difference (%) at 1/2/5/10 m ---
                std::cout << "Theoretical Depth Error Difference " << "@1m:" << std::fixed << std::setprecision(2) << q.depthErrorDifference[0] << "%, "
                          << "2m:" << q.depthErrorDifference[1] << "%, " << "5m:" << q.depthErrorDifference[2] << "%, " << "10m:" << q.depthErrorDifference[3]
                          << "%" << std::endl;

                // (Optional) Trigger a calibration step if desired:
                // dynCalibInputControl->send(DCC::calibrate(true));

                // Reset temporary data after reading metrics
                dynCalibInputControl->send(DCC::resetData());
            }
        } else {
            std::cout << "Dynamic calibration: no result received." << std::endl;
        }

        int key = cv::waitKey(1);
        if(key == 'q') break;
    }

    return 0;
}
```

#### Dynamic Calibration

#### Python

按照 [Github 上的 README](https://github.com/luxonis/depthai-core/blob/main/examples/python/DynamicCalibration/README.md) 运行此示例。

```python
import depthai as dai
import numpy as np
import time
import cv2

# ---------- Pipeline definition ----------
with dai.Pipeline() as pipeline:
    # Cameras
    monoLeft  = pipeline.create(dai.node.Camera).build(dai.CameraBoardSocket.CAM_B)
    monoRight = pipeline.create(dai.node.Camera).build(dai.CameraBoardSocket.CAM_C)

    # Full-res NV12 outputs
    monoLeftOut  = monoLeft.requestFullResolutionOutput()
    monoRightOut = monoRight.requestFullResolutionOutput()

    # Initialize the DynamicCalibration node
    dynCalib = pipeline.create(dai.node.DynamicCalibration)

    # Link the cameras to the DynamicCalibration
    monoLeftOut.link(dynCalib.left)
    monoRightOut.link(dynCalib.right)

    # Stereo (for disparity + synced previews)
    stereo = pipeline.create(dai.node.StereoDepth)
    monoLeftOut.link(stereo.left)
    monoRightOut.link(stereo.right)

    # Output queues
    syncedLeftQueue  = stereo.syncedLeft.createOutputQueue()
    syncedRightQueue = stereo.syncedRight.createOutputQueue()
    disparityQueue   = stereo.disparity.createOutputQueue()

    # Initialize the command output queues for calibration and coverage
    dynCalibCalibrationQueue = dynCalib.calibrationOutput.createOutputQueue()
    dynCalibCoverageQueue    = dynCalib.coverageOutput.createOutputQueue()

    # Initialize the command input queue
    dynCalibInputControl = dynCalib.inputControl.createInputQueue()

    device = pipeline.getDefaultDevice()
    device.setCalibration(device.readCalibration())

    # Setup the colormap for visualization
    colorMap = cv2.applyColorMap(np.arange(256, dtype=np.uint8), cv2.COLORMAP_JET)
    colorMap[0] = [0, 0, 0]  # to make zero-disparity pixels black
    maxDisparity = 1.0

    pipeline.start()
    time.sleep(1) # wait for auto exposure to settle

    # Set performance mode
    dynCalibInputControl.send(
        dai.DynamicCalibrationControl.setPerformanceMode(
            dai.DynamicCalibrationControl.OPTIMIZE_PERFORMANCE
        )
    )

    # Start periodic calibration
    dynCalibInputControl.send(
        dai.DynamicCalibrationControl.startCalibration()
    )

    while pipeline.isRunning():
        leftSynced  = syncedLeftQueue.get()
        rightSynced = syncedRightQueue.get()
        disparity = disparityQueue.get()

        cv2.imshow("left", leftSynced.getCvFrame())
        cv2.imshow("right", rightSynced.getCvFrame())

        # --- Disparity visualization ---
        npDisparity = disparity.getFrame()
        curMax = float(np.max(npDisparity))
        if curMax > 0:
            maxDisparity = max(maxDisparity, curMax)

        # Normalize to [0,255] and colorize; keep zero-disparity as black
        denom = maxDisparity if maxDisparity > 0 else 1.0
        normalized = (npDisparity / denom * 255.0).astype(np.uint8)
        colorizedDisparity = cv2.applyColorMap(normalized, cv2.COLORMAP_JET)
        colorizedDisparity[normalized == 0] = (0, 0, 0)
        cv2.imshow("disparity", colorizedDisparity)

        # --- Coverage (non-blocking) ---
        coverage = dynCalibCoverageQueue.tryGet()
        if coverage is not None:
            print(f"2D Spatial Coverage = {coverage.meanCoverage} / 100 [%]")
            print(f"Data Acquired       = {coverage.dataAcquired} / 100 [%]")

        # --- Calibration result (non-blocking) ---
        dynCalibrationResult = dynCalibCalibrationQueue.tryGet()
        calibrationData = dynCalibrationResult.calibrationData if dynCalibrationResult is not None else None

        if dynCalibrationResult is not None:
            print(f"Dynamic calibration status: {dynCalibrationResult.info}")

        # --- Apply calibration if available, print quality deltas, then reset+continue ---
        if calibrationData:
            print("Successfully calibrated")
            # Apply to device
            dynCalibInputControl.send(
                dai.DynamicCalibrationControl.applyCalibration(calibrationData.newCalibration)
            )

            q = calibrationData.calibrationDifference
            rotDiff = float(np.sqrt(q.rotationChange[0]**2 +
                                    q.rotationChange[1]**2 +
                                    q.rotationChange[2]**2))
            print(f"Rotation difference: || r_current - r_new || = {rotDiff:.2f} deg")
            print(f"Mean Sampson error achievable = {q.sampsonErrorNew:.3f} px")
            print(f"Mean Sampson error current    = {q.sampsonErrorCurrent:.3f} px")
            print("Theoretical Depth Error Difference "
                  f"@1m:{q.depthErrorDifference[0]:.2f}%, "
                  f"2m:{q.depthErrorDifference[1]:.2f}%, "
                  f"5m:{q.depthErrorDifference[2]:.2f}%, "
                  f"10m:{q.depthErrorDifference[3]:.2f}%")

            # Reset accumulators and continue periodic calibration
            dynCalibInputControl.send(
                dai.DynamicCalibrationControl.resetData()
            )
            dynCalibInputControl.send(
                dai.DynamicCalibrationControl.startCalibration()
            )

        key = cv2.waitKey(1)
        if key == ord('q'):
            pipeline.stop()
            break
```

#### C++

来自我们的 [Github](https://github.com/luxonis/depthai-core/blob/main/examples/cpp/DynamicCalibration/calibration_dynamic.cpp) 的示例：

```cpp
// examples/cpp/DynamicCalibration/calibrate.cpp
#include <algorithm>
#include <chrono>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <opencv2/opencv.hpp>
#include <thread>

#include "depthai/depthai.hpp"

int main() {
    auto device = std::make_shared<dai::Device>();

    // ---------- Pipeline definition ----------
    dai::Pipeline pipeline(device);

    auto monoLeft = pipeline.create<dai::node::Camera>()->build(dai::CameraBoardSocket::CAM_B);
    auto monoRight = pipeline.create<dai::node::Camera>()->build(dai::CameraBoardSocket::CAM_C);

    auto* leftOut = monoLeft->requestFullResolutionOutput();
    auto* rightOut = monoRight->requestFullResolutionOutput();

    // Dynamic-calibration node
    auto dynCalib = pipeline.create<dai::node::DynamicCalibration>();
    leftOut->link(dynCalib->left);
    rightOut->link(dynCalib->right);

    auto stereo = pipeline.create<dai::node::StereoDepth>();
    leftOut->link(stereo->left);
    rightOut->link(stereo->right);

    // In-pipeline host queues
    auto leftSyncedQueue = stereo->syncedLeft.createOutputQueue();
    auto rightSyncedQueue = stereo->syncedRight.createOutputQueue();
    auto disparityQueue = stereo->disparity.createOutputQueue();

    auto dynCalibOutQ = dynCalib->calibrationOutput.createOutputQueue();
    auto dynCoverageOutQ = dynCalib->coverageOutput.createOutputQueue();

    auto dynCalibInputControl = dynCalib->inputControl.createInputQueue();

    device->setCalibration(device->readCalibration());

    pipeline.start();
    std::this_thread::sleep_for(std::chrono::seconds(1));  // wait for autoexposure to settle

    using DCC = dai::DynamicCalibrationControl;
    // Optionally set performance mode:
    dynCalibInputControl->send(DCC::setPerformanceMode(DCC::PerformanceMode::OPTIMIZE_PERFORMANCE));

    // Start calibration (optimize performance)
    dynCalibInputControl->send(DCC::startCalibration());

    double maxDisparity = 1.0;
    while(pipeline.isRunning()) {
        auto leftSynced = leftSyncedQueue->get<dai::ImgFrame>();
        auto rightSynced = rightSyncedQueue->get<dai::ImgFrame>();
        auto disparity = disparityQueue->get<dai::ImgFrame>();

        cv::imshow("left", leftSynced->getCvFrame());
        cv::imshow("right", rightSynced->getCvFrame());

        cv::Mat npDisparity = disparity->getFrame();

        double minVal = 0.0, curMax = 0.0;
        cv::minMaxLoc(npDisparity, &minVal, &curMax);
        maxDisparity = std::max(maxDisparity, curMax);

        // Normalize the disparity image to an 8-bit scale.
        cv::Mat normalized;
        npDisparity.convertTo(normalized, CV_8UC1, 255.0 / (maxDisparity > 0 ? maxDisparity : 1.0));

        cv::Mat colorizedDisparity;
        cv::applyColorMap(normalized, colorizedDisparity, cv::COLORMAP_JET);

        // Set pixels with zero disparity to black.
        colorizedDisparity.setTo(cv::Scalar(0, 0, 0), normalized == 0);

        cv::imshow("disparity", colorizedDisparity);

        // Coverage (non-blocking)
        if(auto coverageMsg = dynCoverageOutQ->tryGet<dai::CoverageData>()) {
            std::cout << "2D Spatial Coverage = " << coverageMsg->meanCoverage << "  / 100 [%]\n";
            std::cout << "Data Acquired       = " << coverageMsg->dataAcquired << "  / 100 [%]\n";
        }

        // Calibration result (non-blocking)
        if(auto dynCalibrationResult = dynCalibOutQ->tryGet<dai::DynamicCalibrationResult>()) {
            std::cout << "Dynamic calibration status: " << dynCalibrationResult->info << std::endl;

            if(dynCalibrationResult->calibrationData) {
                std::cout << "Successfully calibrated." << std::endl;

                // Apply the produced calibration
                const auto& newCalib = dynCalibrationResult->calibrationData->newCalibration;
                dynCalibInputControl->send(DCC::applyCalibration(newCalib));

                // Print quality deltas
                const auto& q = dynCalibrationResult->calibrationData->calibrationDifference;

                float rotDiff = std::sqrt(q.rotationChange[0] * q.rotationChange[0] + q.rotationChange[1] * q.rotationChange[1]
                                          + q.rotationChange[2] * q.rotationChange[2]);
                std::cout << "Rotation difference: " << rotDiff << " deg\n";
                std::cout << "Mean Sampson error achievable = " << q.sampsonErrorNew << " px\n";
                std::cout << "Mean Sampson error current    = " << q.sampsonErrorCurrent << " px\n";
                std::cout << "Theoretical Depth Error Difference " << "@1m:" << std::fixed << std::setprecision(2) << q.depthErrorDifference[0] << "%, "
                          << "2m:" << q.depthErrorDifference[1] << "%, " << "5m:" << q.depthErrorDifference[2] << "%, " << "10m:" << q.depthErrorDifference[3]
                          << "%\n";

                // Reset and start a new round if desired
                dynCalibInputControl->send(DCC::startCalibration());
            }
        }

        int key = cv::waitKey(1);
        if(key == 'q') break;
    }

    return 0;
}
```

> 动态标定不会完全恢复出厂指定的绝对深度精度。请使用工厂工具进行生产级重新标定。

更多信息请参阅示例的 README。

## 场景指南

好的校准场景能让算法更容易检测、匹配和跟踪特征。建议的特征如下：

 * 包含多个深度范围内的纹理物体。
 * 避免使用空白墙壁或毫无特征的表面。
 * 缓慢移动摄像头以覆盖整个视场；避免突然的运动。

| 建议 | 原始图像 VS. 特征覆盖（绿色区域） |
| --- | --- |
| ✅**确保丰富的纹理和视觉细节** - 纹理丰富、边缘清晰且在视场中均匀分布的物体，能创造理想的校准条件。 | |
| 🚫**避免平坦或毫无特征的表面** - 缺乏纹理表面或视觉上可区分的物体，能提供的可用特征很少。 | |
| 🚫**避免反光和透明表面** - 反光和透明表面会产生错误的3D特征。 | |
| 🚫**避免黑暗场景** - 低对比度、阴影以及照明不足的场景，能检测到的特征很少。 | |
| 🚫**避免重复图案** - 许多有图案的区域看起来过于相似，难以区分。 | |

## PerformanceMode 调优

| 模式 | 使用场景 |
| --- | --- |
| `DEFAULT` | 平衡精度与速度。 |
| `STATIC_SCENERY` | 相机固定，场景稳定。 |
| `OPTIMIZE_SPEED` | 最快标定，精度降低。 |
| `OPTIMIZE_PERFORMANCE` | 在特征丰富的场景中实现最大精度。 |
| `SKIP_CHECKS` | 自动化流水线，忽略确保场景质量的内部检查。 |

> 为了获得最高精度，将
> **OPTIMIZE_PERFORMANCE**
> 与动态、功能丰富的环境相结合。

通过将合适的场景与正确的 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!"` |
设备标定过于陈旧，动态重新标定无法提供任何好处。 | 需要更新的设备 |

## 另请参阅

 * [通用动态标定信息页面](https://docs.luxonis.com/hardware/platform/depth/dynamic-calibration.md)
 * [手动立体和ToF标定指南](https://docs.luxonis.com/hardware/platform/depth/manual-calibration.md)
 * 如果您希望自动标定操作，请查看[自动标定文档页面](https://docs.luxonis.com/software-v3/depthai/depthai-components/host_nodes/auto_calibration.md)。
