Sådan opbygges en iOS-app til billedgenkendelse med Apples CoreML- og Vision-API'er

Med frigivelsen af ​​CoreML og nye Vision API'er på dette års Apple World Wide Developers Conference har maskinindlæring aldrig været lettere at komme ind på. I dag skal jeg vise dig, hvordan du bygger en simpel billedgenkendelsesapp.

Vi lærer, hvordan man får adgang til iPhones kamera, og hvordan man sender det, kameraet ser, ind i en maskinlæringsmodel til analyse. Vi gør alt dette programmatisk uden brug af storyboards! Skør, jeg ved det.

Her er et kig på, hvad vi skal udrette i dag:

// // ViewController.swift // cameraTest // // Created by Mark Mansur on 2017-08-01. // Copyright © 2017 Mark Mansur. All rights reserved. // import UIKit import AVFoundation import Vision class ViewController: UIViewController, AVCaptureVideoDataOutputSampleBufferDelegate { let label: UILabel = { let label = UILabel() label.textColor = .white label.translatesAutoresizingMaskIntoConstraints = false label.text = "Label" label.font = label.font.withSize(30) return label }() override func viewDidLoad() { super.viewDidLoad() setupCaptureSession() view.addSubview(label) setupLabel() } func setupCaptureSession() { let captureSession = AVCaptureSession() // search for available capture devices let availableDevices = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaType.video, position: .back).devices // setup capture device, add input to our capture session do { if let captureDevice = availableDevices.first { let captureDeviceInput = try AVCaptureDeviceInput(device: captureDevice) captureSession.addInput(captureDeviceInput) } } catch { print(error.localizedDescription) } // setup output, add output to our capture session let captureOutput = AVCaptureVideoDataOutput() captureOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "videoQueue")) captureSession.addOutput(captureOutput) let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) previewLayer.frame = view.frame view.layer.addSublayer(previewLayer) captureSession.startRunning() } // called everytime a frame is captured func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { guard let model = try? VNCoreMLModel(for: Resnet50().model) else {return} let request = VNCoreMLRequest(model: model) { (finishedRequest, error) in guard let results = finishedRequest.results as? [VNClassificationObservation] else { return } guard let Observation = results.first else { return } DispatchQueue.main.async(execute: { self.label.text = "\(Observation.identifier)" }) } guard let pixelBuffer: CVPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } // executes request try? VNImageRequestHandler(cvPixelBuffer: pixelBuffer, options: [:]).perform([request]) } func setupLabel() { label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true label.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -50).isActive = true } }

?? Trin 1: Opret et nyt projekt.

Sluk Xcode, og opret en ny applikation med en enkelt visning. Giv det et navn, måske "ImageRecognition." Vælg hurtig som hovedsprog, og gem dit nye projekt.

Trin 2: Sig farvel til storyboardet.

Til denne vejledning skal vi gøre alt programmatisk uden behov for storyboardet. Måske forklarer jeg hvorfor i en anden artikel.

Slet main.storyboard.

Naviger til info.plistog rul ned til Implementeringsinfo. Vi er nødt til at fortælle Xcode, at vi ikke længere bruger storyboardet.

Slet hovedgrænsefladen.

Uden storyboard er vi nødt til manuelt at oprette appvinduet og root view-controlleren.

Føj følgende til application()funktionen i AppDelegate.swift:

 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. window = UIWindow() window?.makeKeyAndVisible() let vc = ViewController() window?.rootViewController = vc return true }

Vi opretter manuelt appvinduet med UIWindow(),Opret vores view controller, og bed vinduet om at bruge det som sin root view controller.

Appen skal nu bygge og køre uden storyboardet ?

Trin 3: Konfigurer AVCaptureSession.

Før vi starter, skal du importere UIKit, AVFoundation og Vision. AVCaptureSession-objektet håndterer optagelsesaktivitet og styrer datastrømmen mellem inputenheder (såsom det bageste kamera) og output.

Vi skal starte med at oprette en funktion til opsætning af vores capture-session.

Opret setupCaptureSession()indeniViewController.swiftog instantier et nyt AVCaptureSession.

