Profesjonalne szkolenia technologii Apple

Apple Vision

Na WWDC2017 Apple pokazało frameworki do uczenia maszynowego (CoreML).

Jednym z elementów jest automatyczne rozpoznawanie twarzy, oparte o algorytmy uczenia maszynowego. W telefonach z procesorem A11 Bionic (czyli 8,8+ i X) proces jest realizowany sprzętowo. Dodatkowo w modelu X wykorzystuje kamerę 3D, co ma dawać jeszcze większą skuteczność.

Tymczasem spójrzmy jak wygląda rozpoznawanie twarzy od strony programistycznej. Nasz cel, to stworzenie aplikacji 3D (w SceneKit), pozwalającej na podglądanie obiektu 3D z różnych stron za pomocą śledzenia twarzy.

Zaczniemy więc od stworzenia projektu gry w technologii SceneKit. Otrzymamy tu gotową scenę z samolotem i kamerę. Dodamy do niego obsługę rozpoznawania twarzy i zmodyfikujemy na tej podstawie położenie kamery.  Wszystkie operacje wykonamy w nowej klasie GameSceneController, która będzie delegatem AVCaptureVideoDataOutputSampleBufferDelegate (aby dobrze przechwytywać obraz). Praca podzielona będzie na kilka etapów

  1. Konfiguracja klasy GameSceneController
  2. Konfiguracja przechwytywania obrazu z kamery przedniej
  3. Rozpoznawanie twarzy
  4. Modyfikacja położenia kamery

Konfiguracja GameSceneController

Tworzymy nową klasę:

class GameSceneController:NSObject{
    var cam:SCNNode?
    var camp:SCNVector3?

    init(cam:SCNNode?) {
        super.init()
        self.cam = cam;
        self.camp = self.cam?.position
    }
}

Dwie właściwości będą 1) referencją do SCNNode zawierającego kamerę (tworzony jest automatycznie) i 2) startową pozycją kamery. Inicjalizowane będą w inicjalizatorze.

Musimy jeszcze ją odpowiednio zainicjalizować w klasie GameViewController

var gc: GameSceneController?

/*...*/ 

cameraNode.camera = SCNCamera()
 scene.rootNode.addChildNode(cameraNode)
 
 // place the camera
 cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
 gc = GameSceneController(cam: cameraNode)

Konfiguracja przechwytywania obrazu z kamery przedniej

Przechwytywanie obrazu odbywa się w klasie GameSceneController z wykorzystaniem frameworku AVFoundation, dlatego należy go zaimportować. Klasa musi również być zgodna z protokołem AVCaptureVideoDataOutputSampleBufferDelegate.

Dwie nowe właściwości będą reprezentować urządzenie przechwytywania i sesję przechwytywania.

var device: AVCaptureDevice?

var captureSession: AVCaptureSession?

 

Teraz trzeba zaimplementować funkcję konfigurującą sesję przechwytywania obrazu. Funkcja ta powinna zostać uruchomiona podczas inicjalizacji klasy.

func setupCamera() {
 let discoverySession = AVCaptureDevice.DiscoverySession(
 deviceTypes: [.builtInWideAngleCamera], 
 mediaType: AVMediaType.video, 
 position: .front) 
 device = discoverySession.devices[0]

Wybraliśmy urządzenia

        let input: AVCaptureDeviceInput/
        do {
            input = try AVCaptureDeviceInput(device: device!)
        } catch {
            return
        }

Wybraliśmy strumień wejściowy.

        let output = AVCaptureVideoDataOutput()
        output.alwaysDiscardsLateVideoFrames = true

Skonfigurowaliśmy sposób pobierania danych.

        let queue = DispatchQueue(label: "cameraQueue")
        output.setSampleBufferDelegate(self, queue: queue)

Określiliśmy delegata (czyli obiekt naszej klasy)

        output.videoSettings = 
              [kCVPixelBufferPixelFormatTypeKey as AnyHashable as! String: kCVPixelFormatType_32BGRA]
        captureSession = AVCaptureSession()
        captureSession?.addInput(input)
        captureSession?.addOutput(output)
        captureSession?.sessionPreset = AVCaptureSession.Preset.cif352x288

Ustaliliśmy parametry , w tym rozdzielczość, która ma znaczenie przy starszych urządzeniach. np.iPhone 6s będzie przy wyższej rozdzielczości działał dość powolnie.

        captureSession?.startRunning()
}

I uruchomiliśmy sesję. Od tej pory zacznie działać delegat. Funkcja z delegata, która będzie uruchamiana po przechwyceniu każdej ramki powinna wyglądać tak.

func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
        var myPixelBuffer : CVImageBuffer? = CMSampleBufferGetImageBuffer(sampleBuffer)
        checkFacePosition(b: myPixelBuffer)
    }

uruchamiana przez nią funkcja checkFacePosition służy już do właściwego rozpoznania twarzy.

Rozpoznawanie twarzy

Kolejne fragmenty kodu są elementami funkcji checkFacePosition

Framework Vision

Przetwarzanie zagadnień związanych z widzeniem maszynowym jest realizowane przez elementy frameworku Vision. Dlatego należy go zaimportować.

import Vision

VNDetectFaceLandmarksRequest

Wniosek o analizę obrazu, który odnajduje rysy twarzy (takie jak oczy i usta) w obrazie. Domyślnie, żądanie punktów orientacyjnych twarzy najpierw wykrywa wszystkie twarze na obrazie wejściowym, a następnie analizuje każde z nich w celu wykrycia rysów twarzy.

let request = VNDetectFaceLandmarksRequest()

VNImageRequestHandler

VNImageRequestHandler to obiekt przetwarzający jedno lub więcej żądań analizy obrazu dotyczących jednego obrazu. Jego inicjalizator przyjmuje jako argumenty bufor obrazu i orientację obrazu

 let handler = VNImageRequestHandler(cvPixelBuffer: b!, orientation: .leftMirrored)

Następnie trzeba wykonać żądanie na uchwycie.

        try! handler.perform([request])

Dla celów tej prezentacji pominiemy obsługę błędów. Jeśli rozpoznanie twarzy się powiedzie, to w obiekcie results będziemy mieć dostępną listę twarzy. W naszym przypadku pobieramy pierwszą.

        guard let face = request.results?.first as? VNFaceObservation else {return}

        let box = face.boundingBox

Wyznaczamy środek twarzy, choć można też (a nawet lepiej tak zrobić) pobrać pozycję oczu

        let px =  Float((box.minX+box.maxX)/2.0-0.5)

        let py =  Float((box.minY+box.maxY)/2.0-0.5)

        self.cam?.position = SCNVector3Make(camp!.x+px*20.0,camp!.y+py*20.0,camp!.z)

Ostatnim krokiem jest modyfikacja położenia kamery.

Koniec

Powyższe kroki pozwoliły na śledzenie twarzy i modyfikację sceny tak, aby kamera zmieniała swoją pozycję. w zależności od położenia twarzy przed urządzeniem. Dobrze byłoby żeby jeszcze tylko cały czas patrzyła na nasz samolot.

let ship = scene.rootNode.childNode(withName: "ship", recursively: true)
cameraNode.constraints = [SCNLookAtConstraint(target: ship)]

Powyższy kod pozwoli osiągnąć także ten cel.
Udało się.

Jak widać śledzenie twarzy nie jest skomplikowanym zadaniem. A w nowych modelach iPhone może być jeszcze bardziej efektywne.

Źródło: opracowanie własne na podstawie materiałów z developer.apple.com

Chcesz wiedzieć więcej o programowaniu urządzeń iOS?

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.