在运行时在 kotlin 中的相机上运行的不同 ML 模型之间切换

Posted

技术标签:

【中文标题】在运行时在 kotlin 中的相机上运行的不同 ML 模型之间切换【英文标题】:Switch between different ML models that run on camera in kotlin on runtime 【发布时间】:2022-01-12 01:00:15 【问题描述】:

我正在尝试制作一个允许用户在不同的 ML 模型之间动态切换的应用程序。现在我有一个简单的应用程序,可以运行没有模型的相机和带有对象检测模型的相机。最终应用中将有五个这样的模型。

这是我的Home Activity

class HomeActivity : AppCompatActivity(), EasyPermissions.PermissionCallbacks 
private val rqSpeechRec = 102
private var tts: TextToSpeech? = null
private lateinit var binding: ActivityHomeBinding
private lateinit var objectDetector: ObjectDetector
private lateinit var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>

override fun onCreate(savedInstanceState: Bundle?) 
    super.onCreate(savedInstanceState)

    // Binding ViewData
    binding = ActivityHomeBinding.inflate(layoutInflater)
    setContentView(binding.root)

    supportActionBar?.hide() // Hiding App bar

    requestCameraPermission()   // Requesting Camera Permission

这是我的Request Camera Permission 函数。

private fun requestCameraPermission() 
    speakOut("Allow Eynetic to access the camera to take photos or videos.")

    EasyPermissions.requestPermissions(
        this,
        "This app can not work without camera.",
        Constants.PERMISSION_CAMERA_REQUEST_CODE,
        permission.CAMERA
    )


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

    EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this)


override fun onPermissionsDenied(requestCode: Int, perms: List<String>) 
    speakOut("Permissions Denied.")

    if (EasyPermissions.somePermissionDenied(this, perms.first()))
        SettingsDialog.Builder(this).build()
            .show()  // If permissions are permanently denied show settings.
    else 
        requestCameraPermission()


override fun onPermissionsGranted(requestCode: Int, perms: List<String>) 
    cameraProviderFuture = ProcessCameraProvider.getInstance(this)
    cameraProviderFuture.addListener(
        startCamera(cameraProviderFuture.get())
    , ContextCompat.getMainExecutor(this))

    // Introduction
    val intro =
        "Welcome to Eyenetic. Please activate any module through voice command.The nodules are obstacle detection, scene recognition, currency detection, human object interaction and human human interaction."
    speakOut(intro)

    allButtons()

只有在获得许可时,我才 addListenercameraProviderFuture。当获得许可时,我在没有运行模型的情况下启动相机。请注意每次没有模型运行时应用打开的时间。

@SuppressLint("UnsafeOptInUsageError")
private fun startCamera(cameraProvider: ProcessCameraProvider) 
    val preview = Preview.Builder().build()

    val cameraSelector =
        CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()

    preview.setSurfaceProvider(binding.previewView.surfaceProvider)

    cameraProvider.bindToLifecycle(this as LifecycleOwner, cameraSelector, preview)


以及获取Object-Detection模型的代码。

private fun readyObjectDetectionModel()

    val localModel = LocalModel.Builder().setAbsoluteFilePath("object_detection.tflite").build()
    val customObjectDetectionOptions = CustomObjectDetectorOptions.Builder(localModel)
        .setDetectorMode(CustomObjectDetectorOptions.STREAM_MODE)
        .enableClassification()
        .setClassificationConfidenceThreshold(0.5f)
        .build()

    objectDetector = ObjectDetection.getClient(customObjectDetectionOptions)

Object-Detection 的代码

    @SuppressLint("UnsafeOptInUsageError")
private fun startObjectDetection(cameraProvider: ProcessCameraProvider) 
    val preview = Preview.Builder().build()

    val cameraSelector =
        CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()

    preview.setSurfaceProvider(binding.previewView.surfaceProvider)
    
    val imageAnalysis = ImageAnalysis.Builder().setTargetResolution(Size(1280, 720))
        .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST).build()

    imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(this),
        imageProxy ->
            val rotationDegrees =imageProxy.imageInfo.rotationDegrees
            val image = imageProxy.image

            if (image != null) 
                val processImage = InputImage.fromMediaImage(image, rotationDegrees)

                objectDetector.process(processImage)
                    .addOnSuccessListener 
                        imageProxy.close()
                    .addOnFailureListener
                        imageProxy.close()
                    
            
    )
    cameraProvider.bindToLifecycle(this as LifecycleOwner, cameraSelector, imageAnalysis, preview)

我可以通过一次调用每个代码并评论另一个来运行相机代码。但我制作了不同的按钮来触发不同的模型。在调用每个方法之前我应该​​做什么。

现在我能想到的唯一方法是取消绑定以前的生命周期并创建一个新的。但是像

这样的冗余代码呢?
val preview = Preview.Builder().build()

val cameraSelector =
    CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()

preview.setSurfaceProvider(binding.previewView.surfaceProvider)

他们的方式是只改变当前绑定生命周期的用例吗?

我应该制作camerafragment并在不同的fragment之间切换吗?

关于更好地解决这个问题的任何建议。

【问题讨论】:

【参考方案1】:

我想到的一个想法是创建一个ImageAnalysis.Analyzer 子类,它可以处理不同的 ML 功能(用您的话来说就是 ML 模型)。

在这种情况下,您只需设置一次 imageAnalysis 用例,相机就会不断将图像输入到您的 Analyzer。

在您的分析器中,您可以设计一些 API,例如 switchTo(MLFeatureName name)。当你的 UI 按钮被按下时,你可以调用 switchTo 方法。在这样的切换之后,图像将被馈送到不同的检测器。

确保关闭未使用的检测器以释放系统资源。

【讨论】:

以上是关于在运行时在 kotlin 中的相机上运行的不同 ML 模型之间切换的主要内容,如果未能解决你的问题,请参考以下文章

SceneKit中的相机旋转

Kotlin 中的“宁愿在该类上运行匕首处理器”

当我在设备上运行程序时,AssetsLibrary 没有获取保存在相机胶卷中的图像

Ktor-Kotlin 中的 Quartz 调度器运行在 Kubernetes 集群的所有 Pod 上

在多租户环境中,如何在运行时在不同的 url(子域)上为不同的服务提供者提供不同的元数据?

Android - 录制视频时在前后摄像头之间切换