评价此页

在 Raspberry Pi 4 和 5 上进行实时推理(40 fps!)#

创建日期:2022年2月8日 | 最后更新:2025年9月30日 | 最后验证:2024年11月5日

作者: Tristan Rice

PyTorch 对 Raspberry Pi 4 和 5 提供了开箱即用的支持。本教程将指导您如何配置 Raspberry Pi 以运行 PyTorch,并在 CPU 上实时(30-40 fps)运行 MobileNet v2 分类模型。

所有测试均在 Raspberry Pi 4 Model B 4GB 版本上进行,但也应适用于 2GB 版本,以及性能较低的 3B 版本。

https://user-images.githubusercontent.com/909104/153093710-bc736b6f-69d9-4a50-a3e8-9f2b2c9e04fd.gif

先决条件#

要学习本教程,您需要一台 Raspberry Pi 4 或 5、相应的摄像头以及所有其他标准配件。

Raspberry Pi 设置#

PyTorch 仅提供 Arm 64 位 (aarch64) 的 pip 安装包,因此您需要在 Raspberry Pi 上安装 64 位版本的操作系统。

您需要安装 官方 rpi-imager 来安装 Raspberry Pi OS。

32 位 Raspberry Pi OS 无法运行。

https://user-images.githubusercontent.com/909104/152866212-36ce29b1-aba6-4924-8ae6-0a283f1fca14.gif

安装过程至少需要几分钟,具体取决于您的网络速度和 SD 卡速度。完成后,界面应如下所示:

https://user-images.githubusercontent.com/909104/152867425-c005cff0-5f3f-47f1-922d-e0bbb541cd25.png

现在将 SD 卡放入 Raspberry Pi,连接摄像头并启动它。

https://user-images.githubusercontent.com/909104/152869862-c239c980-b089-4bd5-84eb-0a1e5cf22df2.png

Raspberry Pi 4 配置#

如果您使用的是 Raspberry Pi 4,则需要进行一些额外的配置更改。Raspberry Pi 5 无需这些更改。

操作系统启动并完成初始设置后,您需要编辑 /boot/config.txt 文件以启用摄像头。

# This enables the extended features such as the camera.
start_x=1

# This needs to be at least 128M for the camera processing, if it's bigger you can just leave it as is.
gpu_mem=128

然后重启。

安装 PyTorch 和 picamera2#

PyTorch 和我们所需的其他库都有 ARM 64位/aarch64 版本,因此您可以像在其他 Linux 系统上一样通过 pip 安装它们。

$ sudo apt install -y python3-picamera2 python3-libcamera
$ pip install torch torchvision --break-system-packages
https://user-images.githubusercontent.com/909104/152874260-95a7a8bd-0f9b-438a-9c0b-5b67729e233f.png

现在我们可以检查一切是否安装正确。

$ python -c "import torch; print(torch.__version__)"
https://user-images.githubusercontent.com/909104/152874271-d7057c2d-80fd-4761-aed4-df6c8b7aa99f.png

视频采集#

首先在终端运行 libcamera-hello 测试摄像头是否工作。

对于视频采集,我们将使用 picamera2 来获取视频帧。

我们使用的模型 (MobileNetV2) 接收 224x224 大小的图像,因此我们可以直接从 picamera2 以 36fps 的帧率请求该尺寸。我们的目标模型运行帧率为 30fps,但我们请求稍高的帧率以确保始终有足够的帧。

from picamera2 import Picamera2

picam2 = Picamera2()

# print available sensor modes
print(picam2.sensor_modes)

config = picam2.create_still_configuration(main={
    "size": (224, 224),
    "format": "BGR888",
}, display="main")
picam2.configure(config)
picam2.set_controls({"FrameRate": 36})
picam2.start()

要采集帧,我们可以调用 capture_image 返回一个 PIL.Image 对象,供 PyTorch 使用。

# read frame
image = picam2.capture_image("main")

# show frame for testing
image.show()

数据读取和处理大约需要 3.5 ms

图像预处理#

我们需要获取帧并将其转换为模型预期的格式。这与您在任何其他机器上使用标准 torchvision 变换进行的预处理相同。

from torchvision import transforms

preprocess = transforms.Compose([
    # convert the frame to a CHW torch tensor for training
    transforms.ToTensor(),
    # normalize the colors to the range that mobilenet_v2/3 expect
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])
input_tensor = preprocess(image)
# The model can handle multiple images simultaneously so we need to add an
# empty dimension for the batch.
# [3, 224, 224] -> [1, 3, 224, 224]
input_batch = input_tensor.unsqueeze(0)

模型选择#

您可以选择多种具有不同性能特征的模型。并非所有模型都提供 qnnpack 预训练版本,因此为了测试,您应该选择一个支持该版本的模型;但如果您训练并量化自己的模型,则可以使用任何模型。

本教程中我们使用 mobilenet_v2,因为它在性能和准确性之间取得了良好的平衡。

Raspberry Pi 4 基准测试结果

模型

FPS

总时间(ms/帧)

模型推理时间(ms/帧)

qnnpack 预训练

mobilenet_v2

33.7

29.7

26.4

