Face Detection com Android e Kotlin
Tags: android, kotlin, androidstudio, facedetection
Published: 2021-09-23 18:37:10

O Google disponibiliza uma API que permite realizar detecção de rostos de maneira offline, sem depender de nenhum serviço na nuvem. Essa API é a Google Vision e foi introduzida no Play Services na versão 8.1. Fazer uma implementação básica é bem simples:

Inclua a referência no build.gradle:
`implementation 'com.google.android.gms:play-services-vision:8.1.0'`

Em seguida vem a implementação do controle
``` kotlin
package com.avanade.myfacedetection

import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.util.SparseArray
import android.view.View
import androidx.core.util.valueIterator
import com.google.android.gms.vision.Frame
import com.google.android.gms.vision.face.Face
import com.google.android.gms.vision.face.FaceDetector

public class FaceOverlayView(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int) : View(context, attrs, defStyleAttr) {
    constructor(context: Context, attrs: AttributeSet) : this(context, attrs, 0)
    private lateinit var mBitmap: Bitmap
    private var mFaces: SparseArray<Face> = SparseArray<Face>()

    fun setBitmap(bitmap: Bitmap){
        mBitmap = bitmap

        val faceDetector : FaceDetector = FaceDetector.Builder(context)
            .setTrackingEnabled(true)
            .setLandmarkType(FaceDetector.ALL_LANDMARKS)
            .setMode(FaceDetector.ACCURATE_MODE)
            .build()

        if(!faceDetector.isOperational()){
            //TODO: Colocar verificação
        }
        else{
            val frame : Frame = Frame.Builder()
                .setBitmap(mBitmap)
                .build()
            mFaces = faceDetector.detect(frame)
            faceDetector.release()
        }

        invalidate()
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)

        if(mBitmap != null && mFaces != null){
            var scale : Int? = canvas?.let { drawBitmap(it) }
            if(canvas != null && scale != null){
                drawFaceBox(canvas, scale)
            }
        }
    }

    fun drawBitmap(canvas: Canvas) : Int{
        val viewWidth : Int = canvas?.width ?: 0
        val viewHeight : Int = canvas?.height ?: 0
        val imageWidth : Int = mBitmap.width
        val imageHeight : Int = mBitmap.height
        val scale : Int = Math.min(viewWidth / imageWidth, viewHeight / imageHeight)
        val bounds : Rect = Rect(0, 0, imageWidth * scale, imageHeight * scale)
        canvas.drawBitmap(mBitmap, null, bounds, null)
        return scale
    }

    fun drawFaceBox(canvas: Canvas, scale: Int){
        var paint: Paint = Paint()
        paint.setColor(Color.BLUE)
        paint.style = Paint.Style.STROKE
        paint.strokeWidth = 5F

        var left : Float = 0F
        var top : Float = 0F
        var right : Float = 0F
        var bottom : Float = 0F

        for(face in mFaces.valueIterator()){
            left = face.position.x * scale
            top = face.position.y * scale
            right = scale * (face.position.x + face.width)
            bottom = scale * (face.position.y + face.height)

            canvas.drawRect(left, top, right, bottom, paint)
        }
    }
}
```

Aqui fazemos a criação do objeto que faz a detecção dos rostos na foto
``` kotlin
        val faceDetector : FaceDetector = FaceDetector.Builder(context)
            .setTrackingEnabled(true)
            .setLandmarkType(FaceDetector.ALL_LANDMARKS)
            .setMode(FaceDetector.ACCURATE_MODE)
            .build()
```

Logo abaixo temos ` if(!faceDetector.isOperational())` que é uma verificação importante. Caso o usuário nunca tenha usado em nenhuma aplicação a API do Google Vision o device vai baixar essa lib e, pode ser que nesse momento a API ainda não esteja disponível. Então é importante fazer essa verificação para evitar um erro de NullPointerException.

``` kotlin
            val frame : Frame = Frame.Builder()
                .setBitmap(mBitmap)
                .build()
            mFaces = faceDetector.detect(frame)
            faceDetector.release()
```
Aqui é onde o código irá efetivamente analisar a imagem e verificar os rostos das pessoas. As informações dos rostos encontrados é armazenado em um array de Faces usado aqui para desenhar os quadrados sobre o frame:

``` kotlin
        for(face in mFaces.valueIterator()){
            left = face.position.x * scale
            top = face.position.y * scale
            right = scale * (face.position.x + face.width)
            bottom = scale * (face.position.y + face.height)

            canvas.drawRect(left, top, right, bottom, paint)
        }
```

O código completo está aqui no [repo](https://github.com/dgbarreto/facedetectionandroidkotlin).

Aqui também está um vídeo da implementação:
<a href="http://www.youtube.com/watch?feature=player_embedded&v=YlA1X6zcw_8" target="_blank"><img src="http://img.youtube.com/vi/YlA1X6zcw_8/0.jpg" alt="Face Detection com Android e Kotlin" width="240" height="180" border="10" /></a>