将 YOLOv10 部署至 LiteRT:在 Android 上使用 Google AI Edge 进行目标检测

点击下方卡片,关注“小白玩转Python”公众号

介绍

在大型语言模型(LLMs)兴起之前,边缘 AI 是一个热门话题,这得益于其在设备上直接运行机器学习模型的显著能力。这并不是说这个话题已经失去了相关性;事实上,许多科技巨头现在正将注意力转向在移动平台上部署 LLMs。

虽然我们今天不会讨论生成性 AI,但我们将重新审视经典的计算机视觉任务——目标检测。这篇博客提供了一个全面的教程,介绍如何将 Ultralytics 的最新 YOLOv10 目标检测模型转换和量化为 LiteRT(前称 TensorFlow Lite)格式,在生成的 LiteRT 模型上运行推理,并将其部署在 Android 上进行实时检测。

如果你有目标检测和在设备上部署模型的经验,你可能想知道为什么 MobileNet SSD 或 EfficientDet Lite 不是最佳选择。原因如下:

为什么选择 YOLOv10 而不是其他?

虽然 MobileNet SSD 和 EfficientDet Lite 表现良好,但它们在检测较小物体时存在困难。然而,YOLOv10 可以快速有效地检测到较小的物体。

在我们开始之前,让我们简要了解一下 YOLOv10 模型以及 LiteRT 是什么。

YOLOv10

作为 YOLO 模型家族的高级版本,YOLOv10 是实时目标检测任务的最新首选。其增强的架构和训练技术使其特别适合边缘部署。

bbbe5022c865fd620cd085ce52451d63.pngYOLOv10 模型变体

在所有变体中,纳米版本(YOLOv10-N)最适合移动部署,因为它能够在资源受限的环境中运行。在此处了解更多关于 YOLOv10 的信息。

意:我们将使用在 COCO 数据集上训练过的预训练 YOLOv10-N 模型。

LiteRT

LiteRT,前称 TensorFlow Lite,是 Google 的高性能设备上 AI 运行时。它允许你轻松地将 TensorFlow、PyTorch 和 JAX 模型转换并以 TFLite 格式运行。现在你已经有了概览,让我们深入编码部分。这是我们项目的流程:

2c532d26ac521d41e1182b351d51e62b.png流程:在 Android 上将 YOLOv10-N 转换为 LiteRT

步骤 1:模型转换

几年前,将 YOLO 模型转换为 TF Lite 是相当具有挑战性的,因为模型的复杂步骤和显著的架构差异。然而,现在情况已不再如此,因为 Ultralytics 现在为你处理了所有的繁重工作。通过克隆此仓库开始获取全部代码:https://github.com/NSTiwari/YOLOv10-LiteRT-Android

# Install Ultralytics.
!pip install ultralytics


# Load the YOLOv10n model.
model = YOLO("yolov10n.pt")


# Export the model to LiteRT (TF Lite) format.
model.export(format="tflite")

export() 函数接受以下参数:

  • format:模型的输出格式,如 tflite、onnx、tfjs、openvino、torchscript 等。

  • imgsz:模型输入的期望图像大小(高度、宽度)。默认为 640 x 640。

  • int8:启用模型的 INT8 量化以加快推理速度。默认设置为 false。

你可以根据用例调整许多其他参数,但上面提到的参数现在应该足够好了。在仅仅两行代码中,你可以完全将 YOLO PyTorch 模型转换为 LiteRT 格式。以下是转换过程的背景:PyTorch → ONNX 图 → TensorFlow SavedModel → LiteRT。

步骤 2:解释 LiteRT 模型

Google AI Edge 提供了模型探索器,这是一个类似于 Netron 的模型可视化工具,提供对模型图和架构的详细洞察。

# Install Model Explorer.
!pip install ai-edge-model-explorer


LITE_RT_EXPORT_PATH = "yolov10n_saved_model/" # @param {type : 'string'}
LITE_RT_MODEL = "yolov10n_float16.tflite" # @param {type : 'string'}


LITE_RT_MODEL_PATH = LITE_RT_EXPORT_PATH + LITE_RT_MODEL


