Logotipo de Zephyrnet

CameraX: ¡facilita la fotografía en Android!

Fecha:


Un retroceso a Google I / O 2019 ...

Uno de los cambios más emocionantes que Google anunció este año en Google I / O 2019 es Camarax. Es una API que traerá una gran cantidad de nuevas funciones y, supuestamente, facilita la implementación de las funciones de la cámara en Android.

Pero, ¿cómo es esto mejor que el actual? Camera2 API? ¿CameraX realmente tiene mucho que ofrecer o es solo un truco?

Veamos en este artículo. Pero primero, echemos un vistazo a cómo se está implementando la API Camera2 en las aplicaciones en estos días.

Note: Los Camera2 API, que reemplazó el legado API de cámara, le da más control sobre la cámara de su dispositivo. Pero también tenga en cuenta que la API de Camera2 es compatible con Lollipop 5.0 y superior, por lo que si desea admitir dispositivos más bajos que eso, deberá atenerse a la API de cámara heredada.

La API de Camera2 (¿ahora desactualizada?)

En este artículo, nos centraremos en capturar fotos con una interfaz de usuario de cámara personalizada en Android. Dejaremos de lado los videos, ya que una implementación de video en Android requiere un artículo por separado.

Echemos un vistazo a una implementación básica para capturar fotos con la API de Camera2.

Primero, tendríamos que configurar un TextureView y un Botón en nuestro diseño para capturar la imagen:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <TextureView android:id="@+id/texture_view" android:layout_width="match_parent" android:layout_height="match_parent" … /> <Button android:id="@+id/btn_capture" android:layout_width="wrap_content" android:layout_height="wrap_content" … />
</RelativeLayout>

Luego, tendríamos que configurar una vista previa de la cámara, así:

try { val texture = texture_view.getSurfaceTexture() texture.setDefaultBufferSize(imageDimension.getWidth(), imageDimension.getHeight()) val surface = Surface(texture) captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW) captureRequestBuilder.addTarget(surface) cameraDevice.createCaptureSession(Arrays.asList(surface), object:CameraCaptureSession.StateCallback() { fun onConfigured(@NonNull cameraCaptureSession:CameraCaptureSession) { if (null == cameraDevice) { return } cameraCaptureSessions = cameraCaptureSession updatePreview() } fun onConfigureFailed(@NonNull cameraCaptureSession:CameraCaptureSession) { Toast.makeText(this@AndroidCameraApi, "Configuration changed!", Toast.LENGTH_SHORT).show() } }, null)
} catch (e:CameraAccessException) { e.printStackTrace()
}

Todavía necesitamos:

  • Obtenga una instancia de CameraManager
  • Obten lo CameraCharacteristics y StreamConfigurationMap
  • Configuración de una CameraDevice.StateCallback y CameraCaptureSession.CaptureCallback para la vista previa de la imagen y la captura de imagen respectivamente
  • Configurar el TextureView.SurfaceTextureListener para la vista previa en el TextureView
  • Abrir la cámara
  • Escribe el código para tomar la foto
  • Escribir código para actualizar la vista previa
  • Cierre la cámara cuando no esté en uso.

¡Uf! ¡Eso suena como un monton de trabajo! No me gustaría pasar el resto de este artículo sobre cómo implementar la API de Camera2. Así que vamos a resumirlo aquí y hablemos sobre por qué Camera2 API es realmente difícil de implementar en una aplicación de Android:

  • Requiere un tonelada de código, como se puede inferir desde arriba. Este es un gran inconveniente si eres nuevo en la implementación de la cámara en Android.
  • Es demasiado complejo. Gran parte del código en la API podría simplificarse, pero debido a que la API fue diseñada para proporcionar más control sobre la cámara, se ha vuelto demasiado difícil de comprender para los desarrolladores que son nuevos en implementar la cámara en su aplicación.
  • Requiere que implemente muchos estados, y deberá ejecutar un montón de métodos específicos para manejar cuando estos estados cambien. Puedes echar un vistazo a la agotadora lista de estados esta página.
  • También presenta algunos errores en la parte de la linterna de su cámara. Hay mucha confusión con respecto a las diferencias entre un modo "antorcha" y un modo "flash" en Camera2.
  • Si esto no es suficiente para impulsar a los nuevos desarrolladores de Camera2, hay muchos errores específicos del proveedor que requieren reparación y, por lo tanto, más código. Estos también se conocen como problemas de compatibilidad de dispositivos, que requieren que los desarrolladores escriban código específico del dispositivo para administrar una solución alternativa a la solución.

