Как заставить CameraX Preview зависать при съемке фотографии?

У меня есть поток с моим пользовательским CameraX, например:

  • Открыть предварительный просмотр камеры (в реальном времени)
  • Нажмите кнопку, чтобы сделать фото
  • Выполните процесс при нажатии этой кнопки (преобразовать путь в растровое изображение, повернуть изображение, автоматически обрезать изображение, сохранить на устройстве)
  • После успешного выполнения всего процесса отправьте изображение в другой фрагмент и отобразите его в режиме скольжения.

Вопрос в том, когда у running all the process (in step 3) есть delayed 2 seconds, а превью камеры по-прежнему live (not freeze или lock). Как сделать camera preview freeze or lock when running the process?

Это мой код для запуска предварительного просмотра камеры в Camera X:

class CameraFragment : Fragment() {

        override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
            // Inflate the layout for this fragment
            return inflater.inflate(R.layout.fragment_camera, container, false)
        }

        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)

            viewFinder.post { setupCamera() }
        }

        private fun setupCamera() {
            CameraX.unbindAll()
            CameraX.bindToLifecycle(
                this,
                buildPreviewUseCase(),
                buildImageCaptureUseCase()
            )
        }

        private fun buildPreviewUseCase(): Preview {
            val preview = Preview(
                UseCaseConfigBuilder.buildPreviewConfig(
                    viewFinder.display
                )
            )
            preview.setOnPreviewOutputUpdateListener { previewOutput ->
                updateViewFinderWithPreview(previewOutput)
                correctPreviewOutputForDisplay(previewOutput.textureSize)
            }
            return preview
        }

        private fun buildImageCaptureUseCase(): ImageCapture {
            val capture = ImageCapture(
                UseCaseConfigBuilder.buildImageCaptureConfig(
                    viewFinder.display
                )
            )
            cameraCaptureImageButton.setOnClickListener {
                capture.takePicture(
                    FileCreator.createTempFile(JPEG_FORMAT),
                    Executors.newSingleThreadExecutor(),
                    object : ImageCapture.OnImageSavedListener {
                        override fun onImageSaved(file: File) {
                            // I want make a freeze camera preview when execute this before launch *launchGalleryFragment(path)*
                            val bitmap = BitmapFactory.decodeFile(file.absolutePath)
                            val rotatedBitmap = bitmap.rotate(90)
                            val croppedImage = cropImage(rotatedBitmap, viewFinder, rectangle)
                            val path = saveImage(croppedImage)
                            requireActivity().runOnUiThread {
                                launchGalleryFragment(path)
                            }
                        }

                        override fun onError(
                            imageCaptureError: ImageCapture.ImageCaptureError,
                            message: String,
                            cause: Throwable?
                        ) {
                            Toast.makeText(requireContext(), "Error: $message", Toast.LENGTH_LONG)
                                .show()
                            Log.e("CameraFragment", "Capture error $imageCaptureError: $message", cause)
                        }
                    })
            }
            return capture
        }

        private fun launchGalleryFragment(path: String) {
            val action = CameraFragmentDirections.actionLaunchGalleryFragment(path)
            findNavController().navigate(action)
        }

    }

person R Rifa Fauzi Komara    schedule 09.01.2020    source источник


Ответы (1)


Может быть, вы можете попробовать отвязать предварительный вариант использования:

версия 1.0.0-alpha06: CameraX.unbind(preview);

версия> 1.0.0-alpha07: cameraProvider.unbind(preview);

В вашем случае вам нужно сохранить предварительный вариант использования в переменной, чтобы затем отвязать его:

// Declare the preview use case variable (as in the CameraXBasic example)
private var preview: Preview? = null

Затем создайте экземпляр переменной (как и вы):

private fun buildPreviewUseCase(): Preview {
    preview = Preview(
        UseCaseConfigBuilder.buildPreviewConfig(
            viewFinder.display
        )
    )
    preview.setOnPreviewOutputUpdateListener { previewOutput ->
        updateViewFinderWithPreview(previewOutput)
        correctPreviewOutputForDisplay(previewOutput.textureSize)
    }
    return preview
}

Затем, когда вы хотите заморозить предварительный просмотр, просто отвяжите вариант использования:

CameraX.unbind(preview);

ИЗМЕНИТЬ Как сказал @Billda в этом сообщении: CameraX - сбой при отмене привязки Preview UseCase:

Чтобы заморозить предварительный просмотр, не следует отменять привязку варианта использования предварительного просмотра. В будущем для этого может быть API, но в настоящее время рекомендуется сохранить последний кадр из ImageAnalysis и поместить его в ImageView, перекрывая предварительный просмотр.

Поэтому я решил обновить свой ответ, чтобы дать другое решение, реализующее анализатор с ImageAnalysis (1.0.0-beta02).

1- Создайте класс FreezeAnalyzer:

class FreezeAnalyzer(private val callback: FreezeCallback) : ImageAnalysis.Analyzer {
    private var flag = false

    override fun analyze(image: ImageProxy) {
        if(flag){
            flag = false
            val bitmap = toBitmap(image)
            callback.onLastFrameCaptured(bitmap)
        }
        image.close()
    }

