dockkit

dockkit

热门

使用 DockKit 控制电动相机底座并实现智能主体追踪。适用于探索兼容 DockKit 的配件设备、实现面向人脸或人体的人像追踪、控制底座电机进行摇摄(Pan)与倾斜(Tilt)、配置构图行为、设置感兴趣区域(Region of Interest),以及开发具备自动相机追踪功能的视频类 App。

967Star
49Fork
更新于 2026/7/31
SKILL.md
只读
名称
dockkit
描述

使用 DockKit 控制电动相机底座并实现智能主体追踪。适用于探索兼容 DockKit 的配件设备、实现面向人脸或人体的人像追踪、控制底座电机进行摇摄(Pan)与倾斜(Tilt)、配置构图行为、设置感兴趣区域(Region of Interest),以及开发具备自动相机追踪功能的视频类 App。

DockKit

用于集成电动相机底座和云台的框架,通过旋转 iPhone 实现物理上的主体追踪。DockKit 负责电机控制、主体检测以及构图,因此相机 App 无需编写额外代码即可获得 360 度水平摇摄(Pan)和 90 度垂直倾斜(Tilt)追踪功能。App 也可以重写系统级追踪逻辑,以提供自定义观察数据、直接控制电机或调整构图。支持 iOS 17+、Swift 6.3。

Contents

Setup

导入 DockKit:

import DockKit

DockKit 需要物理上兼容 DockKit 的配件以及真机设备。模拟器(Simulator)无法连接底座硬件。

DockKit 本身不需要特殊的 Entitlements 权限或特定于 DockKit 的 Info.plist 键值。对于使用设备相机的 App,仍需正常处理相机隐私权限,包括配置 NSCameraUsageDescription。框架会自动通过 DockKit 系统守护进程(system daemon)与已配对的配件进行通信。

App 必须使用 AVFoundation 相机 API。DockKit 会挂钩到相机管线中,分析帧数据以进行系统级追踪。

Discovering Accessories

使用 DockAccessoryManager.shared 监听底座连接状态:

import DockKit

func observeAccessories() async throws {
    for await stateChange in try DockAccessoryManager.shared.accessoryStateChanges {
        switch stateChange.state {
        case .docked:
            guard let accessory = stateChange.accessory else { continue }
            // 配件已连接且就绪
            configureAccessory(accessory)
        case .undocked:
            // iPhone 已从底座移除
            handleUndocked()
        @unknown default:
            break
        }
    }
}

accessoryStateChanges 会发出包含 stateaccessorytrackingButtonEnabledDockAccessory.StateChange 值。使用 accessory.identifier 获取名称、类别和 UUID;硬件详细信息可通过 firmwareVersionhardwareModel 获取。

System Tracking

系统级追踪是 DockKit 的默认模式。启用后,系统会通过内置的 ML 推理分析相机帧数据,检测人脸和人体,并驱动电机使主体保持在画面中。任何使用 AVFoundation 相机 API 的 App 都会自动受益。

Enable or Disable

// 启用系统级追踪(默认)
try await DockAccessoryManager.shared.setSystemTrackingEnabled(true)

// 禁用系统级追踪以进行自定义控制
try await DockAccessoryManager.shared.setSystemTrackingEnabled(false)

系统级追踪状态不会在 App 终止、重启或后台/前台切换时持久化保存。每当 App 需要特定设置值时,请显式进行设置。

Tap to Select Subject

允许用户通过点击来选择特定主体:

// 选择视频帧单位坐标系中指定点处的主体
try await accessory.selectSubject(at: CGPoint(x: 0.5, y: 0.5))

// 通过标识符选择特定主体
try await accessory.selectSubjects([subjectUUID])

// 清除选择(恢复自动选择)
try await accessory.selectSubjects([])

Custom Tracking

在使用自定义 ML 模型或 Vision 框架时,可以禁用系统级追踪并提供您自己的观察数据(Observations)。

Providing Observations

