树莓派上YOLOv5-Lite实时推理部署与性能优化指南

树莓派上YOLOv5-Lite实时推理部署与性能优化指南 简介基于树莓派的YOLOv5-Lite部署工程为计算机、通信、人工智能、自动化等专业的毕设与课程设计提供了一套可直接落地的轻量化目标检测方案。资源包含完整的Python推理源码、四种ONNX模型文件及项目说明文档覆盖模型转换、单张图片测试、视频流检测等关键环节适合希望快速掌握边缘端YOLO部署流程的初学者进阶使用。压缩包共10个文件以Python脚本5个和ONNX模型4个为主另含1个Markdown说明文件整体大小26.9MB结构精简、便于对照学习。工程代码经调试测试可稳定运行作者毕设答辩评分95分具有较好的工程参考价值。目前已有1255人学习可作为课程设计或毕业设计的基础框架在此基础上二次修改即可实现不同功能。1. 树莓派跑 YOLOv5-Lite不只是能跑这么简单一块树莓派 4B4GB 内存CPU 满载跑 YOLOv5s实测帧率通常在 2~3 FPS 左右。这个速度做静态图片检测勉强够用但放到视频流或小车上几乎等于不可用。而 YOLOv5-Lite 通过替换骨干网络、裁掉一个检测头、减少通道数能在树莓派上把推理帧率推到 8~12 FPS代价是 mAP 下降 3~5 个百分点。对于行人检测、零件分拣、小车避障这类粗粒度识别场景这个取舍相当划算。本文要讲的就是怎么把这份 YOLOv5-Lite 的部署源码和模型权重完整落在树莓派上跑通摄像头实时推理并顺手解决内存不足、温度降频、量化掉点这几个部署路上的主要麻烦。适合手里有树莓派 4B 或 5、打算做嵌入式视觉项目但对 TensorRT 之类重框架没有需求的开发者。2. YOLOv5-Lite 的模型结构与部署选型2.1 YOLOv5-Lite 和标准 YOLOv5s 的差异在哪YOLOv5-Lite 本质上是 YOLOv5 的轻量化变体核心改动集中在三处骨干网络从 CSPDarknet 换成 ShuffleNetV2 或 MobileNetV3 这类轻量结构检测头从三个缩减为两个去掉大尺度检测层减少计算量通道数和 Bottleneck 块数量整体压缩。实际模型文件的差异标准 YOLOv5s 的 ONNX 权重通常在 27MB 左右YOLOv5-Lite 的 ONNX 权重在 10~15MB 之间。参数量约 50 万 vs 702 万GFLOPs 从 16.5 降到 3.2 左右。这个数字直接决定了树莓派能否实时跑起来。值得注意的一个细节是YOLOv5-Lite 有两个常见变体按骨干分类别。ShuffleNetV2 版本在树莓派 CPU 上延迟略低MobileNetV3 版本精度略高。热词里出现的模型端/推理端话题对应到这边的选择就是CPU 推理优先 ShuffleNetV2精度敏感场景选 MobileNetV3。在部署选型时我一般会先问三个问题推理硬件是不是只有树莓派 CPU、内存够不够跑系统服务、需不需要在端侧重新训练。如果答案分别是是是不需要那就走 ONNX Runtime NCNN 的双后端路线一个用于桌面开发调试一个用于树莓派部署。2.2 为什么不用 TensorRT 和 OpenVINO树莓派的 GPU 是 VideoCore VI计算能力有限TensorRT 官方不支持。OpenVINO 对 ARM 后端虽然有实验性支持但算子覆盖率和性能收益在树莓派上都谈不上理想。常见做法是用 ONNX Runtime 的 ARM64 版本简单直接兼容性好追求最大帧率就编译 NCNN专门针对 ARM CPU 做了 NEON 优化。两个方案跑同一个模型NCNN 在树莓派 4B 上通常能比 ONNX Runtime 再快 15%~25%。代价是 NCNN 需要自行编译工具链链路上的坑多一些。我建议把它做成二选一而非二选二先 ONNX Runtime 跑通流程再决定要不要换 NCNN。2.3 是否需要量化精度损失怎么衡量YOLOv5-Lite 在树莓派上部署量化不是可选项而是刚需。原因很简单FP32 的模型在树莓派 CPU 上推理4 核全开也会因为内存带宽瓶颈卡在 5~6 FPS。INT8 量化后权重缩小到 1/4访存压力显著下降帧率直接提升 30% 以上。量化的精度损失因模型而异。我自己常用的做法是准备一个约 100~200 张的真实场景验证集统计量化前后 mAP0.5 的下降幅度模型变体FP32 mAP0.5INT8 mAP0.5下降幅度YOLOv5-Lite-Shuffle58.254.9约 3.3YOLOv5-Lite-MobileNet61.559.1约 2.4YOLOv5s66.863.2约 3.6下降幅度和训练数据分布、量化校准集的选取都相关。如果某个类别掉点严重检查一下是不是该类别的目标在图片里占比过小这种情况通常需要手动给校准数据集里加权。如果项目允许保留 FP16 模型配合加速库做混合精度推理也是树莓派上的一个中间方案但树莓派 CPU 对 FP16 的加速效果远不如桌面 GPU性价比不高。3. 树莓派部署的环境搭建与依赖选型3.1 操作系统与 Python 环境树莓派部署 YOLOv5-Lite首推系统是 Raspberry Pi OS2023 年之后的版本基于 Debian Bookworm选择 64 位版本。32 位系统在内存访问效率和部分算子实现上有明显劣势带来的性能损失接近 20%。Python 环境建议直接用系统 Python 3.11 venv不要装 Anaconda。树莓派上 Anaconda 没有官方 ARM64 包第三方构建的 conda-forge 版本问题比较多在纯部署场景里纯属自找麻烦。sudo apt update sudo apt upgrade -y sudo apt install -y python3-venv python3-dev python3-pip cmake git mkdir -p ~/yolo-lite cd ~/yolo-lite python3 -m venv .venv source .venv/bin/activate pip install --upgrade pip setuptools wheelvenv 创建后所有依赖安装在这个独立环境里不会污染系统 Python。cmake 和 git 是为后面可能编译 NCNN 准备的如果只用 ONNX Runtime 可以跳过。3.2 推理引擎安装ONNX Runtime 优先NCNN 备用pip install onnxruntime pip install opencv-python-headless numpy简化安装验证python3 -c import onnxruntime as ort; print(ort.__version__)需要重点说明的是树莓派上不要装 opencv-python 完整版它包含 GUI 和许多桌面端依赖安装体积大且容易触发系统库冲突。opencv-python-headless 适合纯服务端推理。如果要用 OpenCV 显示实时画面需要额外装 libgl1 等图形库根据部署方式不同选择也不一样这部分在第四章的推理脚本里细讲。NCNN 编译可以在 Python 依赖验证通过后再进行选择顺序上我的建议是先用 ONNX 验证模型正确性再考虑性能优化。如果只装 ONNX Runtime 就能到 8 FPSNCNN 可以不折腾。git clone https://github.com/Tencent/ncnn.git cd ncnn mkdir build cd build cmake -DCMAKE_TOOLCHAIN_FILE../toolchains/pi3.toolchain.cmake -DNCNN_VULKANOFF -DNCNN_BUILD_EXAMPLESON .. make -j4 sudo make installtoolchains 目录下的工具链文件版本不同路径有差异编不过就看报错信息找实际路径。Vulkan 在树莓派上编译不过或者性能反而下降是常态直接关掉比较省事。3.3 摄像头驱动确认树莓派部署 YOLOv5-Lite摄像头是主要输入源。先验证 Camera 接口是否被系统正确识别vcgencmd get_camera libcamera-hello --list-cameras官方摄像头用 libcamera 框架树莓派 5 上旧版 raspicam 系列命令已废弃。如果输出有摄像头设备说明驱动正常。如果列表为空检查 ribbon 线方向是否接反、连接是否牢固然后在sudo raspi-config里确认 Camera 接口处于 enabled 状态。4. 部署源码结构和推理链路4.1 源码目录的核心文件部署源码一般从 YOLOv5 官方仓库裁剪而来。实际项目里的目录结构通常长这样deploy/ ├── main.py # 主推理入口 ├── models.py # 模型结构定义 ├── detect_utils.py # 后处理工具函数 ├── datasets.py # 数据加载器 ├── runs/ # 日志和输出目录 ├── weights/ │ ├── model_v5lite_shuffle.pt │ ├── model_v5lite_shuffle.onnx │ ├── model_v5lite_fp16.ncnn.param │ └── model_v5lite_fp16.ncnn.bin └── requirements.txt # 部署依赖清单main.py 是核心它在部署时替代了训练用的 detect.py。训练和部署模式的差异我习惯这样区分训练时有梯度计算和优化器逻辑部署时所有层切到 eval 模式torch.no_grad() 包整个推理过程。eval 模式下 BatchNorm 层使用累计均值和方差而非当前 batch 统计量Dropout 层直接失效这两点与训练时输出不同容易让第一次做部署的人踩坑。4.2 用 ONNX Runtime 实现推理的最小代码这段代码可以直接运行输入是本地图片输出是画好框的结果图import cv2 import numpy as np import onnxruntime as ort class YOLOv5LiteDetector: def __init__(self, onnx_path, conf_thres0.4, iou_thres0.5): self.session ort.InferenceSession(onnx_path, providers[CPUExecutionProvider]) self.conf_thres conf_thres self.iou_thres iou_thres self.input_shape self.session.get_inputs()[0].shape[2:] def preprocess(self, img): # 保持宽高比缩放图片不足部分用灰色填充 h, w img.shape[:2] target_h, target_w self.input_shape scale min(target_w / w, target_h / h) nw, nh int(w * scale), int(h * scale) resized cv2.resize(img, (nw, nh)) canvas np.full((target_h, target_w, 3), 114, dtypenp.uint8) dx, dy (target_w - nw) // 2, (target_h - nh) // 2 canvas[dy:dynh, dx:dxnw] resized blob canvas[:, :, ::-1].transpose(2, 0, 1) blob np.expand_dims(blob, axis0).astype(np.float32) blob / 255.0 return blob, scale, dx, dy def postprocess(self, pred, scale, dx, dy, orig_shape): pred pred[0] boxes, scores, class_ids [], [], [] for det in pred: x_center, y_center, w, h det[:4] obj_conf det[4] class_scores det[5:] score obj_conf * np.max(class_scores) if score self.conf_thres: continue cls_id np.argmax(class_scores) x0 (x_center - w / 2 - dx) / scale y0 (y_center - h / 2 - dy) / scale x1 (x_center w / 2 - dx) / scale y1 (y_center h / 2 - dy) / scale boxes.append([x0, y0, x1 - x0, y1 - y0]) scores.append(float(score)) class_ids.append(int(cls_id)) if not boxes: return [] indices cv2.dnn.NMSBoxes(boxes, scores, self.conf_thres, self.iou_thres) results [] for i in indices.flatten(): results.append([boxes[i], scores[i], class_ids[i]]) return results def infer(self, image): blob, scale, dx, dy self.preprocess(image) output self.session.run(None, {self.session.get_inputs()[0].name: blob}) return self.postprocess(output[0], scale, dx, dy, image.shape[:2])这段代码的重点在两个地方。第一预处理时用的是等比例缩放加灰边填充而不是直接拉伸直接拉伸会让目标的宽高比失真检测框位置偏差明显尤其对细长物体影响很大。第二后处理时把框坐标从填充后的坐标系还原回原始图像坐标系。模型输出做 NMS 时YOLOv5 系列的输出格式是[cx, cy, w, h, objectness, class_scores...]objectness 乘以最大类别分数才是最终置信度只用类别分数会保留大量低置信度误检框。4.3 摄像头实时推理的完整流程import cv2 from main import YOLOv5LiteDetector detector YOLOv5LiteDetector(weights/model_v5lite_shuffle.onnx) cap cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) cap.set(cv2.CAP_PROP_FPS, 30) while True: ret, frame cap.read() if not ret: break results detector.infer(frame) for box, score, cls in results: x, y, w, h box cv2.rectangle(frame, (int(x), int(y)), (int(xw), int(yh)), (0, 255, 0), 2) cv2.putText(frame, f{cls} {score:.2f}, (int(x), int(y)-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2) cv2.imshow(YOLOv5-Lite, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()摄像头分辨率选择 640x480 而不是直接流式读 1080p原因在于低分辨率采集可以降低 USB 带宽和转码开销把 CPU 算力尽量留给推理。如果跑 USB 摄像头出现画面延迟先用v4l2-ctl --list-formats-ext确认设备支持的分辨率和像素格式优先选 YUYV 或 MJPG部分摄像头对 H.264 和原始 RGB 的驱动支持差异非常大。4.4 模型格式转换与权重整理如果源码只提供 .pt 权重在桌面端训练机上完成导出不要在树莓派上跑导出流程原因包括但不限于树莓派内存不够加载 PyTorch 完整训练图、导出过程 CPU 耗时几十分钟、导错了还要反复试。import torch model torch.load(best.pt, map_locationcpu)[model].float() model.eval() dummy_input torch.randn(1, 3, 320, 320) torch.onnx.export( model, dummy_input, model_v5lite_shuffle.onnx, opset_version11, input_names[images], output_names[output], dynamic_axes{images: {0: batch}, output: {0: batch}} )opset 版本在树莓派部署时选 11 比选 13 或 17 更稳妥。ONNX Runtime 对旧版 opset 的兼容实现更成熟新版算子可能触发个别不常见的 fallback 路径在 ARM CPU 上性能反而下降。dynamic_axes 对树莓派部署不是必须的固定 batch1 可以带来略微的输入张量优化空间。5. 树莓派上的推理性能实测参数与调优技巧5.1 不做任何优化时的基线树莓派 4B 4GB 版本CPU 运行 1.5GHz 频率模型输入 320x320测试条件完全一致统一的 100 帧视频推理耗时取均值方便和你的设备对位配置帧率FPS单帧推理耗时ms备注FP32 ONNX4 线程4.8208CPU 频率锁 1.5GHzFP32 ONNX2 线程3.7270温度高时降频明显INT8 量化 ONNX4 线程6.9145需要 opencv 处理耗时NCNN FP164 线程8.2122编译时不开 VulkanNCNN INT84 线程10.595精度可接受前提下最优这个表能看出两件事树莓派 CPU 上的推理瓶颈主要在内存带宽和 NEON 指令利用率量化带来的帧率提升比换推理引擎的收益更稳定。另外 320x320 输入是部署基准配置上调到 416 会真正拖慢到让人放弃除非检测目标面积特别小。5.2 必调的三个核心参数第一个是--threads。ONNX Runtime 的线程数不是越大越好树莓派 4 核 CPU 上设 4 线程达到峰值设到 6 或 8 反而因线程切换开销掉 10% 上下。用 taskset 把推理进程绑到特定核心可以避免类调度器把进程在不同核心间迁移导致的缓存命中率下降。taskset -c 0-3 python3 main.py第二个是 CPU 频率策略。可以和初始化脚本放在一起sudo cpupower frequency-set -g performance vcgencmd measure_temp树莓派默认为 ondemand 策略频繁升频降频在 100 毫秒级推理场景里几乎等于时刻处于升频过渡段。performance 模式固定最高频率能省掉约 15% 的不确定性延迟代价是功耗上升、温度上升。如果你的部署场景是电池供电的移动小车把温度墙放在 70℃ 比较稳妥超过 80℃ 触发硬件降频后帧率波动会显著变大。第三个是输入尺寸。320x320 和 352x320 这种非方形的输入对某些检测目标比例能轻微改善召回但计算量的增加接近 15%部署时除非特定目标在维度上失衡严重否则不要乱调。如果你检测的是行人可以试试 272x320 或者 288x320行人竖长比例的输入换回 5% 级别的帧率提升是一个在视觉上基本无感、帧率上效果明显的部署技巧。5.3 内存不足问题定位sudo dmesg -T | grep -i memory查看是否有 OOM 记录free -h看当前剩余内存。以下三条可以避开 80% 的内存问题关闭交换或者交换只在 sd 卡剩余空间充足的条件下仍保留较小值sudo swapoff -a限制 OpenCV 缓冲帧数capture.set(cv2.CAP_PROP_BUFFERSIZE, 1)。数据流时默认缓冲十几帧不会立刻被消费连续跑 10 分钟内存占用差额极大。设置 Python 垃圾回收策略。推理主循环不涉及大量对象创建时gc.disable()可以避免周期性 GC 停顿带来的帧率尖刺。这个要看源码具体情况不是无脑禁用。5.4 树莓派 5 的相对优势树莓派 5 的 BCM2712 在整数运算能力上比树莓派 4B 提升约 2~3 倍同样的 YOLOv5-Lite 模型在树莓派 5 上直接跑 ONNX Runtime帧率能到 15 FPS 左右。但树莓派 5 的内存带宽依然有限量化后的模型收益比 FP32 更明显。另外树莓派 5 的温控风扇是必须的被动散热片在持续推理 5 分钟后就会开始降频温度阈值可以在/boot/firmware/config.txt里调整。如果换到树莓派 5部署时还多一个选型沿用 ONNX Runtime或者重新评估 NCNN 的性价比。我的实测结论是树莓派 5 上两者差距收窄为了 NCNN 多出的几十行代码和编链接成本不再值得切换。直接用 ONNX Runtime 即可省下的时间用来调整输入分辨率和线程数更划算。本文还有配套的精品资源点击获取