作为我的 ML 项目的一部分,我想生成训练数据,用于使用面部检测器 Google Firebase ML-Kit Face detection library 分析来自不同图像的多个人的面部。 .我创建了一个非常简单的服务类来封装初始化并启动人脸检测的过程:
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))
}
}
函数检测器!!.detectInImage (FirebaseVisionImage.detectInImage ) 返回一个 Task表示异步操作。
在我的 MainActivity 的 onResume() 函数中,在 CoroutineScope 内,我启动 lib 并开始迭代图像,首先将它们转换为 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 block 内,将对库的调用封装在一个任务中:
val tsk = async {
detector!!.analyzeAsync(act, uri)
}
val result = tsk.await()
和一份工作:
val tsk = withContext(IO) {
detector!!.analyzeAsync(act, uri)
}
但是我手动启动的异步操作总是只有在 Firebase 任务启动时才会持续,而不是等待内部任务运行完成。我还尝试在 FaceDetectorService 类中添加不同的 withcontext(...) 和 ...launch {} 变体,但无济于事。
我显然对 kotlin 协程很陌生,所以我认为我在这里遗漏了一些非常基本的东西,但我无法理解它。
(PS:请不要评论代码的草率,这只是一个原型(prototype):))
最佳答案
analyzeAsync()
是 suspend fun
并且还返回一个类似 future 的 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
中提供了优化版本。模块)。由于人脸检测 API 已经是异步的,这意味着您调用它的线程是无关紧要的,它会在内部处理其计算资源。因此,您无需在
IO
中启动调度员,您可以使用Main
并在任何时候自由地做 GUI 工作,没有上下文切换。至于你的主要观点:我找不到明确的细节,但很可能一个人脸检测调用已经使用了所有可用的 CPU,甚至现在出现在智能手机中的专用 ML 电路,这意味着没有什么可做的从外部并行化。只需一个人脸检测请求就已经让所有资源都在处理它。
关于android - Firebase ML-Kit 并行运行多个人脸检测 session ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60583180/