欢迎光临
我们一直在努力

Android App如何集成yolo?

你想在Android App中集成YOLO实现移动端实时目标检测,核心是解决模型移动端适配、Android工程集成、实时推理优化三大问题。需要注意的是:YOLOv1不适合移动端(设计为PC端GPU推理,无轻量化优化),移动端主流选择是YOLOv5/YOLOv8的轻量化版本(转TensorFlow Lite/ONNX格式),以下是完整、可落地的集成方案(以YOLOv8 + TensorFlow Lite为例,最成熟、适配性最好)。

一、核心选型(移动端适配关键)

选型维度推荐方案原因
YOLO版本 YOLOv8n(nano版)/YOLOv5s(small版) 轻量化,参数量少,移动端推理速度快(10~30FPS)
模型格式 TensorFlow Lite(TFLite)(推荐)/ONNX Runtime for Android TFLite是Google专为移动端优化的框架,支持GPU/NNAPI加速,集成成本最低
推理框架 TensorFlow Lite Android Library 官方维护,适配Android系统,无需复杂NDK配置
输入尺寸 320×320(优先)/416×416 平衡精度和速度,320×320在中低端手机也能实时(≥15FPS)

二、环境准备

1. 开发工具
  • Android Studio 2022.3.1+(Arctic Fox及以上)
  • NDK 25+(可选,用于C++推理优化)
  • Python 3.8+(用于模型转换)
2. 依赖库(Android端)

在app/build.gradle中添加依赖:

dependencies {
// TensorFlow Lite核心库(核心推理)
implementation 'org.tensorflow:tensorflow-lite:2.15.0'
// TFLite GPU加速(可选,提升推理速度2~3倍)
implementation 'org.tensorflow:tensorflow-lite-gpu:2.15.0'
// TFLite NNAPI加速(适配安卓神经网络API)
implementation 'org.tensorflow:tensorflow-lite-support:0.4.4'
// 相机相关(用于实时预览)
implementation 'androidx.camera:camera-camera2:1.3.0'
implementation 'androidx.camera:camera-lifecycle:1.3.0'
implementation 'androidx.camera:camera-view:1.3.0'
// 图像处理
implementation 'com.github.bumptech.glide:glide:4.16.0'
}

三、第一步:模型转换(YOLO→TFLite)

先将YOLO官方PyTorch模型转为TFLite格式(以YOLOv8n为例):

1. 安装依赖(Python端)

pip install ultralytics onnx tensorflow==2.15.0

2. 模型转换代码(Python)

from ultralytics import YOLO
import tensorflow as tf

# 1. 加载YOLOv8n预训练模型
model = YOLO('yolov8n.pt')

# 2. 导出为ONNX格式(中间格式)
model.export(format='onnx', imgsz=320, opset=12) # imgsz=320 对应移动端输入尺寸

# 3. ONNX转TFLite(量化优化,减小模型体积+提升速度)
onnx_model_path = 'yolov8n.onnx'
tflite_model_path = 'yolov8n_320.tflite'

# 转换并启用INT8量化(可选,模型体积减小4倍,速度提升~50%)
converter = tf.lite.TFLiteConverter.from_onnx_model(onnx_model_path)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
# (可选)添加校准数据集实现INT8量化,需准备少量图片,这里简化为默认优化
tflite_model = converter.convert()

# 保存TFLite模型
with open(tflite_model_path, 'wb') as f:
f.write(tflite_model)

print(f"模型转换完成:{tflite_model_path}")

3. 模型放入Android工程

将转换后的yolov8n_320.tflite复制到Android项目的app/src/main/assets/目录(无assets则新建)。

四、第二步:Android端核心代码实现

1. 权限配置(AndroidManifest.xml)

<!– 相机权限(实时检测) –>
<uses-permission android:name="android.permission.CAMERA" />
<!– 存储权限(可选,检测本地图片) –>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!– 声明相机特性 –>
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />

<!– 启用硬件加速(必须,TFLite GPU加速依赖) –>
<application

android:hardwareAccelerated="true">

</application>

2. 核心工具类:YOLODetector(封装推理逻辑)

import android.content.Context
import android.graphics.Bitmap
import android.graphics.RectF
import org.tensorflow.lite.Interpreter
import org.tensorflow.lite.gpu.CompatibilityList
import org.tensorflow.lite.gpu.GpuDelegate
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.FloatBuffer

