Самый быстрый способ создания сканера QR-кодов для Android
Узнайте о самом быстром способе создания сканера QR-кодов для Android. Пошаговый предварительный просмотр камеры реализации, а также как интегрировать SDK для сканирования QR-кодов.
Приложение для сканирования QR-кодов в основном состоит из двух частей: предварительного просмотра с помощью камеры и сканирования QR-кода. Существует множество приложений для сканирования QR-кодов для Android, которые можно скачать в Google Play Store. Однако интереснее создать сканер QR-кода самостоятельно, чем использовать уже существующий. Цель этой статьи — рассказать о самом быстром способе создания сканера QR-кодов для Android. Вы увидите, как шаг за шагом реализовать предварительный просмотр камеры, а также как интегрировать SDK для сканирования QR-кодов.
Предварительные условия
Для реализации сканера QR-кодов необходимы следующие библиотеки Android. Вы можете свободно заменить их своими собственными библиотеками.
- Camera Preview SDK
- CameraX
Поскольку Android Camera2 API чрезвычайно сложен для новичков, Google выпустил CameraX, чтобы упростить разработку приложений для камер. Учебник codelab является хорошей отправной точкой для изучения CameraX.
Установка
В AndroidManifest.xml
, добавить разрешение на камеру:
<uses-feature android:name="android.hardware.camera.any" /> <uses-permission android:name="android.permission.CAMERA" />
В app/build.gradle
, добавьте зависимость:
dependencies { ... def camerax_version = "1.0.1" implementation "androidx.camera:camera-camera2:$camerax_version" implementation "androidx.camera:camera-lifecycle:$camerax_version" implementation "androidx.camera:camera-view:1.0.0-alpha27" }
Dynamsoft Camera Enhancer
Similar to CameraX, [Dynamsoft Camera Enhancer](https://www.dynamsoft.com/camera-enhancer/docs/introduction/) is also a wrapper for Android Camera2 API. In addition to basic camera capabilities, it features frame filtering for better image quality. We use Dynamsoft Camera Enhancer to make a comparison with CameraX.
Dynamsoft Camera Enhancer
Подобно CameraX, [Dynamsoft Camera Enhancer] также является оберткой для Android Camera2 API. В дополнение к базовым возможностям камеры, в ней предусмотрена фильтрация кадров для улучшения качества изображения. Мы используем Dynamsoft Camera Enhancer для сравнения с CameraX.
Установка
В «settings.gradle» добавьте пользовательский репозиторий Maven:
dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { ... maven{url "https://download.dynamsoft.com/maven/dce/aar"} } }
В app/build.gradle
, добавьте зависимость:
dependencies { ... implementation 'com.dynamsoft:dynamsoftcameraenhancer:2.1.0@aar' }
SDK для сканирования QR-кодов
Dynamsoft Barcode Reader:
SDK для считывания штрихкодов, поддерживающий все основные форматы линейных и двумерных штрихкодов.
Установка
В settings.gradle
, добавить пользовательский репозиторий Maven:
dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { ... maven{url "https://download.dynamsoft.com/maven/dbr/aar"} } }
В app/build.gradle
, добавьте зависимость:
dependencies { ... implementation 'com.dynamsoft:dynamsoftbarcodereader:8.9.0@aar' }
Вам также необходим лицензионный ключ для активации SDK для штрихкодов.
Создание предварительного просмотра камеры Android за 5 минут
Три шага для реализации предварительного просмотра камеры с помощью CameraX
Официальное руководство по CameraX написано на языке Kotlin. Здесь мы используем Java.
- Создайте макет пользовательского интерфейса, который содержит представление предварительного просмотра CameraX:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".CameraXActivity"> <androidx.camera.view.PreviewView android:id="@+id/camerax_viewFinder" android:layout_width="match_parent" android:layout_height="match_parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
2. Проверьте и запросите разрешения на съемку:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.camerax_main); previewView = findViewById(R.id.camerax_viewFinder); if (!CameraUtils.allPermissionsGranted(this)) { CameraUtils.getRuntimePermissions(this); } else { startCamera(); } } private static String[] getRequiredPermissions(Context context) { try { PackageInfo info = context.getPackageManager() .getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS); String[] ps = info.requestedPermissions; if (ps != null && ps.length > 0) { return ps; } else { return new String[0]; } } catch (Exception e) { return new String[0]; } } public static boolean allPermissionsGranted(Context context) { for (String permission : getRequiredPermissions(context)) { if (!isPermissionGranted(context, permission)) { return false; } } return true; } public static void getRuntimePermissions(Activity activity) { List<String> allNeededPermissions = new ArrayList<>(); for (String permission : getRequiredPermissions(activity)) { if (!isPermissionGranted(activity, permission)) { allNeededPermissions.add(permission); } } if (!allNeededPermissions.isEmpty()) { ActivityCompat.requestPermissions( activity, allNeededPermissions.toArray(new String[0]), PERMISSION_REQUESTS); } } private static boolean isPermissionGranted(Context context, String permission) { if (ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED) { Log.i(TAG, "Permission granted: " + permission); return true; } Log.i(TAG, "Permission NOT granted: " + permission); return false; }
3. Запустите предварительный просмотр камеры:
private void startCamera() { ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(getApplication()); cameraProviderFuture.addListener( () -> { try { ProcessCameraProvider cameraProvider = cameraProviderFuture.get(); Preview.Builder builder = new Preview.Builder(); Preview previewUseCase = builder.build(); previewUseCase.setSurfaceProvider(previewView.getSurfaceProvider()); CameraSelector cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA; cameraProvider.unbindAll(); cameraProvider.bindToLifecycle(this, cameraSelector, previewUseCase); } catch (ExecutionException | InterruptedException e) { Log.e(TAG, "Unhandled exception", e); } }, ContextCompat.getMainExecutor(getApplication())); }
Второй шаг для реализации предварительного просмотра камеры с помощью Dynamsoft Camera Enhancer
Используя Dynamsoft Camera Enhancer, вы напишите меньше кода, чем при использовании CameraX для реализации той же функциональности.
- Создайте макет пользовательского интерфейса, который содержит представление предварительного просмотра DCE:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".CameraXActivity"> <com.dynamsoft.dce.DCECameraView android:id="@+id/dce_viewFinder" android:layout_width="match_parent" android:layout_height="match_parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
2. Запустите предварительный просмотр камеры:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.dce_main); previewView = findViewById(R.id.dce_viewFinder); cameraEnhancer = new CameraEnhancer(this); cameraEnhancer.setCameraView(previewView); cameraEnhancer.addListener(this); } @Override protected void onResume() { super.onResume(); try { cameraEnhancer.open(); } catch (CameraEnhancerException e) { e.printStackTrace(); } } @Override protected void onPause() { super.onPause(); try { cameraEnhancer.close(); } catch (CameraEnhancerException e) { e.printStackTrace(); } }
Все в одном
Мы создаем входную активность для запуска CameraX и Dynamsoft Camera Enhancer соответственно:
package com.example.qrcodescanner; import android.content.Intent; import android.os.Bundle; import android.view.View; import androidx.appcompat.app.AppCompatActivity; public class EntryChoiceActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.entry_choice); findViewById(R.id.camerax_entry_point).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(EntryChoiceActivity.this, CameraXActivity.class); startActivity(intent); } }); findViewById(R.id.dce_entry_point).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(EntryChoiceActivity.this, DceActivity.class); startActivity(intent); } }); } }
Превращение камеры Android в сканер QR-кодов
Для сканирования QR-кодов нам необходимо постоянно получать кадры предварительного просмотра камеры и передавать их в детектор QR-кодов.
Как установить обратный вызов кадра камеры
При использовании CameraX мы можем использовать класс «ImageAnalysis» для получения кадров камеры:
ImageAnalysis analysisUseCase = new ImageAnalysis.Builder().build(); analysisUseCase.setAnalyzer(cameraExecutor, imageProxy -> { // image processing // Must call close to keep receiving frames. imageProxy.close(); }); cameraProvider.bindToLifecycle(this, cameraSelector, previewUseCase, analysisUseCase);
В отличие от него, Dynamsoft Camera Enhancer намного проще. Функция обратного вызова похожа на ту, что используется в Android Camera1:
public class DceActivity extends AppCompatActivity implements DCEFrameListener { @Override public void frameOutputCallback(DCEFrame dceFrame, long l) { // image processing } }
Типы данных, возвращаемые их функциями обратного вызова, различны. Для дальнейшего использования требуется преобразование типов данных.
Декодирование QR-кода
В CameraX мы сначала преобразуем «ByteBuffer» в «byte[]», а затем вызываем метод «decodeBuffer()»:
analysisUseCase.setAnalyzer(cameraExecutor, imageProxy -> { TextResult[] results = null; ByteBuffer buffer = imageProxy.getPlanes()[0].getBuffer(); int nRowStride = imageProxy.getPlanes()[0].getRowStride(); int nPixelStride = imageProxy.getPlanes()[0].getPixelStride(); int length = buffer.remaining(); byte[] bytes = new byte[length]; buffer.get(bytes); try { results = reader.decodeBuffer(bytes, imageProxy.getWidth(), imageProxy.getHeight(), nRowStride * nPixelStride, EnumImagePixelFormat.IPF_NV21, ""); } catch (BarcodeReaderException e) { e.printStackTrace(); } // Must call close to keep receiving frames. imageProxy.close(); });
В то время как в Dynamsoft Camera Enhancer мы получаем Bitmap
изDCEFrame
и затем вызываем метод decodeBufferedImage()
public void frameOutputCallback(DCEFrame dceFrame, long l) { TextResult[] results = null; try { results = reader.decodeBufferedImage(dceFrame.toBitmap(), ""); } catch (BarcodeReaderException e) { e.printStackTrace(); } }
Использование зума и фонарика для повышения качества кадра
На точность распознавания всегда влияет качество входного изображения. Если QR-код слишком мал, мы можем увеличить масштаб камеры, чтобы увеличить изображение. Если входное изображение слишком темное, мы можем включить фонарик, чтобы осветлить изображение. И CameraX, и Dynamsoft Camera Enhancer полностью поддерживают управление камерой.
Зум камеры Android
Для запуска зума мы используем жест «щипок пальцем». Таким образом, первым шагом будет создание детектора жестов и передача ему метода «onTouchEvent()»:
public class ZoomController { public final static String TAG = "ZoomController"; private float currentFactor = 1.0f; private float minZoomRatio = 1.0f, maxZoomRatio = 1.0f; private ZoomStatus zoomStatus; private ScaleGestureDetector scaleGestureDetector; private ScaleGestureDetector.OnScaleGestureListener scaleGestureListener = new ScaleGestureDetector.OnScaleGestureListener() { @Override public boolean onScale(ScaleGestureDetector detector) { Log.i(TAG, "onScale: " + detector.getScaleFactor()); currentFactor = detector.getScaleFactor() * currentFactor; if (currentFactor < minZoomRatio) currentFactor = minZoomRatio; if (currentFactor > maxZoomRatio) currentFactor = maxZoomRatio; if (zoomStatus != null) { zoomStatus.onZoomChange(currentFactor); } return true; } @Override public boolean onScaleBegin(ScaleGestureDetector detector) { return true; } @Override public void onScaleEnd(ScaleGestureDetector detector) { } }; public ZoomController(Activity activity) { scaleGestureDetector = new ScaleGestureDetector(activity, scaleGestureListener); } public interface ZoomStatus { void onZoomChange(float ratio); } public void addListener(ZoomStatus zoomStatus) { this.zoomStatus = zoomStatus; } public void initZoomRatio(float minZoomRatio, float maxZoomRatio) { this.minZoomRatio = minZoomRatio; this.maxZoomRatio = maxZoomRatio; } public boolean onTouchEvent(MotionEvent event) { return scaleGestureDetector.onTouchEvent(event); } } @Override public boolean onTouchEvent(MotionEvent event) { zoomController.onTouchEvent(event); return super.onTouchEvent(event); }
При обнаружении жеста мы получаем масштабный коэффициент и используем его в качестве коэффициента масштабирования.
Установка коэффициента масштабирования камеры с помощью CameraX
if (camera != null) { camera.getCameraControl().setZoomRatio(ratio); }
Настройка коэффициента масштабирования камеры с помощью Dynamsoft Camera Enhancer
try { cameraEnhancer.setZoom(ratio); } catch (CameraEnhancerException e) { e.printStackTrace(); }
Фонарик для камеры Android
Для автоматического включения фонарика мы отслеживаем значение освещенности, возвращаемое датчиком освещенности.
public class AutoTorchController implements SensorEventListener { public final static String TAG = "AutoTorchController"; private SensorManager sensorManager; private TorchStatus torchStatus; public interface TorchStatus { void onTorchChange(boolean status); } public AutoTorchController(Activity activity) { sensorManager = (SensorManager)activity.getSystemService(SENSOR_SERVICE); } public void onStart() { Sensor lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); if(lightSensor != null){ sensorManager.registerListener( this, lightSensor, SensorManager.SENSOR_DELAY_NORMAL); } } public void onStop() { sensorManager.unregisterListener(this); } @Override public void onSensorChanged(SensorEvent event) { if(event.sensor.getType() == Sensor.TYPE_LIGHT){ if (event.values[0] < 20) { if (torchStatus != null) torchStatus.onTorchChange(true); } else { if (torchStatus != null) torchStatus.onTorchChange(false); } } } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } public void addListener(TorchStatus torchStatus) { this.torchStatus = torchStatus; } }
Переключить фонарь камеры с помощью CameraX
if (camera != null) camera.getCameraControl().enableTorch(status);
Переключение вспышки камеры с помощью Dynamsoft Camera Enhancer
if (status) { try { cameraEnhancer.turnOnTorch(); } catch (CameraEnhancerException e) { e.printStackTrace(); } } else { try { cameraEnhancer.turnOffTorch(); } catch (CameraEnhancerException e) { e.printStackTrace(); } }