Cómo crear una aplicación iOS de reconocimiento de imágenes con las API de Vision y CoreML de Apple

Con el lanzamiento de CoreML y las nuevas API de Vision en la Apple World Wide Developers Conference de este año, el aprendizaje automático nunca ha sido tan fácil de ingresar. Hoy les mostraré cómo crear una aplicación de reconocimiento de imágenes simple.

Aprenderemos cómo obtener acceso a la cámara del iPhone y cómo pasar lo que está viendo la cámara a un modelo de aprendizaje automático para su análisis. ¡Haremos todo esto programáticamente, sin el uso de guiones gráficos! Loco, lo sé.

Aquí hay un vistazo a lo que vamos a lograr hoy:

// // 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 } }

?? Paso 1: Crea un nuevo proyecto.

Inicie Xcode y cree una nueva aplicación de vista única. Ponle un nombre, tal vez "ImageRecognition". Elija swift como idioma principal y guarde su nuevo proyecto.

? Paso 2: Dile adiós al guión gráfico.

Para este tutorial, haremos todo de manera programática, sin la necesidad del guión gráfico. Quizás explique por qué en otro artículo.

Eliminar main.storyboard.

Navegar a info.plisty desplácese hacia abajo hasta Información de implementación. Necesitamos decirle a Xcode que ya no usamos el guión gráfico.

Elimina la interfaz principal.

Sin el guión gráfico, necesitamos crear manualmente la ventana de la aplicación y el controlador de vista raíz.

Agregue lo siguiente a la application()función en 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 }

Creamos manualmente la ventana de la aplicación con UIWindow(),cree nuestro controlador de vista y dígale a la ventana que lo use como su controlador de vista raíz.

La aplicación ahora debería compilarse y ejecutarse sin el guión gráfico ?

⚙️ Paso 3: Configure AVCaptureSession.

Antes de comenzar, importe UIKit, AVFoundation y Vision. El objeto AVCaptureSession maneja la actividad de captura y administra el flujo de datos entre los dispositivos de entrada (como la cámara trasera) y las salidas.

Vamos a empezar creando una función para configurar nuestra sesión de captura.

Crea setupCaptureSession()adentroViewController.swifte instanciar un nuevo AVCaptureSession.

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

No olvide llamar a esta nueva función desde ViewDidLoad().

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

A continuación, vamos a necesitar una referencia a la cámara de visión trasera. Podemos usar unDiscoverySessionpara consultar los dispositivos de captura disponibles según nuestros criterios de búsqueda.

Agrega el siguiente código:

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

AvailableDevicesahora contiene una lista de dispositivos disponibles que coinciden con nuestros criterios de búsqueda.

Ahora necesitamos acceder a nuestro captureDevicey agregarlo como entrada a nuestro captureSession.

Agregue una entrada a la sesión de captura.

// 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) }

El primer dispositivo disponible será la cámara trasera. Creamos un nuevoAVCaptureDeviceInputusando nuestro dispositivo de captura y agregarlo a la sesión de captura.

Ahora que tenemos nuestra configuración de entrada, podemos comenzar a aprender a generar lo que está capturando la cámara.

Agregue una salida de video a nuestra sesión de captura.

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

AVCaptureVideoDataOutputes una salida que captura video. También nos proporciona acceso a los fotogramas que se capturan para su procesamiento con un método de delegado que veremos más adelante.

A continuación, debemos agregar la salida de la sesión de captura como una subcapa a nuestra vista.

Agregue la salida de la sesión de captura como una subcapa a la vista de los controladores de vista.

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

Creamos una capa basada en nuestra sesión de captura y agregamos esta capa como una subcapa a nuestra vista. CaptureSession.startRunning()inicia el flujo de entradas a las salidas que conectamos anteriormente.

? Paso 4: ¿Permiso para usar la cámara? Permiso concedido.

Casi todo el mundo ha abierto una aplicación por primera vez y se le ha pedido que permita que la aplicación utilice la cámara. A partir de iOS 10, nuestra aplicación se bloqueará si no le avisamos al usuario antes de intentar acceder a la cámara.

Navegar a info.plisty agregue una nueva clave llamada NSCameraUsageDescription. En la columna de valor, simplemente explique al usuario por qué su aplicación necesita acceso a la cámara.

Ahora, cuando el usuario inicia la aplicación por primera vez, se le pedirá que permita el acceso a la cámara.

? Paso 5: Obtener el modelo.

Lo más probable es que el corazón de este proyecto sea el modelo de aprendizaje automático. El modelo debe poder tomar una imagen y devolvernos una predicción de lo que es la imagen. Puedes encontrar modelos entrenados gratis aquí. El que elegí es 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.

Like what you see? Give this post a thumbs up ?, follow me on Twitter, GitHub, or check out my personal page.