mobilenet_v3_large

29.3

34.1

30.7

resnet18

9.2

109.0

100.3

resnet50

4.3

233.9

225.2

resnext101_32x8d

1.1

892.5

885.3

inception_v3

4.9

204.1

195.5

googlenet

7.4

135.3

132.0

shufflenet_v2_x0_5

46.7

21.4

18.2

shufflenet_v2_x1_0

24.4

41.0

37.7

shufflenet_v2_x1_5

16.8

59.6

56.3

shufflenet_v2_x2_0

11.6

86.3

82.7

MobileNetV2:量化与 JIT#

为了获得最佳性能,我们需要一个经过量化和融合的模型。量化意味着使用 int8 进行计算,这比标准的 float32 数学运算性能高得多。融合意味着在可能的情况下,将连续的运算合并为性能更高的版本。通常,激活函数(如 ReLU)可以在推理期间合并到前一层的运算(如 Conv2d)中。

aarch64 版本的 PyTorch 需要使用 qnnpack 引擎。

import torch
torch.backends.quantized.engine = 'qnnpack'

在此示例中,我们将使用 torchvision 开箱即提供的预量化和融合版本的 MobileNetV2。

from torchvision import models
net = models.quantization.mobilenet_v2(pretrained=True, quantize=True)

然后,我们通过 JIT 对模型进行编译,以减少 Python 开销并融合算子。使用 JIT 可以使帧率从约 20fps 提升至约 30fps。

net = torch.jit.script(net)

整合运行#

现在我们可以将所有部分整合在一起并运行它。

import time

import torch
from torchvision import models, transforms
from picamera2 import Picamera2

torch.backends.quantized.engine = 'qnnpack'

picam2 = Picamera2()

# print available sensor modes
print(picam2.sensor_modes)

config = picam2.create_still_configuration(main={
    "size": (224, 224),
    "format": "BGR888",
}, display="main")
picam2.configure(config)
picam2.set_controls({"FrameRate": 36})
picam2.start()

preprocess = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])

net = models.quantization.mobilenet_v2(pretrained=True, quantize=True)
# jit model to take it from ~20fps to ~30fps
net = torch.jit.script(net)

started = time.time()
last_logged = time.time()
frame_count = 0

with torch.no_grad():
    while True:
        # read frame
        image = picam2.capture_image("main")


        # preprocess
        input_tensor = preprocess(image)

        # create a mini-batch as expected by the model
        input_batch = input_tensor.unsqueeze(0)

        # run model
        output = net(input_batch)
        # do something with output ...
        print(output.argmax())

        # log model performance
        frame_count += 1
        now = time.time()
        if now - last_logged > 1:
            print(f"{frame_count / (now-last_logged)} fps")
            last_logged = now
            frame_count = 0

运行结果显示,在 Raspberry Pi 4 上约为 30 fps,在 Raspberry Pi 5 上约为 41 fps。

https://user-images.githubusercontent.com/909104/152892609-7d115705-3ec9-4f8d-beed-a51711503a32.png

这是在 Raspberry Pi OS 默认设置下的表现。如果您禁用了 UI 和默认开启的其他后台服务,性能会更出色且更稳定。

检查 htop 可以发现 CPU 利用率接近 100%。

https://user-images.githubusercontent.com/909104/152892630-f094b84b-19ba-48f6-8632-1b954abc59c7.png

为了验证端到端是否正常工作,我们可以计算类别的概率并 使用 ImageNet 类标签 来打印检测结果。

top = list(enumerate(output[0].softmax(dim=0)))
top.sort(key=lambda x: x[1], reverse=True)
for idx, val in top[:10]:
    print(f"{val.item()*100:.2f}% {classes[idx]}")

mobilenet_v3_large 实时运行中

https://user-images.githubusercontent.com/909104/153093710-bc736b6f-69d9-4a50-a3e8-9f2b2c9e04fd.gif

检测到一个橙子

https://user-images.githubusercontent.com/909104/153092153-d9c08dfe-105b-408a-8e1e-295da8a78c19.jpg

检测到一个马克杯

https://user-images.githubusercontent.com/909104/153092155-4b90002f-a0f3-4267-8d70-e713e7b4d5a0.jpg

故障排查:性能#

默认情况下,PyTorch 会使用所有可用内核。如果 Raspberry Pi 后台运行了其他任务,可能会与模型推理产生竞争,导致延迟突增。为了缓解这种情况,您可以减少线程数,这会以极小的性能损耗为代价,降低峰值延迟。

torch.set_num_threads(2)

对于 shufflenet_v2_x1_5,使用 2 个线程 而非 4 个线程,可以将最佳延迟从 60 ms 增加到 72 ms,但消除了 128 ms 的延迟波动。

下一步#

您可以创建自己的模型或微调现有模型。如果您基于 torchvision.models.quantized 中的模型进行微调,大部分融合和量化工作已为您完成,因此您可以直接部署并在 Raspberry Pi 上获得良好的性能。

了解更多

  • 量化:有关如何量化和融合模型的更多信息。

  • 迁移学习教程:如何使用迁移学习将预先存在的模型微调到您的数据集。