1. 基于TensorFlow Lite的图像分类器部署

任务目标

在树莓派(Raspberry Pi 4B/5,64位系统)上部署TensorFlow Lite运行时,加载预训练的MobileNetV2图像分类模型,通过摄像头或静态图片输入,实时输出Top-5类别名称及置信度分数。

环境与依赖

  • 硬件:树莓派4B/5(建议2GB+内存),USB摄像头或静态图片
  • 系统:Raspberry Pi OS (64-bit) Bookworm 或更新版本
  • Python:3.9+
  • 安装依赖:pip install tflite-runtime numpy pillow
  • 模型文件:从官方链接下载,解压得到 mobilenet_v2_1.0_224_quantized.tflite(约3.5MB)
  • 标签文件:同一压缩包内的 labels_mobilenet_v2.txt(含1001个类别,索引0为"background")

完整实现代码

将以下代码保存为 classifier.py。代码支持两种输入模式:--image 处理单张图片,--camera 实时摄像头推理(按Ctrl+C退出)。

import argparse
import time
import numpy as np
from PIL import Image
import tflite_runtime.interpreter as tflite

# 模型与标签路径
MODEL_PATH = "mobilenet_v2_1.0_224_quantized.tflite"
LABEL_PATH = "labels_mobilenet_v2.txt"

def load_labels(path):
    with open(path, "r") as f:
        return [line.strip() for line in f.readlines()]

def load_model(model_path):
    interpreter = tflite.Interpreter(model_path=model_path)
    interpreter.allocate_tensors()
    return interpreter

def preprocess_image(image, input_size):
    """将PIL图像缩放到模型输入尺寸并转为uint8数组"""
    image = image.resize((input_size, input_size), Image.Resampling.LANCZOS)
    # 量化模型输入为uint8,范围[0,255]
    return np.expand_dims(np.array(image, dtype=np.uint8), axis=0)

def classify_image(interpreter, image_array):
    """执行推理并返回Top-5 (索引, 标签, 置信度)"""
    input_details = interpreter.get_input_details()
    output_details = interpreter.get_output_details()

    interpreter.set_tensor(input_details[0]["index"], image_array)
    interpreter.invoke()

    output = interpreter.get_tensor(output_details[0]["index"])[0]
    # 量化模型输出为uint8,需除以255得到0~1置信度
    scores = output.astype(np.float32) / 255.0
    top5_idx = np.argsort(scores)[::-1][:5]
    return [(idx, labels[idx], float(scores[idx])) for idx in top5_idx]

def print_results(results, inference_ms):
    print(f"\n推理耗时: {inference_ms:.1f} ms")
    print("Top-5 预测结果:")
    for rank, (idx, label, score) in enumerate(results, 1):
        print(f"  {rank}. [{idx}] {label}: {score:.4f}")

def run_on_image(interpreter, image_path):
    image = Image.open(image_path).convert("RGB")
    input_size = interpreter.get_input_details()[0]["shape"][1]
    input_array = preprocess_image(image, input_size)

    start = time.perf_counter()
    results = classify_image(interpreter, input_array)
    elapsed_ms = (time.perf_counter() - start) * 1000
    print_results(results, elapsed_ms)

def run_on_camera(interpreter):
    try:
        from picamera2 import Picamera2
    except ImportError:
        print("需要安装 picamera2: sudo apt install python3-picamera2")
        return

    picam2 = Picamera2()
    config = picam2.create_preview_configuration(
        main={"size": (640, 480)}, format="RGB888")
    picam2.configure(config)
    picam2.start()

    input_size = interpreter.get_input_details()[0]["shape"][1]
    print("摄像头推理中... 按 Ctrl+C 退出")
    try:
        while True:
            frame = picam2.capture_array()
            image = Image.fromarray(frame).convert("RGB")
            input_array = preprocess_image(image, input_size)

            start = time.perf_counter()
            results = classify_image(interpreter, input_array)
            elapsed_ms = (time.perf_counter() - start) * 1000

            # 清屏并输出当前帧结果
            print("\033[2J\033[H", end="")
            print(f"FPS: {1000/elapsed_ms:.1f}")
            print_results(results, elapsed_ms)
            time.sleep(0.05)
    except KeyboardInterrupt:
        print("\n已退出")
    finally:
        picam2.stop()

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="树莓派TFLite图像分类器")
    group = parser.add_mutually_exclusive_group(required=True)
    group.add_argument("--image", type=str, help="静态图片路径")
    group.add_argument("--camera", action="store_true", help="使用摄像头实时推理")
    args = parser.parse_args()

    labels = load_labels(LABEL_PATH)
    interpreter = load_model(MODEL_PATH)
    print(f"模型加载完成,输入尺寸: {interpreter.get_input_details()[0]['shape']}")

    if args.image:
        run_on_image(interpreter, args.image)
    elif args.camera:
        run_on_camera(interpreter)

运行与验证

  1. 下载模型与标签(与代码同目录):
    wget https://storage.googleapis.com/download.tensorflow.org/models/tflite/mobilenet_v2_1.0_224_quantized.tgz && tar -xzf mobilenet_v2_1.0_224_quantized.tgz
  2. 静态图片测试:
    python3 classifier.py --image test.jpg
    预期输出:打印5行结果,每行包含索引、标签和0~1之间的置信度,且所有置信度之和接近1(因softmax归一化)。例如对一张狗的照片,Top-1应为"golden retriever"或类似犬种。
  3. 摄像头实时测试:
    python3 classifier.py --camera
    预期输出:终端每秒刷新约10~20次,每次显示当前画面的Top-5结果和推理耗时。将物体移入画面,观察类别标签随之变化。

通过判定标准

  • 静态图片模式:程序无报错退出,输出的Top-5标签与图片内容明显相关(如风景图出现"seashore"、"alp"等),置信度数值在0~1之间且Top-1置信度通常大于0.3。
  • 摄像头模式:画面刷新率不低于5 FPS,移动物体时Top-1标签能正确跟随变化(如举起手机时出现"cellular telephone")。
  • 推理耗时:树莓派4B上单次推理约80~150ms,5上约40~80ms(量化模型)。若耗时超过500ms,检查是否启用了CPU频率调节或系统负载过高。

常见问题

  • 模型加载失败:确认tflite文件与代码在同一目录,且路径拼写正确。
  • 摄像头无法打开:执行 sudo apt install python3-picamera2,并确认摄像头已通过 raspi-config 启用。
  • 输出全为0或全为1:检查输入图像是否成功resize到224×224,且未做错误归一化(uint8输入不应除以255)。