Пишем сканера QR-кодов под Android

5405
Пишем сканера QR-кодов под Android
Пишем сканера QR-кодов под Android

Самый быстрый способ создания сканера 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.

  1. Создайте макет пользовательского интерфейса, который содержит представление предварительного просмотра 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 для реализации той же функциональности.

  1. Создайте макет пользовательского интерфейса, который содержит представление предварительного просмотра 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();

    }

}

Исходный код