Android CameraX How to implement Wide Angle / Zoom Out
Asked Answered
S

1

8

I tried to implement wide angle option for my in-app camera using CameraX api but ran into an issue - CameraControl.setZoomRatio allows to set zoom between ZoomState.getMinZoomRatio() and ZoomState.getMaxZoomRatio(), where on phones I tested it minZoomRatio is 1.0f. The same phone supports zoom down to 0.5f in system camera.

Current snippets how I initialize the camera:

private var camera: Camera? = null
private var imageCapture: ImageCapture? = null

private fun startCamera() {
    val cameraProviderFuture = ProcessCameraProvider.getInstance(this)

    cameraProviderFuture.addListener({
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

            val preview = Preview.Builder()
                .build()
                .also {
                    it.setSurfaceProvider(binding.viewFinder.surfaceProvider)
                }

            imageCapture = ImageCapture.Builder()
                .setFlashMode(ImageCapture.FLASH_MODE_AUTO)
                .build()

            val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

            try {
                cameraProvider.unbindAll()

                camera = cameraProvider.bindToLifecycle(
                    this, cameraSelector, preview, imageCapture
                )
                viewModel.onCameraStarted()
            } catch (exc: Exception) {
                Timber.e(exc)
            }
        },
        ContextCompat.getMainExecutor(this)
    )
}

Current pinch to zoom implementation:

val scaleGestureDetector = ScaleGestureDetector(this,
    object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
        override fun onScale(detector: ScaleGestureDetector): Boolean {
            val camera = camera ?: return false
            val zoomState = camera.cameraInfo.zoomState.value ?: return false
            val scale = zoomState.zoomRatio * detector.scaleFactor
            val finalScale =
                scale.coerceIn(MIN_ZOOM, MAX_ZOOM).coerceIn(zoomState.minZoomRatio, zoomState.maxZoomRatio)
            camera.cameraControl.setZoomRatio(finalScale)
            return true
        }
    })

binding.viewFinder.setOnTouchListener { view, event ->
    view.performClick()
    scaleGestureDetector.onTouchEvent(event)
    return@setOnTouchListener true
}

My question is it possible to use wide angle camera (or achieve 0.5f zoom) using CameraX api, or do I have to rewrite whole implementation using Camera2.

Stokehole answered 1/7, 2021 at 23:51 Comment(5)
OpenCV has some great libraries! Would highly recommend it on this front. My experience lies in dewarping, but it has been proven for incredibly fast (gpu utilization) for this sort of thing with massive amounts of open source items.Findlay
Unfortunately APIs that device camera app uses are generally not available with CameraX. I could not encounter a device that gives a value less than 1 for minimum available zoom even though most of the device camera apps have a wide camera option.Quantic
Could you provide the name of the device? What is the value of of CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE on this device?Biforked
Hi @Stokehole found any solution for the same?Kelt
@KrunalKapadiya Hey, yes, but only partially. Wide angle camera is separate camera and minZoomRatio is always 1.0. I ended with having button in my camera screen that switches between normal camera and wide lens camera (if phone allows access to wide lens camera, which isn't always the case). I'll post my camera selector as answer.Stokehole
S
2

As explained in comment below the question, wide lens camera (and any other camera, for example large zoom if we tried to implement zoom x20) is separate camera, so to allow switching between normal camera and (for example) wide lens camera, we have to restart camera view with different camera selector. How we restart it, it's up to us (for example restart after certain pinch or by having button that switches camera).

Here is camera selector that gets either back camera or wide lens back camera depending on startCameraData.ultraWide param:

val cameraSelector = if (startCameraData.ultraWide) {
    CameraSelector.Builder()
        .addCameraFilter { cameraInfos ->
            // filter back cameras with minimum sensor pixel size
            val backCameras = cameraInfos.filterIsInstance<Camera2CameraInfoImpl>()
                .filter {
                    val pixelWidth = it.cameraCharacteristicsCompat.get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE)?.width ?: 0
                    it.lensFacing == CameraSelector.LENS_FACING_BACK && pixelWidth >= 2000 // arbitrary number resolved empirically
                }  

            // try to find wide lens camera, if not present, default to general backCameras
            backCameras.minByOrNull {
                val focalLengths = it.cameraCharacteristicsCompat.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS)
                focalLengths?.getOrNull(0) ?: 0f
            }
                ?.let { listOf(it) } ?: backCameras
        }
        .build()
} else {
    CameraSelector.DEFAULT_BACK_CAMERA
}

Note: Camera2 api might not give us access to certain cameras depending on phone, but sometimes also unless app package is whitelisted by Google. For example Open Camera app (net.sourceforge.opencamera) is whitelisted, and has access to all cameras. I tested this by changing my app package to net.sourceforge.opencamera and suddenly got 6 camera ids instead of 3.

Stokehole answered 21/10, 2022 at 8:12 Comment(4)
Why use pixelWidth? If they are back cameras, they are back cameras, no matter the pixels. By the way, thanks for the soluition of getting the focal lenghts.Pirandello
the whitelist info is interesting. How do you know this info?Ausgleich
@Ausgleich I know it because I noticed that when I built opencamera from their repo, I got more cameras accessible than in my app. Then I changed my package name to theirs, and suddenly I get more cameras tooStokehole
It is a really nice finding!Ausgleich

© 2022 - 2025 — McMap. All rights reserved.