Flutter中如何将CameraImage转换为InputImage传入ML Kit扫码?
将CameraImage转换为InputImage以适配google_mlkit_barcode_scanning
要解决CameraImage无法直接传入ML Kit条码识别的问题,核心是把CameraImage格式转换为ML Kit要求的InputImage对象,以下是具体实现步骤和优化后的代码:
1. 核心转换逻辑
ML Kit的InputImage支持通过字节数组创建,我们需要先将CameraImage的多平面数据拼接为连续字节数组,再结合图像元数据(尺寸、旋转角度、格式等)生成InputImage。
2. 优化后的完整代码
import 'dart:async'; import 'dart:typed_data'; import 'dart:ui'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:camera/camera.dart'; import 'package:google_mlkit_barcode_scanning/google_mlkit_barcode_scanning.dart'; import 'package:flutter/services.dart'; class BarcodeScanPage extends StatefulWidget { const BarcodeScanPage({super.key}); @override State<BarcodeScanPage> createState() => _BarcodeScanPageState(); } class _BarcodeScanPageState extends State<BarcodeScanPage> { List<CameraDescription>? _cameras; CameraController? _controller; bool _isCameraInitialized = false; BarcodeScanner? _barcodeScanner; @override void initState() { super.initState(); // 初始化条码扫描器(只需创建一次,避免重复实例化) final List<BarcodeFormat> formats = [BarcodeFormat.all]; _barcodeScanner = BarcodeScanner(formats: formats); _initializeCamera(); } Future<void> _initializeCamera() async { _cameras = await availableCameras(); if (_cameras == null || _cameras!.isEmpty) return; _controller = CameraController( _cameras!.first, ResolutionPreset.medium, enableAudio: false, ); await _controller!.initialize(); if (mounted) { setState(() { _isCameraInitialized = true; }); // 启动图像流,传入CameraImage给处理方法 _controller!.startImageStream((CameraImage image) async { await processQrcode(image); }); } } Future<void> processQrcode(CameraImage image) async { if (_barcodeScanner == null || !mounted) return; InputImage inputImage; try { // 根据平台处理图像格式 if (Platform.isAndroid) { inputImage = InputImage.fromBytes( bytes: _concatenatePlanes(image.planes), metadata: InputImageMetadata( size: Size(image.width.toDouble(), image.height.toDouble()), // 注意:旋转角度需根据摄像头类型调整,后置通常为90,前置为270 rotation: InputImageRotationValue.fromRawValue(90)!, format: InputImageFormatValue.fromRawValue(image.format.raw)!, bytesPerRow: image.planes[0].bytesPerRow, ), ); } else if (Platform.isIOS) { inputImage = InputImage.fromBytes( bytes: _concatenatePlanes(image.planes), metadata: InputImageMetadata( size: Size(image.width.toDouble(), image.height.toDouble()), rotation: InputImageRotationValue.fromRawValue(0)!, format: InputImageFormatValue.fromRawValue(image.format.raw)!, bytesPerRow: image.planes[0].bytesPerRow, ), ); } else { return; } // 执行条码识别 final List<Barcode> barcodes = await _barcodeScanner!.processImage(inputImage); if (barcodes.isNotEmpty) { // 处理识别结果,示例:打印条码内容 print('识别到条码内容:${barcodes.first.rawValue}'); // 如需更新UI,可在此调用setState } } catch (e) { print('条码识别出错:$e'); } } // 拼接CameraImage的多平面数据为连续字节数组 Uint8List _concatenatePlanes(List<Plane> planes) { final WriteBuffer allBytes = WriteBuffer(); for (final Plane plane in planes) { allBytes.putUint8List(plane.bytes); } return allBytes.done().buffer.asUint8List(); } @override void dispose() { // 释放资源,避免内存泄漏 _controller?.dispose(); _barcodeScanner?.close(); super.dispose(); } @override Widget build(BuildContext context) { if (!_isCameraInitialized || _controller == null) { return const Center(child: CircularProgressIndicator()); } return CameraPreview(_controller!); } }
3. 关键注意事项
- 旋转角度调整:不同设备和摄像头(前置/后置)的图像旋转角度可能不同,需根据实际测试结果修改
InputImageMetadata中的rotation值。 - 资源释放:务必在页面销毁时调用
_barcodeScanner?.close()和_controller?.dispose(),避免内存泄漏。 - 性能优化:不要在图像流回调中重复创建
BarcodeScanner实例,应在initState中初始化一次,提升扫描性能。
内容的提问来源于stack exchange,提问作者Toqgcm
相关产品推荐
相关产品推荐