class YOLODetector(context: Context) {
// YOLO配置(与模型转换时一致)
private val INPUT_SIZE = 320 // 输入尺寸
private val NUM_CLASSES = 80 // COCO80类(YOLO默认)
private val CONF_THRESHOLD = 0.5f // 置信度阈值
private val NMS_THRESHOLD = 0.3f // NMS阈值

// TFLite解释器
private val interpreter: Interpreter

// 检测结果数据类
data class DetectionResult(
val className: String, // 类别名称
val confidence: Float, // 置信度
val boundingBox: RectF // 边界框(原图坐标)
)

init {
// 1. 加载TFLite模型
val modelBuffer = context.assets.open("yolov8n_320.tflite").readBytes()
// 2. 配置TFLite(启用GPU加速)
val compatList = CompatibilityList()
val options = Interpreter.Options().apply {
if (compatList.isDelegateSupportedOnThisDevice) {
// GPU加速(优先)
val delegate = GpuDelegate(compatList.bestOptionsForThisDevice)
addDelegate(delegate)
} else {
// 备用:CPU多线程
setNumThreads(4)
}
}
interpreter = Interpreter(modelBuffer, options)
}

// 核心:图像预处理 + 模型推理 + 后处理
fun detect(bitmap: Bitmap, originalWidth: Int, originalHeight: Int): List<DetectionResult> {
// 步骤1:图像预处理(匹配YOLO输入要求)
val inputBuffer = preprocessImage(bitmap)

// 步骤2:模型推理
val outputBuffer = runInference(inputBuffer)

// 步骤3:后处理(解析输出 + NMS + 坐标映射到原图)
return postprocessOutput(outputBuffer, originalWidth, originalHeight)
}

// 图像预处理:缩放至320×320 → 归一化 → 转为FloatBuffer
private fun preprocessImage(bitmap: Bitmap): ByteBuffer {
// 缩放为模型输入尺寸
val resizedBitmap = Bitmap.createScaledBitmap(bitmap, INPUT_SIZE, INPUT_SIZE, true)
// 初始化输入缓冲区(3×320×320×4字节(Float))
val inputBuffer = ByteBuffer.allocateDirect(3 * INPUT_SIZE * INPUT_SIZE * 4)
inputBuffer.order(ByteOrder.nativeOrder())

val intValues = IntArray(INPUT_SIZE * INPUT_SIZE)
resizedBitmap.getPixels(intValues, 0, resizedBitmap.width, 0, 0, resizedBitmap.width, resizedBitmap.height)

var pixelIndex = 0
for (y in 0 until INPUT_SIZE) {
for (x in 0 until INPUT_SIZE) {
val pixel = intValues[pixelIndex++]
// 归一化:RGB → 0~1(YOLO输入要求)
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
// 存入缓冲区(CHW格式:通道优先)
inputBuffer.putFloat(r)
inputBuffer.putFloat(g)
inputBuffer.putFloat(b)
}
}
resizedBitmap.recycle()
return inputBuffer
}

// 模型推理
private fun runInference(inputBuffer: ByteBuffer): Array<FloatArray> {
// YOLOv8输出:1×84×8400(84=4坐标+80类别)
val outputShape = interpreter.getOutputTensor(0).shape()
val outputBuffer = Array(1) { FloatArray(outputShape[1] * outputShape[2]) }
// 执行推理
interpreter.run(inputBuffer, outputBuffer)
return outputBuffer
}

// 后处理:解析输出 + NMS + 坐标映射
private fun postprocessOutput(
outputBuffer: Array<FloatArray>,
originalWidth: Int,
originalHeight: Int
): List<DetectionResult> {
val results = mutableListOf<DetectionResult>()
val output = outputBuffer[0]

// YOLOv8输出解析:每个检测框占84个值(x,y,w,h,class1…class80)
val numBoxes = output.size / (4 + NUM_CLASSES)
for (i in 0 until numBoxes) {
val baseIndex = i * (4 + NUM_CLASSES)
// 提取坐标(模型输出为相对值:0~1)
val x = output[baseIndex]
val y = output[baseIndex + 1]
val w = output[baseIndex + 2]
val h = output[baseIndex + 3]

// 找最大置信度的类别
var maxConf = 0.0f
var classId = 1
for (c in 0 until NUM_CLASSES) {
val conf = output[baseIndex + 4 + c]
if (conf > maxConf) {
maxConf = conf
classId = c
}
}

// 过滤低置信度
if (maxConf < CONF_THRESHOLD || classId == 1) continue

// 坐标映射到原图(核心!匹配YOLO坐标逻辑)
// 步骤1:将模型输出的中心坐标+宽高转为原图绝对坐标
val scaleX = originalWidth.toFloat() / INPUT_SIZE
val scaleY = originalHeight.toFloat() / INPUT_SIZE

// 模型输出的x/y是相对于320×320的中心坐标,需转换为原图坐标
val centerX = x * INPUT_SIZE * scaleX
val centerY = y * INPUT_SIZE * scaleY
val width = w * INPUT_SIZE * scaleX
val height = h * INPUT_SIZE * scaleY

// 步骤2:转为左上角/右下角坐标(适配Android画布)
val left = centerX width / 2
val top = centerY height / 2
val right = centerX + width / 2
val bottom = centerY + height / 2

// 限制坐标在原图范围内
val boundingBox = RectF(
left.coerceIn(0f, originalWidth.toFloat()),
top.coerceIn(0f, originalHeight.toFloat()),
right.coerceIn(0f, originalWidth.toFloat()),
bottom.coerceIn(0f, originalHeight.toFloat())
)

// 添加结果
results.add(
DetectionResult(
className = getClassName(classId),
confidence = maxConf,
boundingBox = boundingBox
)
)
}

// NMS去重(消除重复检测框)
return applyNMS(results)
}

// NMS非极大值抑制
private fun applyNMS(results: List<DetectionResult>): List<DetectionResult> {
val sortedResults = results.sortedByDescending { it.confidence }
val keep = mutableListOf<DetectionResult>()

while (sortedResults.isNotEmpty()) {
val best = sortedResults.first()
keep.add(best)

// 移除与best IOU>阈值的框
val remaining = sortedResults.filter {
iou(best.boundingBox, it.boundingBox) < NMS_THRESHOLD
}
if (remaining.isEmpty()) break
}
return keep
}

// 计算IOU
private fun iou(box1: RectF, box2: RectF): Float {
val intersectLeft = maxOf(box1.left, box2.left)
val intersectTop = maxOf(box1.top, box2.top)
val intersectRight = minOf(box1.right, box2.right)
val intersectBottom = minOf(box1.bottom, box2.bottom)

val intersectArea = maxOf(0f, intersectRight intersectLeft) * maxOf(0f, intersectBottom intersectTop)
val box1Area = (box1.right box1.left) * (box1.bottom box1.top)
val box2Area = (box2.right box2.left) * (box2.bottom box2.top)

return intersectArea / (box1Area + box2Area intersectArea + 1e-6f)
}

// COCO80类名称映射(简化版,可补全所有类别)
private fun getClassName(classId: Int): String {
val classes = arrayOf(
"person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat",
"traffic light", "fire hydrant", "stop sign", "parking meter", "bench", "bird", "cat",
"dog", "horse", "sheep", "cow", "elephant", "bear", "zebra", "giraffe", "backpack",
"umbrella", "handbag", "tie", "suitcase", "frisbee", "skis", "snowboard", "sports ball",
"kite", "baseball bat", "baseball glove", "skateboard", "surfboard", "tennis racket",
"bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple",
"sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut", "cake",
"chair", "couch", "potted plant", "bed", "dining table", "toilet", "tv", "laptop",
"mouse", "remote", "keyboard", "cell phone", "microwave", "oven", "toaster", "sink",
"refrigerator", "book", "clock", "vase", "scissors", "teddy bear", "hair drier", "toothbrush"
)
return if (classId in classes.indices) classes[classId] else "unknown"
}

// 释放资源
fun close() {
interpreter.close()
}
}