Si bien la API heredada de la cámara es más fácil de usar y es compatible con dispositivos con sistema operativo Android inferior, no proporciona mucho control sobre la cámara. Tampoco faltan sus propios errores y problemas específicos del proveedor. Comparte muchos de los mismos problemas impredecibles que la API Camera2 y, por lo tanto, no es una buena idea implementar en su cámara personalizada.

Con estos obstáculos en mente, Google presentó la nueva API CameraX para resolver estos problemas.

¡Saluda a la API de CameraX! 👋

Como se mencionó anteriormente, CameraX hace que construir una cámara personalizada sea mucho más fácil. También trae consigo una lista completamente nueva de características que son fáciles de implementar.

Note: La API de CameraX todavía está en la etapa alfa, por lo que esta API está sujeta a cambios en los próximos meses. Úselo con precaución. ⚠️

Estas son algunas de las principales características de CameraX hasta ahora:

Sencillez

La biblioteca CameraX proporciona una API simple y fácil de usar. Esta API es coherente en la mayoría de los dispositivos Android que ejecutan Lollipop 5.0 y superior.

Resuelve problemas de compatibilidad de dispositivos

CameraX también tiene como objetivo resolver problemas que aparecen en dispositivos específicos. Con Camera2 y la cámara heredada, los desarrolladores a menudo tenían que escribir una gran cantidad de código específico del dispositivo. ¡Ahora pueden decir adiós a escribir ese código, gracias a CameraX!

Google logró esto al probar CameraX en un laboratorio de pruebas automatizado, en muchos dispositivos diferentes de muchos fabricantes.

Aprovecha todas las funciones API de Camera2

Si bien CameraX resuelve los problemas que tenía Camera2, curiosamente, también aprovecha todas las características API de Camera2. Este hecho por sí solo me dificulta pensar en una razón para no migrar a CameraX en los próximos meses.

Menos código

Esto es una bendición para los desarrolladores que desean comenzar con la API de CameraX. Escribe menos código, en comparación con Camera2 y la API de cámara heredada, para lograr el mismo nivel de control sobre la cámara.

Casos de uso en CameraX

Usar CameraX es una delicia, debido a que su uso se puede dividir en 3 casos de uso:

  • Vista previa de imagen: Vista previa de la imagen en su dispositivo antes de tomar una foto.
  • Captura de imagen: Capturar una imagen de alta calidad y guardarla en su dispositivo.
  • Análisis de imagen: Analizar su imagen para realizar el procesamiento de la imagen, la visión por computadora o la inferencia de aprendizaje automático en ella.

Extensiones CameraX

Éste es mi favorito personal. ¡Las extensiones CameraX son pequeños complementos que le permiten implementar funciones como Retrato, HDR, Modo nocturno y Modo belleza con solo 2 líneas de código!

Ahora que hemos cubierto las características que CameraX aporta al desarrollo de la cámara, entremos en una implementación básica de la misma.

Antes de sumergirnos en la codificación, puede encontrar el repositorio de demostración para la aplicación que se presenta en este artículo esta página, en caso de que desee clonarlo y saltar directamente al código.

Agregar CameraX a tu aplicación

Agregue las siguientes dependencias a su nivel de aplicación build.gradle archivo:

def camerax_version = "1.0.0-alpha02"
// CameraX core library
implementation "androidx.camera:camera-core:${camerax_version}"
// CameraX library for compatibility with Camera2
implementation "androidx.camera:camera-camera2:${camerax_version}"

Agreguemos también los siguientes dos permisos en el AndroidManifest.xml archivo:

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

Usando CameraX en su aplicación

Construyendo una IU básica

Vamos a configurar el mismo diseño básico para su MainActivity como a continuación:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <TextureView android:id="@+id/texture_view" android:layout_width="match_parent" android:layout_height="match_parent" … /> <Button android:id="@+id/btn_capture" android:layout_width="wrap_content" android:layout_height="wrap_content" … />
</RelativeLayout>

Solicitud de permisos requeridos

Ahora que hemos creado la interfaz de usuario básica, debe solicitar CAMERA y WRITE_EXTERNAL_STORAGE permisos en tu aplicación. Puedes aprender cómo solicitar permisos en Android esta página, o puede consultar mi repositorio de Github para este proyecto en su lugar.

Configuración TextureView

A continuación, implemente LifecycleOwner y configurar TextureView en tu MainActivity, como sigue:

