Ansigtsgenkendelse til Virtuel Receptionist

I forbindelse med Touchlogic’s hackaton omkring Internet of Things, var idéen med dette projekt at implemente en en virtuel receptionist app til iPad, som f.eks. kunne sige “Godmorgen Tim, idag har du et møde klokken 11”?

I første omgang skulle appen kunne se, at der kommer personer ind, identificere dem, og så finde ud af om de har nogle møder i deres kalendre, for derefter læse dette op for dem.

For at formindske scope, valgte jeg ikke at binde kalenderen ind i, men måske at få appen til at sige et standard svar tilbage ved at bruge en standard “Text-To-Speech” (TTS) funktionalitet. Det store spørgsmål var dog om jeg kunne få FaceDetection og/eller FaceRecognition til at virke.

 

Denne blog post er en beretning af hvordan jeg løbende taklede opgaven og mine tanker undervejs i udviklingsprocessen. Følgende terminologi anvendes:

  • FaceDetection er hvor appen kan se at der er et ansigt.
  • FaceRecognition er hvor appen også kan genkende hvem det er den kan se.
Face detection, face recognition og ansigtsgenkendelse igennem iOS hos Touchlogic i forbindelse med 'Internet of things' Hackaton.

Research

Efter lidt søgen rundt på internettet, er jeg kommet frem til at der er ALT for mange muligheder til at jeg kan nå at afprøve dem alle. Jeg fandt en lang liste af biblioteker på siden https://facedetection.com/software/, hvor det ser ud til at der er mange, som kan lave både FaceDetection samt FaceRecognition.

Umiddelbart er jeg mest interesseret i et bibliotek, som kan køre lokalt på en iPad, og helst noget der enten bliver aktivt vedligeholdt eller noget i standard iOS bibliotekerne. Mange af de værktøjer på listen kræver at man enten streamer eller uploader enkelte billeder til et web-api, som så vil lave ansigtsgenkendelse på det. Disse API’er kræver også en konto og login på deres hjemmesider, så jeg vælger at se på de gratis muligheder længere nede på siden.

