WWDC Quick Look 💓 By SwiftGGTeam
Detect animal poses in Vision

Detect animal poses in Vision

元の動画を見る

ハイライト

Vision フレームワークに Animal Body Pose API が追加され、猫と犬の 25 個の身体関節(しっぽや耳を含む)をリアルタイムで検出できます。Head、Forelegs、Hindlegs、Trunk、Tail、All の 6 つの関節グループに分類されます。1 枚の画像で最大 2 匹まで検出でき、最小入力サイズは 64×64 ピクセル。Neural Engine を活用すればカメラストリームでもリアルタイム性能を実現できます。

主要内容

3 年前、Vision は Human Body Pose を導入し、人体の 19 個の関節を検出できるようになりました。開発者はこれを使って多くの健康・フィットネス App を作ってきました。しかし現実世界には人間だけではありません。ペットも多くの App の中心となるシナリオです。

Vision には以前から Animal Recognition があり、猫と犬を検出・識別してバウンディングボックス、ラベル、信頼度を返せました。しかしそれは「画面に犬がいる」としか教えてくれません。犬が何をしているかは分かりません。立っているのか、座っているのか、走っているのか、丸まって眠っているのか。バウンディングボックスだけでは推測が難しいのです。

Animal Body Pose はこの空白を埋めます。猫と犬の 25 個の身体関節—耳、目、鼻、4 本の脚、首、しっぽ—を検出します。これらの関節は Head(頭部)、Forelegs(前脚)、Hindlegs(後脚)、Trunk(胴体)、Tail(しっぽ)、All(すべて)の 6 グループに分かれます。

入力は静止画像でもビデオストリームでも構いません。処理後、各動物について関節位置のセットが返されます。開発者はこれらの位置を使って骨格を描画し、姿勢を分析し、動きを追跡したり、AR 装飾を重ねたりできます。

詳細

検出リクエストの作成と実行

04:52)カメラストリームから CMSampleBuffer を取得したら、VNDetectAnimalBodyPoseRequest を作成します。

// captureOutput(_:didOutput:from:) でカメラフレームを処理
func captureOutput(
    _ output: AVCaptureOutput,
    didOutput sampleBuffer: CMSampleBuffer,
    from connection: AVCaptureConnection
) {
// 1. 検出リクエストを作成
    let request = VNDetectAnimalBodyPoseRequest()
    
// 2. リクエストハンドラを作成
    let handler = VNImageRequestHandler(
        cmSampleBuffer: sampleBuffer,
        orientation: .up,
        options: [:]
    )
    
// 3. リクエストを実行
    do {
        try handler.perform([request])
        
// 4. 検出結果を取得
        guard let observations = request.results as? [VNAnimalBodyPoseObservation] else {
            return
        }
        
        for observation in observations {
            processAnimalPose(observation)
        }
    } catch {
        print("Detection failed: \(error)")
    }
}

キーポイント:

  • VNDetectAnimalBodyPoseRequest は新しい Vision リクエストタイプです
  • VNImageRequestHandlerCMSampleBuffer を受け取り、ビデオストリームを処理します
  • request.resultsVNAnimalBodyPoseObservation の配列を返し、各要素が検出された 1 匹の動物に対応します
  • 1 枚の画像では最大 2 匹分の観測結果が返されます

関節データへのアクセス

05:36)観測結果から関節を取得します。

func processAnimalPose(_ observation: VNAnimalBodyPoseObservation) {
// すべての関節点を取得
    let allPoints = try? observation.recognizedPoints(.all)
    
// または頭部の関節点のみを取得
    let headPoints = try? observation.recognizedPoints(.head)
    
// 関節点を走査
    for (jointName, point) in allPoints ?? [:] {
// point.location は正規化座標 (0.0 - 1.0)
// point.confidence は信頼度 (0.0 - 1.0)
        if point.confidence > 0.5 {
            let x = point.location.x * viewBounds.width
            let y = point.location.y * viewBounds.height
            drawJoint(at: CGPoint(x: x, y: y), label: jointName.rawValue)
        }
    }
}

