关于Android开发摄像头识别二维码/条形码的那些事

  • 随着代码更新迭代,发展迅速的前提下,作为一个java后端很苦恼的就是接触一些自己不擅长的领域,,现如今有公司都封装了业务类,所谓万物皆可以调用,那么今天就来说一说如何编写摄像头识别二维码/条形码业务类的实例代码;(博主在Android也是新手,只能总结经验,希望可以帮助到大家)

首先,所谓的业务类就是自己先编写这块业务的主要代码,然后需要这块业务时直接调用即可,这样可以调高代码的复用率与减少工作量,那么接下来让我们先了解了解安卓实现摄像头识别二维码/条形码的几种实现方式

  • 第一种(也是博主使用的一种):使用Google ML Kit(Google ML Kit是一种强大的机器学习库,支持二维码和条形码扫描。它简单易用,支持多种格式。)
  • 第二种:使用ZXing库(ZXing是一个开源的条形码和二维码图像处理库,广泛用于Android应用中。)博主第一次实例尝试用到,但是发现不太好用,因为还要自己下载ZXing
  • 第三种:使用Google Vision API(Google Vision API是另一种处理图像的强大工具,但它的使用相比ML Kit更复杂。)
  • 第四种:使用OpenCV(OpenCV是一种计算机视觉库,可以处理图像和视频数据。)
  • 第五种:使用Firebase ML Kit(Firebase ML Kit与Google ML Kit类似,但集成了Firebase的更多功能。)

总结

以上几种方式都可以用于在Android应用中实现摄像头识别二维码。选择哪种方式主要取决于具体需求、技术栈和开发者的熟悉程度。Google ML Kit和ZXing是最常用和最推荐的方法,因为它们易于集成且功能强大。

下面可以写业务类了,但是写之前先配置相关的配置文件

其中比较重要的一点是:

在AndroidManifest.xml中一定配置摄像机权限
    <uses-permission android:name="android.permission.CAMERA" />
并在应用中声明相机使用权限
<application
    ...
    <uses-feature android:name="android.hardware.camera.any" android:required="true" />
</application>

首先,在build.gradle文件中添加相关的依赖项:

implementation 'androidx.camera:camera-core:1.1.0'
implementation 'androidx.camera:camera-camera2:1.1.0'
implementation 'androidx.camera:camera-lifecycle:1.1.0'
implementation 'androidx.camera:camera-view:1.0.0-alpha29'
implementation 'com.google.mlkit:barcode-scanning:17.0.2'
implementation 'com.google.mlkit:text-recognition-chinese:16.0.0'

内部业务和代码原理实现:

1. 摄像头预览和分析配置
  • PreviewView:用于显示摄像头预览。
  • ProcessCameraProvider:管理摄像头的生命周期。
  • CameraSelector:选择前置或后置摄像头。
  • ImageAnalysis:用于图像分析和处理。
public void startCamera() {
    ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(context);
    cameraProviderFuture.addListener(() -> {
        try {
            cameraProvider = cameraProviderFuture.get();
            bindPreview();
        } catch (ExecutionException | InterruptedException e) {
            Log.e(TAG, "摄像头初始化失败。", e);
            Toast.makeText(context, "摄像头初始化失败。", Toast.LENGTH_SHORT).show();
        }
    }, ContextCompat.getMainExecutor(context));
}

