This repository has been archived by the owner on May 23, 2022. It is now read-only.
/
HandDetectionView.swift
232 lines (178 loc) 路 7.96 KB
/
HandDetectionView.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
import UIKit
import Vision
import AVFoundation
protocol HandDetectionViewDelegate : class {
func detected(hand : Hand) -> ()
}
final class HandDetectionView : UIView {
private let handRequest : VNDetectHumanHandPoseRequest = {
let request = VNDetectHumanHandPoseRequest()
request.maximumHandCount = 1
return request
}()
private let outputQueue : DispatchQueue = .init(label: "camera.output.queue",
qos: .userInteractive)
private var session : AVCaptureSession?
private var cameraLayer : AVCaptureVideoPreviewLayer?
private var isDetecting : Bool = false
private weak var delegate : HandDetectionViewDelegate?
private func setupSession() -> () {
guard let device = AVCaptureDevice.default(.builtInWideAngleCamera,
for: .video, position: .back),
let input = try? AVCaptureDeviceInput(device: device)
else { return }
let session = AVCaptureSession()
session.beginConfiguration()
session.sessionPreset = .photo
guard session.canAddInput(input) else { return }
session.addInput(input)
let output = AVCaptureVideoDataOutput()
guard session.canAddOutput(output) else { return }
session.addOutput(output)
output.alwaysDiscardsLateVideoFrames = true
output.setSampleBufferDelegate(self, queue: self.outputQueue)
session.commitConfiguration()
self.cameraLayer = .init(session: session)
self.cameraLayer?.videoGravity = .resizeAspectFill
self.cameraLayer?.connection?.videoOrientation = .landscapeRight
self.cameraLayer?.frame = self.frame
self.session = session
}
@objc private func handleOrientationChange(notification : NSNotification) -> () {
let currentOrientation = UIDevice.current.orientation
print(currentOrientation.rawValue)
switch currentOrientation {
case .landscapeLeft: self.cameraLayer?.connection?.videoOrientation = .landscapeLeft
case .landscapeRight: self.cameraLayer?.connection?.videoOrientation = .landscapeRight
case .portrait: self.cameraLayer?.connection?.videoOrientation = .portrait
case .portraitUpsideDown: self.cameraLayer?.connection?.videoOrientation = .portraitUpsideDown
default: break
}
}
private func convertedPoint(for joint : VNHumanHandPoseObservation.JointName, at points : [VNHumanHandPoseObservation.JointName : CGPoint]) -> CGPoint? {
guard let point = points[joint] else { return nil }
return self.cameraLayer?.layerPointConverted(fromCaptureDevicePoint: point)
}
private func fingerPoints(from observation : VNHumanHandPoseObservation) -> [VNHumanHandPoseObservation.JointName : CGPoint] {
var points : [VNHumanHandPoseObservation.JointName : VNRecognizedPoint] = .init()
let hand = try! observation.recognizedPoints(.all)
if let thumbTip = hand[.thumbTip],
let thumbIP = hand[.thumbIP],
let thumbMP = hand[.thumbMP],
let thumbCMC = hand[.thumbCMC] {
points[.thumbTip] = thumbTip
points[.thumbIP] = thumbIP
points[.thumbMP] = thumbMP
points[.thumbCMC] = thumbCMC
}
if let indexTip = hand[.indexTip],
let indexDIP = hand[.indexDIP],
let indexPIP = hand[.indexPIP],
let indexMCP = hand[.indexMCP] {
points[.indexTip] = indexTip
points[.indexDIP] = indexDIP
points[.indexPIP] = indexPIP
points[.indexMCP] = indexMCP
}
if let middleTip = hand[.middleTip],
let middleDIP = hand[.middleDIP],
let middlePIP = hand[.middlePIP],
let middleMCP = hand[.middleMCP] {
points[.middleTip] = middleTip
points[.middleDIP] = middleDIP
points[.middlePIP] = middlePIP
points[.middleMCP] = middleMCP
}
if let ringTip = hand[.ringTip],
let ringDIP = hand[.ringDIP],
let ringPIP = hand[.ringPIP],
let ringMCP = hand[.ringMCP] {
points[.ringTip] = ringTip
points[.ringDIP] = ringDIP
points[.ringPIP] = ringPIP
points[.ringMCP] = ringMCP
}
if let littleTip = hand[.littleTip],
let littleDIP = hand[.littleDIP],
let littlePIP = hand[.littlePIP],
let littleMCP = hand[.littleMCP] {
points[.littleTip] = littleTip
points[.littleDIP] = littleDIP
points[.littlePIP] = littlePIP
points[.littleMCP] = littleMCP
}
if let wrist = hand[.wrist] {
points[.wrist] = wrist
}
var filteredPoints = points.filter { $0.value.confidence > 0.3 }.mapValues { value in
return CGPoint(x: value.location.x, y: 1 - value.location.y)
}
for (key, value) in filteredPoints {
filteredPoints[key] = self.convertedPoint(for: key, at: filteredPoints)
}
return filteredPoints
}
private func process(result : VNHumanHandPoseObservation) -> () {
let fingerPoints = self.fingerPoints(from: result)
guard let thumb = Thumb(from: fingerPoints),
let index = Finger(type: .indexFinger, from: fingerPoints),
let middle = Finger(type: .middleFinger, from: fingerPoints),
let ring = Finger(type: .ringFinger, from: fingerPoints),
let little = Finger(type: .littleFinger, from: fingerPoints),
let wrist = fingerPoints[.wrist]
else { return }
let hand = Hand(thumbFinger: thumb, indexFinger: index,
middleFinger: middle, ringFinger: ring,
littleFinger: little, wrist: wrist)
DispatchQueue.main.async {
self.delegate?.detected(hand: hand)
}
}
public func startDetecting() -> () {
self.isDetecting = true
}
public func stopDetecting() -> () {
self.isDetecting = false
}
public func startSession() -> () {
self.session?.startRunning()
}
public func stopSession() -> () {
self.session?.stopRunning()
}
override public func layoutSubviews() -> () {
super.layoutSubviews()
self.cameraLayer?.frame = self.frame
}
public convenience init(delegate : HandDetectionViewDelegate?) {
self.init()
self.delegate = delegate
}
override internal init(frame: CGRect) {
super.init(frame: frame)
self.setupSession()
self.translatesAutoresizingMaskIntoConstraints = false
guard let cameraLayer = self.cameraLayer else { return }
self.layer.addSublayer(cameraLayer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension HandDetectionView : AVCaptureVideoDataOutputSampleBufferDelegate {
public func captureOutput(_ output: AVCaptureOutput,
didOutput sampleBuffer: CMSampleBuffer,
from connection: AVCaptureConnection) -> () {
guard self.isDetecting else { return }
let handler = VNImageRequestHandler(cmSampleBuffer: sampleBuffer, orientation: .up, options: .init())
do {
try handler.perform([self.handRequest])
guard let result = self.handRequest.results?.first as? VNHumanHandPoseObservation,
self.isDetecting
else { return }
self.process(result: result)
} catch {
print(error)
}
}
}