SDKC++ SDK

采集

采集的作用、引导式交互、采集进度与控制和手动发起采集。

当前版本 · 1.4.0

采集

先读:设备发现与连接(设备连接成功后才能开始采集) · 读完你能:要么让默认流程跑完采集,要么在程序里接管采集交互

采集配置

自动流程

默认自动流程在创建 Sdk 时从 SdkConfig::algorithms.capture 读取采集配置。配置前先按目的选择:

目的配置
在交互式终端由 SDK 显示动作提示interaction = qnbot::CaptureInteractionMode::auto_terminal
由 GUI、ROS2 或远程程序显示提示和控件interaction = qnbot::CaptureInteractionMode::external
忽略可复用的旧采集,完整重新采样force = true
按佩戴者分别查找和保存结果operator_id = "operator-a"

例如,使用默认终端引导,并强制 operator-a 完整重新采集:

qnbot::SdkConfig config;
config.devices = {device_config};
config.targets = {target_config};
config.algorithms.capture.interaction =
    qnbot::CaptureInteractionMode::auto_terminal;
config.algorithms.capture.force = true;
config.algorithms.capture.operator_id = "operator-a";

qnbot::Sdk sdk(std::move(config));

字段类型、默认值与约束见 CaptureConfig。

手动流程

手动流程在 start_capture() 调用中直接提供本次采集所需的参数:

目的参数
选择本次使用的算法包和采集计划package_ids
按佩戴者查找和保存结果operator_id
忽略可复用的旧采集,完整重新采样force = true

例如,强制 operator-a 使用一个算法包完整重新采集:

const bool force_recapture = true;
auto session = device.start_capture(
    {"<package-id>"}, "operator-a", force_recapture);

如果你只想使用已有采集重新计算标定,先看概览中两个 force 的区别。

采集状态与控制

默认自动流程会自行检查并推进采集,不需要调用下面的接口。只有两种接管场景需要它们:

  • 应用决定何时启动:capture_status() → 判断状态 → start_capture()。
  • 外部 UI 接管提示:capture_progress() → 展示当前动作 → capture_control()。

判断是否启动手动采集

先查询当前算法包和佩戴者是否仍需采集:

const auto readiness = device.capture_status(package_ids, operator_id);
render_capture_readiness(readiness.state, readiness.plan, readiness.stages);

根据 readiness.state 决定下一步:

状态下一步
needs_capture调用 start_capture() 创建采集会话
ready复用已有采集,继续标定
not_required本次选择无需采集,继续后续流程

plan 和 stages 用于向佩戴者展示本次计划。完整字段见 CaptureReadiness,启动代码见手动发起采集。

在外部 UI 中推进采集

订阅进度,展示当前动作,并用同一个进度中的标识响应:

auto progress = device.capture_progress();
auto control = device.capture_control();
auto subscription = progress.subscribe(
    [&](const qnbot::Sample<qnbot::CaptureProgress>& sample) {
        const auto& stage = sample.value.stage;
        render_capture(stage);
        if (stage.state !=
                qnbot::CaptureStageState::awaiting_confirmation ||
            !stage.request_id) return;
        if (stage.optional && should_skip())
            control.skip(*stage.request_id);
        else
            control.confirm(*stage.request_id);
    });

render_capture() 和 should_skip() 由应用实现。重试阶段时调用 control.retry(stage_id),取消当前请求时调用 control.cancel(request_id);标识必须来自最新进度。完整流程见外部接管引导交互,字段定义见 Capture 模型。

采集方式

引导式采集交互

默认 auto_terminal 会在交互控制台中引导采集:前台 run_forever() 可以启用终端交互;后台运行启动时若仍需采集或标定,也会先完成终端引导。真实 TTY 会自动启用,其他控制台的判断见 终端输出与交互。

终端输出示例:

