Інформація про дані Платона.
Вертикальний пошук і штучний інтелект.

CameraX: полегшуйте фотографування на Android!

Дата:


Повернення до Google I/O 2019…

Однією з найцікавіших змін, про які Google оголосила цього року на Google I/O 2019, є CameraX. Це API, який принесе низку нових функцій і, нібито, полегшить реалізацію функцій камери на Android.

Але чим це краще нинішнього Camera2 API? CameraX справді може багато чого запропонувати чи це просто трюк?

Давайте розберемося в цій статті. Але спочатку давайте поглянемо на те, як Camera2 API реалізується в додатках сьогодні.

примітки: Camera2 API, яка замінила спадщину API камеридає вам більше контролю над камерою вашого пристрою. Але також майте на увазі, що API Camera2 підтримує Lollipop 5.0 і новіших версій, тому, якщо ви хочете підтримувати пристрої з нижчою версією, вам доведеться дотримуватися застарілого API Camera.

(Застарілий?) Camera2 API

У цій статті ми зосередимося на зйомці фотографій за допомогою спеціального інтерфейсу користувача камери в Android. Ми залишимо відео, оскільки для реалізації відео в Android потрібна окрема стаття.

Давайте розглянемо базову реалізацію для зйомки фотографій за допомогою Camera2 API.

По-перше, нам потрібно налаштувати TextureView і кнопку в нашому макеті, щоб захопити зображення:

<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>

Потім нам потрібно буде налаштувати попередній перегляд камери, ось так:

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()
}

Нам ще потрібно:

  • Отримайте екземпляр CameraManager
  • Отримати CameraCharacteristics та StreamConfigurationMap
  • Налаштування a CameraDevice.StateCallback та CameraCaptureSession.CaptureCallback для попереднього перегляду та захоплення зображення відповідно
  • Налаштуйте TextureView.SurfaceTextureListener для попереднього перегляду на TextureView
  • Відкрий камеру
  • Напишіть код для фотографування
  • Напишіть код для оновлення попереднього перегляду
  • Закривайте камеру, коли вона не використовується

Фу! Це звучить як багато роботи! Я не хотів би витрачати решту цієї статті на те, як реалізувати Camera2 API. Отже, давайте скоротимо це тут і поговоримо про те, чому Camera2 API є справжньою проблемою для впровадження в програмі Android:

  • Це вимагає тонна коду, як ви можете зробити висновок з вище. Це величезний недолік, якщо ви новачок у застосуванні камери в Android.
  • Це просто надто складно. Значну частину коду в API можна було б спростити, але оскільки API було розроблено для забезпечення більшого контролю над камерою, це стало надто складним для розуміння розробникам, які тільки почали використовувати камеру у своїй програмі.
  • Це вимагає від вас реалізації багатьох станів, і вам потрібно буде виконати купу конкретних методів для обробки, коли ці стани змінюються. Ви можете поглянути на виснажливий список держав тут.
  • Це також вводить деякі помилки в частині ліхтарика вашої камери. Існує багато плутанини щодо відмінностей між режимом «факел» і режимом «спалах» у Camera2.
  • Якщо цього ще недостатньо, щоб спонукати нових розробників до Camera2, існує багато помилок, пов’язаних із постачальниками, які потребують виправлення та, отже, додаткового коду. Вони також відомі як проблеми сумісності пристроїв, які вимагають від розробників написання коду для конкретного пристрою, щоб керувати вирішенням проблеми.

Хоча застарілий Camera API простіший у використанні та підтримує пристрої з меншою ОС Android, він не забезпечує повного контролю над камерою. У нього також є власні помилки та проблеми, пов’язані з постачальниками. Він має багато тих самих непередбачуваних проблем, що й Camera2 API, а отже, його не варто використовувати у вашій камері.

Зважаючи на ці перешкоди, Google представив новий CameraX API для вирішення цих проблем.

