如何修改扫描类实现CameraX+Zxing/MLKit任意方向识别二维码与条形码(含MLKit扫描失效排查)
Fixing Your Barcode/QR Code Scanning Issues
Hey there! Let's break down and solve both of your scanning problems step by step.
1. Modifying ZXing's QRCodeImageAnalyzer for Any-Orientation Barcode Scanning
Your current implementation only targets QR codes because you're using QRCodeMultiReader. To support all common barcode formats (and any orientation), we'll switch to MultiFormatReader and add handling for image rotation (since CameraX delivers images in a fixed orientation that might not match the device's current rotation).
Here's the updated code:
public class QRCodeImageAnalyzer implements ImageAnalysis.Analyzer { private final QRCodeFoundListener listener; private final MultiFormatReader multiFormatReader; public QRCodeImageAnalyzer(QRCodeFoundListener listener) { this.listener = listener; // Configure the reader to support all common barcode formats multiFormatReader = new MultiFormatReader(); Hashtable<DecodeHintType, Object> hints = new Hashtable<>(); List<BarcodeFormat> formats = new ArrayList<>(); formats.add(BarcodeFormat.QR_CODE); formats.add(BarcodeFormat.CODE_128); formats.add(BarcodeFormat.EAN_13); formats.add(BarcodeFormat.EAN_8); formats.add(BarcodeFormat.UPC_A); formats.add(BarcodeFormat.UPC_E); formats.add(BarcodeFormat.CODE_39); hints.put(DecodeHintType.POSSIBLE_FORMATS, formats); // Enable auto-rotation to detect barcodes in any orientation hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE); multiFormatReader.setHints(hints); } @SuppressLint("UnsafeOptInUsageError") @Override public void analyze(@NonNull ImageProxy imageProxy) { Image image = imageProxy.getImage(); if (image == null) { imageProxy.close(); return; } if (image.getFormat() == ImageFormat.YUV_420_888 || image.getFormat() == ImageFormat.YUV_422_888 || image.getFormat() == ImageFormat.YUV_444_888) { ByteBuffer byteBuffer = image.getPlanes()[0].getBuffer(); byte[] imageData = new byte[byteBuffer.capacity()]; byteBuffer.get(imageData); int imageWidth = image.getWidth(); int imageHeight = image.getHeight(); // Adjust for image rotation from CameraX int rotationDegrees = imageProxy.getImageInfo().getRotationDegrees(); PlanarYUVLuminanceSource source; if (rotationDegrees == 90 || rotationDegrees == 270) { // Rotate the luminance source to match device orientation source = new PlanarYUVLuminanceSource( imageData, imageWidth, imageHeight, 0, 0, imageWidth, imageHeight, false ).rotateCounterClockwise(); // Swap width/height after rotation imageWidth = source.getWidth(); imageHeight = source.getHeight(); } else { source = new PlanarYUVLuminanceSource( imageData, imageWidth, imageHeight, 0, 0, imageWidth, imageHeight, false ); } BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(source)); try { Result result = multiFormatReader.decode(binaryBitmap); listener.onQRCodeFound(result.getText()); } catch (FormatException | ChecksumException | NotFoundException e) { listener.qrCodeNotFound(); } } imageProxy.close(); } public interface QRCodeFoundListener { void onQRCodeFound(String code); void qrCodeNotFound(); } }
Key changes made:
- Replaced
QRCodeMultiReaderwithMultiFormatReaderand configured it to support multiple barcode formats - Added
TRY_HARDERhint to enable more aggressive scanning (helps with rotated or blurry codes) - Added logic to rotate the luminance source based on CameraX's image rotation degrees, ensuring barcodes in any orientation are detected
- Added null check for the
Imageobject to avoid crashes
2. Fixing ML Kit's QrCodeAnalyzer Detection Issues
Your ML Kit implementation has a few critical bugs that are preventing detection. Let's fix them:
import android.annotation.SuppressLint import android.util.Log import androidx.camera.core.ExperimentalGetImage import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageProxy import com.google.mlkit.vision.barcode.Barcode import com.google.mlkit.vision.barcode.BarcodeScannerOptions import com.google.mlkit.vision.barcode.BarcodeScanning import com.google.mlkit.vision.common.InputImage @OptIn(ExperimentalGetImage::class) class QrCodeAnalyzer( private val onQrCodesDetected: (qrCodes: List<Barcode>?) -> Unit ) : ImageAnalysis.Analyzer { private val TAG: String = "Kamran" // Create the scanner once as a member variable (avoid recreating it every frame) private val scanner = BarcodeScanning.getClient( BarcodeScannerOptions.Builder() .setBarcodeFormats( Barcode.FORMAT_QR_CODE, Barcode.FORMAT_EAN_8, Barcode.FORMAT_EAN_13 ) .build() ) private fun rotationDegreesToFirebaseRotation(rotationDegrees: Int): Int { return when (rotationDegrees) { 0 -> 0 90 -> 1 180 -> 2 270 -> 3 else -> 0 // Fallback to 0 if rotation is unexpected } } @SuppressLint("UnsafeOptInUsageError") override fun analyze(imageProxy: ImageProxy) { val mediaImage = imageProxy.image ?: run { imageProxy.close() return } // Use the Firebase-compatible rotation value, not raw degrees val rotation = rotationDegreesToFirebaseRotation(imageProxy.imageInfo.rotationDegrees) val inputImage = InputImage.fromMediaImage(mediaImage, rotation) scanner.process(inputImage) .addOnSuccessListener { barcodes -> Log.d(TAG, "Detected ${barcodes.size} barcodes") onQrCodesDetected(barcodes) } .addOnFailureListener { failure -> failure.printStackTrace() onQrCodesDetected(null) } .addOnCompleteListener { // Always close the imageProxy after processing, regardless of success/failure imageProxy.close() Log.d(TAG, "Processing complete, closed imageProxy") } } }
Key fixes explained:
- Reused Barcode Scanner: Created the
BarcodeScanningclient once as a member variable instead of recreating it every frame (this improves performance and avoids potential initialization issues) - Correct Rotation Value: Fixed the rotation parameter passed to
InputImage.fromMediaImage()—we now use the Firebase-compatible rotation constant from yourrotationDegreesToFirebaseRotation()method, not the raw rotation degrees - Improved Image Handling: Added a null check for
mediaImageand moved theimageProxy.close()call toaddOnCompleteListener(ensures the image is always closed after processing, even if detection fails) - Simplified Callbacks: Cleaned up the success listener to directly pass results to your callback, making the code more readable
内容的提问来源于stack exchange,提问作者Kamran Khan
相关产品推荐
相关产品推荐