[Capture] glove.primary.right
open_hand: Open your hand
Press Enter to begin...
[####################] 2/2

[Calibration] glove.primary.right -> hand.primary.right completed

glove.primary.right 是右手手套,hand.primary.right 是输出目标。按 Enter 后,进度条在同一行刷新;动作提示和采样数由算法包决定。非交互环境或手动更新流程需要引导采集时,应使用 external 模式,由应用接管确认等操作。

外部接管引导交互

GUI、ROS2 或远程程序应选择 external,订阅 source-scoped capture_progress(),并把当前采集请求原样交给 capture_control()。 Capture 完成后,每个 target 的标定状态见 标定。

下面的片段接续前文已经创建的 device_config 和 target_config;open_calibration_ui()、render_capture()、render_calibration_job()、bind_waiting_actions() 和 bind_cancel_action() 都由应用实现。包含相同 external 交互和后台清理流程的完整示例见 external_input_run_background.cpp:

qnbot::SdkConfig config;
config.devices = {device_config};
config.targets = {target_config};
config.algorithms.capture.interaction =
    qnbot::CaptureInteractionMode::external;
config.algorithms.capture.operator_id = "operator-42";

qnbot::Sdk sdk(std::move(config));
auto glove = sdk.glove();
auto device = glove.device();
auto readiness = device.calibration_status("dexhand");
if (readiness.state ==
    qnbot::CalibrationReadinessState::needs_calibration) {
    open_calibration_ui(readiness.operator_id);
}
auto capture_progress = device.capture_progress();
auto calibration_progress = device.calibration_progress("dexhand");
auto control = device.capture_control();
auto on_confirm = [&control](const std::string& request_id) {
    control.confirm(request_id);
};
auto on_skip = [&control](const std::string& request_id) {
    control.skip(request_id);
};
auto on_cancel = [&control](const std::string& request_id) {
    control.cancel(request_id);
};
auto capture_subscription = capture_progress.subscribe(
    [&](const qnbot::Sample<qnbot::CaptureProgress>& sample) {
        render_capture(sample.value.stage);
        if (!sample.value.stage.request_id) return;
        if (sample.value.stage.state ==
            qnbot::CaptureStageState::awaiting_confirmation) {
            bind_waiting_actions(*sample.value.stage.request_id,
                                 sample.value.stage.optional,
                                 on_confirm, on_skip, on_cancel);
        } else {
            bind_cancel_action(*sample.value.stage.request_id, on_cancel);
        }
    });
auto calibration_subscription = calibration_progress.subscribe(
    [](const qnbot::Sample<qnbot::CalibrationProgress>& sample) {
        render_calibration_job(sample.value.job);
    });

CaptureInteractionMode、operator_id 与两个 force 的定义和约束见 CaptureConfig 与 CalibrationConfig。

手动发起采集

需要把采集拆成独立业务阶段、自行决定何时查询和启动采集时,应使用独立采集流程,先选定目标设备,再在该 GloveDevice 上显式调用 capture_status() 和 start_capture();采集完成后的标定一侧见 手动发起标定。

启动参数和强制重采的最小调用见采集配置中的手动流程。

采集与后续标定共用同一个设备,先完成整次采集,再开始标定。回环表示继续读取同一个会话的状态,不会再次创建 Session;标定接收仍打开的已完成采集会话;失败、取消或超时应关闭已创建的会话:

流程图渲染中

参数、类型和默认值见 API 参考的 Capture/Calibration 会话。

完整的左右手顺序采集、再分侧保存标定结果的可运行示例见 host_capture_calibration.cpp。

算法包应先通过产品 CLI 安装。应用把本次所需的算法包 ID 交给 SDK,并按返回的采集计划展示动作。

先用 capture_status() 查询准备情况,再启动采集;capture_status() 不创建 Session。采集与后续标定必须使用同一个 GloveDevice:

auto device = glove.device(qnbot::Side::left);

auto readiness = device.capture_status(
    {"<package-id-1>", "<package-id-2>"}, "operator-a");
render_capture_readiness(readiness.state, readiness.plan, readiness.stages);

const bool force_recapture = true;
auto left_session = device.start_capture(
    {"<package-id-1>", "<package-id-2>"}, "operator-a", force_recapture);
glove.start(); // 首次 start_capture() 在 start() 前创建
while (true) {
    const auto snapshot = left_session.snapshot();
    render_capture_plan(snapshot.plan, snapshot.stage_progress, snapshot.next_action);
    if (snapshot.state == qnbot::CaptureSessionState::completed) break;
    if (snapshot.state == qnbot::CaptureSessionState::failed ||
        snapshot.state == qnbot::CaptureSessionState::cancelled)
        throw std::runtime_error("capture stopped");

    const auto current = std::find_if(
        snapshot.stage_progress.begin(), snapshot.stage_progress.end(),
        [&](const auto& stage) {
            return snapshot.current_stage &&
                   stage.stage_id == *snapshot.current_stage;
        });
    if (current != snapshot.stage_progress.end() &&
        current->state == qnbot::CaptureStageState::awaiting_confirmation) {
        if (!snapshot.request_id)
            throw std::runtime_error("capture request_id is missing");
        wait_for_operator_confirmation();
        left_session.confirm(*snapshot.request_id);
        continue;
    }
    static_cast<void>(glove.update().sleep());
}
auto capture_result = left_session.result();
if (!capture_result)
    throw std::runtime_error("completed capture has no result");

完整字段、类型和可空行为见 CaptureReadiness、 CaptureSessionSnapshot 和 CaptureResult。

CaptureSession 一次执行一只手的完整计划。左右手必须顺序创建两个 Session,不交替采集。完成前 result() 返回 std::nullopt;完成后返回采集摘要。后续标定直接传入仍保持打开的已完成 Session,应用无需接触原始帧文件或 SDK 内部标识,见 手动发起标定。

capture_status() 用于查询当前计划是否还需要采集,不创建 Session;start_capture() 才会启动实际会话。状态对应的下一步见判断是否启动手动采集。

以上片段展示主流程,render_* 和 wait_for_operator_confirmation() 由应用实现。实际应用还应设置超时,在取消或异常时关闭 Session;可选步骤使用当前 request_id 调用 skip(),重采指定步骤时使用 retry(stage_id)。完整的交互、超时与资源清理见本节链接的可运行示例。