Flutter CameraImage转Kotlin Bitmap失败,求MediaPipe图像处理优化方案
解决Flutter YUV_420_888图像转Kotlin Bitmap并对接MediaPipe的方案
一、直接在Kotlin层处理YUV数据,跳过BitmapFactory
BitmapFactory对YUV格式支持有限,直接用YUV原始数据转Bitmap或直接喂给MediaPipe是更高效的方案——MediaPipe本身支持YUV输入,没必要强制转Bitmap。
实现步骤
- Flutter端传递YUV平面数据:捕获到
CameraImage后,把Y/U/V三个平面的字节数组、图像宽高、行间距(bytesPerRow)通过Platform Channel传给Kotlin:
// 处理CameraImage回调 await platformChannel.invokeMethod('processCameraImage', { 'yBytes': image.planes[0].bytes, 'uBytes': image.planes[1].bytes, 'vBytes': image.planes[2].bytes, 'width': image.width, 'height': image.height, 'yStride': image.planes[0].bytesPerRow, 'uStride': image.planes[1].bytesPerRow, 'vStride': image.planes[2].bytesPerRow, });
- Kotlin端直接转Bitmap或喂给MediaPipe:
- 转Bitmap:用
YuvImage压缩成JPEG流再解码,避免直接用BitmapFactory处理YUV:
- 转Bitmap:用
fun processCameraImage(args: Map<String, Any>) { val yBytes = args["yBytes"] as ByteArray val uBytes = args["uBytes"] as ByteArray val vBytes = args["vBytes"] as ByteArray val width = args["width"] as Int val height = args["height"] as Int val strides = intArrayOf(args["yStride"] as Int, args["uStride"] as Int, args["vStride"] as Int) val yuvImage = YuvImage(yBytes, ImageFormat.FLEX_YUV_420_888, width, height, strides) val outputStream = ByteArrayOutputStream() yuvImage.compressToJpeg(Rect(0, 0, width, height), 90, outputStream) val jpegData = outputStream.toByteArray() val bitmap = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.size) // 后续传给MediaPipe处理 }
- 直接给MediaPipe:跳过Bitmap转换,直接构建MediaPipe支持的
Image对象:
fun processCameraImage(args: Map<String, Any>) { // 读取参数(同上面的变量) val mediaImage = Image.builder() .setWidth(width) .setHeight(height) .setFormat(ImageFormat.YUV_420_888) .setPlanes( listOf( Image.Plane.builder() .setBuffer(ByteBuffer.wrap(yBytes)) .setRowStride(yStride) .setPixelStride(1) .build(), Image.Plane.builder() .setBuffer(ByteBuffer.wrap(uBytes)) .setRowStride(uStride) .setPixelStride(2) .build(), Image.Plane.builder() .setBuffer(ByteBuffer.wrap(vBytes)) .setRowStride(vStride) .setPixelStride(2) .build() ) ) .build() val faceResult = faceDetector.process(mediaImage) // 处理特征点 }
二、用MediaPipe官方Flutter插件,彻底规避跨平台传输
直接使用MediaPipe官方提供的Flutter插件(如mediapipe或集成MediaPipe能力的google_mlkit_face_detection),插件内部已经完成了相机YUV图像的捕获、格式转换和MediaPipe对接,不需要手动处理跨平台数据传输。
示例代码
import 'package:camera/camera.dart'; import 'package:google_mlkit_face_detection/google_mlkit_face_detection.dart'; // 初始化人脸检测器 final faceDetector = FaceDetector(options: FaceDetectorOptions()); // 监听相机数据流 cameraController.startImageStream((image) async { final inputImage = InputImage.fromCameraImage(image, CameraLensDirection.front); final faces = await faceDetector.processImage(inputImage); // 直接获取特征点 for (final face in faces) { for (final landmark in face.landmarks) { print("特征点坐标: ${landmark.position}"); } } });
这种方式性能最优,完全避免了格式转换和跨平台传输的开销,也不会出现Bitmap为空的问题。
三、优化Platform Channel传输(需内存管理)
如果必须自己实现跨平台传输,不要直接传完整字节数组,改用内存共享减少拷贝开销:
- Flutter端传递字节数组的指针地址和长度
- Kotlin端通过JNI或直接内存读取数据
这种方式需要手动管理内存(避免泄漏),适合对性能要求极高的场景,代码示例:
// Flutter端传递指针 final yBuffer = image.planes[0].bytes.buffer.asByteData(); await platformChannel.invokeMethod('processImage', { 'yPtr': yBuffer.pointer.address, 'yLength': image.planes[0].bytes.length, // 其他平面的指针和参数 });
// Kotlin端读取内存数据 fun processImage(args: Map<String, Any>) { val yPtr = args["yPtr"] as Long val yLength = args["yLength"] as Int val yBytes = ByteArray(yLength) val buffer = ByteBuffer.allocateDirect(yLength).order(ByteOrder.nativeOrder()) buffer.put(yBytes) // 后续处理YUV数据 }
四、排查之前Bitmap为空的原因
如果要排查原有问题,重点检查:
- YUV各平面的字节长度是否正确(Y平面应为
width*height,U/V平面为(width*height)/4) YuvImage的格式参数是否匹配(Android中YUV_420_888对应ImageFormat.FLEX_YUV_420_888)- 压缩JPEG时的矩形区域是否超出图像范围
内容的提问来源于stack exchange,提问作者garfiv
相关产品推荐
相关产品推荐

