[iOS] WWDC2020 Detect Body and Hand Pose with Vision (2)

Hamo·2022년 5월 9일
0
post-thumbnail

지난 포스트에 이어서 실제 코드로 Vision을 어떻게 적용하는지 정리하였습니다.

CameraView의 초기세팅은 이곳에서 참고하였습니다.

1. request handler 만들기

VNImageRequestHandler(이미지로부터 얼굴정보나 텍스트를 찾는 등 이미지 분석을 처리) 를 이용하여 request handler를 만들어 줍니다.

카메라를 이용하기 때문에 위의 이니셜라이저를 사용합니다.

2. request 만들기

3. performRequests 호출을 통해 handler에게 요청을 제공


perform([VNRequest]) 메소드를 이용해서 handler에게 요청을 보내줍니다.

4. observation 받기

3번을 수행하면 requests result propertyobservation을 가집니다.

애플의 샘플코드 주석에 따르면

// Since we set the maximumHandCount property of the request to 1, there will be at most one observation.

VNDetectHumanHandPoseRequestmaximumHandCount 프로퍼티를 1개로 설정했기 때문에 observation을 1개만 가질 수 있습니다.

handPoseRequest.maximumHandCount = 1
// observation 받기
guard let observation = handPoseRequest.results?.first else {
	return
}

1개의 손만 인식하기 때문에 result 프로퍼티에 첫 번째 요소만 가져와서 사용하고 만약 2개이상의 손을 인식하려면 모든 요소를 다 사용해야합니다.

observations에는 발견된 모든 손의 랜드마크가 포함되어 있고 이를 통해서 원하는 손의 랜드마크에 접근할 수 있습니다.

5. observation으로 부터 landmark에 접근하기

let allPoints = handPoseObservation.recognizedPoints(forGroupKey: VNRecognizedPointGroupKeyAll) 
// 손에 있는 landmark 전부

발표 영상에서는 위와 같이 접근하였는데 현재는 조금 다르게 아래와 같이 접근합니다.

.recognizedPoints(:jointGroupName) 을 호출하여 랜드마크 그룹의 Dictionary를 리턴합니다.

.recognizedPoints(:jointName) 을 호출하여 각각의 랜드마크에 접근할 수도 있습니다.

let allPoint: [VNRecognizedPointKey : VNRecognizedPoint]
// allPoint의 타입

allPoint를 콘솔에 찍어보면 다음과 같습니다.

VNRecognizedPoint를 개발자 문서를 통해 확인해 보면 아래와 같은 계층을 가집니다.

이는 발표에서 설명한 순서와 일치합니다.

손의 모든 랜드마크를 가져왔고 화면에 점을 찍기위해 화면의 좌표와 일치하게 랜드마크의 좌표를 변환해주면 끝입니다.

6. 랜드마크의 좌표 변환해주기

// Convert points from Vision coordinates to AVFoundation coordinates.
// Convert points from AVFoundation coordinates to UIKit coordinates.
// 애플 샘플코드에 있는 주석

Vision Cooperation -> AVFoundation coordinate -> UIKit coordinates으로 변환해 줍니다.

모두 완료하면 손가락에 있는 21개의 랜드마크를 모두 인식합니다.

같은 방법으로 Body Pose를 사용하면 다음과 같습니다.

애플의 샘플코드를 통해서 랜드마크들의 좌표값을 어떻게 응용하는지 확인할 수 있습니다.

간단하게 살펴보면 엄지와 검지의 랜드마크의 좌표값을 비교해서 상태를 정의하고 엄지와 검지가 pinch상태일 때 화면에 그림을 그리는 동작을 합니다.

영상처럼 hello를 쓰려고 했는데 안써지네요..


전체 코드

CamereViewController

import UIKit
import AVFoundation
import Vision

class CameraViewController: UIViewController {
    
    @IBOutlet var cameraView: CameraView!
    private var captureSession: AVCaptureSession?
    private var videoPreviewLayer: AVCaptureVideoPreviewLayer?
    
    // Hand Pose Request 만들기
    private let handPoseRequest = VNDetectHumanHandPoseRequest()
    // Body Pose Request 만들기
    private let bodyPoseRequest = VNDetectHumanBodyPoseRequest()


    override func viewDidLoad() {
        super.viewDidLoad()
        prepareCaptureSession()
        prepareCaptureUI()
        handPoseRequest.maximumHandCount = 1
        

        // Do any additional setup after loading the view.
    }
    
    private func prepareCaptureSession() {
        
        // 인풋(카메라 녹음기능 등등 폰에 내장되어있는거) 아웃풋을 연결해줌
        let captureSession = AVCaptureSession()
        
        // 카메라 설정
        guard let captureDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) else { return }
        guard let input = try? AVCaptureDeviceInput(device: captureDevice) else { return }
        captureSession.addInput(input)

