Frameworki #1: Śledzenie twarzy z Vision

Nauka programownia

Opublikowany: Sep 18, 2018

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) lub A12 Bionic (Xs, Xs Max i Xr) proces jest realizowany sprzętowo. Dodatkowo w modelu X wykorzystuje kamerę z analizą głębi, 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. Dzięki temu obiekt będzie wydawał się wystawać ponad ekran urządzenia.

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.


dr Błażej Zyglarski

Autoryzowany Trener Apple (Swift), praktyk z wieloletnim doświadczeniem

Od lat pasjonat technologii mobilnych. Autor dziesiątek aplikacji dla systemów iOS, tvOS i watchOS. Wykładowca na Wydziale Matematyki i Informatyki Uniwersytetu Mikołaja Kopernika w Toruniu. Praktykujący deweloper iOS. Współzałożyciel Asuri Solutions.



Wpis ten pojawił się wcześniej na naszym blogu: swiftacademy.pl

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

Źródło obrazka tytułowego: pexels.com


Image
Programowanie Swift
Swift on Windows
Sep 23, 2020
Image
Felieton
Zarabianie Schrodingera
Sep 03, 2019
Image
Felieton
Rozliczanie aplikacji sprzedanych w AppStore
Jun 10, 2019
Image
Felieton
Cały internet na płycie CD
Apr 01, 2019
Image
Nauka programownia
Swift: O co chodzi z tymi kolejkami?
Feb 17, 2019

Nasze szkolenia