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

如何将Flutter CameraImage转换为色彩正常、无旋转的PNG/JPG?

解决CameraImage转JPG/PNG的灰度与旋转问题

以下是修复后的跨平台实现方案,同时解决色彩丢失和图片旋转问题:

import 'package:camera/camera.dart';
import 'package:image/image.dart' as imglib;

// 转换CameraImage为PNG字节,支持色彩校正与旋转
Future<List<int>> convertCameraImageToPng(CameraImage image, int sensorOrientation) async {
  imglib.Image? convertedImage;

  if (image.format.group == ImageFormatGroup.yuv420) {
    convertedImage = _convertYUV420ToImage(image);
  } else if (image.format.group == ImageFormatGroup.bgra8888) {
    convertedImage = _convertBGRA8888ToImage(image);
  }

  if (convertedImage == null) {
    throw Exception('不支持的图像格式');
  }

  // 根据相机传感器方向旋转图片
  imglib.Image rotatedImage = _rotateImage(convertedImage, sensorOrientation);

  // 编码为PNG(如需JPG,替换为JpegEncoder()即可)
  return imglib.PngEncoder().encodeImage(rotatedImage);
}

// 转换BGRA8888格式的CameraImage为imglib.Image
imglib.Image _convertBGRA8888ToImage(CameraImage image) {
  return imglib.Image.fromBytes(
    image.width,
    image.height,
    image.planes[0].bytes,
    format: imglib.Format.bgra,
  );
}

// 正确转换YUV420_888格式为彩色图像
imglib.Image _convertYUV420ToImage(CameraImage image) {
  final int width = image.width;
  final int height = image.height;

  final yPlane = image.planes[0];
  final uPlane = image.planes[1];
  final vPlane = image.planes[2];

  final yBytes = yPlane.bytes;
  final uBytes = uPlane.bytes;
  final vBytes = vPlane.bytes;

  final int yRowStride = yPlane.bytesPerRow;
  final int yPixelStride = yPlane.bytesPerPixel!;

  final int uvRowStride = uPlane.bytesPerRow;
  final int uvPixelStride = uPlane.bytesPerPixel!;

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

  for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
      final int yIndex = y * yRowStride + x * yPixelStride;

      // 计算UV通道索引(YUV420为4:2:0采样,UV分辨率是Y的一半)
      final int uvIndex = (y ~/ 2) * uvRowStride + (x ~/ 2) * uvPixelStride;

      final int yValue = yBytes[yIndex];
      final int uValue = uBytes[uvIndex] - 128;
      final int vValue = vBytes[uvIndex] - 128;

      // YUV转RGB标准公式
      int r = (yValue + 1.370705 * vValue).round();
      int g = (yValue - 0.698001 * vValue - 0.337633 * uValue).round();
      int b = (yValue + 1.732446 * uValue).round();

      // 限制RGB值在0-255范围内,避免色彩溢出
      r = r.clamp(0, 255);
      g = g.clamp(0, 255);
      b = b.clamp(0, 255);

      // 组装ARGB格式像素值(imglib库默认使用ARGB)
      img.data[y * width + x] = (0xFF << 24) | (r << 16) | (g << 8) | b;
    }
  }

  return img;
}

// 根据传感器方向旋转图片
imglib.Image _rotateImage(imglib.Image image, int sensorOrientation) {
  switch (sensorOrientation) {
    case 90:
      return imglib.copyRotate(image, 90);
    case 180:
      return imglib.copyRotate(image, 180);
    case 270:
      return imglib.copyRotate(image, 270);
    default:
      return image;
  }
}

使用示例

// 假设已初始化CameraController实例_controller
_controller?.startImageStream((CameraImage image) async {
  try {
    // 获取相机传感器的物理方向
    final int sensorOrientation = _controller!.description.sensorOrientation;
    final pngBytes = await convertCameraImageToPng(image, sensorOrientation);
    // 后续可将字节写入文件或上传
    // 示例:final file = File('路径/文件名.png'); await file.writeAsBytes(pngBytes);
  } catch (e) {
    print('图片转换失败: $e');
  }
});

关键修复说明

  1. 解决灰度问题:
    原代码仅使用YUV420格式的Y亮度通道,完全忽略U、V色彩通道。修复后同时读取三个通道数据,通过标准YUV转RGB公式计算真实色彩值,确保输出彩色图片。

  2. 解决旋转问题:
    相机传感器物理方向通常与屏幕方向不一致(多数手机传感器为横置),通过获取相机的sensorOrientation属性,调用image库的旋转方法修正图片方向,保证输出与屏幕显示一致。

  3. 跨平台兼容:
    自动适配Android常用的YUV420格式和iOS常用的BGRA8888格式,依赖的camera和image库均为Flutter主流跨平台库,无需额外平台定制代码。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 18:24:58