You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 19:57:02