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

