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

Android相机应用使用YOLOv5转TFLite模型启动崩溃问题排查

问题描述

我用Kotlin开发一款目标检测APP,使用由YOLOv5转换而来的TensorFlow Lite模型,转换命令为:

python export.py --weights best.pt --include tflite --nms

模型输出张量详情:

Name: StatefulPartitionedCall:0
Shape: [ 1 100 4]
Type: <class 'numpy.float32'>

APP编译正常,但真机运行时闪屏后崩溃,报错信息:

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.sightfulkotlin, PID: 23100
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.sightfulkotlin/com.example.sightfulkotlin.MainActivity}: java.lang.IllegalStateException: Internal error: Unexpected failure when preparing tensor allocations: Regular TensorFlow ops are not supported by this interpreter. Make sure you apply/link the Flex delegate before inference.
    Node number 402 (FlexCombinedNonMaxSuppression) failed to prepare.

MainActivity代码如下:

package com.example.sightfulkotlin

import  android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.*
import android.hardware.camera2.CameraCaptureSession
import android.hardware.camera2.CameraDevice
import android.hardware.camera2.CameraManager
import android.os.Bundle
import android.os.Handler
import android.os.HandlerThread
import android.view.Surface
import android.view.TextureView
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.example.sightfulkotlin.ml.ObjectDetection
import org.tensorflow.lite.DataType
import org.tensorflow.lite.support.common.FileUtil
import org.tensorflow.lite.support.image.ImageProcessor
import org.tensorflow.lite.support.image.TensorImage
import org.tensorflow.lite.support.image.ops.ResizeOp
import org.tensorflow.lite.support.tensorbuffer.TensorBuffer

class MainActivity : AppCompatActivity() {

    var colors = listOf(
        Color.BLUE, Color.GREEN, Color.RED, Color.CYAN, Color.GRAY, Color.BLACK, Color.DKGRAY, Color.MAGENTA, Color.YELLOW, Color.LTGRAY, Color.WHITE)
    val paint = Paint()
    private lateinit var labels:List<String>
    private lateinit var cameraManager: CameraManager
    lateinit var cameraDevice: CameraDevice
    lateinit var handler: Handler
    lateinit var textureView: TextureView
    lateinit var model: ObjectDetection
    lateinit var bitmap: Bitmap
    lateinit var imageView: ImageView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        getPermission()

        labels = FileUtil.loadLabels(this, "labels.txt")
        model = ObjectDetection.newInstance(this)


        var imageProcessor = ImageProcessor.Builder().add(ResizeOp(640, 640, ResizeOp.ResizeMethod.BILINEAR)).build()

        val handlerThread = HandlerThread("videoThread")
        handlerThread.start()
        handler = Handler(handlerThread.looper)

        paint.color = Color.GREEN
        imageView = findViewById(R.id.imageView)
        textureView = findViewById(R.id.textureView)
        textureView.surfaceTextureListener = object: TextureView.SurfaceTextureListener
        {
            override fun onSurfaceTextureAvailable(p0: SurfaceTexture, p1: Int, p2: Int) {
                openCamera()
            }

            override fun onSurfaceTextureSizeChanged(p0: SurfaceTexture, p1: Int, p2: Int) {
            }

            override fun onSurfaceTextureDestroyed(p0: SurfaceTexture): Boolean {
                return false
            }

            override fun onSurfaceTextureUpdated(p0: SurfaceTexture) {
                bitmap = textureView.bitmap!!

                var tensorImage = TensorImage(DataType.FLOAT32)
                tensorImage.load(bitmap)
                tensorImage = imageProcessor.process(tensorImage)



                val inputFeature0 = TensorBuffer.createFixedSize(intArrayOf(1, 640, 640, 3), DataType.FLOAT32)
                inputFeature0.loadBuffer(tensorImage.buffer)

                val outputs = model.process(inputFeature0)
                val locations = outputs.outputFeature0AsTensorBuffer.floatArray
                val scores = outputs.outputFeature1AsTensorBuffer.floatArray
                val classes = outputs.outputFeature2AsTensorBuffer.floatArray
                val numberOfDetections = outputs.outputFeature3AsTensorBuffer.floatArray


                val mutable = bitmap.copy(Bitmap.Config.ARGB_8888, true)
                val canvas = Canvas(mutable)

                val h = mutable.height
                val w = mutable.width

                paint.textSize = h/15f
                paint.strokeWidth = h/85f

                scores.forEachIndexed{index, fl ->
                    var x = index
                    x *= 4
                    if(fl > 0.5)
                    {
                        paint.color = colors[index]
                        paint.style = Paint.Style.STROKE
                        canvas.drawRect(RectF(locations[x+1] *w, locations[x] *h, locations[x+3] *w, locations[x+2] *h), paint)
                        paint.style = Paint.Style.FILL
                        canvas.drawText(labels[classes[index].toInt()] +" "+fl.toString(), locations[x+1] *w, locations[x] *h, paint)
                    }
                }
                imageView.setImageBitmap(mutable)
            }
        }

