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

Flutter Camera包流式CameraImage显示异常求助:无效图像数据

问题原因

CameraImage的原始数据是摄像头输出的YUV/RGB原始格式(比如常见的YUV420_888),Image.memory()只能解析JPEG/PNG这类编码后的图像字节,直接传入planes的原始字节会触发「无效图像数据」错误。

正确实现步骤

1. 实现CameraImage到JPEG字节的转换

需要把CameraImage的原始数据编码成标准图像格式,这里以YUV420转JPEG为例,借助image包完成格式转换:

import 'dart:typed_data';
import 'package:image/image.dart' as img;
import 'package:camera/camera.dart';

Future<Uint8List> convertCameraImageToJpeg(CameraImage image) async {
  final img.Image convertedImage;
  // 处理主流的YUV420格式
  if (image.format.group == ImageFormatGroup.yuv420) {
    convertedImage = _convertYUV420(image);
  } else {
    // 兼容RGB类格式(部分设备输出)
    convertedImage = _convertBGRA8888(image);
  }
  // 编码为JPEG字节
  return img.encodeJpg(convertedImage);
}

img.Image _convertYUV420(CameraImage image) {
  final int width = image.width;
  final int height = image.height;

  final int uvRowStride = image.planes[1].bytesPerRow;
  final int uvPixelStride = image.planes[1].bytesPerPixel!;

  final img.Image imgBuffer = img.Image(width, height);

  for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
      final int uvIndex = uvPixelStride * (x ~/ 2) + uvRowStride * (y ~/ 2);
      final int index = y * width + x;

      final yp = image.planes[0].bytes[y * image.planes[0].bytesPerRow + x];
      final up = image.planes[1].bytes[uvIndex];
      final vp = image.planes[2].bytes[uvIndex];

      // YUV转RGB公式
      int r = (yp + 1.370705 * (vp - 128)).round();
      int g = (yp - 0.698001 * (vp - 128) - 0.337633 * (up - 128)).round();
      int b = (yp + 1.732446 * (up - 128)).round();

      r = r.clamp(0, 255);
      g = g.clamp(0, 255);
      b = b.clamp(0, 255);

      imgBuffer.data[index] = (0xFF << 24) | (r << 16) | (g << 8) | b;
    }
  }
  return imgBuffer;
}

img.Image _convertBGRA8888(CameraImage image) {
  final int width = image.width;
  final int height = image.height;
  final img.Image imgBuffer = img.Image(width, height);
  final plane = image.planes[0];
  final bytes = plane.bytes;
  int index = 0;
  for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
      final int b = bytes[index];
      final int g = bytes[index + 1];
      final int r = bytes[index + 2];
      imgBuffer.data[y * width + x] = (0xFF << 24) | (r << 16) | (g << 8) | b;
      index += 4;
    }
  }
  return imgBuffer;
}

2. 处理摄像头数据流,加入转换与图像增强逻辑

修改你的数据流,确保每个CameraImage先完成格式转换(同时插入你的图像增强代码):

// 假设你已获取摄像头原始数据流Stream<CameraImage> cameraStream
Stream<Uint8List> get processedImageStream => cameraStream.asyncMap((cameraImage) async {
  // 1. 先转换格式
  img.Image convertedImage;
  if (cameraImage.format.group == ImageFormatGroup.yuv420) {
    convertedImage = _convertYUV420(cameraImage);
  } else {
    convertedImage = _convertBGRA8888(cameraImage);
  }
  
  // 2. 在这里加入你的图像增强逻辑,比如:
  // convertedImage = img.brightness(convertedImage, 0.2); // 提亮示例
  
  // 3. 编码为JPEG字节返回
  return img.encodeJpg(convertedImage);
});

3. 更新StreamBuilder显示图像

将StreamBuilder的泛型改为Uint8List,直接使用转换后的JPEG字节渲染:

Widget streamBuilder = StreamBuilder<Uint8List>(
  stream: processedImageStream,
  builder: (BuildContext context, AsyncSnapshot<Uint8List> snapshot) {
    if (snapshot.hasError || !snapshot.hasData) {
      return Container(color: Colors.black); // 加载/错误状态显示黑色背景
    }
    switch (snapshot.connectionState) {
      case ConnectionState.none:
      case ConnectionState.waiting:
      case ConnectionState.done:
        return Container(color: Colors.black);
      case ConnectionState.active:
        return Image.memory(
          snapshot.data!,
          fit: BoxFit.cover, // 适配屏幕尺寸
        );
    }
  },
);

4. 优化画面同步与性能

  • 复杂图像增强逻辑放到Isolate中执行,避免阻塞主线程导致卡顿
  • 用Stream.throttle(Duration(milliseconds: 33))限制输出帧率(约30帧),平衡性能与流畅度
  • 摄像头预览尺寸尽量与显示组件尺寸匹配,减少拉伸变形

内容的提问来源于stack exchange,提问作者Andrew

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 05:15:35