Building an Open Source Realtime Face Recognition Android App
Tags: android, octograd2020, devgrad2020, opensource
Published: 2020-05-21 22:17:12

We've all seen things in movies we wish were real. The first thing that comes to my mind is [The Matrix](https://www.imdb.com/title/tt0133093/) and the ability to download any skill onto a person's brain. While this is still far from possible, I've always wanted to create futuristic software I've seen in movies.

> Kindly let me know in the comments: What's the first thing that comes to mind when you think of technology you've seen in movies or series? 

Sadly, we are still far from creating an artificial intelligence as brilliant as the ones in [_Transcendence_](https://www.imdb.com/title/tt2209764/), [_Ex Machina_](https://www.imdb.com/title/tt0470752/) or [_I, Robot_](https://www.imdb.com/title/tt0343818/). Even though I would love to start working on something that huge one day, I've also had this desire to build a live face recognition application for a while now.

I don't remember exactly when the idea came to mind; however, I know that face recognition is a pretty common thing in movies or series. So, I really wanted to start building one I could keep in my pocket.

So I decided to blog the process of me building one. There might be multiple stages. Small sneak peeks of possible future stages are collecting data on the go, setting up a smarter model, and updating and loading models online through dependency injection.

For now, we will be creating the code required for the application to detect the faces and communicate with the face recognition model.

##### Here's a quick demo video

{% vimeo 420028513 %}

I think I should mention that this works with multiple faces in the field of view of the camera at the same time. *Sadly, at the time of recording the demo, I was alone.*

### Separating the Tasks

Before being able to start with the code, we need to envision how the application will be working.

This is the list of things we need to do, in the following order:
1. [Previewing camera frames](#previewing-camera-frames)
2. [Capturing camera frames](#capturing-camera-frames)
3. [Face detection](#face-detection)
3. [Highlighting detected faces](#highlighting-detected-faces)
4. [Face recognition](#face-recognition)
5. [Labeling highlighted faces](#labeling-highlighted-faces)

By building each part separately, we can, later on, improve certain aspects of the application without having to rewrite a lot of code.

### Previewing Camera Frames

In order to display the camera frames to the user, I used [AndroidX CameraView][AndroidX CameraView]. This provided a simple view of what the camera sees. Also, it would be bound to the lifecycle of the activity/fragment. It was a better option than implementing my own preview because it resulted in much better FPS.

However, the camera view can be given different configurations and settings. A factory helped me specify which configuration I wanted to use, and it would get automatically applied to the camera view.

So, initializing the camera view looked something like this.
```kotlin
cameraView.apply {
    bindToLifecycle(this@FaceRecognitionFragment)

    CameraViewFactory.applyConfig(cameraView, cameraViewConfig)
}
```
> ##### [Click here to check the Camera View Factory and Configs code](https://github.com/nizarmah/trinity/tree/master/app/src/main/java/me/nizarmah/trinity/utils/camera/view)

### Capturing Camera Frames

The view was being shown thanks to the [AndroidX CameraView][AndroidX CameraView]. Yet, I still didn't have access to the frames, which I need to run the face detection model on.

Luckily, the [AndroidX CameraX][AndroidX CameraX] can analyze images. Following the [CameraX Analyze images][CameraX Analyze images] documentation, I created my own `CameraAnalysisFactory`, which analyzes images according to certain configurations passed to the factory.

I had to decrease the resolution of the images because the models and the devices and the models we have currently are far from being able to handle high quality pictures fast.

Thus, in the camera analysis configuration, I specified a low resolution. **While the model is seeing low resolution frames, the user is still seeing great resolution frames in the camera view.**

```kotlin
object LowResFaceDetectionCameraAnalysisConfig : CameraAnalysisConfig {
    override val resolution: Size = Size(240, 320)
    override val readerMode: ImageAnalysis.ImageReaderMode = ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE
}
```

I also created a lambda function that receives the camera's image and rotation degrees from the camera frame analyzer, which later on deals with the frame received by the analyzer. Then, initialized a camera analyzer by giving the factory a camera analysis configuration and said lambda function.
```kotlin
private val cameraFameAnalyzer: CameraFrameAnalyzerLambdaType = { imageProxy, rotation ->  }

public val cameraFrameAnalysisConfig = LowResFaceDetectionCameraAnalysisConfig

public val cameraFrameAnalysis: ImageAnalysis = CameraAnalysisFactory.createCameraAnalysis(
    cameraFrameAnalysisConfig,
    analyzer = cameraFameAnalyzer
)
```

Now that I had the camera frame analysis object initialized, I had to bind it to our lifecycle, alongside with the camera view.

```kotlin
CameraX.bindToLifecycle(this, viewModel.cameraFrameAnalysis)
```

> ##### [Click here to check the Camera Analysis Factory and Configs code](https://github.com/nizarmah/trinity/tree/master/app/src/main/java/me/nizarmah/trinity/utils/camera/analysis)

### Face Detection

Since I just made a method to pass the frames from the camera to anything I wanted, I passed those frames onto the face detection model.

I chose [Firebase MLKit][Firebase MLKit] for face detection since it is able to *detect* faces and *track* them as they move in the camera's field of view.

Accordingly, I created an abstract face detector object, with the necessary functionality. Meanwhile, the face detector objects inheriting that abstract class will have their own options for the firebase model.

Simply, I can, then, just initialize the face detector model I want to use.
```kotlin
private val faceDetector: FaceDetector = FaceDetector()
```

All that was left was to pass the frames I was getting from the analyzer onto the face detector. Following the [Firebase MLKit Face Detection][Firebase Face Detection] documentation, I specified the image's rotation and let the model process it.

```kotlin
private val cameraFameAnalyzer: CameraFrameAnalyzerLambdaType = { imageProxy, rotation ->
    val mediaImage = imageProxy?.image

    val imageRotation = when (rotation) {
        0 -> FirebaseVisionImageMetadata.ROTATION_0
        90 -> FirebaseVisionImageMetadata.ROTATION_90
        180 -> FirebaseVisionImageMetadata.ROTATION_180
        270 -> FirebaseVisionImageMetadata.ROTATION_270
        else -> throw Exception("Rotation must be 0, 90, 180, or 270.")
    }

    mediaImage?.let {
        faceDetector.processImage(mediaImage, imageRotation, { faces, frame ->  })
    }
}
```

> ##### [Click here to check the Face Detector code](https://github.com/nizarmah/trinity/tree/master/app/src/main/java/me/nizarmah/trinity/utils/face/detector)

### Highlighting Detected Faces

Since the face detector model returns the faces and the frame read by the images, I sent those faces and their coordinates in the frame to a face highlighter object. The face highlighter object would be responsible for drawing the highlights around the faces.

Therefore, I created a lambda function, similar to the one passed to the `faceDetector.processImage`, where face highlight objects would be assigned to every detected face.

```kotlin
val highlightedFacesLiveData = MutableLiveData<List<FaceHighlight>>()
private val highlightDetectedFaces: (List<FirebaseVisionFace>) -> Unit = { faces ->
    val highlightedFacesList = ArrayList<FaceHighlight>()

    faces.forEach {
        highlightedFacesList.add(RectangularFaceHighlight(it))
    }

    highlightedFacesLiveData.postValue(highlightedFacesList)
}
```

Then, I would specify that lambda function as the callback for the `faceDetector.processImage`
```kotlin
faceDetector.processImage(mediaImage, imageRotation, highlightDetectedFaces)
```

But I wasn't done yet. Before I drew those highlights on top of the camera view, I remembered that the camera view and the frames passed to the face detector don't have the same resolution. Therefore, I had to create a transforming object to transform the coordinates of the face detected and their sizes to match the resolution of the camera view.

So, it was important to attach the face highlighter to both the camera view and the camera analyzer. The face highlighter would read the size of the camera view and the resolution of the camera analyzer and transform the highlights accordingly.

```kotlin
private val highlighterFaces: FaceHighlighter = findViewById(R.id.highlighter_faces)

highlighterFaces.attachCameraView(cameraView, cameraViewConfig)

highlighterFaces.attachCameraAnalysisConfig(viewModel.cameraFrameAnalysisConfig)
``` 

Following that, I took the face highlights posted through `LiveData` and drew them on the canvas.
```kotlin
viewModel.highlightedFacesLiveData.observe(this, Observer { highlightedFaces ->
    highlighterFaces.clear()

    highlightedFaces.forEach {
        highlighterFaces.add(it)
    }

    highlighterFaces.postInvalidate()
})
```

> ##### [Click here to check the Face Highlighter and the Face Highlight Objects](https://github.com/nizarmah/trinity/tree/master/app/src/main/java/me/nizarmah/trinity/utils/face/highlighter)

### Face Recognition

The face recognition model was already done previously as a university course project using the [*sklearn.fetch_lfw_dataset* dataset](https://scikit-learn.org/stable/modules/generated/sklearn.datasets.fetch_lfw_people.html), you can check it on github, [Oracle](https://github.com/nizarmah/oracle). **This model will be later on rebuilt with VGGFace2 and improved even further.**

Following the [Firebase MLKit Custom Models][Firebase Custom Models] documentation, I converted the model I had to a Tensorflow Lite model and created a face classifier object which can initialize that model and communicate with it.

I needed to give myself the option to switch the model easily later on. So, I created the models as a configuration class so the face classifier object can know the input shape, output shape, labels, and model path (whether local or remote). **For now, the face classifier only supports local models.**

Initializing the face classifier was simple, I just get its instance based on the configuration I want to use.
```kotlin
var faceClassifier: FaceClassifier = FaceClassifier.getInstance(context, OracleFaceClassifierConfig)
```

After the face classifier was initialized, I tried passing every frame's detected faces onto the face classifier, on a separate thread. **This resulted in a lot of lag, which made the face classifier not usable.**

In order to solve that issue, I decided to approach this differently. **I ended up running the face classifier only when a new face enters the camera's field of view.** The highlight would be drawn as soon as the face is detected. However, the classification would run on a separate thread as soon as the face is detected.

Accordingly, I had to update the `highlightDetectedFaces` lambda function by keeping track of the `trackingId` given from the firebase face detection model and running the face classification only on newly detected faces or `trackingId`s.
```kotlin
private val highlightedFacesHashMap = HashMap<Int, Face>()
private val highlightDetectedFaces: onDetectLambdaType = { faces, frame ->
    faces.forEach {
        if (highlightedFacesHashMap.containsKey(it.trackingId)) {
            highlightedFacesHashMap[it.trackingId]?.updateFace(it, frame)
        } else {
            val face = Face(it, frame)
            highlightedFacesHashMap.put(it.trackingId, face)
            viewModelScope.launch {
                highlightedFacesHashMap[it.trackingId]?.apply { face.classifyFace(faceClassifier) }
            }
        }
    }

    val highlightedFacesList = ArrayList<FaceHighlight>()
    highlightedFacesHashMap.values.forEach {
        highlightedFacesList.add(LabeledRectangularFaceHighlight(it))
    }

    highlightedFacesLiveData.postValue(highlightedFacesList)
}
```

> ##### [Click here to check the Face Classifier code](https://github.com/nizarmah/trinity/tree/master/app/src/main/java/me/nizarmah/trinity/utils/face/classifier)

### Labeling Highlighted Faces

I had just ran the face classification the way I wished. The result was coming up slightly delayed at first, but then was smooth while the user continued to move inside the camera's field of view. I just had to draw the label onto the screen for the user to see.

I already had a highlighter set, which can transform the highlights from small resolution frames to the resolution the view was using. I already had a rectangular highlight drawing around the person's face. And I was already getting the response from the model!

So I just created a highlight that inherits from my rectangular face highlight object. However, this new highlight checks if the face received by the model has a label or not. **If the face does have a label, the label is drawn, and the highlights were being drawn every frame.**

So, I created a new face object, instead of using the one provided by firebase. Simply, that face has a coroutine which is responsible for classifying the face. Once the result of that classification is out for that face, it is stored in that object. **If the face is already in the camera's field of view, the face object doesn't change.** So, I just wait for the classification to be done, and when it is, the face highlights drawn every frame would recognize that the face object being used has a label. Thus, it would draw the label under the face.

The face object's classification was shown in the previous code segment. But here it is again...
```kotlin
highlightedFacesHashMap[it.trackingId]?.apply { face.classifyFace(faceClassifier) }
```

> ##### [Click here to check the Face object](https://github.com/nizarmah/trinity/blob/master/app/src/main/java/me/nizarmah/trinity/utils/face/face/Face.kt)

---

> ##### It is important to note that the code shown in this post is segmented from a [fragment, or view](https://github.com/nizarmah/trinity/blob/master/app/src/main/java/me/nizarmah/trinity/app/facerecognition/FaceRecognitionFragment.kt), and a [viewmodel](https://github.com/nizarmah/trinity/blob/master/app/src/main/java/me/nizarmah/trinity/app/facerecognition/FaceRecognitionViewModel.kt).

--- 

If you would like to fork or contribute, feel free to check out the repository.

{% github https://github.com/nizarmah/trinity no-readme %}

[demo video]: https://vimeo.com/420028513
[AndroidX CameraView]: https://developer.android.com/reference/kotlin/androidx/camera/view/CameraView
[AndroidX CameraX]: https://developer.android.com/training/camerax
[CameraX Analyze images]: https://developer.android.com/training/camerax/analyze
[Firebase MLKit]: https://firebase.google.com/docs/ml-kit
[Firebase Face Detection]: https://firebase.google.com/docs/ml-kit/detect-faces
[Firebase Custom Models]: https://firebase.google.com/docs/ml-kit/use-custom-models