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

使用Teachable Machine的TFLite模型图像识别返回null问题排查

图像识别中detectedObjectName为null的排查与解决

我按教程实现图像识别功能,用Teachable Machine生成模型,但扫描图像后,在Log.d("Catching message","${it.detectedObjectName}")里捕获到的detectedObjectName总是null,偶尔能拿到置信度数值。想问这是代码实现问题还是模型适配问题?要不要调整代码适配模型?


UI实现代码

fun PlantAnalysisScreen(navController: NavController) {
    val context = LocalContext.current
    var classifications by remember {
        mutableStateOf(emptyList<Classification>()) }


    // Preparing Image Analyzer
    val imageAnalyzer =  remember {
        ImageAnalyzer(
            classifier = TfLitePlantClassifier(
                context = context
            ),
            onClassificationResults = {
                classifications = it
            }
        )
    }

    val cameraPreviewController = remember {
        LifecycleCameraController(context).apply {
            setEnabledUseCases(CameraController.IMAGE_ANALYSIS)
            setImageAnalysisAnalyzer(
                ContextCompat.getMainExecutor(context),
                imageAnalyzer
            )
        }
    }
    Scaffold(
        topBar = {
            //TopBar
            TopAppBar(
                backgroundColor = TealPrimary,
                elevation = 0.dp
            ) {
                Row(horizontalArrangement = Arrangement.Start) {
                    Icon(imageVector = Icons.Default.ArrowBack,
                        contentDescription = "Arrow Back",
                        tint = Color.White,
                        modifier = Modifier.clickable {
                            navController.popBackStack()
                        })
                    Spacer(modifier = Modifier.width(120.dp))
                    Text(text = "Camera", fontWeight = FontWeight.Bold, color = Color.White,)
                    Spacer(modifier = Modifier.width(115.dp))

                }

            }

        }

    ) {
Column(modifier = Modifier.padding(all = 160.dp)) {


        classifications.forEach {
            Row() {
                Text(
                    text = it.detectedObjectName,
                    modifier = Modifier

                        .padding(8.dp),

                    )
                Log.d("Catching message","${it.detectedObjectName}")

                Text(
                    text = it.confidenceScore.toString(),
                    modifier = Modifier

                        .padding(8.dp),

                    )
            }

    }
        CameraPreview(
            controller = cameraPreviewController,
            modifier = Modifier.fillMaxSize()
        )


    }}}

TfLitePlantClassifier类代码

class TfLitePlantClassifier (
    private val context: Context,
    private val confidenceThreshold: Float = 0.75f,
    private val maxResults: Int = 1
): PlantClassifier {
    private var classifier: ImageClassifier? = null

    override fun classifyCurrentFrame(bitmap: Bitmap, rotation: Int): List<Classification> {
        if (classifier == null) {
            initializeClassifier()
        }

        val tfLiteImageProcessingOptions = initializeImageProcessingOptions(rotation)
        val tfLiteImageProcessor: ImageProcessor = ImageProcessor.Builder().build()
        val tensorImage: TensorImage = tfLiteImageProcessor.process(
            TensorImage.fromBitmap(bitmap)
        )

        val classificationResults = classifier?.classify(
            tensorImage,
            tfLiteImageProcessingOptions
        )

        // Obtain each category from the result, map them to 'Classification' & lastly remove duplicates using 'distinctBy'
        return classificationResults?.flatMap { curClassification ->
            curClassification.categories.map { curCategory ->
                Classification(
                    detectedObjectName = curCategory.displayName,
                    confidenceScore = curCategory.score
                )
            }
        }?.distinctBy { it.detectedObjectName } ?: emptyList()
    }

    /**
     * Initializes the TfLite Image Classifier
     */
    private fun initializeClassifier() {
        val baseOptions = BaseOptions.builder()
            .setNumThreads(2)
            .build()

        val options = ImageClassifier.ImageClassifierOptions.builder()
            .setBaseOptions(baseOptions)
            .setMaxResults(maxResults)
            .setScoreThreshold(confidenceThreshold)
            .build()

        try {
            // Create the classifier from the TfLite File
            classifier = ImageClassifier.createFromFileAndOptions(
                context,
                "model.tflite",
                options
            )
        } catch (exception: IllegalStateException) {
            exception.printStackTrace()
        }
    }

    /**
     * Builds and returns the TfLite ImageProcessingOptions
     *
     * @param rotation
     * @return ImageProcessingOptions
     */
    private fun initializeImageProcessingOptions(rotation: Int): ImageProcessingOptions {
        return ImageProcessingOptions.builder()
            .setOrientation(getOrientationFromRotation(rotation))
            .build()
    }

    /**
     * Returns the orientation of image from the rotation using the
     * TfLite ImageProcessingOptions.Orientation used to internally make use of image axes as an Image metadata
     *
     * @param rotation
     * @return ImageProcessingOptions.Orientation
     */
    private fun getOrientationFromRotation(rotation: Int): ImageProcessingOptions.Orientation {
        return when(rotation) {
            Surface.ROTATION_270 -> ImageProcessingOptions.Orientation.BOTTOM_RIGHT
            Surface.ROTATION_90 -> ImageProcessingOptions.Orientation.TOP_LEFT
            Surface.ROTATION_180 -> ImageProcessingOptions.Orientation.RIGHT_BOTTOM
            else -> ImageProcessingOptions.Orientation.RIGHT_TOP
        }
    }
}

