dockkit

dockkit

熱門

使用 DockKit 控制電動攝影機底座並啟用智慧主體追蹤。適用於搜尋相容於 DockKit 的配件、實現針對人臉或身體的相機主體追蹤、控制底座馬達進行水平與垂直旋轉(Pan & Tilt)、設定構圖行為與感興趣區域(Region of Interest),以及建置具備自動相機追蹤功能的影片應用程式。

967星標
49分支
更新於 2026/7/31
SKILL.md
唯讀
名稱
dockkit
描述

使用 DockKit 控制電動攝影機底座並啟用智慧主體追蹤。適用於搜尋相容於 DockKit 的配件、實現針對人臉或身體的相機主體追蹤、控制底座馬達進行水平與垂直旋轉(Pan & Tilt)、設定構圖行為與感興趣區域(Region of Interest),以及建置具備自動相機追蹤功能的影片應用程式。

DockKit

用於整合電動攝影機支架與雲台的框架,能透過旋轉 iPhone 來實體追蹤主體。DockKit 負責處理馬達控制、主體偵測與構圖,讓相機應用程式無需額外程式碼即可獲得 360 度水平旋轉(Pan)與 90 度垂直傾斜(Tilt)的追蹤能力。應用程式可覆寫系統追蹤以提供自訂的觀察值(Observation)、直接控制馬達,或是調整構圖。支援 iOS 17+、Swift 6.3。

目錄

設定

匯入 DockKit:

import DockKit

DockKit 需要實體相容 DockKit 的配件以及真實裝置。模擬器(Simulator)無法連線至底座硬體。

DockKit 本身不需要任何特殊的 Entitlement 或 DockKit 專屬的 Info.plist 鍵值。使用裝置相機的相機應用程式仍需進行正常的相機隱私處理,包含 NSCameraUsageDescription。此框架會自動透過 DockKit 系統守護程式(System daemon)與已配對的配件進行通訊。

應用程式必須使用 AVFoundation 相機 API。DockKit 會掛鉤(hook)至相機管道(camera pipeline)中,分析影格以進行系統追蹤。

搜尋配件

使用 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 會發出包含 state、accessory 及 trackingButtonEnabled 的 DockAccessory.StateChange 值。使用 accessory.identifier 取得名稱、類別與 UUID;硬體詳細資訊可透過 firmwareVersion 與 hardwareModel 取得。

系統追蹤

系統追蹤是 DockKit 的預設模式。啟用後,系統會透過內建的 ML 推論分析相機影格、偵測人臉與身體,並驅動馬達讓主體保持在畫面(Frame)中。任何使用 AVFoundation 相機 API 的應用程式皆可自動受益。

啟用或停用

// 啟用系統追蹤(預設)
try await DockAccessoryManager.shared.setSystemTrackingEnabled(true)

// 停用系統追蹤以進行自訂控制
try await DockAccessoryManager.shared.setSystemTrackingEnabled(false)

系統追蹤狀態不會在應用程式終止、重新開機或背景/前景切換時保持。每當應用程式需要特定數值時,請明確設定。

點擊選擇主體

允許使用者透過點擊選擇特定主體:

// 在影片影格座標的單位點上選擇主體
try await accessory.selectSubject(at: CGPoint(x: 0.5, y: 0.5))

// 透過識別碼選擇特定主體
try await accessory.selectSubjects([subjectUUID])

// 清除選擇(返回自動選擇)
try await accessory.selectSubjects([])

自訂追蹤

使用自訂 ML 模型或 Vision 框架時,請停用系統追蹤並提供自訂觀察值(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)
}

觀察值類型

審查自訂追蹤時,請在唯一支援的 ObservationType 事例中明確選擇:.humanFace、.humanBody 和 .object。當可能偵測到身體或物件時,請勿僅回答 .humanFace。

rect 使用原點位於左下角的歸一化座標(與 Vision 框架相同的座標系統——無需轉換)。

相機資訊

