在移动端部署计算机视觉模型正变得越来越普遍,而YOLOv11作为最新的目标检测算法之一,以其轻量级和高精度特性成为移动端部署的理想选择。我将通过一个实际案例——加拿大硬币计数应用,详细讲解如何从零开始构建一个完整的YOLOv11安卓应用。
这个项目最吸引人的地方在于它完全在设备端运行,不需要依赖云端服务。这意味着用户可以享受实时检测的同时,还能确保数据隐私。整个流程包括四个关键阶段:模型训练、格式转换、安卓集成和界面开发。每个阶段都有其独特的技术挑战和解决方案。
提示:虽然本教程以硬币检测为例,但同样的技术栈可以应用于任何目标检测场景,如零售商品识别、工业质检等。
硬币检测项目的第一步是准备高质量的训练数据。我建议收集至少200张不同角度、光照条件下的硬币照片。关键是要覆盖各种使用场景:
使用Roboflow进行标注时,我发现采用数字标签(如1代表1加元硬币,2代表25分硬币)可以显著提高标注效率。但务必在项目文档中明确记录标签对应关系,避免后期混淆。
python复制# 示例标注文件(YOLO格式)
0 0.5 0.5 0.2 0.2 # 类别0,中心点(0.5,0.5),宽高0.2
1 0.3 0.7 0.15 0.15 # 类别1,中心点(0.3,0.7),宽高0.15
在Roboflow上训练YOLOv11模型时,有几个关键参数需要特别注意:
训练完成后,应检查以下指标:
安卓平台无法直接运行PyTorch的.pt模型,必须转换为TorchScript格式。这个转换过程可能会遇到几个常见问题:
python复制from ultralytics import YOLO
model = YOLO("best.pt") # 加载训练好的模型
# 关键转换参数
model.export(
format="torchscript",
imgsz=[640,640], # 必须与训练尺寸一致
optimize=True, # 启用优化
half=True # FP16量化
)
转换后的模型会丢失一些Python特性,因此必须:
为了进一步提升移动端性能,可以采用以下优化策略:
python复制quantized_model = torch.quantization.quantize_dynamic(
model, {torch.nn.Linear}, dtype=torch.qint8
)
优化后模型大小通常可减少40-60%,而精度损失控制在2%以内。
使用Android Studio新建项目时,关键依赖包括:
gradle复制dependencies {
implementation 'org.pytorch:pytorch_android_lite:1.12.1'
implementation 'org.pytorch:pytorch_android_torchvision:1.12.1'
// CameraX依赖
implementation "androidx.camera:camera-core:1.2.0"
implementation "androidx.camera:camera-camera2:1.2.0"
implementation "androidx.camera:camera-lifecycle:1.2.0"
implementation "androidx.camera:camera-view:1.2.0"
}
项目结构应包含:
kotlin复制object ImageProcessor {
private const val TARGET_SIZE = 640
fun processForInference(bitmap: Bitmap): Tensor {
val resized = Bitmap.createScaledBitmap(
bitmap, TARGET_SIZE, TARGET_SIZE, true
)
return TensorImageUtils.bitmapToFloat32Tensor(
resized,
floatArrayOf(0f, 0f, 0f), // 均值
floatArrayOf(255f, 255f, 255f) // 标准差
)
}
}
kotlin复制class YoloModelManager(context: Context) {
private val module: Module by lazy {
val modelFile = FileUtil.modelFile(context, "weights_torchscript.pt")
Module.load(modelFile.absolutePath)
}
fun detect(imageTensor: Tensor): List<Detection> {
val output = module.forward(IValue.from(imageTensor))
return parseYoloOutput(output)
}
private fun parseYoloOutput(output: IValue): List<Detection> {
val tensor = output.toTensor()
val data = tensor.dataAsFloatArray()
// 解析YOLO格式输出...
}
}
kotlin复制class CameraManager(
private val context: Context,
private val lifecycleOwner: LifecycleOwner
) {
private val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
fun startCamera(
previewView: PreviewView,
analysisExecutor: Executor,
analysisCallback: (Bitmap) -> Unit
) {
val preview = Preview.Builder().build().also {
it.setSurfaceProvider(previewView.surfaceProvider)
}
val imageAnalysis = ImageAnalysis.Builder()
.setBackpressureStrategy(STRATEGY_KEEP_ONLY_LATEST)
.build()
.also {
it.setAnalyzer(analysisExecutor) { imageProxy ->
val bitmap = imageProxy.toBitmap()
analysisCallback(bitmap)
imageProxy.close()
}
}
val cameraProvider = cameraProviderFuture.get()
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
lifecycleOwner,
CameraSelector.DEFAULT_BACK_CAMERA,
preview,
imageAnalysis
)
}
}
kotlin复制viewModelScope.launch(Dispatchers.Default) {
val results = modelManager.detect(tensor)
withContext(Dispatchers.Main) {
updateUI(results)
}
}
安卓设备内存有限,必须注意:
kotlin复制override fun onDestroy() {
modelManager.cleanUp() // 释放模型资源
super.onDestroy()
}
症状:应用崩溃,日志显示"Unable to load model"
症状:检测框错位或类别错误
症状:界面卡顿或延迟高
kotlin复制// 在构建Module时启用GPU
Module.load(modulePath, Device.GPU)
基础功能实现后,可以考虑以下增强功能:
对于想要进一步优化的开发者,我建议:
这个项目的完整代码我已经在GitHub上开源,包含详细的配置说明和常见问题解答。在实际开发过程中,最重要的是保持耐心,因为移动端AI部署的每个环节都可能遇到意想不到的挑战。我花了近两周时间才解决所有兼容性问题,但最终的效果证明这些努力是值得的。