Biblioteket dlib virker ekstra interessant fordi det er openSource, og måske kan køre på iOS, men det lader dog til at den ikke kan FaceRecognition - kun FaceDetection (se http://blog.dlib.net/2014/02/dlib-186-released-make-your-own-object.html).

OpenCV er den klassiske kilde for al “Image Processing”, men sidste gang jeg arbejdede med det tog det adskillige dage at få noget til at køre. Det var dog adskillige år siden, men jeg vælger ikke at forsøge OpenCV i denne omgang.

Efter at have kigget Apple’s egen dokumentation igennem, lader det til at der har været indbygget FaceDetection siden iOS 5.0. Det ser dog ikke ud som om den kan genkende hvilke ansigter der kan ses, men måske kan jeg finde ud af noget; måske kan jeg sammenligne vinklerne mellem ansigtstræk eller kigge på farverne rundt om øjnene, eller noget lignende.

Jeg vælger så at bruge iOS’ indbyggede funktionalitet, og vil prøve at få det til at virke i Swift.

Implementation

Ifølge dokumentationen skal jeg bruge en CIDetector, som kan finde øjne, mund samt om man smiler eller ej. Eksemplerne skal dog først konverteres til Swift før jeg kan bruge dem. Jeg kunne godt vælge at bruge ObjC, men jeg vælger at se fremad, og vil derfor fortsætte med konvertering til Swift.

For at få taget et billede som kan sendes til CIDetector’en, vælger jeg at bruge AVCapturePhotoOutput, som dog først er implementeret i iOS 10.0. Jeg vælger denne, fordi Apple har et ønske om at udfase gammel funktionalitet hurtigt muligt, og alle aktivt understøttede iOS enheder kan køre iOS 10.0.

Den første initialisering af CIDetector bliver således:

func capture(_ captureOutput: AVCapturePhotoOutput,
             didFinishProcessingPhotoSampleBuffer 
             photoSampleBuffer: CMSampleBuffer?, 
             previewPhotoSampleBuffer: CMSampleBuffer?,
             resolvedSettings: AVCaptureResolvedPhotoSettings,
             bracketSettings: AVCaptureBracketedStillImageSettings?, 
             error: Error?) {       

    if let photoSampleBuffer = photoSampleBuffer {
    photoData = AVCapturePhotoOutput.jpegPhotoDataRepresentation(
      forJPEGSampleBuffer: photoSampleBuffer,
      previewPhotoSampleBuffer: previewPhotoSampleBuffer)

    let ciImage = CIImage(data: photoData!)            

    let documentsPath = URL(string: NSSearchPathForDirectoriesInDomains(
                            .documentDirectory, 
                            .userDomainMask, true)[0])?
                            .appendingPathComponent(
                                Date.init(timeIntervalSinceNow: 0)
                                .description)
      
        do {
            try photoData?.write(to: documentsPath!, 
                                 options: Data.WritingOptions.atomic)
            } catch {
                print("failed to save image!") }
            
            if ciImage != nil {
                let checkedValue = ciImage?.properties["Orientation"]
                let context = CIContext()
                var opts = [CIDetectorAccuracy : CIDetectorAccuracyHigh]
                let detector = CIDetector(ofType: CIDetectorTypeFace,
                                          context: context, 
                                          options: opts)
                
                opts = [CIDetectorImageOrientation : "\(checkedValue!)"]

                let features = detector?.features(in: ciImage!,
                                                  options: opts)
                                                  
                var leftEyePosition:CGPoint? = nil
                var rightEyePosition:CGPoint? = nil
                var mouthPosition:CGPoint? = nil
} }

Med dette kan appen finde positionerne for øjnene og munden, men det lader dog ikke til at den kan se om der smiles eller ej (måske er jeg ikke glad nok? :).

Jeg fortsætter dog med at implementere noget improviseret ansigtsgenkendelse, som jeg godt ved i bedste fald bliver ustabilt, og meget vel ikke kan komme til at virke. 

Jeg har positionerne for øjnene samt munden, og bruger derved vektor-algebra til at finde vinklerne mellem dem, som jeg vil se om jeg kan finde et mønster i. Det er noget tid siden jeg sidst har lavet sådanne beregninger, så formlerne fremfindes på http://onlinemschool.com/math/library/vector/angl/.

 

Dette kan nu implementeres i Swift således:

if(leftEyePosition != nil && 
   rightEyePosition != nil && mouthPosition != nil) {

    let vAC = CGPoint(xmouthPosition!.x - leftEyePosition!.x, 
                      ymouthPosition!.y - leftEyePosition!.y)

    let vAB = CGPoint(xrightEyePosition!.x - leftEyePosition!.x, 
                      yrightEyePosition!.y - leftEyePosition!.y)

    let vBC = CGPoint(xmouthPosition!.x - rightEyePosition!.x, 
                      ymouthPosition!.y - rightEyePosition!.y)

    let vBA = CGPoint(xleftEyePosition!.x - rightEyePosition!.x, 
                      yleftEyePosition!.y - rightEyePosition!.y)

    let dotACAB = dotProduct(a: [Float(vAC.x), Float(vAC.y)], 
                             b: [Float(vAB.x), Float(vAB.y)])

    let cosACAB = (dotACAB / (magnitudeVector(vector: vAC) *
                              magnitudeVector(vector: vAB)))

    let angleACAB = acos(cosACAB); 
}

 

Efterfølgende kan prik-produktet laves ved følgende funktion:

func dotProduct(a: [Float], b: [Float]) -> Float {
    return zip(a, b).map(*).reduce(0, +) 
}

 

Og længden af en vektor kan findes ved:

func magnitudeVector(vector: CGPoint ) -> Float {
    return sqrt(Float(pow(vector.x, 2.0) + pow(vector.y, 2.0))) 
}

 

Jeg har nu vinklerne mellem øjnene og munden, men efter at have testet i noget tid, ser det ikke ud til at give noget, som ligefrem kan genkende en person. Og i forbindelse med Hackatonnet er jeg desværre løbet tør for tid, så det kommer til at forblive en FaceDetection app i denne omgang.

Konklusion

Det endte med at appen kan finde placeringen på øjnene og munden i et CIImage på iOS 10.0 på en iPad. Med dette kan vi potentielt lave noget billedmanipulation ud fra placeringerne, såsom f.eks. “anonymiseringsalgoritmen” i Apple’s dokumentation eller en af de klassiske “sæt briller og hat på en person”.

Hvis jeg skulle prøve denne virtuelle receptionist igen, vil jeg nok tage fat i OpenCV biblioteket og gå den “lange vej”, med at implementere deres algoritmer til ansigtsgenkendelse. Jeg véd dog ikke om, eller hvordan, det vil komme til at virke, men jeg tror dog der er nok som har arbejdet med OpenCV i løbet af de sidste 16 år til at man burde kunne få det til at virke. Der er også en risiko for at det vil tage for meget processorkraft at køre dette løbende på iOS enheder, og hvis det sker, så kan man prøve ansigtsgenkendelse i færre frames. Muligvis blot tage et billede når CIDetector kan se at der er et ansigt, og så lave OpenCV algoritmerne på det i en async process. Hvis det stadig går for langsomt, så var der adskillige online API’ere som man i stedet for kan se på at implementere.

Hvis jeg ville have feedback, ville jeg have brugt iOS’ standard Text-To-Speech API til at få den til at sige svar-sætningen.

 

Af Tim Kofoed
iOS app udvikler hos Touchlogic