ImageAnalyzer类代码

class ImageAnalyzer(
private val classifier: PlantClassifier,
private val onClassificationResults: (List<Classification>) -> Unit
): ImageAnalysis.Analyzer {
private var frameSkipCounter = 0

override fun analyze(image: ImageProxy) {
    // Analyze only 1 frame per-second
    if (frameSkipCounter % 60 == 0) {
        val rotationDegrees = image.imageInfo.rotationDegrees
        val bitmap = image
            .toBitmap()
            .centerCrop(321, 321)

        // Calling Domain layer classification method
        val classificationResults = classifier.classifyCurrentFrame(
            bitmap,
            rotationDegrees
        )
        onClassificationResults(classificationResults)
    }
    frameSkipCounter++

    // Fully processed the frame
    image.close()
}}

问题排查与解决思路

可能的原因

  • 模型标签缺失:Teachable Machine导出模型时会附带labels.txt文件,如果这个文件没放到assets目录里,或者文件名/格式不对,TFLite分类器就拿不到标签名称,导致displayName为null。
  • 图像方向错误:相机预览的旋转角度和模型训练时的图像方向不匹配,导致模型无法正确识别,返回的结果中标签为空但有置信度。
  • 置信度阈值过高:当前设置的confidenceThreshold是0.75,如果模型识别的置信度刚好卡在阈值边缘,可能只会返回置信度数值,标签无法正常解析。
  • Classifier初始化失败:如果model.tflite文件损坏、路径不对,初始化时会抛出异常,但代码里只做了printStackTrace,没有提示,导致classifier为空,返回不完整的结果。

解决方案

  1. 检查标签文件:确认assets目录下存在labels.txt,文件内容是每行一个标签,和Teachable Machine里的分类标签完全一致,文件名不能写错。
  2. 修正图像旋转逻辑:打印rotationDegrees的值,对比实际相机方向,调整getOrientationFromRotation里的映射关系。比如测试Surface.ROTATION_0对应的Orientation是否应该是RIGHT_TOP,确保输入模型的图像方向和训练时一致。
  3. 降低置信度阈值:把confidenceThreshold改成0.5试试,看是否能稳定拿到标签和置信度,排除阈值过高的问题。
  4. 完善错误日志:在initializeClassifier的catch块里加一句Log.e("ClassifierInit", "模型初始化失败: ${exception.message}"),确认模型是否成功加载。
  5. 处理空值情况:在UI中显示detectedObjectName时,做空值兜底,比如text = it.detectedObjectName ?: "未识别",避免空指针问题,同时方便调试。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 04:22:03