3. 实时相机检测(Activity实现)

import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Size
import android.view.Surface
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.example.yolodemo.databinding.ActivityMainBinding
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors

class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var yoloDetector: YOLODetector
private lateinit var cameraExecutor: ExecutorService

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)

// 初始化YOLO检测器
yoloDetector = YOLODetector(this)
cameraExecutor = Executors.newSingleThreadExecutor()

// 检查权限并启动相机
if (allPermissionsGranted()) {
startCamera()
} else {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.CAMERA),
REQUEST_CODE_PERMISSIONS
)
}
}

// 启动相机预览+实时分析
private fun startCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)

cameraProviderFuture.addListener({
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

// 1. 预览配置
val preview = Preview.Builder()
.setTargetResolution(Size(640, 480)) // 预览分辨率
.build()
.also { it.setSurfaceProvider(binding.viewFinder.surfaceProvider) }

// 2. 图像分析(实时检测)
val imageAnalyzer = ImageAnalysis.Builder()
.setTargetResolution(Size(640, 480))
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) // 只处理最新帧
.build()
.also {
it.setAnalyzer(cameraExecutor, YoloImageAnalyzer(yoloDetector) { results ->
// 主线程更新UI(绘制检测框)
runOnUiThread {
binding.overlay.setResults(results)
binding.overlay.invalidate()
}
})
}

// 选择后置摄像头
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

try {
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageAnalyzer
)
} catch (e: Exception) {
e.printStackTrace()
}
}, ContextCompat.getMainExecutor(this))
}