func setupCaptureSession() { // creates a new capture session let captureSession = AVCaptureSession() }

Glem ikke at kalde denne nye funktion fra ViewDidLoad().

override func viewDidLoad() { super.viewDidLoad() setupCaptureSession() }

Dernæst har vi brug for en henvisning til bakkameraet. Vi kan bruge enDiscoverySessionfor at søge efter tilgængelige fangsenheder baseret på vores søgekriterier.

Tilføj følgende kode:

// search for available capture devices let availableDevices = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaType.video, position: .back).devices 

AvailableDevicesindeholder nu en liste over tilgængelige enheder, der matcher vores søgekriterier.

Vi er nu nødt til at få adgang til vores captureDeviceog tilføje det som et input til vores captureSession.

Tilføj et input til capture-sessionen.

// get capture device, add device input to capture session do { if let captureDevice = availableDevices.first { captureSession.addInput(try AVCaptureDeviceInput(device: captureDevice)) } } catch { print(error.localizedDescription) }

Den første tilgængelige enhed er det bagudvendte kamera. Vi skaber et nytAVCaptureDeviceInputved hjælp af vores capture-enhed og tilføje den til capture session.

Nu hvor vi har vores inputopsætning, kan vi komme i gang med, hvordan vi output, hvad kameraet fanger.

Føj et videoudgang til vores capture-session.

// setup output, add output to our capture session let captureOutput = AVCaptureVideoDataOutput() captureSession.addOutput(captureOutput)

AVCaptureVideoDataOutputer et output, der optager video. Det giver os også adgang til de rammer, der fanges til behandling med en delegeretmetode, som vi vil se senere.

Dernæst er vi nødt til at tilføje capture-sessionens output som et underlag til vores opfattelse.

Føj output-sessionoptagelse som et underlag til visningskontrollenes visning.

let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) previewLayer.frame = view.frame view.layer.addSublayer(previewLayer) captureSession.startRunning()

Vi opretter et lag baseret på vores capture-session og tilføjer dette lag som et underlag til vores opfattelse. CaptureSession.startRunning()starter strømmen fra indgange til de udgange, som vi tilsluttede tidligere.

? Trin 4: Tilladelse til at bruge kameraet? Tilladelse givet.

Næsten alle har åbnet en app for første gang og er blevet bedt om at lade appen bruge kameraet. Fra og med iOS 10 vil vores app gå ned, hvis vi ikke beder brugeren, inden vi forsøger at få adgang til kameraet.

Naviger til info.plistog tilføj en ny nøgle med navnet NSCameraUsageDescription. I værdikolonnen skal du blot forklare brugeren, hvorfor din app har brug for kameraadgang.

Nu, når brugeren starter appen for første gang, bliver de bedt om at give adgang til kameraet.

Trin 5: Sådan hentes modellen.

Hjertet i dette projekt er sandsynligvis maskinlæringsmodellen. Modellen skal være i stand til at tage et billede ind og give os en forudsigelse af, hvad billedet er. Du kan finde gratis trænede modeller her. Den jeg valgte er ResNet50.

Once you obtain your model, drag and drop it into Xcode. It will automatically generate the necessary classes, providing you an interface to interact with your model.

? Step 6: Image analysis.

To analyze what the camera is seeing, we need to somehow gain access to the frames being captured by the camera.

Conforming to the AVCaptureVideoDataOutputSampleBufferDelegategives us an interface to interact with and be notified every time a frame is captured by the camera.

Conform ViewController to the AVCaptureVideoDataOutputSampleBufferDelegate.

We need to tell our Video output that ViewController is its sample buffer delegate.

Add the following line in SetupCaptureSession():

captureOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "videoQueue")) 

Add the following function:

func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { guard let model = try? VNCoreMLModel(for: Resnet50().model) else { return } let request = VNCoreMLRequest(model: model) { (finishedRequest, error) in guard let results = finishedRequest.results as? [VNClassificationObservation] else { return } guard let Observation = results.first else { return } DispatchQueue.main.async(execute: { self.label.text = "\(Observation.identifier)" }) } guard let pixelBuffer: CVPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } // executes request try? VNImageRequestHandler(cvPixelBuffer: pixelBuffer, options: [:]).perform([request]) }

Each time a frame is captured, the delegate is notified by calling captureOutput(). This is a perfect place to do our image analysis with CoreML.

First, we create a VNCoreMLModelwhich is essentially a CoreML model used with the vision framework. We create it with a Resnet50 Model.

Next, we create our vision request. In the completion handler, we update the onscreen UILabel with the identifier returned by the model. We then convert the frame passed to us from a CMSampleBuffer to a CVPixelBuffer. Which is the format our model needs for analysis.

Lastly, we perform the Vision request with a VNImageRequestHandler.

? Step 7: Create a label.

The last step is to create a UILabel containing the model’s prediction.

Create a new UILabeland position it using constraints.

let label: UILabel = { let label = UILabel() label.textColor = .white label.translatesAutoresizingMaskIntoConstraints = false label.text = "Label" label.font = label.font.withSize(30) return label }() func setupLabel() { label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true label.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -50).isActive = true }

Don’t forget to add the label as a subview and call setupLabel() from within ViewDidLoad().

view.addSubview(label) setupLabel()

You can download the completed project from GitHub here.

Kan du lide det, du ser? Giv dette indlæg tommelfinger op ?, følg mig på Twitter, GitHub, eller tjek min personlige side.