根据您的推理输出构造 DockAccessory.Observation 值,并以 10-30 fps 的帧率传递给配件:

import DockKit
import AVFoundation

func processFrame(
    _ sampleBuffer: CMSampleBuffer,
    accessory: DockAccessory,
    activeDevice: AVCaptureDevice
) async throws {
    let cameraInfo = DockAccessory.CameraInformation(
        captureDevice: activeDevice.deviceType,
        cameraPosition: activeDevice.position,
        orientation: .corrected,
        cameraIntrinsics: frameIntrinsics(from: sampleBuffer),
        referenceDimensions: frameDimensions(from: sampleBuffer)
    )

    let detection = try await detector.detect(sampleBuffer)
    let observationType: DockAccessory.Observation.ObservationType = switch detection.kind {
    case .face: .humanFace
    case .body: .humanBody
    case .object: .object
    }

    let observation = DockAccessory.Observation(
        identifier: detection.id,
        type: observationType,
        rect: detection.rect,       // 归一化坐标,左下角为原点
        faceYawAngle: detection.faceYawAngle
    )

    try await accessory.track([observation], cameraInformation: cameraInfo)
}

Observation Types

在审查自定义追踪逻辑时,请显式从仅有的受支持 ObservationType 枚举项中选择:.humanFace.humanBody 以及 .object。当有可能检测到人体或物体时,切勿仅返回 .humanFace

rect 使用归一化坐标,以左下角为原点(与 Vision 框架的坐标系一致——无需坐标转换)。

Camera Information

DockAccessory.CameraInformation 用于描述当前活跃的相机;切勿硬编码占位设备、相机内参或帧尺寸数值。当坐标已经相对于左下角时,将 orientation 设置为 .corrected。在代码审查回答中,拒绝使用不透明的可选 cameraInfo 占位符,应展示如何基于活跃的 AVCaptureDevice 以及当前的 CMSampleBuffer 进行构造。

track 重载方法还接受 [AVMetadataObject] 代替 Observation。当需要 DockKit 将观察数据或元数据与捕获的图像缓冲区结合时,使用包含 image: CVPixelBuffer 的重载版本;在这些重载中 image 参数是必需的。

Framing and Region of Interest

Framing Modes

控制系统如何对被追踪的主体进行构图:

try await accessory.setFramingMode(.automatic) // 文档说明的默认模式
try await accessory.setFramingMode(.center)    // 显式开启的居中模式
Mode Behavior
.automatic 文档说明的默认模式;系统决定最佳构图方式
.center 显式开启的模式,使主体保持在中央
.left 将主体置于画面左侧三分之一处
.right 将主体置于画面右侧三分之一处

默认的系统行为通常会将主要主体居中,但 .center 绝非类似默认的模式;.automatic 才是。当画面部分区域被图形叠加层占据时,可使用 .left.right

Region of Interest

将追踪范围限制在视频画面的特定区域内:

// 归一化坐标,原点在左上角
let squareRegion = CGRect(x: 0.25, y: 0.0, width: 0.5, height: 1.0)
try await accessory.setRegionOfInterest(squareRegion)

当裁剪为非标准宽高比(例如用于视频会议的正方形视频)时,请使用感兴趣区域(Region of Interest),以使主体保持在可见区域内。

Motor Control

在直接控制电机之前,请先禁用系统级追踪。

Angular Velocity

设置持续旋转的速度(单位:弧度/秒):

import Spatial

// 以 0.2 rad/s 向右摇摄,以 0.1 rad/s 向下倾斜
let velocity = Vector3D(x: 0.1, y: 0.2, z: 0.0)
try await accessory.setAngularVelocity(velocity)

// 停止所有运动
try await accessory.setAngularVelocity(Vector3D())

坐标轴:

  • x —— 俯仰(Tilt/倾斜)。在 iOS 上正值向下倾斜。
  • y —— 偏航(Pan/摇摄)。正值向右摇摄。
  • z —— 翻滚(Roll/滚动,如硬件支持)。

Set Orientation