// 图像分析器:将Camera图像转为Bitmap并传入YOLO检测
private class YoloImageAnalyzer(
private val detector: YOLODetector,
private val onResult: (List<YOLODetector.DetectionResult>) -> Unit
) : ImageAnalysis.Analyzer {
override fun analyze(imageProxy: ImageProxy) {
val rotationDegrees = imageProxy.imageInfo.rotationDegrees
// 将ImageProxy转为Bitmap(适配旋转)
val bitmap = imageProxy.toBitmap(rotationDegrees)
// 执行检测
val results = detector.detect(bitmap, bitmap.width, bitmap.height)
// 回调结果
onResult(results)
// 关闭ImageProxy释放资源
imageProxy.close()
}

// ImageProxy转Bitmap(工具方法)
private fun ImageProxy.toBitmap(rotationDegrees: Int): Bitmap {
val buffer = planes[0].buffer
val bytes = ByteArray(buffer.remaining())
buffer.get(bytes)
val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
// 旋转适配
val matrix = android.graphics.Matrix().apply {
postRotate(rotationDegrees.toFloat())
if (rotationDegrees == 90 || rotationDegrees == 270) {
postScale(1f, 1f, bitmap.width / 2f, bitmap.height / 2f)
}
}
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
}
}

// 权限检查
private fun allPermissionsGranted() = ContextCompat.checkSelfPermission(
this, Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED

override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == REQUEST_CODE_PERMISSIONS) {
if (allPermissionsGranted()) {
startCamera()
} else {
Toast.makeText(this, "权限被拒绝,无法使用相机", Toast.LENGTH_SHORT).show()
finish()
}
}
}

override fun onDestroy() {
super.onDestroy()
cameraExecutor.shutdown()
yoloDetector.close()
}

companion object {
private const val REQUEST_CODE_PERMISSIONS = 10
}
}

4. 绘制检测框的OverlayView(自定义View)

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.RectF
import android.util.AttributeSet
import android.view.View

class OverlayView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

private val paint = Paint().apply {
color = Color.RED
strokeWidth = 4f
style = Paint.Style.STROKE
textSize = 30f
}
private var results: List<YOLODetector.DetectionResult> = emptyList()

fun setResults(results: List<YOLODetector.DetectionResult>) {
this.results = results
}

override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// 绘制每个检测框+类别+置信度
results.forEach { result ->
// 绘制边界框
canvas.drawRect(result.boundingBox, paint)
// 绘制类别+置信度
val text = "${result.className} ${String.format("%.2f", result.confidence)}"
canvas.drawText(
text,
result.boundingBox.left,
result.boundingBox.top 10,
paint
)
}
}
}

五、移动端优化技巧(关键!提升速度/降低功耗)

  • 模型量化:使用INT8量化(模型体积减小4倍,推理速度提升50%+),上文Python转换代码已支持;
  • GPU加速:优先启用TFLite GPU Delegate(推理速度提升2~3倍,功耗降低);
  • 输入尺寸优化:中低端手机用320×320,高端机用416×416(平衡精度/速度);
  • 线程优化:推理放在子线程,避免阻塞主线程;相机分析用STRATEGY_KEEP_ONLY_LATEST只处理最新帧;
  • 图像预处理优化:使用RenderScript/NDK加速图像缩放,替代Bitmap.createScaledBitmap;
  • 裁剪推理区域:仅检测画面中心区域(如人像检测),减少计算量。
  • 总结

  • 核心流程:选轻量化YOLO版本(v8n/v5s)→ 转TFLite → Android工程集成 → 预处理+推理+后处理 → UI展示;
  • 关键适配:移动端需做模型量化、GPU加速、坐标映射(适配Android图像坐标系);
  • 性能目标:中低端安卓机(骁龙6系)320×320尺寸可达到15~20FPS,高端机(骁龙8系)可达30+FPS;
  • 扩展方向:可替换为ONNX Runtime for Android(支持更多YOLO版本),或集成自定义数据集训练的YOLO模型(只需修改类别映射)。
  • 赞(0)
    未经允许不得转载:171主机测评 » Android App如何集成yolo?
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址