Repository navigation
Expand file tree
/
Copy pathViewController.swift
More file actions
177 lines (144 loc) · 5.7 KB
/
Copy pathViewController.swift
File metadata and controls
177 lines (144 loc) · 5.7 KB
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
import CoreMedia
import CoreML
import UIKit
import Vision
class ViewController: UIViewController {
@IBOutlet var videoPreview: UIView!
var videoCapture: VideoCapture!
var currentBuffer: CVPixelBuffer?
let coreMLModel = MobileNetV2_SSDLite()
lazy var visionModel: VNCoreMLModel = {
do {
return try VNCoreMLModel(for: coreMLModel.model)
} catch {
fatalError("Failed to create VNCoreMLModel: \(error)")
}
}()
lazy var visionRequest: VNCoreMLRequest = {
let request = VNCoreMLRequest(model: visionModel, completionHandler: {
[weak self] request, error in
self?.processObservations(for: request, error: error)
})
// NOTE: If you use another crop/scale option, you must also change
// how the BoundingBoxView objects get scaled when they are drawn.
// Currently they assume the full input image is used.
request.imageCropAndScaleOption = .scaleFill
return request
}()
let maxBoundingBoxViews = 10
var boundingBoxViews = [BoundingBoxView]()
var colors: [String: UIColor] = [:]
override func viewDidLoad() {
super.viewDidLoad()
setUpBoundingBoxViews()
setUpCamera()
}
func setUpBoundingBoxViews() {
for _ in 0..<maxBoundingBoxViews {
boundingBoxViews.append(BoundingBoxView())
}
// The label names are stored inside the MLModel's metadata.
guard let userDefined = coreMLModel.model.modelDescription.metadata[MLModelMetadataKey.creatorDefinedKey] as? [String: String],
let allLabels = userDefined["classes"] else {
fatalError("Missing metadata")
}
let labels = allLabels.components(separatedBy: ",")
// Assign random colors to the classes.
for label in labels {
colors[label] = UIColor(red: CGFloat.random(in: 0...1),
green: CGFloat.random(in: 0...1),
blue: CGFloat.random(in: 0...1),
alpha: 1)
}
}
func setUpCamera() {
videoCapture = VideoCapture()
videoCapture.delegate = self
videoCapture.setUp(sessionPreset: .hd1280x720) { success in
if success {
// Add the video preview into the UI.
if let previewLayer = self.videoCapture.previewLayer {
self.videoPreview.layer.addSublayer(previewLayer)
self.resizePreviewLayer()
}
// Add the bounding box layers to the UI, on top of the video preview.
for box in self.boundingBoxViews {
box.addToLayer(self.videoPreview.layer)
}
// Once everything is set up, we can start capturing live video.
self.videoCapture.start()
}
}
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
resizePreviewLayer()
}
func resizePreviewLayer() {
videoCapture.previewLayer?.frame = videoPreview.bounds
}
func predict(sampleBuffer: CMSampleBuffer) {
if currentBuffer == nil, let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) {
currentBuffer = pixelBuffer
// Get additional info from the camera.
var options: [VNImageOption : Any] = [:]
if let cameraIntrinsicMatrix = CMGetAttachment(sampleBuffer, key: kCMSampleBufferAttachmentKey_CameraIntrinsicMatrix, attachmentModeOut: nil) {
options[.cameraIntrinsics] = cameraIntrinsicMatrix
}
let handler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer, orientation: .up, options: options)
do {
try handler.perform([self.visionRequest])
} catch {
print("Failed to perform Vision request: \(error)")
}
currentBuffer = nil
}
}
func processObservations(for request: VNRequest, error: Error?) {
DispatchQueue.main.async {
if let results = request.results as? [VNRecognizedObjectObservation] {
self.show(predictions: results)
} else {
self.show(predictions: [])
}
}
}
func show(predictions: [VNRecognizedObjectObservation]) {
for i in 0..<boundingBoxViews.count {
if i < predictions.count {
let prediction = predictions[i]
/*
The predicted bounding box is in normalized image coordinates, with
the origin in the lower-left corner.
Scale the bounding box to the coordinate system of the video preview,
which is as wide as the screen and has a 16:9 aspect ratio. The video
preview also may be letterboxed at the top and bottom.
Based on code from https://github.com/Willjay90/AppleFaceDetection
NOTE: If you use a different .imageCropAndScaleOption, or a different
video resolution, then you also need to change the math here!
*/
let width = view.bounds.width
let height = width * 16 / 9
let offsetY = (view.bounds.height - height) / 2
let scale = CGAffineTransform.identity.scaledBy(x: width, y: height)
let transform = CGAffineTransform(scaleX: 1, y: -1).translatedBy(x: 0, y: -height - offsetY)
let rect = prediction.boundingBox.applying(scale).applying(transform)
// The labels array is a list of VNClassificationObservation objects,
// with the highest scoring class first in the list.
let bestClass = prediction.labels[0].identifier
let confidence = prediction.labels[0].confidence
// Show the bounding box.
let label = String(format: "%@ %.1f", bestClass, confidence * 100)
let color = colors[bestClass] ?? UIColor.red
boundingBoxViews[i].show(frame: rect, label: label, color: color)
} else {
boundingBoxViews[i].hide()
}
}
}
}
extension ViewController: VideoCaptureDelegate {
func videoCapture(_ capture: VideoCapture, didCaptureVideoFrame sampleBuffer: CMSampleBuffer) {
predict(sampleBuffer: sampleBuffer)
}
}