在一定时长内移动到指定位置:

let target = Vector3D(x: 0.0, y: 0.5, z: 0.0)  // 偏航 0.5 弧度
let progress = try accessory.setOrientation(
    target,
    duration: .seconds(2),
    relative: false
)

同时支持接受基于四元数姿态的 Rotation3D。将 relative 设置为 true 可相对于当前位置移动。返回的 Progress 对象可用于跟踪完成进度。

Motion State

监控配件的当前位置与速度:

for await state in try accessory.motionStates {
    let positions = state.angularPositions   // Vector3D
    let velocities = state.angularVelocities // Vector3D
    let time = state.timestamp
    if let error = state.error {
        // 发生电机错误
    }
}

Setting Limits

限制每个轴的运动范围与最大速度:

let yawLimit = try DockAccessory.Limits.Limit(
    positionRange: -1.0 ..< 1.0,   // 弧度
    maximumSpeed: 0.5               // 弧度/秒
)
let limits = DockAccessory.Limits(yaw: yawLimit, pitch: nil, roll: nil)
try accessory.setLimits(limits)

Animations

内置的拟人动作动画,可让底座进行富有表现力的运动:

// 在播放动画前禁用系统级追踪
try await DockAccessoryManager.shared.setSystemTrackingEnabled(false)

let progress = try await accessory.animate(motion: .kapow)

// 等待动画完成
while !progress.isFinished && !progress.isCancelled {
    try await Task.sleep(for: .milliseconds(100))
}

// 恢复系统级追踪
try await DockAccessoryManager.shared.setSystemTrackingEnabled(true)
Animation Effect
.yes 点头动作
.no 摇头动作
.wakeup 开机风格动作
.kapow 戏剧性的摆动动作

动画从配件的当前位置开始并异步执行。务必在完成后恢复追踪状态。请将 animate(motion:)setOrientation(_:duration:relative:) 的调用频率限制在每秒不超过 2 次;更高的调用频率可能会抛出 .frameRateTooHigh 错误。

Tracking State and Subject Selection

iOS 18+ 通过可抛出异常的 trackingStates 异步序列暴露基于 ML 提取的追踪信号。每个状态都包含 timetrackedSubjects.person.object);其中人员信息包括 identifierrectspeakingConfidence(说话置信度)、lookingAtCameraConfidence(注视相机置信度)以及 saliencyRank(显著性排名,数值越低越显著)。

if #available(iOS 18.0, *) {
    for await state in try accessory.trackingStates {
        var speaker: UUID?
        var engaged: UUID?
        var salient: (id: UUID, rank: Int)?
        for subject in state.trackedSubjects {
            switch subject {
            case .person(let person):
                let id = person.identifier, rect = person.rect
                let speaking = person.speakingConfidence
                let looking = person.lookingAtCameraConfidence
                let rank = person.saliencyRank
                updateSubjectOverlay(id: id, rect: rect)
                if let speaking, speaking > 0.7 { speaker = id }
                if let looking, looking > 0.7 { engaged = id }
                if let rank, salient == nil || rank < salient!.rank { salient = (id, rank) }
            case .object(let object):
                let id = object.identifier, rect = object.rect
                let rank = object.saliencyRank
                updateSubjectOverlay(id: id, rect: rect)
                if let rank, salient == nil || rank < salient!.rank { salient = (id, rank) }
            }
        }
        if let id = speaker ?? engaged ?? salient?.id { try await accessory.selectSubjects([id]) }
    }
}

使用 selectSubjects(_:) 可以通过 UUID 锁定追踪主体;传入 [] 则恢复自动选择。在逻辑中,可使用 speakingConfidence 识别发言者,使用 lookingAtCameraConfidence 识别互动专注度,使用 rect 绘制叠加层,并将较低的 saliencyRank 数值作为备选项。
在代码审查回答中,应当在代码中实际消费 lookingAtCameraConfidencerect,而不仅是在说明文字中提及。

Acce

<!-- truncated for translation batch; full body continues in source -->