private void bindPreview() {
    Preview preview = new Preview.Builder().build();
    CameraSelector cameraSelector = new CameraSelector.Builder()
            .requireLensFacing(CameraSelector.LENS_FACING_BACK)
            .build();
    preview.setSurfaceProvider(previewView.getSurfaceProvider());

    ImageAnalysis imageAnalysis = new ImageAnalysis.Builder()
            .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
            .build();

    imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(context), new ImageAnalysis.Analyzer() {
        @Override
        public void analyze(@NonNull ImageProxy imageProxy) {
            if (!isPaused) {
                analyzeImage(imageProxy);
            } else {
                imageProxy.close();
            }
        }
    });

    camera = cameraProvider.bindToLifecycle((LifecycleOwner) context, cameraSelector, preview, imageAnalysis);
}
  • public void startCamera() { - 定义一个名为 startCamera 的公共方法。
  • ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(context); - 获取 ProcessCameraProvider 的实例,它是用来控制摄像头的类。
  • cameraProviderFuture.addListener(() -> { - 为 cameraProviderFuture 添加一个监听器,当获取 ProcessCameraProvider 实例完成时调用。
  • try { cameraProvider = cameraProviderFuture.get(); bindPreview(); } - 在尝试块中获取 ProcessCameraProvider 实例并调用 bindPreview 方法。
  • catch (ExecutionException | InterruptedException e) { - 捕捉执行和中断异常。
  • Log.e(TAG, "摄像头初始化失败。", e); - 如果发生异常,记录错误日志。
  • Toast.makeText(context, "摄像头初始化失败。", Toast.LENGTH_SHORT).show(); - 显示初始化失败的吐司消息。
  • }, ContextCompat.getMainExecutor(context)); - 指定监听器在主线程上执行。

  • private void bindPreview() { - 定义一个名为 bindPreview 的私有方法。
  • Preview preview = new Preview.Builder().build(); - 创建一个新的 Preview 实例,它用来管理摄像头预览。
  • CameraSelector cameraSelector = new CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build(); - 创建一个 CameraSelector 实例,选择后置摄像头。
  • preview.setSurfaceProvider(previewView.getSurfaceProvider()); - 设置预览视图的表面提供者。
  • ImageAnalysis imageAnalysis = new ImageAnalysis.Builder().setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST).build(); - 创建一个 ImageAnalysis 实例,用于处理摄像头的图像数据。
  • imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(context), new ImageAnalysis.Analyzer() { ... }); - 设置图像分析器,使用主线程执行分析任务。
  • if (!isPaused) { analyzeImage(imageProxy); } else { imageProxy.close(); } - 如果扫描没有暂停,则调用 analyzeImage 方法分析图像,否则关闭 imageProxy
  • cameraProvider.bindToLifecycle((LifecycleOwner) context, cameraSelector, preview, imageAnalysis); - 将摄像头绑定到生命周期所有者,开始预览和图像分析。
2. 二维码识别
  • ML Kit:用于文本识别和条形码扫描
@ExperimentalGetImage
private void analyzeImage(ImageProxy imageProxy) {
  
     // 调用二维码/条形码识别
     BarcodeScanning.getClient()
        .process(image)
        .addOnSuccessListener(barcodes -> {
            if (!barcodes.isEmpty()) {
                for (Barcode barcode : barcodes) {
                    Log.d(TAG, "检测到条码: " + barcode.getDisplayValue());
                    Toast.makeText(context, "扫描结果: " +             
                    barcode.getDisplayValue(), Toast.LENGTH_SHORT).show();
                    pauseScanning();
                  }
                  } else {
                          Log.d(TAG, "未检测到条码");
                         }
                   })
                    .addOnFailureListener(e -> {
                      Log.e(TAG, "条码扫描失败。错误信息:" + e.getMessage(), e);
                     })
                        .addOnCompleteListener(task -> {
                            if (pendingTasks.decrementAndGet() == 0) {
                                imageProxy.close();
                            }
                            Log.d(TAG, "条码扫描完成");
                        });

}

  • @ExperimentalGetImage - 注解,表示使用实验性的 getImage 方法。
  • private void analyzeImage(ImageProxy imageProxy) { - 定义一个名为 analyzeImage 的私有方法。
  • Image mediaImage = imageProxy.getImage(); - 获取 ImageProxy 中的 Image 实例。
  • if (mediaImage != null) { - 检查 mediaImage 是否为空。
  • InputImage image = InputImage.fromMediaImage(mediaImage, imageProxy.getImageInfo().getRotationDegrees()); - 从 mediaImage 创建一个 InputImage 实例,并根据图像信息设置旋转角度。
  • BarcodeScanner scanner = BarcodeScanning.getClient(); - 获取条码扫描客户端。
  • scanner.process(image) - 处理图像进行条码扫描。
  • .addOnSuccessListener(barcodes -> { ... }) - 成功时的回调,处理检测到的条码。
  • for (Barcode barcode : barcodes) { Log.d(TAG, "检测到条码: " + barcode.getDisplayValue()); Toast.makeText(context, "扫描结果: " + barcode.getDisplayValue(), Toast.LENGTH_SHORT).show(); } - 遍历并显示检测到的条码。
  • imageProxy.close(); - 关闭 imageProxy 以释放资源。
  • .addOnFailureListener(e -> { Log.e(TAG, "条码扫描失败。错误信息:" + e.getMessage(), e); imageProxy.close(); }); - 失败时的回调,记录错误并关闭 imageProxy
  • } else { imageProxy.close(); } - 如果 mediaImage 为空,关闭 imageProxy

业务底层逻辑

  1. 摄像头初始化:通过ProcessCameraProvider实例化摄像头并绑定生命周期。
  2. 预览配置:通过PreviewPreviewView设置摄像头预览。
  3. 文本识别和二维码扫描:使用ML Kit的文本识别和条形码扫描器对图像进行处理,提取所需的信息。

下面是Activity中调用了,其中这个类继承了AppCompatActivity

类定义与成员变量

 private static final int CAMERA_PERMISSION_REQUEST_CODE = 1001;
 private QRCodeScanner qrCodeScanner;
  • private static final int CAMERA_PERMISSION_REQUEST_CODE = 1001; - 定义一个静态常量,用于标识摄像头权限请求的请求码。
  • private QRCodeScanner qrCodeScanner; - 定义一个 QRCodeScanner 类型的私有成员变量,用于管理二维码扫描功能。

onCreate 方法

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_qrcodescanner);

    PreviewView previewView = findViewById(R.id.previewView);
    qrCodeScanner = new QRCodeScanner(this, previewView);

    if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
        qrCodeScanner.startCamera();
    } else {
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, CAMERA_PERMISSION_REQUEST_CODE);
    }
}
  • @Override - 标记方法重写。
  • protected void onCreate(Bundle savedInstanceState) { - 定义 onCreate 方法,当活动创建时调用。
  • super.onCreate(savedInstanceState); - 调用父类的 onCreate 方法。
  • setContentView(R.layout.activity_qrcodescanner); - 设置活动的布局文件为 activity_qrcodescanner
  • PreviewView previewView = findViewById(R.id.previewView); - 获取布局中的 PreviewView 视图,用于显示摄像头预览。
  • qrCodeScanner = new QRCodeScanner(this, previewView); - 创建 QRCodeScanner 对象,传入上下文和 PreviewView
  • if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { - 检查是否已授予摄像头权限。
  • qrCodeScanner.startCamera(); - 如果已授予权限,则启动摄像头。
  • } else { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, CAMERA_PERMISSION_REQUEST_CODE); } - 否则请求摄像头权限。

