Перейти к основному содержимому
Версия: 3.23.0

Детекция лиц с камеры телефона

В этом руководстве вы узнаете, как использовать объекты Processing Block Api из Face SDK для обнаружения лиц и определения их возраста и пола.

Задетектированные лица будут выделены зеленым ограничительным прямоугольником (bbox). Слева появится информация о возрасте и поле человека. 

Для работы вам потребуется телефон с операционной системой Android версии 7.0 и выше, а также инструменты Android Studio.

Исходный код проекта доступен в Face SDK examples/tutorials/kotlin/KotlinTutorial

Подготовка проекта

  1. Запустите AndroidStudio и создайте новый проект File > New > Project. Выберите шаблон Empty Views Activity и нажмите next.
  2. Укажите название и расположение вашего проекта, установите минимальную версию Android sdk 24 или выше и нажмите finish.

Работа c камерой

Получение разрешений для работы с камерой

  1. Добавьте в AndroidManifest.xml.

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    // ................................................
    <uses-feature
    android:name="android.hardware.camera"
    android:required="false" />

    <uses-permission android:name="android.permission.CAMERA"/>
  2. В файле MainActivity.kt в классе MainActivity добавьте метод getPermision и вызовите в методе onCreate.

    private fun getPermission(){
    if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA) !=
    PackageManager.PERMISSION_GRANTED){
    requestPermissions(arrayOf(android.Manifest.permission.CAMERA), 0)
    }
    }
  3. Переопределите метод onRequestPermissionsResult и вызовите в нем метод getPermission.

    override fun onRequestPermissionsResult(
    requestCode: Int,
    permissions: Array<out String>,
    grantResults: IntArray
    ) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    getPermission()
    }