        cameraManager =  getSystemService(Context.CAMERA_SERVICE) as CameraManager
    }

    override fun onDestroy() {
        super.onDestroy()
        model.close()
    }

    @SuppressLint("MissingPermission")
    fun openCamera()
    {
        cameraManager.openCamera(cameraManager.cameraIdList[0], object: CameraDevice.StateCallback(){
            @SuppressLint("MissingPermission")
            override fun onOpened(p0: CameraDevice) {
                cameraDevice = p0

                val surfaceTexture = textureView.surfaceTexture
                val surface  = Surface(surfaceTexture)
                val captureRequest = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
                captureRequest.addTarget(surface)

                cameraDevice.createCaptureSession(listOf(surface), object: CameraCaptureSession.StateCallback(){
                    override fun onConfigured(p0: CameraCaptureSession) {
                        p0.setRepeatingRequest(captureRequest.build(), null, null)
                    }

                    override fun onConfigureFailed(p0: CameraCaptureSession) {
                    }
                }, handler)
            }

            override fun onDisconnected(p0: CameraDevice) {
            }

            @SuppressLint("MissingPermission")
            override fun onError(p0: CameraDevice, p1: Int) {
            }
        },handler)
    }

    private fun getPermission()
    {
        if(ContextCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA)!=PackageManager.PERMISSION_GRANTED)
        {
            requestPermissions(arrayOf(android.Manifest.permission.CAMERA), 101)
        }
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (grantResults[0] != PackageManager.PERMISSION_GRANTED)
        {
            getPermission()
        }
    }
}
解决方案

方法一:添加TensorFlow Lite Flex依赖并配置Delegate

报错核心是模型包含FlexCombinedNonMaxSuppression这类TensorFlow原生OP,标准TFLite不支持,需启用Flex Delegate兼容。

  1. 添加依赖:在app模块的build.gradle(Module level)中加入Flex依赖:
dependencies {
    // 其他依赖...
    implementation 'org.tensorflow:tensorflow-lite-flex:2.15.0' // 版本可匹配你的TFLite版本调整
}
  1. 修改模型初始化逻辑:手动创建带Flex Delegate的Interpreter,替换自动生成的初始化方式:
// 替换原有的model = ObjectDetection.newInstance(this)
val options = Interpreter.Options().apply {
    addDelegate(FlexDelegate())
}
val modelFile = FileUtil.loadMappedFile(this, "model.tflite") // 替换为你的实际模型文件名
val model = ObjectDetection.newInstance(this, options)

方法二:转换模型时移除内置NMS,手动在APP端实现

若不想引入Flex依赖,可在模型转换时关闭内置NMS,将NMS逻辑移到Kotlin代码中处理:

  1. 重新转换模型:去掉--nms参数:
python export.py --weights best.pt --include tflite

此时模型输出为原始候选框(形状通常为[1, 25200, 85]),而非经过NMS后的100个结果。

  1. 在Kotlin中实现NMS逻辑:对所有候选框进行非极大值抑制,筛选出置信度达标且不重叠的框后再绘制。

额外优化建议

  • 不要在onSurfaceTextureUpdated中同步执行模型推理,会阻塞UI线程导致卡顿或崩溃,建议将推理逻辑放到你创建的videoThread后台线程中执行。
  • 检查colors列表长度,若检测类别数量超过列表长度会触发数组越界,建议动态生成颜色或扩展列表。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 20:15:04