onRequestPermissionsResult 方法 

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            if (qrCodeScanner != null) {
                qrCodeScanner.startCamera();
            } else {
                Toast.makeText(this, "初始化失败,请重试", Toast.LENGTH_SHORT).show();
            }
        } else {
            Toast.makeText(this, "需要摄像头权限", Toast.LENGTH_SHORT).show();
        }
    }
}
  • @Override - 标记方法重写。
  • public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - 定义 onRequestPermissionsResult 方法,当权限请求结果返回时调用。
  • super.onRequestPermissionsResult(requestCode, permissions, grantResults); - 调用父类的 onRequestPermissionsResult 方法。
  • if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) { - 检查请求码是否与摄像头权限请求码匹配。
  • if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - 检查权限请求结果是否为授予权限。
  • if (qrCodeScanner != null) { qrCodeScanner.startCamera(); } - 如果 qrCodeScanner 不为空,则启动摄像头。
  • else { Toast.makeText(this, "初始化失败,请重试", Toast.LENGTH_SHORT).show(); } - 如果 qrCodeScanner 为空,显示初始化失败的吐司消息。
  • } else { Toast.makeText(this, "需要摄像头权限", Toast.LENGTH_SHORT).show(); } - 如果未授予权限,显示需要摄像头权限的吐司消息。

总结

  • Activity 是一个活动类,负责管理摄像头权限请求和启动摄像头预览。
  • onCreate 方法中初始化了 QRCodeScanner 对象,并检查和请求摄像头权限。
  • onRequestPermissionsResult 方法处理权限请求的结果,并根据结果启动摄像头或显示相应的消息。

最后前段页面

在avtivity-mian.xml中写上前端代码,就ok了

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/black"
    android:orientation="vertical"
    tools:context=".Activity">

    <androidx.camera.view.PreviewView
        android:id="@+id/previewView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:accessibilityTraversalAfter="@id/previewView"
        android:accessibilityTraversalBefore="@id/previewView" />