Привіт CameraX API! 👋

Як згадувалося вище, CameraX значно полегшує створення спеціальної камери. Він також приносить із собою цілий новий список функцій, які легко реалізувати.

примітки: CameraX API все ще перебуває на стадії альфа-версії, тому цей API може бути змінений у найближчі місяці. Використовуйте його з обережністю. ⚠️

Ось деякі з найпопулярніших функцій CameraX:

Простота

Бібліотека CameraX надає простий і легкий у використанні API. Цей API є узгодженим для більшості пристроїв Android із Lollipop 5.0 і вище.

Вирішує проблеми сумісності пристроїв

CameraX також має на меті вирішити проблеми, які з’являються на певних пристроях. З Camera2 і застарілою версією Camera розробникам часто доводилося писати багато коду для конкретного пристрою. Тепер вони можуть попрощатися з написанням цього коду завдяки CameraX!

Google досягла цього шляхом тестування CameraX в автоматизованій тестовій лабораторії на багатьох різних пристроях багатьох виробників.

Використовує всі функції Camera2 API

Незважаючи на те, що CameraX вирішує проблеми, які виникли у Camera2, що цікаво, він також використовує всі функції Camera2 API. Лише через цей факт мені важко придумати причину не переходити на CameraX найближчими місяцями.

Менше коду

Це перевага для розробників, які хочуть почати роботу з CameraX API. Ви пишете менше коду порівняно з Camera2 і застарілим Camera API, щоб досягти того самого рівня контролю над камерою.

Варіанти використання в CameraX

Користуватися CameraX — це задоволення, оскільки його використання можна розбити на 3 варіанти:

  • Попередній перегляд зображення: попередній перегляд зображення на пристрої перед фотографуванням.
  • захоплення зображення: створення високоякісного зображення та збереження його на пристрої.
  • Аналіз зображень: Аналіз вашого зображення для обробки зображення, комп’ютерного зору або машинного навчання.

Розширення CameraX

Це мій особистий фаворит. Розширення CameraX — це невеликі доповнення, які дозволяють реалізувати такі функції, як портрет, HDR, нічний режим і режим краси, лише за допомогою 2 рядків коду!

Тепер, коли ми розглянули функції, які CameraX привносить у розробку камер, давайте перейдемо до їх базової реалізації.

Перш ніж ми зануримося в кодування, ви можете знайти демо-репозиторій програми, яка представлена ​​в цій статті тут, якщо ви хочете його клонувати, і відразу перейдіть до коду.

Додавання CameraX до вашої програми

Додайте наведені нижче залежності до рівня програми build.gradle Файл:

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}"

Давайте також додамо наступні два дозволи в AndroidManifest.xml Файл:

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

Використання CameraX у вашій програмі

Створення базового інтерфейсу користувача

Давайте налаштуємо той самий базовий макет для вашого MainActivity як показано нижче:

<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>

Запит на необхідні дозволи

Тепер, коли ми створили базовий інтерфейс користувача, ви повинні подати запит CAMERA та WRITE_EXTERNAL_STORAGE дозволи у вашій програмі. Ви можете дізнатися, як запитувати дозволи в Android тут, або ви можете натомість звернутися до мого репозиторію Github для цього проекту.

Налаштовуючи TextureView

Далі реалізуйте LifecycleOwner і налаштувати TextureView у вашому MainActivity, наступним чином:

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) }
}

Реалізація попереднього перегляду зображення

Як і в будь-якій іншій програмі камери, нам спочатку потрібно переглянути зображення перед зйомкою в нашій програмі камери. Щоб досягти цього, нам потрібно створити екземпляр PreviewConfig через PreviewConfig.Builder.

Отже, почнемо з написання нашого 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
}

І вуаля! Тепер ви можете переглядати зображення в реальному часі на своєму пристрої. Щоб налаштувати функцію попереднього перегляду зображень, перегляньте документи тут. Ось список публічних методів для PreviewConfig.Builder з офіційних документів:
CameraX PreviewConfig Builder.png