class MainActivity : AppCompatActivity(), LifecycleOwner { override fun onCreate(...) {
if (areAllPermissionsGranted()) {
texture_view.post { startCamera() }
} else {
ActivityCompat.requestPermissions(
this, PERMISSIONS, CAMERA_REQUEST_PERMISSION_CODE
)
} texture_view.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
updateTransform()
}
} private fun startCamera() { // TODO: We’ll implement all the 3 below use cases here, later in this article.
// Setup the image preview val preview = setupPreview() // Setup the image capture val imageCapture = setupImageCapture() // Setup the image analysis val analyzerUseCase = setupImageAnalysis() // Bind camera to the lifecycle of the Activity CameraX.bindToLifecycle(this, preview, imageCapture, analyzerUseCase)
} private fun updateTransform() { val matrix = Matrix() val centerX = texture_view.width / 2f val centerY = texture_view.height / 2f val rotationDegrees = when (texture_view.display.rotation) { Surface.ROTATION_0 -> 0 Surface.ROTATION_90 -> 90 Surface.ROTATION_180 -> 180 Surface.ROTATION_270 -> 270 else -> return } matrix.postRotate(-rotationDegrees.toFloat(), centerX, centerY) texture_view.setTransform(matrix) } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { if (requestCode == CAMERA_REQUEST_PERMISSION_CODE) { if (areAllPermissionsGranted()) { texture_view.post { startCamera() } } else { Toast.makeText(this, "Permissions not granted!", Toast.LENGTH_SHORT).show() finish() } } } private fun areAllPermissionsGranted() = PERMISSIONS.all { ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED } companion object { private const val CAMERA_REQUEST_PERMISSION_CODE = 13 private val PERMISSIONS = arrayOf(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE) }
}

Implementación de vista previa de imagen

Al igual que con cualquier otra aplicación de cámara, primero necesitamos ver una vista previa de la imagen antes de capturarla en nuestra aplicación de cámara. Para lograr esto, necesitamos crear una instancia de Vista previaConfig vía Vista previaConfig.Builder.

Así que comencemos a escribir nuestro setupPreview() in MainActivity:

