지난 포스트에 이어서 실제 코드로 Vision을 어떻게 적용하는지 정리하였습니다.
CameraView의 초기세팅은 이곳에서 참고하였습니다.
VNImageRequestHandler(이미지로부터 얼굴정보나 텍스트를 찾는 등 이미지 분석을 처리)
를 이용하여 request handler를 만들어 줍니다.
카메라를 이용하기 때문에 위의 이니셜라이저를 사용합니다.
perform([VNRequest])
메소드를 이용해서 handler에게 요청을 보내줍니다.
3번을 수행하면 requests result property
에 observation
을 가집니다.
애플의 샘플코드 주석에 따르면
// Since we set the maximumHandCount property of the request to 1, there will be at most one observation.
VNDetectHumanHandPoseRequest
의 maximumHandCount
프로퍼티를 1개로 설정했기 때문에 observation
을 1개만 가질 수 있습니다.
handPoseRequest.maximumHandCount = 1
// observation 받기
guard let observation = handPoseRequest.results?.first else {
return
}
1개의 손만 인식하기 때문에 result 프로퍼티에 첫 번째 요소만 가져와서 사용하고 만약 2개이상의 손을 인식하려면 모든 요소를 다 사용해야합니다.
observations
에는 발견된 모든 손의 랜드마크가 포함되어 있고 이를 통해서 원하는 손의 랜드마크에 접근할 수 있습니다.
let allPoints = handPoseObservation.recognizedPoints(forGroupKey: VNRecognizedPointGroupKeyAll)
// 손에 있는 landmark 전부
발표 영상에서는 위와 같이 접근하였는데 현재는 조금 다르게 아래와 같이 접근합니다.
.recognizedPoints(:jointGroupName)
을 호출하여 랜드마크 그룹의 Dictionary를 리턴합니다.
.recognizedPoints(:jointName)
을 호출하여 각각의 랜드마크에 접근할 수도 있습니다.
let allPoint: [VNRecognizedPointKey : VNRecognizedPoint]
// allPoint의 타입
allPoint
를 콘솔에 찍어보면 다음과 같습니다.
VNRecognizedPoint
를 개발자 문서를 통해 확인해 보면 아래와 같은 계층을 가집니다.
이는 발표에서 설명한 순서와 일치합니다.
손의 모든 랜드마크를 가져왔고 화면에 점을 찍기위해 화면의 좌표와 일치하게 랜드마크의 좌표를 변환해주면 끝입니다.
// 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를 쓰려고 했는데 안써지네요..
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")
}
}
}
인식한 손의 랜드마크에 색깔 포인트를 씌우는 코드입니다.
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 초기세팅