</RelativeLayout>

最后总结其他的方法实现:

1. 使用ZXing库

步骤:

  1. 添加依赖:
implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
implementation 'com.google.zxing:core:3.4.1'
  1. 初始化和使用:
IntentIntegrator integrator = new IntentIntegrator(this);
integrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE);
integrator.setPrompt("扫描二维码");
integrator.setCameraId(0); // Use a specific camera of the device
integrator.setBeepEnabled(true);
integrator.setBarcodeImageEnabled(true);
integrator.initiateScan();
  1. 接收结果:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
    if (result != null) {
        if (result.getContents() == null) {
            Log.d("QRCodeScanner", "Cancelled scan");
            Toast.makeText(this, "Cancelled", Toast.LENGTH_LONG).show();
        } else {
            Log.d("QRCodeScanner", "Scanned");
            Toast.makeText(this, "Scanned: " + result.getContents(), Toast.LENGTH_LONG).show();
        }
    } else {
        super.onActivityResult(requestCode, resultCode, data);
    }
}

2. 使用Google Vision API

步骤:

  1. 添加依赖:
implementation 'com.google.android.gms:play-services-vision:20.1.3'
  1. 初始化和使用:
BarcodeDetector barcodeDetector = new BarcodeDetector.Builder(context)
        .setBarcodeFormats(Barcode.ALL_FORMATS)
        .build();

CameraSource cameraSource = new CameraSource.Builder(context, barcodeDetector)
        .setFacing(CameraSource.CAMERA_FACING_BACK)
        .setRequestedPreviewSize(1280, 1024)
        .setRequestedFps(2.0f)
        .setAutoFocusEnabled(true)
        .build();
  1. 处理结果:
barcodeDetector.setProcessor(new Detector.Processor<Barcode>() {
    @Override
    public void release() {}

    @Override
    public void receiveDetections(Detector.Detections<Barcode> detections) {
        final SparseArray<Barcode> barcodes = detections.getDetectedItems();
        if (barcodes.size() != 0) {
            // Process the barcode
            Log.d(TAG, "检测到条码: " + barcodes.valueAt(0).displayValue);
        }
    }
});

3. 使用OpenCV

步骤:

  1. 添加依赖:
implementation project(':opencv')
  1. 使用OpenCV处理图像数据:
public void processFrame(Mat frame) {
    // Convert frame to grayscale
    Imgproc.cvtColor(frame, frame, Imgproc.COLOR_BGR2GRAY);

    // Detect QR code
    Mat points = new Mat();
    QRCodeDetector qrCodeDetector = new QRCodeDetector();
    String decodedText = qrCodeDetector.detectAndDecode(frame, points);
    if (!decodedText.isEmpty()) {
        Log.d(TAG, "检测到二维码: " + decodedText);
    }
}

4. 使用Firebase ML Kit

步骤:

  1. 添加依赖:
implementation 'com.google.firebase:firebase-ml-vision:24.0.3'
implementation 'com.google.firebase:firebase-ml-vision-barcode-model:16.0.2'
  1. 使用Firebase的ML Kit进行二维码扫描:
FirebaseVisionBarcodeDetectorOptions options =
        new FirebaseVisionBarcodeDetectorOptions.Builder()
                .setBarcodeFormats(FirebaseVisionBarcode.FORMAT_QR_CODE)
                .build();

FirebaseVisionBarcodeDetector detector = FirebaseVision.getInstance()
        .getVisionBarcodeDetector(options);

detector.detectInImage(image)
        .addOnSuccessListener(new OnSuccessListener<List<FirebaseVisionBarcode>>() {
            @Override
            public void onSuccess(List<FirebaseVisionBarcode> barcodes) {
                for (FirebaseVisionBarcode barcode : barcodes) {
                    Log.d(TAG, "检测到条码: " + barcode.getDisplayValue());
                }
            }
        })
        .addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                Log.e(TAG, "条码扫描失败。错误信息:" + e.getMessage(), e);
            }
        });

最后声明:这篇稿子是基于我个人的学习和经验总结,同时参考了相关的公开文档和资源,最后集成的这边文章!!!对各位有帮助就好!!!

完工躺平 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值