    fun freeze(){
        flag = true
    }

    private fun toBitmap(image: ImageProxy): Bitmap {
        // Convert the imageProxy to Bitmap
        // ref https://stackoverflow.com/questions/56772967/converting-imageproxy-to-bitmap
        // ISSUE, on my android 7 when converting the imageProxy to Bitmap I have a problem with the colors...
        var bitmap = ...

        // Rotate the bitmap
        val rotationDegrees = image.imageInfo.rotationDegrees.toFloat()
        if (rotationDegrees != 0f) {
            val matrix = Matrix()
            matrix.postRotate(rotationDegrees)
            bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
        }
        return bitmap
    }
}

2- XML

<androidx.camera.view.PreviewView
    android:id="@+id/preview_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />
<ImageView
    android:id="@+id/image_view"
    android:visibility="invisible"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scaleType="centerCrop"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

3- Инициализировать imageAnalyser

val resolutionSize = Size(preview_view.width, preview_view.height)

// Set up analyser
imageAnalysis = ImageAnalysis.Builder().apply {
    setTargetResolution(resolutionSize)
    setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
}.build()

val analyzer = FreezeAnalyzer(object : FreezeCallback {
    override fun onLastFrameCaptured(bitmap: Bitmap) {
        runOnUiThread {
            preview_view.visibility = View.INVISIBLE
            image_view.visibility = View.VISIBLE
            image_view.setImageBitmap(bitmap)
        }
    }
})
imageAnalysis.setAnalyzer(executor, analyzer)

4- Привязать пример использования imageAnalysis

try {
    val camera = cameraProvider.bindToLifecycle(
        this,
        cameraSelector,
        preview,
        imageAnalysis,
        imageCapture
    )
    preview.setSurfaceProvider(preview_view.createSurfaceProvider(camera.cameraInfo))
}

5- Сделайте снимок

btn_capture.setOnClickListener {
    file = File(externalMediaDirs.first(), "${System.currentTimeMillis()}.jpg")
    val outputFileOptions: ImageCapture.OutputFileOptions =
        ImageCapture.OutputFileOptions.Builder(file!!).build()
    analyzer.freeze()
    imageCapture.takePicture(outputFileOptions, executor, onImageSavedCallback)
}

6- Релиз

btn_release.setOnClickListener {
    preview_view.visibility = View.VISIBLE
    image_view.visibility = View.INVISIBLE
}

Надеюсь, это поможет, я не эксперт, поэтому, если у вас есть какие-то улучшения, добро пожаловать!

person LaurentP22    schedule 10.01.2020
comment
какой код в preview? можешь дать мне полный исходный код? - person R Rifa Fauzi Komara; 10.01.2020
comment
Куда я добавляю CameraX.unbind(preview) функцию? Я пытаюсь внутри onImageSaved() закрыть - person R Rifa Fauzi Komara; 10.01.2020
comment
Да, внутри onImageSaved(), я тестировал его на примере CameraXBasic, и он работает. Думаю, это исходит от вашего исполнителя, можете ли вы попробовать сделать как в примере CameraXBasic, используя ContextCompat.getMainExecutor(requireContext()) - person LaurentP22; 10.01.2020
comment
Да, после того, как я добавлю это в свой код, все заработает. Но почему бы не остановиться сразу после того, как я нажму кнопку камеры? Перед немедленным замораживанием все еще есть задержка. - person R Rifa Fauzi Komara; 13.01.2020
comment
Что ж, я думаю, что есть задержка между моментом, когда вы нажимаете на кнопку, и моментом вызова обратного вызова (время для камеры, чтобы сделать снимок). Попытайтесь отключить предварительный просмотр в onClickListener сразу после вызова capture.takePicture(). Тем не менее, замороженный предварительный просмотр может отличаться от сделанного снимка. - person LaurentP22; 13.01.2020
comment
Ага, я понимаю, отвязать предварительный просмотр в onClickListener после вызова capture.takePicture() нехорошо. Но как, если я обрабатываю задержку между моментом, когда я нажимаю кнопку, и моментом вызова обратного вызова (время для камеры, чтобы сделать снимок). Возможно? - person R Rifa Fauzi Komara; 13.01.2020
comment
Последнее, что мне приходит в голову, это добавить задержку, как в примере cameraXBasic: container.postDelayed({CameraX.unbind(preview)}, 150L). Удачи. - person LaurentP22; 13.01.2020
comment
@ LaurentP22 Только один вопрос, который меня интересует, поскольку imageAnalyzer возвращает растровое изображение в FreezeCallback зачем добавлять ImageCapture вариант использования, растровое изображение может быть обработано в фоновом потоке (обрезано и сохранено в файл) - person Mirwise Khan; 09.06.2020
comment
Что ж, в зависимости от ситуации я полагаю, что нет проблем с удалением ImageCapture варианта использования и последующей обработкой растрового изображения, как вы сказали. Однако у ImageCapture варианта использования есть некоторые преимущества: при съемке в ImageCapture варианте использования некоторая информация сохраняется вместе с фотографией (модель устройства, фокусное расстояние…). Более того, качество картинки могло бы быть лучше. - person LaurentP22; 09.06.2020