Тепер, коли ми налаштували попередній перегляд зображень, давайте попрацюємо над захопленням і збереженням зображень за допомогою нашої нової камери.

Реалізація захоплення зображень

Для цього спочатку потрібно отримати екземпляр ImageCaptureConfig, І ImageConfig об'єкт

Давайте напишемо наш код для налаштування захоплення зображення в setupImageCapture() метод в 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
}

Ви можете прочитати більше про те, як налаштувати процес захоплення зображення тут. Крім того, ось таблиця публічних методів для ImageCaptureConfig.Builder клас з офіційних документів:
CameraX ImageCaptureConfig.png

Впровадження аналізу зображень

Тепер, коли ми розібралися з основними сценаріями використання, давайте навчимося аналізувати зображення в нашій програмі камери. Для цієї демонстрації ми проаналізуємо кількість червоних пікселів у попередньому перегляді живого зображення.

Щоб досягти цього, вам потрібен екземпляр ImageAnalysisConfig, який ми створимо за допомогою ImageAnalysisConfig.Builder клас.

По-перше, нам потрібно буде написати наш аналізатор зображень, який слід реалізувати 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 } }
}

Тепер напишемо setupImageAnalysis() метод:

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() }
}

Тепер ви можете бачити кількість червоних пікселів у попередньому перегляді зображення в реальному часі у своєму Android Logcat!

Налаштуйте свій досвід аналізу зображень і створіть власний аналізатор зображень, переглянувши офіційну документацію тут. Нижче наведено таблицю загальнодоступних методів у ImageAnalysisConfig.Builder з офіційних документів:
CameraX_ImageAnalysisConfig.png

Прив’язка CameraX до життєвого циклу

Ми розглянули, як використовувати всі 3 випадки використання в CameraX, але не забудьте прив’язати CameraX до Activityжиттєвий цикл. Ми можемо зробити це легко, зателефонувавши bindToLifecycle() метод так:

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

Ми проходимо this як перший параметр тут, і наш bindToLifecycle() метод бере а LifecycleOwner як перший параметр у його виклику. Оскільки ми реалізували LifecycleOwner в нашому MainActivity, цим займається Activity для нас.

Команда bindToLifecycle() Метод також приймає 3 інші параметри, які відповідають вищезгаданим випадкам використання. Ось можливі комбінації варіантів використання, які можна використовувати з викликом цього методу безпосередньо з офіційні документи щодо архітектури CameraX:
CameraX_Architecture.png

Розширення CameraX

Майте на увазі, що розширення CameraX підтримуються лише на деякі пристрої В даний час. Сподіваємося, Google планує розширити цю підтримку на більше пристроїв пізніше цього року.

Хоча API ще не готовий для реалізації розширень CameraX, ви можете прочитати про нього більше тут.

Висновок

Ви можете знайти код демонстраційної програми в цьому посібнику тут. Клонуйте репозиторій і пограйте з налаштуваннями, які може запропонувати CameraX!

Хоча API CameraX все ще перебуває на стадії альфа-версії, ви можете побачити, що він уже кращий за API Camera2 і застарілий API Camera. Зважаючи на це, будьте обережні та обережно використовуйте його, якщо ви вирішите використовувати його у виробництві відразу.

Я чекаю нових новин від Google про додаткові налаштування в API, нові пристрої, які підтримуватимуть розширення CameraX, і про те, як API зміниться протягом наступних місяців. CameraX дійсно звучить багатообіцяюче для розробників, як новачків, так і досвідчених.

Ось краща розробка камери в Android! 🚀

Джерело: https://www.codementor.io/blog/camerax-7fh6g0yxyg

spot_img

Остання розвідка

spot_img

Зв'яжіться з нами!

Привіт! Чим я можу вам допомогти?