DockAccessory.CameraInformation 用來描述活躍的相機;請勿硬編碼占位用的裝置、內部參數(intrinsics)或影格尺寸值。當座標已相對於左下角時,請將 orientation 設定為 .corrected。在審查回答中,請拒絕不透明的可選 cameraInfo 占位符,並展示如何從活躍的 AVCaptureDevice 加上目前的 CMSampleBuffer 進行建構。

Track 的變體也接受 [AVMetadataObject] 而非觀察值。當 DockKit 需要將觀察值或元資料與擷取的影像緩衝區結合時,請使用 image: CVPixelBuffer 多載;在這些多載中,image 引數是必需的。

構圖與感興趣區域

構圖模式

控制系統如何對追蹤的主體進行構圖:

try await accessory.setFramingMode(.automatic) // 文件記載的預設值
try await accessory.setFramingMode(.center)    // 明確選擇啟用的模式
模式 行為
.automatic 文件記載的預設值;系統決定最佳構圖
.center 明確選擇啟用的模式,讓主體保持在正中央
.left 將主體構圖在左側三分之一處
.right 將主體構圖在右側三分之一處

預設的系統行為通常會將主要主體置中,但 .center 絕非類似預設的模式;.automatic 才是。當圖層覆蓋(graphic overlays)占用部分影格時,請使用 .left 或 .right。

感興趣區域

將追蹤限制在影片影格的特定區域內:

// 歸一化座標,原點位於左上角
let squareRegion = CGRect(x: 0.25, y: 0.0, width: 0.5, height: 1.0)
try await accessory.setRegionOfInterest(squareRegion)

裁切為非標準長寬比(例如用於視訊會議的正方形影片)時,請使用感興趣區域,以便主體保持在可見區域內。

馬達控制

在直接控制馬達之前,請先停用系統追蹤。

角速度

設定持續旋轉速度(單位為弧度/秒,radians per second):

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 -- 俯仰/傾斜(Pitch)。在 iOS 上正值向下傾斜。
  • y -- 偏航/水平旋轉(Yaw)。正值向右旋轉。
  • z -- 翻滾(Roll,若硬體支援)。

設定姿態

在指定時間長度內移動至特定位置:

let target = Vector3D(x: 0.0, y: 0.5, z: 0.0)  // 水平旋轉 (Yaw) 0.5 rad
let progress = try accessory.setOrientation(
    target,
    duration: .seconds(2),
    relative: false
)

也接受使用 Rotation3D 進行基於四元數(quaternion)的方向設定。設定 relative: true 可相對於目前位置進行移動。傳回的 Progress 物件可追蹤完成進度。

運動狀態

監視配件的目前位置與速度:

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 {
        // 發生馬達錯誤
    }
}

設定限制

限制每個軸向的運動範圍與最大速度:

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

動畫效果

具有表現力的內建角色動畫,可操作底座做出動作:

// 在執行動畫前停用系統追蹤
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)
動畫 效果
.yes 點頭動作
.no 搖頭/搖晃動作
.wakeup 開機風格動作
.kapow 戲劇性的擺動動作

動畫從配件的目前位置開始並非同步(asynchronously)執行。完成後務必還原追蹤狀態。請將 animate(motion:) 和 setOrientation(_:duration:relative:) 的呼叫頻率控制在每秒不超過兩次;較高的呼叫頻率可能會拋出 .frameRateTooHigh 錯誤。

追蹤狀態與主體選擇

iOS 18+ 透過會拋出例外狀況的 trackingStates 非同步序列(async sequence)公開由 ML 衍生出的追蹤訊號。每個狀態都包含 time 和 trackedSubjects(.person 或 .object);人物包含 identifier、rect、speakingConfidence(說話信心度)、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,針對圖層覆蓋(overlays)請使用 rect,將較小的 saliencyRank 數值作為備用方案。
在審查回答中,請在程式碼中處理 lookingAtCameraConfidence 與 rect,而不僅僅是在文字敘述中提及。

Acce

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