Получение кадров с камеры

  1. В /app/src/main/res/layout/activity_main.xml удалите TextView и добавьте TextureView для отображения изображения с камеры.

    <TextureView
    android:layout_height="match_parent"
    android:layout_width="match_parent"
    android:id="@+id/textureView" />
  2. Добавьте переменные cameraManager, cameraDevice, cameraCaptureSession, handlerThread, handler, textureView, previewSize в класс MainActivity.

    class MainActivity : AppCompatActivity() {
    //........................................
    private lateinit var cameraManager: CameraManager
    lateinit var cameraDevice: CameraDevice
    lateinit var cameraCaptureSession: CameraCaptureSession

    private lateinit var handlerThread: HandlerThread
    lateinit var handler: Handler

    lateinit var textureView: TextureView
    val previewSize = Size(1280, 720)
  3. Добавьте методы getFrontalCameraId и openCamera.

    private fun getFrontalCameraId(): String {
    return cameraManager.cameraIdList.first {
    cameraManager
    .getCameraCharacteristics(it)
    .get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT
    }
    }

    @SuppressLint("MissingPermission")
    fun openCamera(){
    cameraManager.openCamera(getFrontalCameraId(), object : CameraDevice.StateCallback(){
    override fun onOpened(p0: CameraDevice) {
    cameraDevice = p0
    val surfaceTexture = textureView.surfaceTexture
    surfaceTexture?.setDefaultBufferSize(previewSize.width, previewSize.height)

    val capReq = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
    val surface = Surface(surfaceTexture)
    capReq.addTarget(surface)

    cameraDevice.createCaptureSession(listOf(surface), object: CameraCaptureSession.StateCallback(){
    override fun onConfigured(p0: CameraCaptureSession) {
    cameraCaptureSession = p0
    cameraCaptureSession.setRepeatingRequest(capReq.build(), null, null)
    }
    override fun onConfigureFailed(p0: CameraCaptureSession) {

    }
    }, handler)
    }

    override fun onDisconnected(p0: CameraDevice) {}

    override fun onError(p0: CameraDevice, p1: Int) {}
    }, handler)
    }
  4. Проинициализируйте все переменные после вызова getPermision.

    textureView = findViewById(R.id.textureView)
    cameraManager = getSystemService(Context.CAMERA_SERVICE) as CameraManager

    handlerThread = HandlerThread("videoThread")
    handlerThread.start()
    handler = Handler(handlerThread.looper)

    textureView.surfaceTextureListener = object : TextureView.SurfaceTextureListener{
    override fun onSurfaceTextureAvailable(p0: SurfaceTexture, p1: Int, p2: Int) { openCamera() }

    override fun onSurfaceTextureUpdated(p0: SurfaceTexture) { }

    override fun onSurfaceTextureDestroyed(p0: SurfaceTexture): Boolean { return false }

    override fun onSurfaceTextureSizeChanged(p0: SurfaceTexture, p1: Int, p2: Int) { }
    }
  5. Добавьте портретную ориентацию для MainActivity. Для этого перейдите в AndroidManifest.xml и в <activity ...> пропишите android:screenOrientation="portrait".

    <activity
    android:name=".MainActivity"
    android:exported="true"
    android:screenOrientation="portrait">

Подключение Face SDK к проекту

Настройка build.gradle.kts

  1. Добавьте копирование библиотек Face SDK в ваш проект. Для этого укажите в поле android.

    sourceSets {
    this.getByName("main"){
    jniLibs {
    srcDir("/path/to/face_sdk/lib")
    }
    }
    }
  2. Добавьте функции для копирования файлов конфигураций и моделей Face SDK в ваш проект.

    task("computeAssetsHash") {
    doLast {
    mkdir("$projectDir/src/main/assets/")
    File("$projectDir/src/main/assets/", "assets-hash.txt").writeText(
    "Buildtime ${LocalDateTime.now().format(DateTimeFormatter.ofPattern("YYYY:MM:dd:HH:mm:ss"))}"
    )
    }
    }

    task<Copy>("copyFiles") {
    description = "Copy files"
    from("/path/to/face_sdk/") {
    include(
    "conf/**",
    "share/processing_block/age_estimator/light/2.enc",
    "share/processing_block/gender_estimator/light/2.enc",
    "share/processing_block/face_fitter/fda/1.enc",
    "share/processing_block/face_detector/blf_front/1.enc",
    "license/**"
    )
    }
    into("$projectDir/src/main/assets/")
    }
  3. Импортируйте dependsOn и вызовите функции computeAssetsHash и copyFiles.

    import com.android.build.gradle.internal.tasks.factory.dependsOn
    //..............................................................
    project.tasks.preBuild.dependsOn("computeAssetsHash")
    project.tasks.preBuild.dependsOn("copyFiles")
  4. В dependencies подключите facerec.jar.

    implementation(files("/path/to/face_sdk/lib/facerec.jar"))

Получение ассетов Face SDK внутри приложения

В приложении ассеты хранятся в сжатом виде. Для получения ассетов Face SDK добавьте новую точку входа в приложение, в которой произойдет их распаковка в другое место.

  1. Создайте новый файл UnpackAssetsActivity.kts и класс UnpackAssetsActivity.

    // activity to upack all assets
    class UnpackAssetsActivity : Activity() {
    public override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    try {
    // read first line from assets-hash.txt
    val newHash =
    BufferedReader(InputStreamReader(assets.open("assets-hash.txt"))).readLine()

    // and compare it with what we have already
    val sharedPreferences = getSharedPreferences("fe9733f0bfb7", 0)

    val prevHash = sharedPreferences.getString("assets-hash", null)

    // unpack everything again, if something changes
    if (prevHash == null || prevHash != newHash) {
    val buffer = ByteArray(10240)

    val persistentDir = applicationInfo.dataDir

    val queue: Queue<String?> = ArrayDeque<String?>()
    queue.add("conf")
    queue.add("share")
    queue.add("license")

    while (!queue.isEmpty()) {
    val path = queue.element()
    queue.remove()

    val list = assets.list(path!!)

    if (list!!.isEmpty()) {
    val fileStream = assets.open(path)

    val fullPath = "$persistentDir/fsdk/$path"

    File(fullPath).parentFile?.mkdirs()

    val outFile = FileOutputStream(fullPath)

    while (true) {
    val read = fileStream.read(buffer)

    if (read <= 0) break

    outFile.write(buffer, 0, read)
    }

    fileStream.close()
    outFile.close()
    } else {
    for (p in list) queue.add("$path/$p")
    }
    }

    val editor = sharedPreferences.edit()
    editor.putString("assets-hash", newHash)
    while (!editor.commit());
    }

    val intent = Intent(applicationContext, MainActivity::class.java)
    startActivity(intent)
    finish()
    } catch (e: Exception) {
    Log.e("UnpackAssetsActivity", e.message!!)
    e.printStackTrace()
    finishAffinity()
    }
    }
    }
  2. В AndroidManifest.xml добавьте поле activity для UnpackAssetsActivity и сделайте его первым при запуске.

    <activity
    android:name="UnpackAssetsActivity"
    android:directBootAware="true"
    android:exported="true">

    <intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    </activity>