# Load the LiteRT model in Model Explorer.
model_explorer.visualize(LITE_RT_MODEL_PATH)

fd121b0d3ae3b5e9d5035632b9037af9.png

在模型探索器上可视化的 yolov10_float16.tflite

如果你查看输出张量,你会看到只有一个节点(Identity),形状为 [1, 300, 6],与 MobileNet SSD 模型不同,后者通常有四个输出张量。你也可以使用 AI Edge LiteRT 库来解释模型。

# Install Google AI Edge LiteRT
!pip install ai-edge-litert


# Load the TF Lite model.
interpreter = Interpreter(model_path = LITE_RT_MODEL_PATH)
interpreter.allocate_tensors()


# Get input and output details.
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()


print(f"Model input size: {input_size}")
print(f"Output tensor shape: {output_details[0]['shape']}")

模型输入大小为 640,输出张量形状 [1, 300, 6] 表示批次大小(1)、每张图片的最大检测数量(300)以及值 [xmin, ymin, xmax, ymax, score, class]。

步骤 3:推理转换后的 LiteRT 模型

现在是推理时间。现在我们已经解释了模型的架构,我们可以继续在 Python 上使用 OpenCV 进行推理。

注意:导出的 LiteRT 模型的结果需要后处理,包括归一化边界框坐标并将类 ID 映射到相应的标签。

在 Colab 笔记本中,我包含了一些实用函数来处理所有必需的后处理步骤。

def detect(input_data, is_video_frame=False):
    input_size = input_details[0]['shape'][1]


    if is_video_frame:
        original_height, original_width = input_data.shape[:2]
        image = cv2.cvtColor(input_data, cv2.COLOR_BGR2RGB)
        image = cv2.resize(image, (input_size, input_size))
        image = image / 255.0
    else:
        image, (original_height, original_width) = load_image(input_data, input_size)


    interpreter.set_tensor(input_details[0]['index'], np.expand_dims(image, axis=0).astype(np.float32))
    interpreter.invoke()


    output_data = [interpreter.get_tensor(detail['index']) for detail in output_details]
    return output_data, (original_height, original_width)






# Postprocess the output.
def postprocess_output(output_data, original_dims, labels, confidence_threshold):
  output_tensor = output_data[0]
  detections = []
  original_height, original_width = original_dims


  for i in range(output_tensor.shape[1]):
    box = output_tensor[0, i, :4]
    confidence = output_tensor[0, i, 4]
    class_id = int(output_tensor[0, i, 5])


    if confidence > confidence_threshold:
      x_min = int(box[0] * original_width)
      y_min = int(box[1] * original_height)
      x_max = int(box[2] * original_width)
      y_max = int(box[3] * original_height)


      label_name = labels.get(str(class_id), "Unknown")


      detections.append({
          "box": [y_min, x_min, y_max, x_max],
          "score": confidence,
          "class": class_id,
          "label": label_name
      })


  return detections

Colab 笔记本支持对图像和视频进行推理。以下是我获得的一些结果。

7fc74d0800ac8b823ee206456f846c44.png在图像上的推理

1d8e99610ec3711c940214c055a90038.png在图像上的推理

ae26c45c000c628c50b5d8a07ef0b102.png在视频上的推理

令人印象深刻的是,转换后的 LiteRT 模型在量化后仍然表现出色,有效地检测到即使是很小的物体。现在,我们准备将模型部署在 Android 上进行设备上推理。

步骤 4:在 Android 上部署模型

在步骤 1 中,我们克隆了仓库来运行 Colab 笔记本,其中也包括了一个示例 Android 应用。笔记本中的最后一步让你可以下载 LiteRT 模型。下载后,将其复制到 Android 应用的 assets 文件夹中。默认文件名为 yolov10n_float16.tflite。如果你使用不同的文件名,请确保相应地更新 Constants.kt 文件中的第 4 行。

// Change this with your TF Lite model name.
const val MODEL_PATH = "yolov10n_float16.tflite"

Detector.kt 文件包含执行推理的逻辑,以及提取检测到的对象的边界框、置信度得分和标签。