キーポイント:

  • recognizedPoints(_:)VNAnimalBodyPoseObservation.JointsGroupName パラメータを受け取ります
  • 選択肢: .head.forelegs.hindlegs.trunk.tail.all
  • キーが VNAnimalBodyPoseObservation.JointName、値が VNRecognizedPoint の辞書を返します
  • location は正規化座標で、ビューサイズを掛けて画面座標に変換します
  • confidence は検出の信頼度を表し、0.5 未満の点はフィルタリングすることを推奨します

骨格の描画

06:04)関節を接続して動物の骨格を描画します。

func drawSkeleton(observation: VNAnimalBodyPoseObservation, in context: CGContext) {
    guard let allPoints = try? observation.recognizedPoints(.all) else { return }
    
// 頭部の接続関係を定義
    let headConnections: [(VNAnimalBodyPoseObservation.JointName, VNAnimalBodyPoseObservation.JointName)] = [
        (.leftEar, .leftEye),
        (.leftEye, .nose),
        (.nose, .rightEye),
        (.rightEye, .rightEar),
        (.leftEar, .nose),
        (.rightEar, .nose)
    ]
    
    context.setStrokeColor(UIColor.green.cgColor)
    context.setLineWidth(2.0)
    
    for (startJoint, endJoint) in headConnections {
        guard let startPoint = allPoints[startJoint],
              let endPoint = allPoints[endJoint],
              startPoint.confidence > 0.5,
              endPoint.confidence > 0.5 else { continue }
        
        context.move(to: CGPoint(
            x: startPoint.location.x * viewBounds.width,
            y: startPoint.location.y * viewBounds.height
        ))
        context.addLine(to: CGPoint(
            x: endPoint.location.x * viewBounds.width,
            y: endPoint.location.y * viewBounds.height
        ))
    }
    
    context.strokePath()
}

キーポイント:

  • 骨格を描画するには関節間の接続関係を定義する必要があります
  • 頭部の接続: 左耳–左目–鼻–右目–右耳
  • 脚部の接続: 肩/腰から対応する脚の関節へ
  • 信頼度が両方とも閾値を超える関節ペアのみ接続します
  • しっぽの関節は曲線として個別に描画できます

関節位置への AR 装飾の重ね合わせ

09:24)関節位置を使って絵文字や装飾物を配置します。

func addEmojiOverlay(observation: VNAnimalBodyPoseObservation, on view: UIView) {
    guard let allPoints = try? observation.recognizedPoints(.all) else { return }
    
// 耳の位置にヘルメットを配置
    if let leftEar = allPoints[.leftEar],
       let rightEar = allPoints[.rightEar],
       leftEar.confidence > 0.5,
       rightEar.confidence > 0.5 {
        let centerX = (leftEar.location.x + rightEar.location.x) / 2 * view.bounds.width
        let centerY = (leftEar.location.y + rightEar.location.y) / 2 * view.bounds.height
        let width = abs(rightEar.location.x - leftEar.location.x) * view.bounds.width * 1.5
        
        let helmetLabel = UILabel()
        helmetLabel.text = "⛑️"
        helmetLabel.font = .systemFont(ofSize: width)
        helmetLabel.center = CGPoint(x: centerX, y: centerY - width / 2)
        view.addSubview(helmetLabel)
    }
    
// 目の位置にサングラスを配置
    if let leftEye = allPoints[.leftEye],
       let rightEye = allPoints[.rightEye],
       leftEye.confidence > 0.5,
       rightEye.confidence > 0.5 {
        let centerX = (leftEye.location.x + rightEye.location.x) / 2 * view.bounds.width
        let centerY = (leftEye.location.y + rightEye.location.y) / 2 * view.bounds.height
        let width = abs(rightEye.location.x - leftEye.location.x) * view.bounds.width * 2
        
        let glassesLabel = UILabel()
        glassesLabel.text = "🕶️"
        glassesLabel.font = .systemFont(ofSize: width)
        glassesLabel.center = CGPoint(x: centerX, y: centerY)
        view.addSubview(glassesLabel)
    }
}

