在这里插入图片描述

关键词:ONNX, TFLite, 量化 (Quantization), YOLOv8, Android

大家好,我是飞哥!👋

前几个月我们一直在云端“冲浪”,用 Python 调 API、玩 Docker。
但作为一名 Android 开发者,你肯定心痒痒:“能不能不联网,直接在手机上跑 AI?

答案是:必须能! 但有个前提:你不能把云端那个几百 GB 的“大胖子”模型直接塞进手机里,手机会爆炸的(夸张了,是会卡死)。💥

今天这节课,飞哥就教你一门“瘦身缩骨功”——模型量化与格式转换


1. 为什么要量化?(Why)

锚定已知 ⚓️

大家平时发微信视频,是不是经常会被压缩?原本 100MB 的高清视频,发过去只有 5MB,画质稍微糊了一点点,但看起来差不多,而且传输极快。

生动类比 🍔

模型量化,就是给 AI 模型做“脱水处理” (Freeze-drying)。

  • 云端模型 (FP32):就像一颗饱满多汁的新鲜苹果。精度极高(小数点后 32 位),但又重又大,必须放冷库(GPU 服务器)。
  • 量化模型 (INT8):就像一片苹果干。虽然水分(精度)少了一点点,口感(效果)略有差异,但营养(核心能力)还在,最重要的是——轻便! 随便一个小口袋(手机 NPU/CPU)就能装下。

提炼骨架 🦴

所以,端侧 AI 的核心流程就是:
训练 (PyTorch) ➡️ 转换 (ONNX) ➡️ 压缩 (TFLite/INT8) ➡️ 部署 (Android)


2. 核心概念详解 (What)

(1) ONNX:AI 界的“普通话” 🌏

不同的 AI 框架(PyTorch, TensorFlow, PaddlePaddle)就像各地的方言,互相听不懂。
ONNX (Open Neural Network Exchange) 就是它们共同商定的“普通话”。
不管你是哪里训练出来的模型,先转成 ONNX,然后再翻译成手机能听懂的 TFLite 或 NCNN。

(2) TFLite:Android 的“亲儿子” 🤖

Google 专门为移动端定制的格式。它去掉了训练相关的累赘功能,只保留“推理”能力,专门为安卓手机优化。

(3) 量化 (Quantization) 📉

这是一个用精度换速度的游戏:

模式 精度 体积 速度 推荐指数
FP32 32位浮点 100% (大) 🐢 慢 ❌ (云端用)
FP16 16位浮点 50% (中) 🏃 快 ✅ (Android 首选)
INT8 8位整数 25% (小) 🚀 极快 ⚡️ (NPU 加速必备)

⚠️ 飞哥提示:INT8 虽然快,但需要“校准 (Calibration)”。这就像拍照时的“自动测光”。真实世界的光线(FP32)范围极大,而照片(INT8)能容纳的亮度有限。转换时,你需要先让模型看一些典型图片(校准数据),让它算出最佳的“曝光参数”(数据分布),这样才能保证压缩后的模型既不过曝也不死黑,保留住核心细节。


3. 实战项目:模型量化实验室 (How) 🛠️

我们要完成一个任务:下载 YOLOv8 模型,把它转换成 Android 能用的 TFLite 格式,并验证它没坏。

本项目代码已开源在:Week13_Model_Quantization 文件夹。

第一步:安装依赖 📦

pip install ultralytics onnx onnxruntime tensorflow opencv-python

第二步:一键导出 (export.py) 📤

YOLOv8 官方极其贴心,一行代码就能搞定。

from ultralytics import YOLO
import shutil
import os

def export_models():
    print("🚀 开始加载 YOLOv8n 模型...")
    # 1. 下载/加载模型
    model = YOLO('yolov8n.pt')
    
    # 2. 导出为 ONNX (通用格式)
    print("\n📦 正在导出为 ONNX 格式...")
    model.export(format='onnx', opset=12)
    
    # 3. 导出为 TFLite (FP16 - 推荐)
    # half=True 表示使用 FP16 半精度,体积减半,精度几乎不损
    print("\n📦 正在导出为 TFLite (FP16) 格式...")
    model.export(format='tflite', half=True)
    
    print("\n✅ 导出完成!")
    
    # 移动文件到 models 目录
    os.makedirs("models", exist_ok=True)
    for f in os.listdir('.'):
        if f.endswith('.onnx') or f.endswith('.tflite') or f.endswith('.pt'):
            try:
                shutil.move(f, f"models/{f}")
            except:
                pass
    print("\n📂 模型已移动到 models/ 文件夹")

if __name__ == "__main__":
    export_models()

运行后,你会发现 models 文件夹里多了 yolov8n_float16.tflite,大小只有 yolov8n.pt 的一半!

第三步:Python 端验证 (inference.py) 🔍

