树莓派5 YOLOv8 NCNN部署实战:从编译优化到帧率调优
1. 为什么要在树莓派5上折腾YOLOv8和NCNN?
如果你和我一样,喜欢在树莓派这种小巧的边缘设备上鼓捣点“智能”玩意儿,比如做个能看能认的监控摄像头、一个会自己找东西的机器人,那你肯定绕不开一个核心问题:如何在资源有限的硬件上,跑得动又大又复杂的AI模型?
我之前在树莓派5上试过直接用PyTorch跑YOLOv5,那体验,怎么说呢,就像让一个小学生去解高等数学题——CPU直接飙到100%,风扇呼呼转,结果一秒钟连一帧都处理不完,画面卡成PPT。后来我换了USB摄像头,把分辨率调到最低,好不容易跑到了10帧左右,但画质已经糊得看不清了。我也尝试过把模型转成ONNX格式,本以为能提速,结果帧率不升反降,折腾一圈,心都凉了半截。
直到我在国外论坛上看到有人分享,说在树莓派5上用NCNN推理框架跑YOLOv8nano模型,在640x640的输入尺寸下,帧率能冲到20 FPS。这个数字瞬间让我来了精神。NCNN是腾讯开源的一个为移动端和嵌入式平台优化的高性能神经网络前向计算框架,它针对ARM架构做了大量优化,体积小、效率高,听起来简直就是为树莓派量身定做的。
所以,这篇文章就是我的完整实战记录。目标很明确:在树莓派5上,从零开始部署YOLOv8模型,并用NCNN进行推理,最终实现尽可能高的实时检测帧率。 我会把每一步操作、每一个坑、每一次参数调整的细节都掰开揉碎了讲清楚。你最终会看到,理论和现实总有差距,论坛里说的20 FPS不是轻易能达到的,但通过一系列编译优化和调参技巧,我们完全可以把性能压榨到极限,稳定在10-15 FPS,这对于很多实际应用场景来说,已经足够实用了。
2. 基础环境搭建:给树莓派5“打地基”
万事开头难,一个稳定、高效的基础环境是后续所有操作的前提。这里我们使用树莓派官方的64位操作系统(Raspberry Pi OS 64-bit, Bookworm版本),它对新硬件的支持最好。
2.1 系统更新与基础依赖安装
拿到树莓派,接上电源、键盘、鼠标和显示器(或者通过SSH连接),第一件事就是更新系统软件包列表并升级已有的软件。这能确保我们安装的是最新、最稳定的库文件。
打开终端,依次输入以下命令:
sudo apt-get update
sudo apt-get upgrade -y
这个过程可能需要几分钟,取决于你的网络速度。更新完成后,我们开始安装编译NCNN和OpenCV所必需的工具链和库。
sudo apt-get install -y cmake wget git
sudo apt-get install -y build-essential gcc g++
sudo apt-get install -y libprotobuf-dev protobuf-compiler
简单解释一下这几个包:
- cmake, gcc, g++, build-essential:这是C/C++项目的编译核心套件,没有它们啥也编不了。
- libprotobuf-dev, protobuf-compiler:NCNN模型文件(.param和.bin)的加载和解析依赖于Google的Protocol Buffers,所以必须安装。
- wget, git:下载工具,后面我们会用它来下载源码。
2.2 编译与安装NCNN:为AI推理装上“引擎”
NCNN是我们的核心推理引擎,直接从GitHub拉取最新源码进行编译,能获得最好的性能和最新的优化。
# 克隆NCNN仓库,--depth=1只克隆最新的一次提交,节省时间和空间
git clone --depth=1 https://github.com/Tencent/ncnn.git
cd ncnn
# 创建并进入构建目录
mkdir build
cd build
接下来是关键的一步:配置CMake。树莓派5采用的是ARM Cortex-A76架构的CPU,我们需要指定交叉编译工具链(虽然我们是在树莓派本机上编译,但使用工具链文件可以确保编译器使用正确的优化指令集)。
cmake -D NCNN_DISABLE_RTTI=OFF \
-D NCNN_BUILD_TOOLS=ON \
-D CMAKE_TOOLCHAIN_FILE=../toolchains/aarch64-linux-gnu.toolchain.cmake ..
这里有两个重要的CMake选项:
-D NCNN_DISABLE_RTTI=OFF:保持运行时类型信息开启,对于某些模型是必要的。-D NCNN_BUILD_TOOLS=ON:编译NCNN配套的工具,比如模型转换工具ncnnoptimize,后期模型优化会用到。
配置完成后,开始编译。树莓派5有4个性能核心,我们可以使用-j4参数让4个核心同时工作,加快编译速度。
make -j4
编译过程大概需要10-15分钟,你可以去泡杯茶。编译成功后,进行安装:
sudo make install
默认情况下,NCNN的头文件和库文件会被安装到/usr/local/目录下。为了确保系统能找到它们,我们手动复制一下(这是一种更稳妥的做法):
sudo mkdir -p /usr/local/lib/ncnn
sudo cp -r install/include/ncnn /usr/local/include/
sudo cp install/lib/libncnn.a /usr/local/lib/ncnn/
至此,NCNN这个高性能“引擎”就安装好了。
3. 编译OpenCV:让树莓派“看见”世界
OpenCV是计算机视觉的“瑞士军刀”,我们用它来读取摄像头、处理图像、画检测框。树莓派官方仓库里的OpenCV版本可能比较旧,或者缺少某些功能,自己编译可以获得更好的控制和性能。
注意:编译OpenCV非常消耗内存和CPU,整个过程可能需要1-2个小时! 如果你的树莓派5是4GB内存版本,在编译到一半时很可能因为内存不足而崩溃。解决方法就是增加交换空间(Swap),相当于把一部分SD卡空间当作低速内存来用。
检查当前交换空间大小:
sudo swapon --show
如果显示为空或者很小(比如100MB),我们需要扩容。编辑配置文件:
sudo nano /etc/dphys-swapfile
找到CONF_SWAPSIZE这一行,将其值修改为2048(单位是MB,即2GB)。保存退出后,重启交换服务:
sudo systemctl restart dphys-swapfile
接下来,我推荐使用网上大神们写好的自动化编译脚本,这比自己一步步配置要省心得多。我们可以从Qengineering的仓库获取一个针对树莓派优化过的OpenCV 4.9.0编译脚本。
wget https://raw.githubusercontent.com/Qengineering/Install-OpenCV-Raspberry-Pi-32-64/main/OpenCV-4-9-0.sh
sudo chmod 755 OpenCV-4-9-0.sh
./OpenCV-4-9-0.sh
运行脚本后,它会自动下载OpenCV及其扩展模块(contrib)的源码,然后进行编译和安装。整个过程完全自动,你可以把它放在后台运行,然后去做点别的。编译成功后,OpenCV的头文件和库也会被安装到/usr/local目录下。
4. 获取并运行YOLOv8-NCNN示例项目
基础环境搭建完毕,现在该请出主角——YOLOv8模型了。我们直接使用一个已经写好的、适配树莓派的示例项目,这能让我们快速验证整个流程是否通畅。
4.1 下载与解压项目
使用wget下载项目压缩包并解压:
wget https://github.com/Qengineering/YoloV8-ncnn-Raspberry-Pi-4/archive/refs/heads/main.zip
unzip main.zip
cd YoloV8-ncnn-Raspberry-Pi-4-main
这个项目里已经包含了转换好的YOLOv8nano模型文件(.param和.bin),以及完整的C++源代码和一个Code::Blocks的工程文件。对于不熟悉命令行编译的朋友,用Code::Blocks这种IDE会更友好。
4.2 使用Code::Blocks编译运行(可选)
如果你喜欢图形化界面,可以安装Code::Blocks:
sudo apt-get install codeblocks -y
然后在文件管理器中双击项目里的.cbp文件,用Code::Blocks打开。在IDE里,记得将编译模式从“Debug”切换到“Release”。Debug模式包含了大量的调试信息,会严重拖慢程序运行速度。点击编译按钮,成功后运行,程序会加载一张示例图片进行检测,并在窗口中显示结果。如果能看到画着框的图片,恭喜你,第一步成功了!
4.3 手动编译与测试(推荐)
不过,我更推荐在终端里手动编译,这样更透明,也方便我们后续修改和优化。项目里已经提供了CMakeLists.txt文件。
# 在项目根目录下
mkdir build
cd build
cmake ..
make -j4
编译完成后,你会得到一个可执行文件,名字可能是yolov8或YoloV8。运行它测试图片检测:
./yolov8
如果一切正常,程序会读取项目自带的测试图片(比如test.jpg),进行推理,并弹出窗口显示检测结果。这是验证NCNN、OpenCV和模型文件是否全部正确安装和链接的终极测试。
5. 从图片到视频:性能初探与第一个“坑”
图片测试成功了,但我们的目标是实时视频流。让我们修改源代码,让它能处理视频文件。打开项目中的主C++文件(可能是main.cpp或yolov8main.cpp),将其内容替换为以下视频检测版本:
#include "yoloV8.h"
#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
YoloV8 yolov8;
int target_size = 640; // 模型输入尺寸,必须是32的倍数
int main(int argc, char** argv) {
// 替换成你自己的视频文件路径
const char* videopath = "/home/pi/test_video.mp4";
cv::VideoCapture cap(videopath);
if (!cap.isOpened()) {
std::cerr << "Failed to open video file!" << std::endl;
return -1;
}
// 加载模型,只需一次
yolov8.load(target_size);
cv::Mat frame;
std::vector<Object> objects;
while (cap.read(frame)) {
// 清空上一帧的结果
objects.clear();
// 执行检测
yolov8.detect(frame, objects);
// 绘制检测框
yolov8.draw(frame, objects);
// 显示结果
cv::imshow("YOLOv8 Detection", frame);
// 按ESC退出
if (cv::waitKey(1) == 27) {
break;
}
}
cap.release();
cv::destroyAllWindows();
return 0;
}
修改保存后,重新执行cmake ..和make进行编译。运行程序,用一段1280x720分辨率、30帧的视频进行测试。这时候,你很可能遇到第一个性能打击:帧率远低于预期。
在我自己的测试中,target_size设为640,处理1280x720的视频,帧率只有5-10 FPS,而且CPU占用率在75%左右徘徊。虽然这比当初PyTorch CPU模式下的0.3 FPS已经是天壤之别,但距离论坛里说的20 FPS还有很大差距。
原因分析:
- 视频解码开销:
cv::VideoCapture读取视频帧本身需要CPU进行解码,尤其是高分辨率视频,这部分开销不小。 - 图像预处理:OpenCV将每一帧图像从原始尺寸缩放到
target_size(640x640),这个缩放操作(cv::resize)是CPU计算,也比较耗时。 - 后处理与绘制:NCNN输出的是检测框的原始数据,需要在CPU上做非极大值抑制(NMS)筛选,然后再用OpenCV画到原图上,这些也都是CPU操作。
所以,即使NCNN模型推理本身很快,但前后处理步骤成了瓶颈。这给我们指明了第一个优化方向:优化前后处理流程,或者接受一个更低的输入分辨率。
6. 摄像头实时检测与帧率调优实战
视频文件的测试让我们对性能有了底,现在进入真正的实战环节:连接USB摄像头,进行实时检测。这是很多嵌入式AI项目的核心场景。
6.1 编写摄像头检测代码
我们继续修改主程序,这次改为从摄像头索引0(通常是第一个USB摄像头)读取数据:
#include "yoloV8.h"
#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
#include <chrono> // 用于计算FPS
YoloV8 yolov8;
int target_size = 640; // 初始尝试640
int main() {
// 设置期望的摄像头采集分辨率
int capture_width = 640;
int capture_height = 480;
cv::VideoCapture cap(0); // 打开默认摄像头
if (!cap.isOpened()) {
std::cerr << "Error: Could not open camera." << std::endl;
return -1;
}
// 设置摄像头分辨率(不是所有摄像头都支持所有分辨率)
cap.set(cv::CAP_PROP_FRAME_WIDTH, capture_width);
cap.set(cv::CAP_PROP_FRAME_HEIGHT, capture_height);
// 加载模型
yolov8.load(target_size);
cv::Mat frame;
std::vector<Object> objects;
double fps = 0.0;
auto start = std::chrono::steady_clock::now();
int frame_count = 0;
while (true) {
cap >> frame;
if (frame.empty()) break;
objects.clear();
yolov8.detect(frame, objects);
yolov8.draw(frame, objects);
// 计算并显示FPS
frame_count++;
auto end = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
if (duration > 1000) { // 每1秒计算一次平均FPS
fps = frame_count * 1000.0 / duration;
std::cout << "Current FPS: " << fps << std::endl;
frame_count = 0;
start = std::chrono::steady_clock::now();
}
cv::putText(frame, "FPS: " + std::to_string(int(fps)), cv::Point(10, 30),
cv::FONT_HERSHEY_SIMPLEX, 0.7, cv::Scalar(0, 255, 0), 2);
cv::imshow("Real-time YOLOv8", frame);
if (cv::waitKey(1) == 27) break; // ESC退出
}
cap.release();
cv::destroyAllWindows();
return 0;
}
6.2 性能测试与参数调整:寻找最佳平衡点
编译并运行上面的程序,我们开始进行一系列测试,记录不同参数组合下的帧率。这是调优中最关键的一步,需要耐心。
测试环境: 树莓派5 (8GB),普通USB 2.0摄像头。
| 模型输入尺寸 (target_size) | 摄像头采集分辨率 | 实测平均FPS | CPU占用率 | 主观体验 |
|---|---|---|---|---|
| 640 | 1280x720 | 6.5 | ~85% | 卡顿明显,延迟高 |
| 640 | 800x600 | 9.5 | ~80% | 略有卡顿,可接受 |
| 480 | 800x600 | 13.5 | ~75% | 相对流畅,延迟较低 |
| 480 | 640x480 | 15.0 | ~70% | 流畅,但画面视野较小 |
| 320 | 640x480 | 22.0 | ~65% | 非常流畅,但检测精度下降明显 |
从测试结果可以清晰地看到几个规律:
target_size是性能的关键:将模型输入尺寸从640降到480,帧率提升非常显著(从9.5到13.5)。再降到320,帧率更高,但小目标的检测能力会大打折扣。480是一个在精度和速度之间很好的平衡点。- 摄像头分辨率的影响:在
target_size固定的情况下,降低摄像头采集分辨率(如从720P到600P)也能提升帧率,因为OpenCV需要缩放的数据量变少了。但分辨率太低会影响图像质量。 - 理论与现实的差距:论坛中提到的640尺寸下20 FPS,在我的环境中无法复现。这可能源于对方使用了更高效的摄像头(如CSI摄像头,占用CPU资源少)、可能对NCNN或OpenCV进行了更极致的编译优化(比如开启NEON指令集、使用Vulkan后端等),或者测试的是纯模型推理时间,而非包含前后处理的端到端延迟。
6.3 深入优化:尝试NCNN的Vulkan后端
NCNN支持使用Vulkan GPU进行加速。树莓派5的VideoCore VII GPU理论上支持Vulkan 1.2。如果启用,可以将部分计算负载从CPU转移到GPU,可能带来性能提升。
首先,你需要确保系统安装了Vulkan驱动。在Raspberry Pi OS上,可以尝试安装:
sudo apt-get install vulkan-tools libvulkan1
然后,重新编译NCNN,这次在CMake配置中开启Vulkan支持:
cd ncnn/build
# 先清理之前的编译结果(可选)
rm -rf *
cmake -D NCNN_DISABLE_RTTI=OFF \
-D NCNN_BUILD_TOOLS=ON \
-D NCNN_VULKAN=ON \ # 关键!开启Vulkan
-D CMAKE_TOOLCHAIN_FILE=../toolchains/aarch64-linux-gnu.toolchain.cmake ..
make -j4
sudo make install
在代码中,你需要在加载模型前,创建Vulkan设备并设置给NCNN的网络。示例项目可能没有默认启用Vulkan,你需要查阅NCNN的示例代码进行修改。请注意:Vulkan加速的收益因模型和操作而异,对于YOLOv8这种包含大量卷积的模型,在树莓派5的GPU上可能提升有限,甚至因为CPU-GPU数据拷贝的开销而导致性能下降。这需要实际测试验证。
7. 进阶调优思路与问题排查
如果经过上述调整,帧率仍然不满足你的需求,这里还有一些进阶的调优思路和常见问题排查点。
7.1 模型优化:瘦身与加速
- 使用更小的模型:YOLOv8有n, s, m, l, x不同尺寸的版本。
nano(n)是最小的。如果精度要求不高,可以尝试自己转换并部署更小的自定义模型。 - NCNN模型优化:使用NCNN提供的
ncnnoptimize工具对转换后的.param和.bin文件进行优化。这个工具可以融合一些操作层,可能带来小幅度的推理加速。# 假设在ncnn/build/tools/目录下 ./ncnnoptimize yolov8n.param yolov8n.bin yolov8n-opt.param yolov8n-opt.bin 65536 - 量化:将模型从FP32(单精度浮点)量化到INT8(8位整数),可以大幅减少模型体积和提升推理速度。但量化需要校准数据集,过程稍复杂,且可能会带来一定的精度损失。
7.2 代码层面优化
- 多线程处理:可以将摄像头采集、图像预处理、模型推理、后处理与显示放在不同的线程中,形成流水线,充分利用多核CPU。例如,当一帧在进行推理时,下一帧已经在进行预读了。
- 降低检测频率:对于某些对实时性要求不是极端高的场景(如安防监控),可以每间隔2帧或3帧做一次检测,中间帧直接使用上一帧的结果或进行简单的跟踪算法,这能直接成倍提升显示帧率。
- 关闭调试信息与图形显示:在最终部署时,移除计算FPS、在图像上画框和文字、以及
cv::imshow这些操作,能节省不少时间。可以通过一个命令行参数来控制是否开启这些功能。
7.3 系统与环境排查
- CPU频率与温度:使用
vcgencmd measure_clock arm和vcgencmd measure_temp检查CPU是否运行在最高频率,以及是否因为过热而降频。确保树莓派散热良好。 - 电源供应:使用官方或足额(5V/3A以上)的电源适配器,供电不足会导致CPU性能受限。
- 内存与交换:使用
htop命令监控内存使用。如果编译OpenCV后交换空间改得很大,记得在编译完成后改回默认值(如100MB),因为大交换空间会加速SD卡磨损。sudo nano /etc/dphys-swapfile # 将 CONF_SWAPSIZE 改回 100 sudo systemctl restart dphys-swapfile
经过这一整套从环境搭建、编译优化到参数细调的实战,我的树莓派5最终能够在480x480的模型输入、800x600的摄像头输入下,稳定跑在13-15 FPS。这个性能对于很多需要实时物体识别的DIY项目,比如智能小车避障、简单的产线瑕疵检测或者一个能识别宠物的喂食器,已经完全可用了。整个过程中,最大的体会就是嵌入式AI部署是一个在硬件限制、模型精度和运行速度之间不断权衡的艺术。没有一劳永逸的最优解,只有最适合你具体场景的解决方案。希望我的这些踩坑经验和数据,能帮你更快地找到属于你的那个“甜蜜点”。
更多推荐
所有评论(0)