// Detects the objects.
class Detector(
    private val context: Context,
    private val modelPath: String,
    private val labelPath: String?,
    private val detectorListener: DetectorListener,
    private val message: (String) -> Unit
) {
    private var interpreter: Interpreter
    private var labels = mutableListOf<String>()


    private var tensorWidth = 0
    private var tensorHeight = 0
    private var numChannel = 0
    private var numElements = 0


    private val imageProcessor = ImageProcessor.Builder()
        .add(NormalizeOp(INPUT_MEAN, INPUT_STANDARD_DEVIATION))
        .add(CastOp(INPUT_IMAGE_TYPE))
        .build()


    init {
        val options = Interpreter.Options().apply{
            this.setNumThreads(4)
        }


        val model = FileUtil.loadMappedFile(context, modelPath)
        interpreter = Interpreter(model, options)


        labels.addAll(extractNamesFromMetadata(model))
        if (labels.isEmpty()) {
            if (labelPath == null) {
                message("Model not contains metadata, provide LABELS_PATH in Constants.kt")
                labels.addAll(MetaData.TEMP_CLASSES)
            } else {
                labels.addAll(extractNamesFromLabelFile(context, labelPath))
            }
        }


        labels.forEach(::println)


        val inputShape = interpreter.getInputTensor(0)?.shape()
        val outputShape = interpreter.getOutputTensor(0)?.shape()


        if (inputShape != null) {
            tensorWidth = inputShape[1]
            tensorHeight = inputShape[2]


            // If in case input shape is in format of [1, 3, ..., ...]
            if (inputShape[1] == 3) {
                tensorWidth = inputShape[2]
                tensorHeight = inputShape[3]
            }
        }


        if (outputShape != null) {
            numElements = outputShape[1]
            numChannel = outputShape[2]
        }
    }


// Extracts bounding box, label, confidence.
private fun bestBox(array: FloatArray) : List<BoundingBox> {
    val boundingBoxes = mutableListOf<BoundingBox>()
    for (r in 0 until numElements) {
        val cnf = array[r * numChannel + 4]
        if (cnf > CONFIDENCE_THRESHOLD) {
            val x1 = array[r * numChannel]
            val y1 = array[r * numChannel + 1]
            val x2 = array[r * numChannel + 2]
            val y2 = array[r * numChannel + 3]
            val cls = array[r * numChannel + 5].toInt()
            val clsName = labels[cls]
            boundingBoxes.add(
                BoundingBox(
                    x1 = x1, y1 = y1, x2 = x2, y2 = y2,
                    cnf = cnf, cls = cls, clsName = clsName
                )
            )
        }
    }
    return boundingBoxes
}

之后,OverlayView.kt 归一化边界框坐标并将它们叠加在摄像头流上以可视化结果。

class OverlayView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {


    private var results = listOf<BoundingBox>()
    private val boxPaint = Paint()
    private val textBackgroundPaint = Paint()
    private val textPaint = Paint()


    private var bounds = Rect()
    private val colorMap = mutableMapOf<String, Int>()


    init {
        initPaints()
    }


    fun clear() {
        results = listOf()
        textPaint.reset()
        textBackgroundPaint.reset()
        boxPaint.reset()
        invalidate()
        initPaints()
    }


    private fun initPaints() {
        textBackgroundPaint.color = Color.WHITE
        textBackgroundPaint.style = Paint.Style.FILL
        textBackgroundPaint.textSize = 42f


        textPaint.color = Color.WHITE
        textPaint.style = Paint.Style.FILL
        textPaint.textSize = 42f
    }


    override fun draw(canvas: Canvas) {
        super.draw(canvas)


        results.forEach { boundingBox ->
            // Get or create a color for this label
            val color = getColorForLabel(boundingBox.clsName)
            boxPaint.color = color
            boxPaint.strokeWidth = 8F
            boxPaint.style = Paint.Style.STROKE


            val left = boundingBox.x1 * width
            val top = boundingBox.y1 * height
            val right = boundingBox.x2 * width
            val bottom = boundingBox.y2 * height


            canvas.drawRoundRect(left, top, right, bottom, 16f, 16f, boxPaint)


            val drawableText = "${boundingBox.clsName} ${Math.round(boundingBox.cnf * 100.0) / 100.0}"


            textBackgroundPaint.getTextBounds(drawableText, 0, drawableText.length, bounds)
            val textWidth = bounds.width()
            val textHeight = bounds.height()


            val textBackgroundRect = RectF(
                left,
                top,
                left + textWidth + BOUNDING_RECT_TEXT_PADDING,
                top + textHeight + BOUNDING_RECT_TEXT_PADDING
            )
            textBackgroundPaint.color = color // Set background color same as bounding box
            canvas.drawRoundRect(textBackgroundRect, 8f, 8f, textBackgroundPaint)


            canvas.drawText(drawableText, left, top + textHeight, textPaint)
        }
    }


    private fun getColorForLabel(label: String): Int {
        return colorMap.getOrPut(label) {
            // Generate a random color or you can use a predefined set of colors
            Color.rgb((0..255).random(), (0..255).random(), (0..255).random())
        }
    }


    fun setResults(boundingBoxes: List<BoundingBox>) {
        results = boundingBoxes
        invalidate()
    }


    companion object {
        private const val BOUNDING_RECT_TEXT_PADDING = 8
    }
}

最后,在 Android Studio 中打开项目,构建它,然后将手机连接起来安装应用。这是 Android 上的最终输出。推理时间接近 300 毫秒。

a124ed67cb645a56645a9189b0642a83.png

Android 上的实时目标检测

·  END  ·

🌟 想要变身计算机视觉小能手?快来「小白玩转Python」公众号!

回复Python视觉实战项目,解锁31个超有趣的视觉项目大礼包!🎁

857f27ebc4058163bf8b5fccd682fe3a.png

本文仅供学习交流使用,如有侵权请联系作者删除

<think>好的,我现在需要帮助用户解决在Python中安装psutil和ai_edge_litert模块时遇到的ModuleNotFoundError问题。根据用户提供的信息和之前的回答,我需要综合现有的引用和知识来给出正确的解决方案。 首先,用户提到了两个模块:psutil和ai_edge_litert。我需要分别处理这两个模块的安装问题。psutil是一个比较常见的库,通常可以通过pip直接安装。但根据之前的引用[3]和[5],用户可能在安装psutil时遇到系统依赖问题,比如缺少libffi-devel之类的库。此外,用户还可能遇到虚拟环境的问题,比如模块安装到了全局环境而不是当前使用的虚拟环境中,如引用[4]提到的__name__问题可能涉及到执行环境的不同。 对于ai_edge_litert,这个模块名称看起来可能有拼写错误,或者是一个较新的或不太常见的库。我需要确认正确的模块名称。例如,用户可能指的是ai_edge_lite或者ai_edge_litert的正确拼写。如果直接使用pip安装ai_edge_litert失败,可能需要检查是否存在拼写错误,或者该库是否在PyPI上可用。如果不存在,可能需要从其他源安装,比如GitHub仓库或私有仓库。 接下来,我需要参考用户提供的引用内容。例如,引用[1]和[4]讨论了模块导入和__name__的使用,这可能与模块的路径和导入方式有关,但当前的问题是关于模块未找到,所以更相关的是安装和环境配置。引用[2]提到了关于__file__变量的问题,这可能与脚本执行路径有关,但暂时不直接相关。引用[3]和[5]提供了psutil的使用示例,说明用户可能在正确安装后需要使用这些功能。 用户在上一次的问题中已经得到关于psutil安装的详细指南,包括系统依赖、虚拟环境配置等。现在需要将这些信息整合,并扩展到ai_edge_litert模块。可能需要检查用户是否遵循了之前的步骤,例如是否在正确的环境中安装,是否有权限问题,或者是否需要额外的依赖。 对于ai_edge_litert,首先需要确认正确的模块名称。假设用户正确拼写,但该模块不在PyPI上,可能需要使用不同的安装方法,比如从GitHub克隆并安装,或者使用特定的pip命令。如果用户拼写错误,比如应该是ai_edge_lite,那么需要指出正确的名称。 另外,用户可能遇到的共同问题包括网络问题导致安装失败,比如使用代理或镜像源。在之前的回答中提到了使用清华镜像源,这可能需要再次建议用户尝试。 总结解决步骤: 1. 安装psutil: - 确保使用正确的pip版本,在虚拟环境中安装。 - 处理系统依赖,如libffi-devel。 - 使用镜像源加速安装。 2. 安装ai_edge_litert: - 检查模块名称是否正确,是否存在拼写错误。 - 尝试从PyPI安装,若失败则寻找其他安装源。 - 可能需要从GitHub或私有仓库安装,使用pip的git+https或指定URL。 此外,需要验证安装后的模块是否可以正确导入,并提供测试代码示例,如之前的回答中的环境验证脚本。 需要注意用户是否在虚拟环境中操作,以及权限问题,如使用--user选项或sudo。 最后,生成相关问题,帮助用户进一步解决可能遇到的问题,如依赖冲突、特定环境下的安装问题等。</think>### **Python模块安装问题解决方案:$psutil$与$ai\_edge\_litert$** --- #### **1. 模块安装通用流程** ```bash # 基础安装命令(适用于绝大多数Python包) pip install psutil ai_edge_litert # 若提示权限不足(Linux/macOS) sudo pip install psutil ai_edge_litert # 系统级安装 # 或 pip install --user psutil ai_edge_litert # 用户级安装 # 若使用虚拟环境 python -m venv myenv && source myenv/bin/activate # 创建并激活 pip install psutil ai_edge_litert # 在虚拟环境中安装 ``` --- #### **2. 特殊模块处理技巧** **(1)$psutil$深度配置** 当出现C扩展编译错误时(常见于旧系统): ```bash # 先安装系统依赖 # Ubuntu/Debian sudo apt-get install python3-dev gcc libffi-dev -y # CentOS/RHEL sudo yum install python3-devel gcc libffi-devel -y # 再重新安装模块 pip install --no-cache-dir psutil ``` **(2)$ai\_edge\_litert$特殊处理** 若标准安装失败,可能是包名拼写错误或私有仓库模块: ```bash # 确认正确包名(可能应为ai_edge_lite等) # 尝试华为ModelArts LiteRT库的特殊安装方式 pip install git+https://github.com/huawei-noah/Edge-Computing.git#subdirectory=ai_edge_litert # 或指定版本安装 pip install ai-edge-litert==1.2.3 # 假设存在该版本 ``` --- #### **3. 镜像加速安装** ```bash # 使用国内镜像源加速 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple psutil ai_edge_litert # 阿里云镜像 pip install -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com psutil ai_edge_litert ``` --- #### **4. 安装验证脚本** ```python # test_installation.py try: import psutil print(f"[成功] psutil版本: {psutil.__version__}") except ImportError: print("[错误] psutil未安装") try: import ai_edge_litert print("[成功] ai_edge_litert已加载") except ImportError as e: print(f"[错误] ai_edge_litert缺失: {str(e)}") # 执行验证 # python test_installation.py ``` --- #### **5. 常见错误解决方案** | 错误类型 | 修复方法 | |---------|----------| | $ERROR: Could not find a version that satisfies the requirement ai\_edge\_litert$ | 确认包名拼写正确,或尝试私有仓库安装[^3] | | $ctypes.CDLL loading failed$ | 安装$libffi-dev$后重新编译Python[^2] | | $ModuleNotFoundError$仅在IDE出现 | 在IDE终端执行`which python`确认解释器路径[^4] | --- ### **高级场景处理** **Docker容器安装** 在Dockerfile中添加: ```dockerfile RUN apt-get update && apt-get install -y python3-dev libffi-dev && \ pip install psutil ai_edge_litert ``` **Jupyter Notebook环境** 在单元格中执行: ```python !pip install psutil ai_edge_litert %load_ext autoreload ``` --- ### **延伸问题** 1. 如何批量安装多个Python模块并确保版本兼容性? 2. 在离线环境中如何部署$psutil$及其依赖? 3. 使用$ai\_edge\_litert$时出现CUDA兼容性问题该如何解决? --- ### **关键参考** [^1]: 模块导入机制与路径解析 [^2]: C扩展编译依赖的系统库要求 [^3]: 私有仓库模块的特殊安装方式 [^4]: 开发环境配置与解释器路径管理
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值