キーポイント:

  • 両耳の中点をヘルメット位置、耳の間隔をヘルメットサイズの基準にします
  • 両目の中点をサングラス位置、目の間隔をサングラスサイズの基準にします
  • 装飾物のサイズは画面内の動物の実際のサイズに比例させます
  • VNRecognizeAnimalsRequest と組み合わせて動物のバウンディングボックスを取得し、装飾物サイズをさらに調整できます

Animal Recognition と Animal Body Pose の組み合わせ

07:43)2 つのリクエストを同時に使ってより完全な情報を取得します。

let animalRecognitionRequest = VNRecognizeAnimalsRequest()
let bodyPoseRequest = VNDetectAnimalBodyPoseRequest()

let handler = VNImageRequestHandler(ciImage: image, options: [:])
try? handler.perform([animalRecognitionRequest, bodyPoseRequest])

// 動物認識結果: 種類、バウンディングボックス
if let recognitionResults = animalRecognitionRequest.results as? [VNRecognizedObjectObservation] {
    for result in recognitionResults {
        print("Detected: \(result.labels.first?.identifier ?? "unknown")")
        print("Bounding box: \(result.boundingBox)")
    }
}

// 姿勢検出結果: 関節点
if let poseResults = bodyPoseRequest.results as? [VNAnimalBodyPoseObservation] {
    for result in poseResults {
        print("Joints detected: \(result.availableJointNames.count)")
    }
}

キーポイント:

  • VNRecognizeAnimalsRequest は動物の種類とバウンディングボックスを返します
  • VNDetectAnimalBodyPoseRequest は関節位置を返します
  • 2 つのリクエストは同じ VNImageRequestHandler で同時に実行できます
  • 組み合わせることで「画面に何の動物がいるか、どこにいるか、どんな姿勢か」が分かります

その他の Vision アップデート

11:07)この Session では他にもいくつかの Vision アップデートが紹介されました。

  • Stateful Requests: VNTargetedImage ベースのリクエストがステートフルリクエストに対応。名前に “Track” 動詞を使うものは追跡シナリオに適しています
  • MLComputeDevice サポート: リクエストがどの計算デバイス(CPU、GPU、Neural Engine)で実行されるかを照会・指定できます
  • Barcode Revision 4: MSIPlessey バーコード形式の追加、色反転 QR コードのサポート
  • Text Recognition: タイ語とベトナム語のサポート追加
  • FaceCaptureQuality Revision 3: 品質と精度の向上

重要ポイント

  1. ペット姿勢認識カメラ App を作る

    • ペットの姿勢をリアルタイムで検出し、「立っている」「座っている」「走っている」「眠っている」などのラベルに自動分類します
    • 価値がある理由: ペットの飼い主は自宅でペットが何をしているか知りたい。自動分類は手動で写真を整理するよりはるかに効率的です
    • 始め方: VNDetectAnimalBodyPoseRequest で関節を取得し、関節角度と相対位置を計算して、シンプルな姿勢分類ルールを構築します
  2. ペット AR ステッカーカメラを開発する

    • ペットに帽子、メガネ、スカーフなどの装飾をリアルタイムで重ね、動きに追従させます
    • 価値がある理由: Animal Body Pose は正確な関節位置を提供し、顔のキーポイントよりペットエフェクトに適しています
    • 始め方: カメラプレビュー層に CALayer を重ね、関節位置に基づいて装飾物の positiontransform をリアルタイム更新します
  3. ペット行動分析ツールを構築する

    • ペットのビデオを録画し、一定期間の姿勢変化を分析して活動レポート(活動時間、休息時間、異常行動アラート)を生成します
    • 価値がある理由: 獣医や動物行動学者はペットの健康状態を評価するために定量的データが必要です
    • 始め方: VNDetectAnimalBodyPoseRequest でビデオの各フレームを処理し、関節の軌跡を記録し、シンプルなルールエンジンで行動パターンを識別します

関連セッション

コメント

GitHub Issues · utterances