private fun setupPreview(): Preview { val previewConfig = PreviewConfig.Builder().apply { // Sets the camera lens to front camera or back camera. setLensFacing(CameraX.LensFacing.BACK) // Sets the aspect ratio for the preview image. setTargetAspectRatio(Rational(1, 1)) // Sets the resolution for the preview image. // NOTE: The below resolution is set to 800x800 only for demo purposes. setTargetResolution(Size(800, 800)) }.build() // Create a Preview object with the PreviewConfig val preview = Preview(previewConfig) // Set a listener for the preview’s output preview.setOnPreviewOutputUpdateListener { // val parent = texture_view.parent as ViewGroup // Update the parent View to show the TextureView parent.removeView(texture_view) parent.addView(texture_view, 0) texture_view.surfaceTexture = it.surfaceTexture updateTransform() } return preview
}

¡Y voilá! Ahora puede ver una vista previa de la imagen en vivo en su dispositivo. Para personalizar la experiencia de la función de vista previa de la imagen, revise los documentos esta página. Aquí está la lista de métodos públicos para PreviewConfig.Builder de los documentos oficiales:
CameraX PreviewConfig Builder.png

Ahora que tenemos nuestra configuración de vista previa de imágenes, trabajemos para capturar y guardar imágenes con nuestra nueva cámara.

Implementación de captura de imagen

Para hacer esto, primero necesita obtener una instancia de Configuración de captura de imagen, Y un ImageConfig objeto.

Escribamos nuestro código para configurar la captura de imágenes en el setupImageCapture() método en MainActivity.

private fun setupImageCapture(): ImageCapture { val imageCaptureConfig = ImageCaptureConfig.Builder() .apply { setTargetAspectRatio(Rational(1, 1)) // Sets the capture mode to prioritise over high quality images // or lower latency capturing setCaptureMode(ImageCapture.CaptureMode.MAX_QUALITY) }.build() val imageCapture = ImageCapture(imageCaptureConfig) // Set a click listener on the capture Button to capture the image btn_capture.setOnClickListener { // Create the image file val file = File( Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "${System.currentTimeMillis()}_CameraXPlayground.jpg" ) // Call the takePicture() method on the ImageCapture object imageCapture.takePicture(file, object : ImageCapture.OnImageSavedListener { // If the image capture failed override fun onError( error: ImageCapture.UseCaseError, message: String, exc: Throwable? ) { val msg = "Photo capture failed: $message" Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() Log.e("CameraXApp", msg) exc?.printStackTrace() } // If the image capture is successful override fun onImageSaved(file: File) { val msg = "Photo capture succeeded: ${file.absolutePath}" Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() Log.d("CameraXApp", msg) } }) } return imageCapture
}

Puede leer más sobre cómo personalizar la experiencia de captura de imágenes esta página. Además, aquí hay una tabla de los métodos públicos para ImageCaptureConfig.Builder clase de los documentos oficiales:
CámaraX ImageCaptureConfig.png

Implementando Análisis de Imagen

Ahora que tenemos los casos de uso básicos, aprendamos cómo analizar imágenes en nuestra aplicación de cámara. En aras de esta demostración, analizaremos la cantidad de píxeles rojos que tiene nuestra vista previa de imagen en vivo.

Para lograr esto, necesita una instancia de ImageAnalysisConfig, que construiremos con el ImageAnalysisConfig.Builder clase.

Primero, necesitaremos escribir nuestro analizador de imágenes, que debería implementar ImageAnalysis.Analyzer:

class RedColorAnalyzer : ImageAnalysis.Analyzer { private var lastAnalyzedTimestamp = 0L // Helper method to convert a ByteBuffer to a ByteArray private fun ByteBuffer.toByteArray(): ByteArray { rewind() val data = ByteArray(remaining()) get(data) return data } override fun analyze(image: ImageProxy, rotationDegrees: Int) { val currentTimestamp = System.currentTimeMillis() if (currentTimestamp > lastAnalyzedTimestamp) { val buffer = image.planes[0].buffer val data = buffer.toByteArray() val pixels = data.map { it.toInt() and 0xFF0000 } val averageRedPixels = pixels.average() Log.d("CameraXPlayground", "Average red pixels: $averageRedPixels") lastAnalyzedTimestamp = currentTimestamp } }
}

Ahora escribamos el setupImageAnalysis() método:

private fun setupImageAnalysis(): ImageAnalysis { val analyzerConfig = ImageAnalysisConfig.Builder().apply {
// Create a HandlerThread for image analysis val analyzerThread = HandlerThread( "RedColorAnalysis" ).apply { start() } setCallbackHandler(Handler(analyzerThread.looper)) // Set the image reader mode to read either the latest image or next image setImageReaderMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE) }.build() // Create an ImageAnalysis object and set the analyzer return ImageAnalysis(analyzerConfig).apply { analyzer = RedColorAnalyzer() }
}

¡Ahora puede ver la cantidad de píxeles rojos en la vista previa de su imagen en vivo en su Android Logcat!

Personalice su experiencia de análisis de imágenes y cree su propio analizador de imágenes consultando los documentos oficiales esta página. A continuación se muestra la tabla de métodos públicos en ImageAnalysisConfig.Builder de los documentos oficiales:
CameraX_ImageAnalysisConfig.png

Vinculando CameraX al ciclo de vida

Hemos cubierto cómo usar los 3 casos de uso en CameraX hasta ahora, pero no olvide vincular CameraX a ActivityEl ciclo de vida. Podemos hacer esto fácilmente llamando al bindToLifecycle() método, así:

// Bind camera to the lifecycle of the Activity
CameraX.bindToLifecycle(this, preview, imageCapture, analyzerUseCase)

Estamos pasando this como el primer parámetro aquí, y nuestro bindToLifecycle() el método toma un LifecycleOwner como el primer parámetro en su llamada. Desde que implementamos LifecycleOwner en nuestro MainActivity, esto es manejado por el Activity para nosotros.

La bindToLifecycle() El método también toma otros 3 parámetros que corresponden a los casos de uso mencionados anteriormente. Estas son las posibles combinaciones de casos de uso que se pueden usar con una llamada a este método, directamente desde documentos oficiales de arquitectura CameraX:
CámaraX_Arquitectura.png

Extensiones CameraX

Tenga en cuenta que las extensiones de CameraX solo son compatibles con algunos dispositivos a partir de ahora. Con suerte, Google pretende extender este soporte a más dispositivos a finales de este año.

Si bien la API aún no está lista con la implementación de CameraX Extensions, puede leer más al respecto esta página.

Conclusión

Puede encontrar el código de la aplicación de demostración en este tutorial esta página. Clone el repositorio y juegue con las personalizaciones que CameraX tiene para ofrecerle.

Si bien la API de CameraX todavía está en etapas alfa, puede ver cómo ya es mejor que la API de Camera2 y la API de cámara heredada. Con eso en mente, tenga cuidado y úselo con cuidado, si decide usarlo en producción de inmediato.

Estoy esperando más noticias de Google sobre más personalizaciones en la API, nuevos dispositivos que admitirán Extensiones CameraX y cómo cambiará la API en los próximos meses. CameraX suena muy prometedor para los desarrolladores, tanto nuevos como experimentados.

¡Aquí está para mejorar el desarrollo de la cámara en Android! 🚀

Fuente: https://www.codementor.io/blog/camerax-7fh6g0yxyg

punto_img

Información más reciente

punto_img