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
- Konfiguracja klasy GameSceneController
- Konfiguracja przechwytywania obrazu z kamery przedniej
- Rozpoznawanie twarzy
- 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.
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