Firebase ML-Kit запускает несколько сеансов распознавания лиц параллельно - PullRequest
1 голос
/ 08 марта 2020

В рамках моего проекта ML я хочу создать обучающие данные для анализа лица нескольких людей на разных изображениях с помощью детектора лиц Google Firebase ML-Kit Библиотека распознавания лиц . Я создал очень простой класс обслуживания для инкапсуляции инициализации и запуска процесса обнаружения лиц:

class FaceDetectorService(private val act: MainActivity)  {

private var opts: FirebaseVisionFaceDetectorOptions? = null
private var detector: FirebaseVisionFaceDetector? = null


init {
    FirebaseApp.initializeApp(act)

    opts = FirebaseVisionFaceDetectorOptions.Builder()
        .setPerformanceMode(FirebaseVisionFaceDetectorOptions.ACCURATE)
        .setLandmarkMode(FirebaseVisionFaceDetectorOptions.NO_LANDMARKS)
        .setClassificationMode(FirebaseVisionFaceDetectorOptions.NO_CLASSIFICATIONS)
        .setContourMode(FirebaseVisionFaceDetectorOptions.ALL_CONTOURS)
        .build()

    detector = FirebaseVision.getInstance()
        .getVisionFaceDetector(opts!!)
}


suspend fun analyzeAsync(cont: Context, uri: Uri) : Pair<String, Task<List<FirebaseVisionFace>>> {
    val image = FirebaseVisionImage.fromFilePath(cont, uri)

    // this is for the UI thread
    withContext(Main){
        act.addItemToAnalyze(uri.lastPathSegment)
    }

    // return the filename too
    return Pair(uri.lastPathSegment, detector!!.detectInImage(image))
}

}

Функция детектор !!. DeteInImage ( FirebaseVisionImage.detectInImage ) возвращает Task , который представляет асинхронные c операции.

В функции onResume () моей функции MainActivity внутри CoroutineScope я запускаю библиотеку и начинаю перебирать изображения, сначала преобразовывая их в Uri, а затем передавая его детектору лиц:

CoroutineScope(IO).launch {
        val executeTime = measureTimeMillis {
            for (uri in uris){

                    val fileNameUnderAnalysis = uri.lastPathSegment
                    //val tsk = withContext(IO) {
                    //    detector!!.analyzeAsync(act, uri)
                    //}
                val tsk = detector!!.analyzeAsync(act, uri)
                tsk.second.addOnCompleteListener { task ->
                    if (task.isSuccessful && task.result!!.isNotEmpty()) {
                        try {
                           // my best
                        } catch (e: IllegalArgumentException) {
                           // fire
                        }
                    } else if (task.result!!.isEmpty()) {
                       // not today :(
                    }

                }

                tsk.second.addOnFailureListener { e ->
                    // on error
                }
            }

        }
        Log.i("MILLIS", executeTime.toString())
    }

Теперь, хотя моя реализация работает одновременно (то есть, , начиная с одновременно), я на самом деле хочу запустить их параллельно (, запущенных в то же время в зависимости от количества потоков (в моем случае это 4 на эмуляторе), поэтому моей целью было бы взять число доступных потоков и назначить операции анализа каждому из них, квартализируя время выполнения.

То, что я пробовал до сих пор, это внутри блока CoroutineScope (IO) .launch , инкапсулирующего вызов библиотеки в задачу:

val tsk = async {
    detector!!.analyzeAsync(act, uri)
}

val result = tsk.await()

и job:

val tsk = withContext(IO) {
   detector!!.analyzeAsync(act, uri)
}

, но асинхронные c операции, которые я запускаю вручную, всегда выполняются только до тех пор, пока запускаются задачи Firebase, не дожидаясь завершения внутренней задачи. Я также попытался добавить различные withcontext (...) и ... launch {} вариантов внутри класса FaceDetectorService , но безрезультатно.

Я, очевидно, очень плохо знаком с kotlin сопрограммами, поэтому я думаю, что мне здесь не хватает чего-то очень базового c, но я просто не могу обернуться вокруг него.

(PS: пожалуйста не комментируйте небрежность кода, это всего лишь прототип :))

1 Ответ

1 голос
/ 08 марта 2020

analyzeAsync() - это suspend fun, а также возвращает похожий на будущее объект Task. Вместо этого он должен возвращать результат Task.await(), который вы можете легко реализовать в принципе, выделив свой addOnCompleteListener вызов:

suspend fun <T> Task<T>.await(): T = suspendCancellableCoroutine { cont ->
    addOnCompleteListener {
        val e = exception
        when {
            e != null -> cont.resumeWithException(e)
            isCanceled -> cont.cancel()
            else -> cont.resume(result)
        }
    }
}

Оптимизированная версия доступна в kotlinx-coroutines-play-services module).

Поскольку API-интерфейс обнаружения лица уже асин c, это означает, что поток, к которому вы его вызываете, не имеет значения и обрабатывает свои вычислительные ресурсы внутренне. Поэтому вам не нужно запускать в диспетчере IO, вы можете использовать Main и свободно выполнять GUI работу в любой точке, без переключения контекста.

Что касается вашего основного пункта: I Не удалось найти подробные сведения об этом, но весьма вероятно, что в одном вызове по распознаванию лиц уже используются все доступные ресурсы ЦП или даже выделенные схемы ML, которые теперь появляются в смартфонах, что означает, что нечего распараллеливать извне. Только один запрос на обнаружение лиц уже получает все ресурсы, работающие над ним.

...