Использование модулей Face SDK

  1. Добавьте переменные service, faceDetector, faceFitter, ageEstimator, genderEstimator в класс MainActivity и проинициализируйте их в методе onCreate.

    private lateinit var service: FacerecService
    private lateinit var faceDetector: ProcessingBlock
    private lateinit var faceFitter: ProcessingBlock
    private lateinit var ageEstimator: ProcessingBlock
    private lateinit var genderEstimator: ProcessingBlock
    //...................................................

    service = FacerecService.createService(
    "libfacerec.so",
    applicationInfo.dataDir + "/fsdk/conf/facerec",
    applicationInfo.dataDir + "/fsdk/license"
    )

    val configDetector = service.createContext()
    configDetector["unit_type"].string = "FACE_DETECTOR"
    configDetector["modification"].string = "blf_front"

    faceDetector = service.createProcessingBlock(configDetector)

    val configFitter = service.createContext()
    configFitter["unit_type"].string = "FACE_FITTER"
    configFitter["modification"].string = "fda"

    faceFitter = service.createProcessingBlock(configFitter)

    val configAgeEstimator = service.createContext()
    configAgeEstimator["unit_type"].string = "AGE_ESTIMATOR"
    configAgeEstimator["modification"].string = "light"
    configAgeEstimator["version"].long = 2

    ageEstimator = service.createProcessingBlock(configAgeEstimator)

    val configGenderEstimator = service.createContext()
    configGenderEstimator["unit_type"].string = "GENDER_ESTIMATOR"
    configGenderEstimator["modification"].string = "light"
    configGenderEstimator["version"].long = 2

    genderEstimator = service.createProcessingBlock(configGenderEstimator)
  2. Для отображения полученных детекций нам понадобятся переменные paint, bitmap и imageView в классе MainActivity, а также ImageView в /app/src/main/res/layout/activity_main.xml.

    ```kotlin
    val paint = Paint()
    lateinit var bitmap: Bitmap
    lateinit var imageView: ImageView
    ```

    ```xml
    <ImageView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#000"
    android:id="@+id/imageView" />
    ```
  3. Проинициализируйте imageView в методе onCreate.

    imageView = findViewById(R.id.imageView)
  4. В textureView.surfaceTextureListener переопределите метод onSurfaceTextureUpdated.

    Добавьте в него формирование входного контейнера-Context с бинарным RGB изображением, вызовы модулей Face SDK и отображение результата.

    override fun onSurfaceTextureUpdated(p0: SurfaceTexture) {
    bitmap = textureView.bitmap!!

    val width = bitmap.width
    val height = bitmap.height

    val pixels = IntArray(width * height)
    val imageData = ByteArray(width * height * 3)

    bitmap.getPixels(pixels, 0, width, 0, 0, width, height)

    for (i in pixels.indices) {
    imageData[i * 3 + 0] = (pixels[i] shr 16).toByte()
    imageData[i * 3 + 1] = (pixels[i] shr 8).toByte()
    imageData[i * 3 + 2] = (pixels[i] shr 0).toByte()
    }

    val ioData = service.createContextFromFrame(imageData, width, height,
    com.vdt.face_recognition.sdk.Context.Format.FORMAT_RGB, 0)
    faceDetector.process(ioData)
    faceFitter.process(ioData)
    ageEstimator.process(ioData)
    genderEstimator.process(ioData)

    val objects = ioData["objects"]

    val mutable = bitmap.copy(Bitmap.Config.ARGB_8888, true)

    val canvas = Canvas(mutable)

    paint.textSize = height/50f
    paint.strokeWidth = width/100f
    val indentionX = (0.01 * width).toFloat()
    val indentionY = paint.textSize
    for (i in 0..< objects.size()) {
    val obj = objects[0]
    val bbox = obj["bbox"]
    val x1 = (bbox[0].double).toFloat() * width
    val y1 = (bbox[1].double).toFloat() * height
    val x2 = (bbox[2].double).toFloat() * width
    val y2 = (bbox[3].double).toFloat() * height

    paint.color = Color.GREEN
    paint.style = Paint.Style.STROKE
    canvas.drawRect(RectF(x1, y1, x2, y2), paint)
    paint.style = Paint.Style.FILL
    canvas.drawText("age: " + obj["age"].long,
    x2 + indentionX, y1 + indentionY, paint)
    canvas.drawText("gender: " + obj["gender"].string,
    x2 + indentionX, y1 + 2 * indentionY, paint)
    }

    imageView.setImageBitmap(mutable)
    }