很多同学转完模型直接扔进 Android 项目,结果报错一脸懵。
飞哥教你一招:先在 Python 里用 tensorflow.lite 跑一遍,确保模型本身没问题。

import tensorflow as tf
import numpy as np
import cv2
import time
import os

def run_inference(tflite_path, image_path):
    print(f"\n🔍 开始测试模型: {tflite_path}")

    # 1. 加载 TFLite 模型
    interpreter = tf.lite.Interpreter(model_path=tflite_path)
    interpreter.allocate_tensors()

    # 2. 获取输入输出详情
    input_details = interpreter.get_input_details()
    output_details = interpreter.get_output_details()

    input_shape = input_details[0]["shape"]  # [1, 640, 640, 3]
    print(f"📥 模型输入形状: {input_shape}")

    # 3. 预处理图片
    img = cv2.imread(image_path)
    if img is None:
        print("❌ 找不到图片,请检查路径")
        return

    # Resize 到模型要求的尺寸 (通常是 640x640)
    img_resized = cv2.resize(img, (640, 640))

    # 归一化 (0-255 -> 0-1)
    input_data = img_resized.astype(np.float32) / 255.0
    input_data = np.expand_dims(input_data, axis=0)  # 增加 batch 维度

    # 4. 推理
    start_time = time.time()
    interpreter.set_tensor(input_details[0]["index"], input_data)
    interpreter.invoke()
    output_data = interpreter.get_tensor(output_details[0]["index"])
    end_time = time.time()

    print(f"⚡️ 推理耗时: {(end_time - start_time)*1000:.2f} ms")
    print(f"📤 输出形状: {output_data.shape}")

    # ---------------------------------------------------------
    # 💡 飞哥小课堂:这个 (1, 84, 8400) 是什么意思?
    # ---------------------------------------------------------
    # 1. Batch Size (1): 我们一次只喂了一张图片。
    # 2. Channels (84): 代表每个预测框包含的信息量。
    #    - 前 4 个数:是框的位置 (中心点x, 中心点y, 宽w, 高h)。
    #    - 后 80 个数:是 COCO 数据集 80 个类别的概率 (比如它是猫的概率、是车的概率...)。
    #    - 4 + 80 = 84
    # 3. Anchors (8400): YOLO 在这张图上总共生成了 8400 个候选框。
    #    - 它是怎么算出来的?(640x640 图片)
    #    - 80x80 (大特征图,看小物体) = 6400
    #    - 40x40 (中特征图,看中物体) = 1600
    #    - 20x20 (小特征图,看大物体) = 400
    #    - 总和:6400 + 1600 + 400 = 8400
    # ---------------------------------------------------------

    print("✅ 测试通过!模型可以正常工作。")

if __name__ == "__main__":
    # 自动查找模型并运行
    # 确保你已经有了 models/yolov8n_saved_model/yolov8n_float16.tflite 和 assets/bus.jpg
    pass 
    # (完整代码请参考 Week13_Model_Quantization/inference.py)

第四步:使用 Netron 查看“内脏” 🔬

拿到模型后,必须看一眼它的结构。
下载神器 Netron (也有网页版)。

打开 yolov8n.onnx,关注两个点:

  1. Input: images (1, 3, 640, 640) -> 记住这个尺寸,Android 端处理图片时必须一模一样。
  2. Output: output0 (1, 84, 8400) -> 这是 YOLO 的原始输出(84 = 4个坐标 + 80个类别概率)。

⚠️ 进阶技巧
在 Android 上解析 1x84x8400 这种原始数据非常痛苦(需要自己写 NMS 算法)。
如果你想偷懒,可以在导出时加上 nms=True(仅部分版本支持),或者使用专门为 TFLite 优化的 YOLO 版本。


4. 总结与作业 📝

一句话记住它
模型量化就是给 AI 做“脱水压缩”,牺牲一丢丢精度,换取手机上的极致速度。

核心三要点

  1. ONNX 是中转站,TFLite 是终点站。
  2. FP16 性价比最高(体积减半,精度不降)。
  3. INT8 虽快,但需要校准数据,且可能有精度损失。

本周作业

  1. 运行 export.py 导出你自己的 TFLite 模型。
  2. 用 Netron 截图模型的输入输出节点。
  3. (选做) 尝试开启 int8=True,看看体积还能小多少?

下篇,我们将正式进入 Android (Jetpack Compose),把这个 TFLite 模型装进手机,做一个能看懂世界的 App!🚀


🎁 课程示例

我用夸克网盘给你分享了「Week13_Model_Quantization.zip」,点击链接或复制整段内容,打开「夸克APP」即可获取。
/483d3ADEGx😕 链接:https://pan.quark.cn/s/69cf8e9d25e4

源码包内含:

  • export.py:一键导出脚本
  • inference.py:Python 验证脚本
  • requirements.txt:环境依赖清单
  • ✅ 超详细 README 文档,小白也能跑通!
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