1. 图像分类入门
任务目标
在Android应用中集成TensorFlow Lite,加载MobileNetV2图像分类模型,对摄像头预览帧执行实时推理,并在屏幕上叠加显示Top-1类别标签与置信度。
环境依赖
- Android Studio Hedgehog或更新版本,minSdk 24+,compileSdk 34+
- CameraX库(1.3.0+)用于预览帧获取
- TensorFlow Lite运行时:
org.tensorflow:tensorflow-lite:2.14.0 - 模型文件:
mobilenet_v2_1.0_224.tflite(输入224×224×3,输出1000类) - 标签文件:
labels.txt(1000行,每行一个类别名)
核心实现
以下代码包含完整的TFLite分类器封装与CameraX分析器接入。模型文件放入app/src/main/assets/目录,标签文件同目录。
// TFLiteClassifier.kt - 模型加载与推理
import android.content.Context
import org.tensorflow.lite.Interpreter
import java.io.FileInputStream
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.MappedByteBuffer
import java.nio.channels.FileChannel
class TFLiteClassifier(context: Context) {
private val interpreter: Interpreter
private val labels: List<String>
init {
interpreter = Interpreter(loadModelFile(context))
labels = context.assets.open("labels.txt").bufferedReader().readLines()
}
private fun loadModelFile(context: Context): MappedByteBuffer {
val fd = context.assets.openFd("mobilenet_v2_1.0_224.tflite")
val inputStream = FileInputStream(fd.fileDescriptor)
val channel = inputStream.channel
return channel.map(FileChannel.MapMode.READ_ONLY, fd.startOffset, fd.declaredLength)
}
fun classify(bitmap: android.graphics.Bitmap): Pair<String, Float> {
val modelInput = preprocess(bitmap)
val output = Array(1) { FloatArray(1000) }
interpreter.run(modelInput, output)
val maxIdx = output[0].indices.maxByOrNull { output[0][it] } ?: 0
val confidence = output[0][maxIdx]
return labels[maxIdx] to confidence
}
private fun preprocess(bitmap: android.graphics.Bitmap): ByteBuffer {
val resized = android.graphics.Bitmap.createScaledBitmap(bitmap, 224, 224, true)
val buffer = ByteBuffer.allocateDirect(4 * 224 * 224 * 3)
buffer.order(ByteOrder.nativeOrder())
val pixels = IntArray(224 * 224)
resized.getPixels(pixels, 0, 224, 0, 0, 224, 224)
for (pixel in pixels) {
val r = (pixel shr 16 and 0xFF) / 255.0f
val g = (pixel shr 8 and 0xFF) / 255.0f
val b = (pixel and 0xFF) / 255.0f
buffer.putFloat(r)
buffer.putFloat(g)
buffer.putFloat(b)
}
resized.recycle()
return buffer
}
fun close() {
interpreter.close()
}
}
// CameraAnalyzer.kt - 实时帧处理
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
import android.graphics.Bitmap
import android.graphics.Matrix
class CameraAnalyzer(
private val classifier: TFLiteClassifier,
private val onResult: (String, Float) -> Unit
) : ImageAnalysis.Analyzer {
override fun analyze(image: ImageProxy) {
val bitmap = image.toBitmap()
val rotated = rotateBitmap(bitmap, image.imageInfo.rotationDegrees)
val (label, confidence) = classifier.classify(rotated)
onResult(label, confidence)
rotated.recycle()
bitmap.recycle()
image.close()
}
private fun ImageProxy.toBitmap(): Bitmap {
val buffer = planes[0].buffer
val bytes = ByteArray(buffer.remaining())
buffer.get(bytes)
return android.graphics.BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
}
private fun rotateBitmap(src: Bitmap, degrees: Int): Bitmap {
if (degrees == 0) return src
val matrix = Matrix().apply { postRotate(degrees.toFloat()) }
return Bitmap.createBitmap(src, 0, 0, src.width, src.height, matrix, true)
}
}
// MainActivity.kt - 相机绑定与UI更新
import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageAnalysis
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.content.ContextCompat
class MainActivity : AppCompatActivity() {
private lateinit var classifier: TFLiteClassifier
private lateinit var resultText: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
resultText = findViewById(R.id.resultText)
classifier = TFLiteClassifier(this)
startCamera()
}
private fun startCamera() {
val providerFuture = ProcessCameraProvider.getInstance(this)
providerFuture.addListener({
val provider = providerFuture.get()
val preview = androidx.camera.core.Preview.Builder().build()
val analysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
analysis.setAnalyzer(ContextCompat.getMainExecutor(this), CameraAnalyzer(classifier) { label, conf ->
runOnUiThread {
resultText.text = String.format("%s %.1f%%", label, conf * 100)
}
})
provider.unbindAll()
provider.bindToLifecycle(this, CameraSelector.DEFAULT_BACK_CAMERA, preview, analysis)
}, ContextCompat.getMainExecutor(this))
}
override fun onDestroy() {
super.onDestroy()
classifier.close()
}
}
布局与配置
<!-- activity_main.xml 核心部分 -->
<androidx.camera.view.PreviewView
android:id="@+id/previewView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView
android:id="@+id/resultText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:textSize="20sp"
android:textColor="#FFFFFF"
android:background="#80000000"
android:padding="8dp" />
// build.gradle (app级别) 关键依赖
dependencies {
implementation 'org.tensorflow:tensorflow-lite:2.14.0'
implementation 'androidx.camera:camera-core:1.3.0'
implementation 'androidx.camera:camera-camera2:1.3.0'
implementation 'androidx.camera:camera-lifecycle:1.3.0'
implementation 'androidx.camera:camera-view:1.3.0'
}
运行与验证
- 将
mobilenet_v2_1.0_224.tflite与labels.txt放入app/src/main/assets/目录 - 连接Android设备(API 24+),点击Run构建安装
- 授予相机权限,将手机摄像头对准常见物体(如杯子、键盘、植物)
通过条件:屏幕底部实时显示类别名称与置信度百分比;对准不同物体时标签随之变化,置信度通常高于50%(光线充足、物体清晰时)。若显示“Unknown”或置信度持续低于30%,检查模型输入是否匹配(224×224、RGB归一化到[0,1])。
常见问题
- 崩溃提示模型加载失败:确认assets目录下文件名完全一致,且模型为有效的TFLite格式
- 预览画面倒转:已通过
rotateBitmap处理旋转角度,若仍异常检查设备方向传感器配置 - 帧率过低:将
ImageAnalysis分辨率设为640×480,或增加setTargetFrameRate(15)限制