        let output = AVCaptureVideoDataOutput()
        
        // AVCaptureVideoDataOutputSampleBufferDelegate는 video data output 에서 sample buffer를 받아오며, 받아오는 video data output의 상태를 감시하는 메쏘드들을 가지고 있는 프로토콜
        output.setSampleBufferDelegate(self, queue: .main)
        captureSession.addOutput(output)
        
        self.captureSession = captureSession
        self.captureSession?.startRunning()
    }
    
    private func prepareCaptureUI() {
        guard let session = captureSession else { return }
        let videoPreviewLayer = AVCaptureVideoPreviewLayer(session: session)
        videoPreviewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
        videoPreviewLayer.frame = view.layer.frame
        view.layer.addSublayer(videoPreviewLayer)
        self.videoPreviewLayer = videoPreviewLayer
    }
    

    /*
    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // Get the new view controller using segue.destination.
        // Pass the selected object to the new view controller.
    }
    */

}
// Hand Pose
//extension CameraViewController: AVCaptureVideoDataOutputSampleBufferDelegate {
//
//    // 받아오는 video data output의 상태를 감시
//    func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
//
//        var UIKitCoordinatePoint: [CGPoint] = []
//
//        // request handler 만들기
//        let handler = VNImageRequestHandler(cmSampleBuffer: sampleBuffer, orientation: .up, options: [:])
//
//        do {
//            // performRequests 호출을 통해 handler에게 요청을 제공
//            try handler.perform([handPoseRequest])
//
//            // observation 받기
//            guard let observation = handPoseRequest.results?.first else {
//                return
//            }
//
//            // observation으로 부터 landmark에 접근하기
//            let allPoint = try observation.recognizedPoints(forGroupKey: .all)
//
//            // Vision coordinates -> AVFoundation coordinates -> UIKit coordinates
//            allPoint.values.forEach { point in
//                let AVFoundationCoordinatePoint = CGPoint(x: point.location.x, y: 1 - point.location.y)
//                UIKitCoordinatePoint.append(videoPreviewLayer!.layerPointConverted(fromCaptureDevicePoint: AVFoundationCoordinatePoint))
//            }
//
//            cameraView.showPoints(UIKitCoordinatePoint, color: .red, view: self.cameraView)
//
//        } catch {
//            print("error")
//        }
//    }
//
//}

// Body Pose
extension CameraViewController: AVCaptureVideoDataOutputSampleBufferDelegate {

    // 받아오는 video data output의 상태를 감시
    func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {

        var UIKitCoordinatePoint: [CGPoint] = []


        // request handler 만들기
        let handler = VNImageRequestHandler(cmSampleBuffer: sampleBuffer, orientation: .up, options: [:])

        do {
            // performRequests 호출을 통해 handler에게 요청을 제공
            try handler.perform([bodyPoseRequest])

            // observation 받기
            guard let observations = bodyPoseRequest.results else {
                return
            }

            for observation in observations {
                // observation으로 부터 landmark에 접근하기
                let allPoint = try observation.recognizedPoints(.all)

                // Vision coordinates -> AVFoundation coordinates -> UIKit coordinates
                allPoint.values.forEach { point in
                    let AVFoundationCoordinatePoint = CGPoint(x: point.location.x, y: 1 - point.location.y)
                    UIKitCoordinatePoint.append(videoPreviewLayer!.layerPointConverted(fromCaptureDevicePoint: AVFoundationCoordinatePoint))
                }

                cameraView.showPoints(UIKitCoordinatePoint, color: .red, view: self.cameraView)
            }

        } catch {
            print("error")
        }
    }

}

CameraView

인식한 손의 랜드마크에 색깔 포인트를 씌우는 코드입니다.

import UIKit
import AVFoundation

class CameraView: UIView {

    private var overlayLayer = CAShapeLayer()
    private var pointsPath = UIBezierPath()

    func showPoints(_ points: [CGPoint], color: UIColor, view: UIView) {
        pointsPath.removeAllPoints()
        for point in points {
            pointsPath.move(to: point)
            pointsPath.addArc(withCenter: point, radius: 5, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
        }
        overlayLayer.fillColor = color.cgColor
        view.layer.addSublayer(overlayLayer)
        CATransaction.begin()
        CATransaction.setDisableActions(true)
        overlayLayer.path = pointsPath.cgPath
        CATransaction.commit()
    }
}

이상으로 Visiond의 실제 사용방법 끝입니다!!!

영상 링크

WWDC2020 Detect Body and Hand Pose with Vision

cameraView 초기세팅

https://eunjin3786.tistory.com/200

0개의 댓글