カメラの映像をHuman Body Pose APIに食わせて身長を算出する

AppleのHumanBodyPose APIを使って遊んでたら身長算出の箇所でどんづまったので備忘録として

## 今回の起こった問題
カメラを使用して映像を取得
HumanBodyPose APIをコールしリクエストを受け取る

受け取った値を確認すると180cmで固定
(実際にカメラで捉えた人物は160cmちょいの人物でした)

https://developer.apple.com/documentation/vision/vnhumanbodypose3dobservation

※ vnhumanbodypose3dobservation.bodyHeightの値を使用

## 端末
iPhone 16 Pro Max
iOS:18.3.1

## 調査過程で分かったこと
VNHumanBodyPose3DObservation.HeightEstimationがreferenceを返却している
※ 基準値からの推定であり、Lidarの性能を活かしきれていませんでした

```
case measured
A technique that uses LiDAR depth data to measure body height, in meters.
case reference
A technique that uses a reference height.
```

https://developer.apple.com/documentation/vision/vnhumanbodypose3dobservation/heightestimation-swift.enum

## 調査過程

わからんことはとりあえずドキュメントみたり、WWDCのビデオみて振り返る！

ちなみにHuman Body PoseについてはWWDC23で触れられています

https://developer.apple.com/jp/videos/play/wwdc2023/111241

ビデオ見ていると、おそらくこれだろうな〜ってのがありました。
※ 10:50ごろ

そこで自分が作成した処理にはHumanBodyPoseAPIをコールする際にDepthの情報を含めていないことが判明しました

ちなみに、自分が書いていたのはこんな感じの処理
ARKitから受け取った映像をCVPixelBufferに変換してリクエストをコールしていました
```.swift
func processFrame(_ frame: ARFrame) {
        guard let pixelBuffer = frame.capturedImage as CVPixelBuffer? else {
            return
        }
        
        let request = VNDetectHumanBodyPose3DRequest(completionHandler: bodyPoseHandler)
        ... etc
}
```

WWDC切り抜き
ファイルにdepthの情報が含まれていたらそのままコールしてもDepth使うよ〜って言っていますが

今回は画像ではなく、映像から取得していたのでDepthの情報が含まれていなかったので？と

![スクリーンショット 2025-03-21 17.07.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3961356/dca9f337-6dec-457d-b374-62a814e938ab.png)

https://developer.apple.com/documentation/vision/vnimagerequesthandler

https://developer.apple.com/documentation/vision/vnimagerequesthandler/init(cmsamplebuffer:depthdata:orientation:options:)

既存の実装ではAR Kitから映像データを受け取っていたので
AVDepthDataってどこから取得すればええねん...??ってなりました

ARKitにもDepthのデータ取得する方法があります。

が、型が違うんですよね

ARだとARDepthData

AVFoundationはAVDepthData

よってカメラ映像取得処理の大幅書き換えが必要になりました...()

https://developer.apple.com/documentation/arkit/ardepthdata

## ARKitによる実装からAVFaundationを使ったカメラ映像取得処理に切り替える

実装自体は~~めんどくさいので~~似たようなサンプルコードを引っ張ってきましょう

AppleからAnimal Body Poseの実装に関するサンプルが出ていますので、それを引っ張ってきて

https://developer.apple.com/documentation/vision/detecting-animal-body-poses-with-vision

必要な箇所を書き換える

1.AnimalPoseDetectorを参考にして、HumanBodyPose3DDetectorを作成する

```.swift
import AVFoundation
import Vision

// The Vision part.
class HumanBodyPose3DDetector: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate, ObservableObject {
    // Get the animal body joints using the VNRecognizedPoint.
    @Published var humanBodyParts = [VNHumanBodyPose3DObservation.JointName : VNHumanBodyRecognizedPoint3D]()
    @Published var humanBodyPoseResult: VNHumanBodyPose3DObservation?
    var lastDepthData: AVDepthData?
    
    // Notify the delegate that a sample buffer was written.
    func captureOutput(
        _ output: AVCaptureOutput,
        didOutput sampleBuffer: CMSampleBuffer,
        from connection: AVCaptureConnection
    ) {
        // Create a new request to recognize an animal body pose.
        // let animalBodyPoseRequest = VNDetectAnimalBodyPoseRequest(completionHandler: detectedAnimalPose)
        let humanBodyPoseRequest = VNDetectHumanBodyPose3DRequest(completionHandler: detectedHumanBodyPose)
        guard let cvPixelBuffer: CVPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
            return
        }
        guard let depthData = self.lastDepthData else {
            print("lastDepthData is nil")
            return
        }
        // Create a new request handler.
        let imageRequestHandler = VNImageRequestHandler(
            cvPixelBuffer: cvPixelBuffer,
            depthData: depthData,
            orientation: .up,
            options: [:]
        )
        do {
            try imageRequestHandler.perform([humanBodyPoseRequest])
        } catch {
            print("Unable to perform the request: \(error).")
        }
    }

    func detectedHumanBodyPose(request: VNRequest, error: Error?) {
        // Get the results from VNAnimalBodyPoseObservations.
        guard let humanBodyPoseResults = request.results as? [VNHumanBodyPose3DObservation] else {
            return
        }
        guard let firstResult = humanBodyPoseResults.first else {
            return
        }
        // Get the animal body recognized points for the .all group.
        guard let humanBodyAllParts = try? humanBodyPoseResults.first?.recognizedPoints(.all) else {
            return
        }
        self.humanBodyPoseResult = firstResult
        self.humanBodyParts = humanBodyAllParts
    }
    
    // LiDARデプスデータ取得
    func depthDataOutput(
        _ output: AVCaptureDepthDataOutput,
        didOutput depthData: AVDepthData,
        timestamp: CMTime,
        connection: AVCaptureConnection
    ) {
        DispatchQueue.main.async {
            self.lastDepthData = depthData
        }
    }
}
```

2.CameraViewControllerでDepthの情報を受け取って、HumanBodyPose3DDetectorへ渡す

```.swift

// LiDARのデプスデータ処理
extension CameraViewController: AVCaptureDepthDataOutputDelegate {
    func depthDataOutput(
        _ output: AVCaptureDepthDataOutput,
        didOutput depthData: AVDepthData,
        timestamp: CMTime,
        connection: AVCaptureConnection
    ) {
        (delegate as? HumanBodyPose3DDetector)?.depthDataOutput(output, didOutput: depthData, timestamp: timestamp, connection: connection)
    }
}

struct HumanDisplayView: UIViewControllerRepresentable {
    var humanJoint: HumanBodyPose3DDetector
    func makeUIViewController(context: Context) -> some UIViewController {
        let cameraViewController = CameraViewController()
        cameraViewController.delegate = humanJoint
        return cameraViewController
    }
    func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
    }
}
```

と、こんな感じで受け渡してあげれば
※ 細かな実装はAnimalBodyPoseのサンプルコード見ながら適宜調整してください

VNHumanBodyPose3DObservation.HeightEstimationの値がmeasuredになり、より正確な身長算出ができました〜

![スクリーンショット 2025-03-21 17.20.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3961356/f58a3708-53aa-4e10-9b9d-e3f5f8afe385.png)

![スクリーンショット 2025-03-21 17.21.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3961356/ba28c7bc-0179-4c06-8dc5-c5a544d1e80f.png)