限时福利领取


最近在Android项目里接入了MediaPipe做实时图像处理,踩了不少坑也积累了些经验。整理成这篇笔记,希望能帮到同样想用MediaPipe的开发者们。

一、为什么选MediaPipe?

Android上跑机器学习模型的选择不少,比如TensorFlow Lite和ML Kit。但MediaPipe有两个独特优势:

  1. 预制解决方案丰富:手部跟踪、姿态估计这些常见需求直接提供现成pipeline
  2. 跨平台一致性:同一套graph配置能在Android/iOS/桌面端运行

不过Android环境特有的碎片化问题还是带来了挑战:

  • 不同CPU架构(armeabi-v7a/arm64-v8a)需要单独编译.so
  • 低端机内存有限,直接跑官方demo容易出现OOM

MediaPipe架构图

二、快速集成指南

1. 环境准备

建议用Bazel构建,虽然学习曲线陡峭但灵活性更高。在WORKSPACE文件添加:

mediapipe_git = "https://github.com/google/mediapipe"
http_archive(
    name = "mediapipe",
    urls = [mediapipe_git + "/archive/%s.tar.gz" % "v0.9.3"],
    strip_prefix = "mediapipe-0.9.3",
)

2. 构建Android AAR

关键命令(需要安装NDK):

bazel build -c opt \
  --config=android_arm64 \
  mediapipe/examples/android/src/java/com/google/mediapipe/apps/handtrackinggpu:handtrackinggpu

3. 代码集成

处理摄像头流的核心代码(Kotlin协程版):

class CameraActivity : AppCompatActivity() {
    private val processor by lazy {
        FrameProcessor(
            this,
            R.raw.hand_tracking_mobile_gpu, // 自定义graph
            "hand_tracking_mobile_gpu.pbtxt"
        ).apply {
            setConsumer { packet ->
                val landmarks = PacketGetter.getProtoVector(packet, NormalizedLandmarkList.parser())
                updateLandmarks(landmarks)
            }
        }
    }

    override fun onCameraStarted(surfaceTexture: SurfaceTexture) {
        processor.videoSurfaceOutput.autoAttachToGLThread = true
        processor.getVideoSurfaceOutput().setSurface(
            Surface(surfaceTexture)
        )
    }
}

三、性能优化实战

1. 内存管理

MediaPipe默认使用GpuBuffer处理图像,但转Bitmap很耗性能。建议:

  • 在graph里直接处理GpuBuffer避免转换
  • 必须转换时复用Bitmap对象
// 错误示例:每帧new Bitmap
Bitmap bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);

// 正确做法:复用对象
if (cachedBitmap == null || 
    cachedBitmap.getWidth() != width || 
    cachedBitmap.getHeight() != height) {
    cachedBitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
}

2. 多线程策略

实测数据(Pixel 4 XL):

| 线程数 | 平均延迟(ms) | CPU占用率 | |--------|-------------|-----------| | 1 | 42.3 | 65% | | 2 | 38.1 | 82% | | 4 | 36.7 | 95% |

建议根据设备核心数动态调整:

val numThreads = when {
    Build.VERSION.SDK_INT >= Build.VERSION_CODES.N -> 
        Runtime.getRuntime().availableProcessors()
    else -> 2
}
processor.setNumThreads(numThreads)

性能对比图

四、避坑经验

  1. ABI过滤:在app/build.gradle里指定需要的架构,减少APK体积
android {
    ndk {
        abiFilters 'armeabi-v7a', 'arm64-v8a'
    }
}
  1. SurfaceView vs TextureView
  2. SurfaceView延迟更低(约低15-20ms)
  3. TextureView支持动画和透明度

  4. ProGuard规则

-keep class com.google.mediapipe.** { *; }
-keep class com.google.protobuf.** { *; }

五、进阶方向

尝试结合CameraX的Analyzer API,可以进一步降低端到端延迟:

val analyzer = ImageAnalysis.Builder()
    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
    .build()
    .also {
        it.setAnalyzer(executor) { image ->
            val gpuBuffer = imageToGpuBuffer(image) // 实现转换逻辑
            processor.process(gpuBuffer)
            image.close()
        }
    }

现在我的demo在Pixel 6上能做到1080p@30fps处理,端到端延迟控制在80ms以内。关键还是吃透了MediaPipe的pipeline设计,后续准备尝试把Hand Tracking和ARCore结合起来玩些新花样。

Logo

音视频技术社区,一个全球开发者共同探讨、分享、学习音视频技术的平台,加入我们,与全球开发者一起创造更加优秀的音视频产品!

更多推荐